android4.4的文件管理器documentsui源碼解析

frgopf2h 8年前發布 | 18K 次閱讀 Android開發 移動開發

 

在4.4以上的版本中如果通過如下的Intent調用Activity:

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方法中:

// 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的人想獲得的是文件系統的內容,也就是說本文開始那樣的調用方式將會轉到這個條件中來,執行

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方法:

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的菜單部分。

點擊某個菜單之后(如圖片)右邊的目錄樹做相應的切換,那么這個過程是如何進行的呢?

RootsFragment中,菜單列表是一個ListView當然點擊菜單就會進入到listvIew的OnItemClickListener中:

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:

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中是如何列出文件和目錄 同時是如何顯示縮略圖的。


DirectoryFragment是使用loader機制來加載內容的。

getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);

其中mCallbacks如下:

mCallbacks = new LoaderCallbacks<DirectoryResult>() {
    @Override
    public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
        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<DirectoryResult> loader, DirectoryResult result) {
        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<Parcelable> container = state.dirState.remove(mStateKey);
        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<DirectoryResult> loader) {
        mAdapter.swapResult(null);
    }
};

其中很重要的是啟用了DirectoryLoader類。DirectoryLoader完成了目錄的加載。本文不對其深入講解。當loader加載完成(onLoadFinished)會將結果result傳遞給mAdaptermAdapter.swapResult(result),這個result的類型是DirectoryResult。上面說了我想知道文件或者目錄的縮略圖是如何加載的,所以需要了解這個mAdapter的內部情況。

mAdapter的聲明如下:

private DocumentsAdapter mAdapter;

DocumentsAdapter 是DirectoryFragment的一個內部類,我直接跳到他的getview方法:

@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,其中有這樣的一段代碼:

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
中獲取緩存圖片,定義如下

final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
                    context, mThumbSize);

如果緩存中沒找到,則開啟ThumbnailAsyncTask

然后再繼續分析ThumbnailAsyncTask吧 總之這是一個很繁瑣的過程。

本文只是理出一條分析的線索,希望對大家還是有所幫助。



 本文由用戶 frgopf2h 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!