我还编写了一个SAF DropBox实现,起初对此我也有点困惑。
从
documentation
:
请注意以下几点:
-
每个文档提供者报告一个或多个正在启动的“根”
指向探索文档树。每个根都有一个唯一的
列_ROOT_ID,它指向一个文档(一个目录)
表示该根下的内容。根是动态的
设计为支持多账户、瞬时USB等用例
存储设备,或用户登录/注销。
-
每个根目录下都有一个文档。该文件指向1到N
文档,每个文档依次可以指向1到N个文档。
-
每个存储后端都会按顺序显示各个文件和目录
使用唯一的列\文档\ ID引用它们。文档ID必须
必须是唯一的,一经发布不得更改,因为它们用于
跨设备重新启动授予持久URI。
-
文档可以是可打开的文件(具有特定的MIME类型),
或包含其他文档的目录(带有
MIME_TYPE_DIR MIME TYPE)。
-
每个文档可以具有不同的功能,如
列_标志。例如,FLAG_支持_WRITE,
FLAG_支持_删除,FLAG_支持_缩略图。相同的
列_DOCUMENT _ID可以包含在多个目录中。
第二颗子弹是关键子弹
.从queryRoots()返回后,对于返回的每个根,SAF都会调用queryDocument()。这实质上是创建列表中显示的“根文件夹”文档。我做的是在queryDocument()中检查传入的documentId是否与我给DocumentsContract的唯一值匹配。根queryRoots()调用中的列\u ROOT\u ID。如果是,那么您知道这个queryDocument()调用需要返回一个表示该根的文件夹。否则,我会在其他地方使用DropBox的路径作为我的documentId,所以我会在通过DbxClientV2的调用中使用该documentId值。
下面是一些示例代码——请注意,在我的例子中,我创建了一个AbstractStorageProvider类,我的所有提供者(Dropbox、Instagram等)都是从该类扩展而来的。基类负责接收来自SAF的调用,并执行一些内务处理(如创建游标),然后调用实现类中的方法,以按照特定服务的要求填充游标:
基层
public Cursor queryRoots(final String[] projection) {
Timber.d( "Lifecycle: queryRoots called");
// If they are not paid up, they do not get to use any of these implementations
if (!InTouchUtils.isLoginPaidSubscription()) {
return null;
}
// Create a cursor with either the requested fields, or the default projection if "projection" is null.
final MatrixCursor cursor = new MatrixCursor(projection != null ? projection : getDefaultRootProjection());
// Classes that extend this one must implement this method
addRowsToQueryRootsCursor(cursor);
return cursor;
}
从DropboxProvider AddRowsToQueryRotScursors:
protected void addRowsToQueryRootsCursor(MatrixCursor cursor) {
// See if we need to init
long l = System.currentTimeMillis();
if ( !InTouchUtils.initDropboxClient()) {
return;
}
Timber.d( "Time to test initialization of DropboxClient: %dms.", (System.currentTimeMillis() - l));
l = System.currentTimeMillis();
try {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(Objects.requireNonNull(getContext()).getApplicationContext());
String displayname = sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_displayname_token_key),
getContext().getResources().getString(R.string.pref_dropbox_displayname_token_default));
batchSize = Long.valueOf(Objects.requireNonNull(sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_query_limit_key),
getContext().getResources().getString(R.string.pref_dropbox_query_limit_key_default))));
final MatrixCursor.RowBuilder row = cursor.newRow();
row.add(DocumentsContract.Root.COLUMN_ROOT_ID, <YOUR_UNIQUE_ROOTS_KEY_HERE>);
row.add(DocumentsContract.Root.COLUMN_TITLE,
String.format(getContext().getString(R.string.dropbox_root_title),getContext().getString(R.string.app_name)));
row.add(DocumentsContract.Root.COLUMN_SUMMARY,displayname+
getContext().getResources().getString(R.string.dropbox_root_summary));
row.add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_SUPPORTS_RECENTS | DocumentsContract.Root.FLAG_SUPPORTS_SEARCH);
row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID,<YOUR_UNIQUE_ROOT_FOLDER_ID_HERE>);
row.add(DocumentsContract.Root.COLUMN_ICON, R.drawable.intouch_for_dropbox);
} catch (Exception e) {
Timber.d( "Called addRowsToQueryRootsCursor got exception, message was: %s", e.getMessage());
}
Timber.d( "Time to queryRoots(): %dms.", (System.currentTimeMillis() - l));
}
然后基类中的queryDocument()方法:
@Override
public Cursor queryDocument(final String documentId, final String[] projection) {
Timber.d( "Lifecycle: queryDocument called for: %s", documentId);
// Create a cursor with either the requested fields, or the default projection if "projection" is null.
// Return a cursor with a getExtras() method, to avoid the immutable ArrayMap problem.
final MatrixCursor cursor = new MatrixCursor(projection != null ? projection : getDefaultDocumentProjection()){
Bundle cursorExtras = new Bundle();
@Override
public Bundle getExtras() {
return cursorExtras;
}
};
addRowToQueryDocumentCursor(cursor, documentId);
return cursor;
}
以及DropboxProvider中的addRowToQueryDocumentCursor():
protected void addRowToQueryDocumentCursor(MatrixCursor cursor,
String documentId) {
try {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(Objects.requireNonNull(getContext()).getApplicationContext());
String displayname = sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_displayname_token_key),
getContext().getString(R.string.pref_dropbox_displayname_token_default));
if ( !InTouchUtils.initDropboxClient()) {
return;
}
if ( documentId.equals(<YOUR_UNIQUE_ROOTS_ID_HERE>)) {
// root Dir
Timber.d( "addRowToQueryDocumentCursor called for the root");
final MatrixCursor.RowBuilder row = cursor.newRow();
row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, <YOUR_UNIQUE_FOLDER_ID_HERE>);
row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME,
String.format(getContext().getString(R.string.dropbox_root_title),
getContext().getString(R.string.app_name)));
row.add(DocumentsContract.Document.COLUMN_SUMMARY,displayname+
getContext().getString(R.string.dropbox_root_summary));
row.add(DocumentsContract.Document.COLUMN_ICON, R.drawable.folder_icon_dropbox);
row.add(DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
row.add(DocumentsContract.Document.COLUMN_FLAGS, 0);
row.add(DocumentsContract.Document.COLUMN_SIZE, null);
row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, null);
return;
}
Timber.d( "addRowToQueryDocumentCursor called for documentId: %s", documentId);
DbxClientV2 mDbxClient = DropboxClientFactory.getClient();
Metadata metadata = mDbxClient.files().getMetadata(documentId);
if ( metadata instanceof FolderMetadata) {
Timber.d( "Document was a folder");
includeFolder(cursor, (FolderMetadata)metadata);
} else {
Timber.d( "Document was a file");
includeFile(cursor, (FileMetadata) metadata);
}
} catch (Exception e ) {
Timber.d( "Called addRowToQueryDocumentCursor got exception, message was: %s documentId was: %s.", e.getMessage(), documentId);
}
}