在DocumentUI中,内置sdcard的数据来源于ExternalStorageProvider这个类。
//frameworks\base\packages\ExternalStorageProvider\src\com\android\externalstorage\ExternalStorageProvider.java
public class ExternalStorageProvider extends FileSystemProvider {
........
public void updateVolumes() {
synchronized (mRootsLock) {
updateVolumesLocked();
}
}
private void updateVolumesLocked() {
mRoots.clear();
VolumeInfo primaryVolume = null;
final int userId = UserHandle.myUserId();
final List<VolumeInfo> volumes = mStorageManager.getVolumes();
........
}
........
}
数据来源于mStorageManager.getVolumes()。
但是在StorageManager中是找不到getVolumes(0)这个函数的。
@SystemService(Context.STORAGE_SERVICE)
public class StorageManager{
..........
/** {@hide} */
public @NonNull List<VolumeInfo> getVolumes() {
try {
return Arrays.asList(mStorageManager.getVolumes(0));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
..........
}
这个存在于IStorageManager .aidl文件中
interface IStorageManager {
..........
VolumeInfo[] getVolumes(int flags) = 45;
..........
}
实际调用的是StorageManagerService中的函数。mVolumes 是关键,实际是StorageManagerService中产生的数据。addInternalVolumeLocked中的mVolumes.put(internal.id, internal)产生的数据。
//frameworks\base\services\core\java\com\android\server\StorageManagerService.java
public class StorageManagerService extends IStorageManager.Stub{
..........
/** Map from volume ID to disk */
@GuardedBy("mLock")
protected final ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>();
..........
private void addInternalVolumeLocked() {
// Create a stub volume that represents internal storage
final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL,
VolumeInfo.TYPE_PRIVATE, null, null);
internal.state = VolumeInfo.STATE_MOUNTED;
internal.path = Environment.getDataDirectory().getAbsolutePath();
mVolumes.put(internal.id, internal);
}
..........
@Override
public VolumeInfo[] getVolumes(int flags) {
synchronized (mLock) {
final VolumeInfo[] res = new VolumeInfo[mVolumes.size()];
for (int i = 0; i < mVolumes.size(); i++) {
res[i] = mVolumes.valueAt(i);
}
return res;
}
}
..........
}
ExternalStorageProvider实际并不产生数据,它是一个数据的提供者。那么ExternalStorageProvider是如何提供数据的呢?
ExternalStorageProvider 由于继承了FileSystemProvider ,而FileSystemProvider 中定义了queryDocument和queryChildDocuments,返回的是Cursor。传递的是Cursor。
//frameworks\base\core\java\com\android\internal\content\FileSystemProvider.java
public abstract class FileSystemProvider extends DocumentsProvider {
..........
@Override
public Cursor queryDocument(String documentId, String[] projection)
throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
includeFile(result, documentId, null);
return result;
}
@Override
public Cursor queryChildDocuments(
String parentDocumentId, String[] projection, String sortOrder)
throws FileNotFoundException {
final File parent = getFileForDocId(parentDocumentId);
final MatrixCursor result = new DirectoryCursor(
resolveProjection(projection), parentDocumentId, parent);
for (File file : parent.listFiles()) {
includeFile(result, null, file);
}
return result;
}
..........
explorer直接打开应用显示的是FilesActivity,但是文件内容显示部分是用的DirectoryFragment,这里用的是一个recyclerview,用到的Adapter是ModelBackedDocumentsAdapter。
//vendor\mediatek\proprietary\packages\xx\xx\src\com\android\documentsui\dirlist\DirectoryFragment.java
public class DirectoryFragment extends Fragment
implements ItemDragListener.DragHost, SwipeRefreshLayout.OnRefreshListener {
..........
mAdapter = new DirectoryAddonsAdapter(
mAdapterEnv, new ModelBackedDocumentsAdapter(mAdapterEnv, mIconHelper));
..........
而通过打印log我发现,ModelBackedDocumentsAdapter中onBindViewHolder并没有被调用,打印log发现原因是ModelBackedDocumentsAdapter.getItemCount = 0,因此判断数据的产生有问题。最后发现uri不正确导致的,内部sdcard存储的uri应该是带primary的,但是实际生成的是带home的数据。通过打印log,查找uri产生的地方,最后发现是ActionHandler中的loadHomeDir函数。
//vendor\mediatek\proprietary\packages\xx\xx\src\com\android\documentsui\AbstractActionHandler.java
/**
* Provides support for specializing the actions (openDocument etc.) to the host activity.
*/
public abstract class AbstractActionHandler<T extends Activity & CommonAddons>
implements ActionHandler {
..........
protected final void loadHomeDir() {
loadRoot(Shared.getDefaultRootUri(mActivity));
}
..........
}
//vendor\mediatek\proprietary\packages\xx\xx\src\com\android\documentsui\base\Shared.java
public final class Shared {
..........
/**
* Returns the default directory to be presented after starting the activity.
* Method can be overridden if the change of the behavior of the the child activity is needed.
*/
public static Uri getDefaultRootUri(Activity activity) {
return shouldShowDocumentsRoot(activity)
? DocumentsContract.buildHomeUri()
: DocumentsContract.buildRootUri(
Providers.AUTHORITY_DOWNLOADS, Providers.ROOT_ID_DOWNLOADS);
}
..........
}
//frameworks\base\core\java\android\provider\DocumentsContract.java
public final class DocumentsContract {
..........
/**
* Builds URI for user home directory on external (local) storage.
* {@hide}
*/
public static Uri buildHomeUri() {
// TODO: Avoid this type of interpackage copying. Added here to avoid
// direct coupling, but not ideal.
return DocumentsContract.buildRootUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, "home");
}
..........
/**
* {@hide}
*/
public static Uri buildPrimaryUri() {
// TODO: Avoid this type of interpackage copying. Added here to avoid
// direct coupling, but not ideal.
return DocumentsContract.buildRootUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, "primary");
}
..........
}
最后,把Shared 类中的getDefaultRootUri函数中的buildHomeUri改成buildPrimaryUri即可,这样产生的就是能查询内部sdcard的uri。
AbstractActionHandler中创建了uri,并把uri传递给了DirectoryLoader,通过uri等参数,让DirectoryLoader 返回fragment显示所属的数据。
//vendor\mediatek\proprietary\packages\xx\xx\src\com\android\documentsui\DirectoryLoader.java
public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
..........
@Override
public final DirectoryResult loadInBackground() {
final DirectoryResult result = new DirectoryResult();
result.doc = mDoc;
..........
cursor = client.query(mUri, null, queryArgs, mSignal);
..........
cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1);
..........
result.cursor = cursor;
..........
return result;
}
..........
}
参考链接:
谷歌原生DocumentUI文件浏览的原理
从跨用户文件拷贝说起DocumentUI记录
documentsUI源码分析
DocumentsUI 文件打开
RecyclerView不调用onCreateViewHolder和onBindViewHolder的解决方法
网友评论