1. 程式人生 > >Android非同步任務處理之AsyncTaskLoader的使用

Android非同步任務處理之AsyncTaskLoader的使用

最近專案中涉及到載入本地的地名.db檔案,資料量大,自然不能直接放在UI執行緒中操作,好在Google在Android3.0以後,提供了AsyncTaskLoader來做一些耗時的非同步任務。

一 官方對AsyncTaskLoader的定義及特點介紹如下:

Abstract Loader that provides an AsyncTask to do the work

Introduced in Android 3.0, loaders make it easy to asynchronously load data in an activity or fragment. Loaders have these characteristics:

1、They are available to every Activity and Fragment.
//支援Activity和Fragment
2、They provide asynchronous loading of data.
//非同步下載 (就是不影響UI執行緒)
3、They monitor the source of their data and deliver new results when the content changes.
//當資料來源改變時能及時通知客戶端
4、They automatically reconnect to the last loader’s cursor when being recreated after a configuration change. Thus, they don’t need to re-query their data.
//發生configuration change時自動重連線

二 實際專案介紹

下面引用官方的一個展示當前裝置所有已安裝應用程式的DEMO,來對AsyncTaskLoader的用法做一個詳細的介紹:
專案結構如圖:

這裡寫圖片描述

第一步:我們需要寫一個對應於每一個應用程式的實體類,該實體類包含應用程式圖示和標籤兩個屬性。
AppEntry.java:

/**
 * Created by Administrator on 2016/5/25.
 */
public class AppEntry {
    private String mLabel;//應用文字標籤
    private Drawable mIcon;//應用圖示

    private final
AppListLoader mLoader; private final ApplicationInfo mInfo;//<application>節點資訊,只有一個 //PackageInfo、ApplicationInfo、ActivityInfo、ResolveInfo四種資訊類的一種 private final File mApkFile; private boolean mMounted; public AppEntry(AppListLoader mLoader,ApplicationInfo mInfo ) { this.mInfo = mInfo; this.mLoader = mLoader; mApkFile=new File(mInfo.sourceDir);//sourceDir=Full path to the location of this package } public ApplicationInfo getApplicationInfo() { return mInfo; } public String getLabel() { return mLabel; } public Drawable getIcon() { if (mIcon == null) { if (mApkFile.exists()) { mIcon = mInfo.loadIcon(mLoader.mPm); //public Drawable loadIcon (PackageManager pm){}獲取應用圖示 return mIcon; } else { mMounted = false; } } else if (!mMounted) { // If the app wasn't mounted but is now mounted, reload its icon. if (mApkFile.exists()) { mMounted = true; mIcon = mInfo.loadIcon(mLoader.mPm); return mIcon; } } else { return mIcon; } return mLoader.getContext().getResources() .getDrawable(android.R.drawable.sym_def_app_icon);//否則返回預設的小機器人 } @Override public String toString() { return mLabel; } void loadLabel(Context context) { if (mLabel == null || !mMounted) { if (!mApkFile.exists()) { mMounted = false; mLabel = mInfo.packageName;//獲取程式名稱 } else { mMounted = true; CharSequence label = mInfo.loadLabel(context.getPackageManager()); mLabel = label != null ? label.toString() : mInfo.packageName; } } } }

第二步:需要寫一個自己的AppListLoader ,繼承自AsyncTaskLoader,並實現其相關抽象方法。

(1)onStartLoading:註冊一些監聽器到loader上,並且執行一次forceLoad(); 否則loader不會開始工作
(2)loadInBackground:不用說,在這裡就是載入資料並且返回,其實這個資料就返回到了LoaderManager的onLoadFinished方法第二個引數
(3)onStopLoading:停止載入資料,但不要停止監聽也不要釋放資料,就可以隨時重啟loader
(4)onReset:先確保已經停止載入資料了,然後釋放掉監聽器並設為null
(5)onCanceled: 在這裡可以釋放資源,如果是list就不需要做什麼了,但是象cursor或者打開了什麼檔案就應該關閉一下;

AppListLoader .java:

public class AppListLoader  extends AsyncTaskLoader<List<AppEntry>> {
    private static final String TAG = "ADP_AppListLoader";
    private static final boolean DEBUG = true;
    final PackageManager mPm;//包管理器
    private List<AppEntry> mApps;//裝在應用程式實體的容器

    // An observer to notify the Loader when new apps are installed/updated.
    private InstalledAppsObserver mAppsObserver;//非系統應用程式安裝或者解除安裝的廣播接收器

    // The observer to notify the Loader when the system Locale has been changed.
    private SystemLocaleObserver mLocaleObserver;//系統應用程式安裝或者解除安裝的廣播接收器

    public AppListLoader(Context context) {
        super(context);
        mPm = getContext().getPackageManager();
        Log.i("TAG","AppListLoader(Context)");
    }

    @Override
    protected void onStartLoading() {
        Log.i("TAG","onStartLoading()");
        if(mApps!=null){
            deliverResult(mApps);
        }
        // Register the observers that will notify the Loader when changes are made.
        if (mAppsObserver == null) {
            mAppsObserver = new InstalledAppsObserver(this);//註冊一個非系統應用程式的接收器
        }
        if (mLocaleObserver == null) {
            mLocaleObserver = new SystemLocaleObserver(this);//註冊一個系統應用程式的接收器
        }

        if (takeContentChanged()) {
            forceLoad();
        } else if (mApps == null) {
            forceLoad();//強制載入資料
        }
    }

    @Override
    public void forceLoad() {
        Log.i("TAG","forceLoad()");
        super.forceLoad();
    }

    @Override
    public List<AppEntry> loadInBackground() {
        Log.i("TAG","loadInBackground()");
        List<ApplicationInfo> apps=mPm.getInstalledApplications(0);
//        public static final int FILTER_ALL_APP = 0; // 所有應用程式  
//        public static final int FILTER_SYSTEM_APP = 1; // 系統程式  
//        public static final int FILTER_THIRD_APP = 2; // 第三方應用程式  
//        public static final int FILTER_SDCARD_APP = 3; // 安裝在SDCard的應用程式 
        if(apps==null){
            apps=new ArrayList<>();
        }
        List<AppEntry> entries=new ArrayList<>(apps.size());
        //開始載入資料
        for(int i=0;i<apps.size();i++){
            AppEntry appEntry=new AppEntry(this,apps.get(i));
            appEntry.loadLabel(getContext());
            entries.add(appEntry);
        }
        //Sort the list
        Collections.sort(entries,ALPHA_COMPARATOR);//對應用程式進行排序
        return entries;
    }

    @Override
    public void deliverResult(List<AppEntry> datas) {//分發loadInBackground()方法返回的結果
        Log.i("TAG","deliverResult()");
        if(isReset()){
            if(datas!=null){
                releaseResources(datas);//可以釋放相關資源
                return;
            }
        }
        List<AppEntry> oldApps=mApps;
        mApps=datas;

        if(isStarted()){
            super.deliverResult(datas);
        }

        if(oldApps!=null&&oldApps!=datas){
            releaseResources(oldApps);
        }
    }

    @Override
    protected void onStopLoading() {//停止載入資料
        Log.i("TAG","onStopLoading()");
        cancelLoad();
    }

    @Override
    protected void onReset() {
        Log.i("TAG","onReset()");
        onStopLoading();
        // At this point we can release the resources associated with 'apps'.
        if (mApps != null) {
            releaseResources(mApps);
            mApps = null;
        }
        // The Loader is being reset, so we should stop monitoring for changes.
        if (mAppsObserver != null) {
            getContext().unregisterReceiver(mAppsObserver);//登出廣播接收器
            mAppsObserver = null;
        }
        if (mLocaleObserver != null) {
            getContext().unregisterReceiver(mLocaleObserver);//登出廣播接收器
            mLocaleObserver = null;
        }
    }

    @Override
    public void onCanceled(List<AppEntry> apps) {  // Attempt to cancel the current asynchronous load.
        super.onCanceled(apps);
        Log.i("TAG","onCanceled()");
        releaseResources(apps);
    }


    /**
     * Helper method to take care of releasing resources associated with an
     * actively loaded data set.
     */
    private void releaseResources(List<AppEntry> apps) {
        // For a simple List, there is nothing to do. For something like a Cursor,
        // we would close it in this method. All resources associated with the
        // Loader should be released here.
    }

    /**
     * Performs alphabetical comparison of {@link AppEntry} objects. This is
     * used to sort queried data in {@link }.
     */
    private static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
        Collator sCollator = Collator.getInstance();

        @Override
        public int compare(AppEntry object1, AppEntry object2) {
            return sCollator.compare(object1.getLabel(), object2.getLabel());
        }
    };
}

第三步:在MainActivity中呼叫AsyncTaskLoader,並繼承LoaderManager.LoaderCallbacks的介面,重寫介面方法:

(1)onCreateLoader: 這個是建立一個AsyncTaskLoader並返回,我們在裡面new一個自己寫的AppListLoader並返回就OK了;
(2)onLoadFinished: 這個是載入完成後可以更新UI,在這裡就是setAdapter了 而這個載入過程其實就是在CursorLoader裡面完成的,
只不過系統幫我們完成了,而如果自定義loader的話就要自己完成,這就是區別;
(3)onLoaderReset: loader的重置,在這裡一般讓UI不顯示資料就行;

MainActivity .java:

public class MainActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//將AppListFragment新增到當前的activity裡 
        FragmentManager fm=getSupportFragmentManager();
        if(fm.findFragmentById(android.R.id.content)==null){
            AppListFragment list=new AppListFragment();
            fm.beginTransaction().add(android.R.id.content,list).commit();
        }

    }
//實現LoaderManager.LoaderCallbacks的介面
    public static class AppListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<List<AppEntry>>{
        private static final String TAG = "ADP_AppListFragment";
        private static final boolean DEBUG = true;
        private AppListAdapter mAdapter;
        private static final int LOADER_ID = 1;

        @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            setHasOptionsMenu(true);
            mAdapter=new AppListAdapter(getActivity());
            setEmptyText("No Applications");
            setListAdapter(mAdapter);
            setListShown(false);

            if (getLoaderManager().getLoader(LOADER_ID) == null) {
                Log.i("TAG", "Initializing the new Loader...");
            } else {
                Log.i("TAG", "Reconnecting with existing Loader (id '1')...");
            }
            getLoaderManager().initLoader(LOADER_ID, null, this);
        }

        @Override
        public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {
            Log.i("TAG", "onCreateLoader()");
            return new AppListLoader(getActivity());
        }

        @Override
        public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {
            Log.i("TAG", "onLoadFinished()");
            mAdapter.setData(data);
            if(isResumed()){
                setListShown(true);
            }else {
                setListShownNoAnimation(true);
            }
        }

        @Override
        public void onLoaderReset(Loader<List<AppEntry>> loader) {
            Log.i("TAG", "onLoaderReset()");
            mAdapter.setData(null);

        }
}

然後執行程式如下:

這裡寫圖片描述

開啟應用,AppListLoader中核心方法執行的先後順序:

05-25 12:57:46.050 11184-11184/com.troy.applistloader I/TAG: +++ Calling initLoader()! +++
05-25 12:57:46.050 11184-11184/com.troy.applistloader I/TAG: +++ Initializing the new Loader... +++
05-25 12:57:46.050 11184-11184/com.troy.applistloader I/TAG: onCreateLoader()
05-25 12:57:46.050 11184-11184/com.troy.applistloader I/TAG: onStartLoading()
05-25 12:57:46.060 11184-11184/com.troy.applistloader I/TAG: forceLoad()
05-25 12:57:46.060 11184-13196/com.troy.applistloader I/TAG: loadInBackground()
05-25 12:57:47.530 11184-11184/com.troy.applistloader I/TAG: deliverResult()
05-25 12:57:47.530 11184-11184/com.troy.applistloader I/TAG: onLoadFinished()

按返回鍵,會執行的方法及執行順序:

05-25 13:00:08.790 11184-11184/com.troy.applistloader I/TAG: onStopLoading()
05-25 13:00:08.790 11184-11184/com.troy.applistloader I/TAG: onLoaderReset()
05-25 13:00:08.790 11184-11184/com.troy.applistloader I/TAG: onReset()
05-25 13:00:08.790 11184-11184/com.troy.applistloader I/TAG: onStopLoading()

三 總結

本專案的學習之後,我們應該掌握以下幾點:
(1)理解AsyncTaskLoader的每一個核心方法的作用及呼叫時機,以及如何自定義一個AsyncTaskLoader。
(2)如何在Fragement中啟動AsyncTaskLoader,繼承LoaderManager.LoaderCallbacks,實現介面的三個方法。
(3)應該瞭解AsyncTaskLoader的底層實際上是執行的AsyncTask,這個可以看看原始碼。
(4)如何應用ApplicationInfo,獲取相關的程式資訊。