在4.4以上的版本中如果通过如下的Intent调用Activity:
1
2
3
4
5
6
|
final Intent intent =
new
Intent(Intent.ACTION_GET_CONTENT);
// The MIME data type filter
intent.setType(
"*/*"
);
// Only return URIs that can be opened with ContentResolver
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivity(intent);
|
则会自动调用系统自带的documentsui文件管理器。因为documentsui的manifest中没有带category等于category.LAUNCHER的属性,因此documentsui是不会显示在LAUNCHER桌面上的。
documentsui的界面如下:
为了研究在android中资源文件是如何访问的的,我决定研究一下其代码。源代码地址
https://github.com/OWLeeMod/android_packages_apps_DocumentsUI
将代码下载下来之后,发现起代码结构非常复杂,这篇文章只是一个初步的分析。
入口:DocumentsActivity。DocumentsActivity的布局是由DrawerLayout构成的,左边是文件类别的菜单,右边是相应的目录树。
onCreate方法中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
// Hide roots when we're managing a specific root
if
(mState.action == ACTION_MANAGE) {
if
(mShowAsDialog) {
findViewById(R.id.dialog_roots).setVisibility(View.GONE);
}
else
{
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
}
}
if
(mState.action == ACTION_CREATE) {
final String mimeType = getIntent().getType();
final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
SaveFragment.show(getFragmentManager(), mimeType, title);
}
if
(mState.action == ACTION_GET_CONTENT) {
final Intent moreApps =
new
Intent(getIntent());
moreApps.setComponent(
null
);
moreApps.setPackage(
null
);
RootsFragment.show(getFragmentManager(), moreApps);
}
else
if
(mState.action == ACTION_OPEN || mState.action == ACTION_CREATE
|| mState.action == ACTION_STANDALONE) {
RootsFragment.show(getFragmentManager(),
null
);
}
if
(!mState.restored) {
if
(mState.action == ACTION_MANAGE) {
final Uri rootUri = getIntent().getData();
if
(rootUri !=
null
) {
new
RestoreRootTask(rootUri).executeOnExecutor(getCurrentExecutor());
}
else
{
new
RestoreStackTask().execute();
}
}
else
{
new
RestoreStackTask().execute();
}
}
else
{
onCurrentDirectoryChanged(ANIM_NONE);
}
|
其中有个判断条件是mState.action == ACTION_GET_CONTENT
表示调用documentsui的人想获得的是文件系统的内容,也就是说本文开始那样的调用方式将会转到这个条件中来,执行
1
2
3
4
5
6
|
if
(mState.action == ACTION_GET_CONTENT) {
final Intent moreApps =
new
Intent(getIntent());
moreApps.setComponent(
null
);
moreApps.setPackage(
null
);
RootsFragment.show(getFragmentManager(), moreApps);
}
|
中的代码。本文也主要是围绕documentsui的这一功能来分析的。除此之外从上面的代码还可以看出documentsui还有些其他的功能对应的是ACTION_CREATE,
ACTION_MANAGE等,目前不清楚这些功能具体做什么。
回到
mState.action == ACTION_GET_CONTENT
这个条件中,他调用了RootsFragment
中的show方法:
1
2
3
4
5
6
7
8
9
|
public static void show(FragmentManager fm, Intent includeApps) {
final Bundle args =
new
Bundle();
args.putParcelable(EXTRA_INCLUDE_APPS, includeApps);
final RootsFragment fragment =
new
RootsFragment();
fragment.setArguments(args);
final FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.container_roots, fragment);
ft.commitAllowingStateLoss();
}
|
可以看出这是为了显示RootsFragment
自己,其实RootsFragment
就是DrawerLayout的菜单部分。
点击某个菜单之后(如图片)右边的目录树做相应的切换,那么这个过程是如何进行的呢?
在
中,菜单列表是一个ListView当然点击菜单就会进入到listvIew的RootsFragment
OnItemClickListener
中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private OnItemClickListener mItemListener =
new
OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.
this
);
final Item item = mAdapter.getItem(position);
if
(item
instanceof
RootItem) {
activity.onRootPicked(((RootItem) item).root,
true
);
}
else
if
(item
instanceof
AppItem) {
activity.onAppPicked(((AppItem) item).info);
}
else
{
throw
new
IllegalStateException(
"Unknown root: "
+ item);
}
}
};
|
我们注意上面的界面图,可以看到左边菜单不仅仅有关于文件类型的,还有可以选择应用程序的。因此点击菜单之后上面的代码首先判断了你点击的RootItem
类型,这里我们不去研究用户点击应用程序的情况。
当用户点击的是文件类型(如图片)菜单,则会调用DocumentsActivity
的onRootPicked:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public void onRootPicked(RootInfo root, boolean closeDrawer) {
// Clear entire backstack and start in new root
mState.stack.root = root;
mState.stack.clear();
mState.stackTouched =
true
;
if
(!mRoots.isRecentsRoot(root)) {
new
PickRootTask(root).executeOnExecutor(getCurrentExecutor());
}
else
{
onCurrentDirectoryChanged(ANIM_SIDE);
}
if
(closeDrawer) {
setRootsDrawerOpen(
false
);
}
}
|
这里又有一个条件分支,判断了是否是“最近”菜单(
isRecentsRoot
,注意比照上面的界面图),如果是则执行new
PickRootTask(root).executeOnExecutor(getCurrentExecutor());
不是则调用onCurrentDirectoryChanged
方法。因为分支过多我只研究最基本的,也就是列出目录文件的功能所以忽略“最近”菜单被点击的情况,进入到
中(其实反之最终也还是要进入到onCurrentDirectoryChanged中),很烦的是onCurrentDirectoryChanged
中又有很多分支情况,但可以肯定的是onCurrentDirectoryChanged
列出目录文件的功能
是调用
DirectoryFragment.showNormal(fm, root, cwd, anim);
完成的。所以我的重点就在
DirectoryFragment
这个类中了。
我想了解在
DirectoryFragment
中是如何列出文件和目录 同时是如何显示缩略图的。
是使用loader机制来加载内容的。DirectoryFragment
getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
其中
mCallbacks
如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
mCallbacks =
new
LoaderCallbacks
@Override
public Loader
final String query = getArguments().getString(EXTRA_QUERY);
Uri contentsUri;
switch
(mType) {
case
TYPE_NORMAL:
contentsUri = DocumentsContract.buildChildDocumentsUri(
doc.authority, doc.documentId);
if
(state.action == ACTION_MANAGE) {
contentsUri = DocumentsContract.setManageMode(contentsUri);
}
return
new
DirectoryLoader(
context, mType, root, doc, contentsUri, state.userSortOrder);
case
TYPE_SEARCH:
contentsUri = DocumentsContract.buildSearchDocumentsUri(
root.authority, root.rootId, query);
if
(state.action == ACTION_MANAGE) {
contentsUri = DocumentsContract.setManageMode(contentsUri);
}
return
new
DirectoryLoader(
context, mType, root, doc, contentsUri, state.userSortOrder);
case
TYPE_RECENT_OPEN:
final RootsCache roots = DocumentsApplication.getRootsCache(context);
return
new
RecentLoader(context, roots, state);
default
:
throw
new
IllegalStateException(
"Unknown type "
+ mType);
}
}
@Override
public void onLoadFinished(Loader
if
(!isAdded())
return
;
mAdapter.swapResult(result);
// Push latest state up to UI
// TODO: if mode change was racing with us, don't overwrite it
if
(result.mode != MODE_UNKNOWN) {
state.derivedMode = result.mode;
}
state.derivedSortOrder = result.sortOrder;
((DocumentsActivity) context).onStateChanged();
updateDisplayState();
// When launched into empty recents, show drawer
if
(mType == TYPE_RECENT_OPEN && mAdapter.isEmpty() && !state.stackTouched) {
((DocumentsActivity) context).setRootsDrawerOpen(
true
);
}
// Restore any previous instance state
final SparseArray
if
(container !=
null
&& !getArguments().getBoolean(EXTRA_IGNORE_STATE,
false
)) {
getView().restoreHierarchyState(container);
}
else
if
(mLastSortOrder != state.derivedSortOrder) {
mListView.smoothScrollToPosition(0);
mGridView.smoothScrollToPosition(0);
}
mLastSortOrder = state.derivedSortOrder;
}
@Override
public void onLoaderReset(Loader
mAdapter.swapResult(
null
);
}
};
|
其中很重要的是启用了DirectoryLoader类。DirectoryLoader完成了目录的加载。本文不对其深入讲解。当loader加载完成(
onLoadFinished
)会将结果result
传递给mAdapter
:mAdapter.swapResult(result),这个result的类型是
DirectoryResult
。上面说了我想知道文件或者目录的缩略图是如何加载的,所以需要了解这个mAdapter
的内部情况。
mAdapter的声明如下:
private DocumentsAdapter mAdapter;
DocumentsAdapter 是
的一个内部类,我直接跳到他的getview方法:DirectoryFragment
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if
(position < mCursorCount) {
return
getDocumentView(position, convertView, parent);
}
else
{
position -= mCursorCount;
convertView = mFooters.get(position).getView(convertView, parent);
// Only the view itself is disabled; contents inside shouldn't
// be dimmed.
convertView.setEnabled(
false
);
return
convertView;
}
}
|
貌似没什么东西,继续跟着跳到getDocumentView
,其中有这样的一段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
if
(showThumbnail) {
final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
final Bitmap cachedResult = thumbs.get(uri);
if
(cachedResult !=
null
) {
iconThumb.setImageBitmap(cachedResult);
cacheHit =
true
;
}
else
{
iconThumb.setImageDrawable(
null
);
final ThumbnailAsyncTask task =
new
ThumbnailAsyncTask(
uri, iconMime, iconThumb, mThumbSize);
iconThumb.setTag(task);
ProviderExecutor.forAuthority(docAuthority).execute(task);
}
}
|
缩略图获得的过程是:首先从thumbs
这个ThumbnailCache
中获取缓存图片,定义如下
1
2
|
final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
context, mThumbSize);
|
如果缓存中没找到,则开启ThumbnailAsyncTask
。
然后再继续分析ThumbnailAsyncTask
吧 总之这是一个很繁琐的过程。
本文只是理出一条分析的线索,希望对大家还是有所帮助。