关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android 异步加载神器 Loader 全解析

    Android 异步加载神器 Loader 全解析

    2015-10-08 00:00:00 出处:安卓弟
    分享

    在之前呢,我们经常会有这种需求,比如在某个activity,或者某个fragment里面,我们需要查找某个数据源,并且显示出来,当数据源自己更新的时候,界面也要及时响应。

    当然咯,查找数据这个过程可能很短,但是也可能很漫长,为了避免anr,我们都是开启一个子线程去查找,然后通过handler来更新我们的ui界面。但是,考虑到activity和

    fragment 复杂的生命周期,上述的方法 使用起来会很不方便,毕竟你要考虑到保存现场 还原现场 等等复杂的工作来保证你的app无懈可击。所以后来呢谷歌就帮我们推出了一个新的东西—Loader。他可以帮我们完成上述所有功能!实在是很强大。

    假如你有阅读英文技术文档的习惯 那么谷歌官方的文档 也许比我所说的更加完美。具体可以参考如下:

    http://developer.android.com/intl/zh-cn/reference/android/app/LoaderManager.html

    http://developer.android.com/intl/zh-cn/reference/android/content/AsyncTaskLoader.html

    http://developer.android.com/intl/zh-cn/guide/components/loaders.html

    我所述的内容也是主要基于上述三篇文档。

    首先呢,我们来看第一个例子,这个例子也是官方的推荐了,我给简化了一下,主要是监听手机里 联系人这个数据源。当数据源改变的时候 自动update 我们的ui。

    package com.example.administrator.modifytestview;
    
    import android.app.Activity;
    import android.app.FragmentManager;
    import android.app.ListFragment;
    import android.app.LoaderManager;
    import android.content.CursorLoader;
    import android.content.Loader;
    import android.database.Cursor;
    import android.net.Uri;
    import android.os.Bundle;
    import android.provider.ContactsContract.Contacts;
    import android.util.Log;
    import android.view.View;
    import android.widget.ListView;
    import android.widget.SimpleCursorAdapter;
    
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            FragmentManager fm = getFragmentManager();
            CursorLoaderListFragment list = new CursorLoaderListFragment();
            fm.beginTransaction().replace(R.id.root, list).commit();
    
        }
    
        public static class CursorLoaderListFragment extends ListFragment
                implements LoaderManager.LoaderCallbacks<Cursor> {
    
            // This is the Adapter being used to display the list's data.
            SimpleCursorAdapter mAdapter;
    
            // If non-null, this is the current filter the user has provided.
            String mCurFilter;
    
            @Override
            public void onActivityCreated(Bundle savedInstanceState) {
    
                mAdapter = new SimpleCursorAdapter(getActivity(),
                        android.R.layout.simple_list_item_2, null,
                        new String[]{Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS},
                        new int[]{android.R.id.text1, android.R.id.text2}, 0);
                setListAdapter(mAdapter);
    
                //这个地方初始化了我们的loader
                getLoaderManager().initLoader(0, null, this);
    
                super.onActivityCreated(savedInstanceState);
            }
    
            @Override
            public void onListItemClick(ListView l, View v, int position, long id) {
                // Insert desired behavior here.
                Log.i("FragmentComplexList", "Item clicked: " + id);
            }
    
            // These are the Contacts rows that we will retrieve.
            static final String[] CONTACTS_SUMMARY_PROJECTION = new String[]{
                    Contacts._ID,
                    Contacts.DISPLAY_NAME,
                    Contacts.CONTACT_STATUS,
                    Contacts.CONTACT_PRESENCE,
                    Contacts.PHOTO_ID,
                    Contacts.LOOKUP_KEY,
            };
    
            //只会调用一次
            public Loader<Cursor> onCreateLoader(int id, Bundle args) {
                // This is called when a new Loader needs to be created.  This
                // sample only has one Loader, so we don't care about the ID.
                // First, pick the base URI to use depending on whether we are
                // currently filtering.
                Uri baseUri;
                if (mCurFilter != null) {
                    baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                            Uri.encode(mCurFilter));
                } else {
                    baseUri = Contacts.CONTENT_URI;
                }
    
                // Now create and return a CursorLoader that will take care of
                // creating a Cursor for the data being displayed.
                String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                        + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                        + Contacts.DISPLAY_NAME + " != '' ))";
                //返回的是对这个数据源的监控
                return new CursorLoader(getActivity(), baseUri,
                        CONTACTS_SUMMARY_PROJECTION, select, null,
                        Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
            }
    
            //每次数据源都有更新的时候,就会回调这个方法,然后update 我们的ui了。
            public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    
                // Swap the new cursor in.  (The framework will take care of closing the
                // old cursor once we return.)
                mAdapter.swapCursor(data);
    
                // The list should now be shown.
                if (isResumed()) {
                    setListShown(true);
                } else {
                    setListShownNoAnimation(true);
                }
            }
    
            public void onLoaderReset(Loader<Cursor> loader) {
                // This is called when the last Cursor provided to onLoadFinished()
                // above is about to be closed.  We need to make sure we are no
                // longer using it.
                mAdapter.swapCursor(null);
            }
        }
    
    }

    可以仔细的观察一下这个代码,我们能发现 使用loader所需要的一些步骤:

    1.需要一个activity或者是fragment,当然在上述的例子里 我们使用的是fragment。

    2.一个LoaderManger的实例,注意看53行,我们get了一个loadermanager。这个地方就是获取实例了。

    3.需要一个CursorLoader,并且从contentProvider获取数据源,90-97行 就是这么做的。

    4.需要实现一个LoaderCallBack的这个接口,然后在几个回调方法里 写上我们自己业务的逻辑 即可。你看34行就是继承的接口。

    还有3个回调方法在那,我们都在里面实现了自己的逻辑。

    到这,其实一看,思路还是很清晰的。那到这里 有人肯定要说了。你这个没用啊,要实现contentprovider,我们的app不需要做数据共享的,能否直接操作数据库呢?答案是可以的。在这里我们也可以构造出一个场景。假设有一张学生表。我们点击add按钮,就自动往这个表里面增加一个数据,然后下面有个listview 会自动捕捉到 这个数据源的变化,然后自动更新列表。

    我们可以知道 上面那个demo里面 CursorLoader的定义是这样的

    public class CursorLoader extends AsyncTaskLoader<Cursor> {

    我们现在要实现一个不用contentProvider的Loader 也是基于AsyncTaskLoader来的。

    先给出一个抽象类:

    package com.example.administrator.activeandroidtest3;
    
    import android.content.AsyncTaskLoader;
    import android.content.Context;
    import android.database.Cursor;
    
    public abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor> {
        private Cursor mCursor;
    
        public SimpleCursorLoader(Context context) {
            super(context);
        }
    
        /* 在子线程里运作 */
        @Override
        public abstract Cursor loadInBackground();
    
        /* 在ui 线程里运作 */
        @Override
        public void deliverResult(Cursor cursor) {
            if (isReset()) {
                // An async query came in while the loader is stopped
                if (cursor != null) {
                    cursor.close();
                }
                return;
            }
            Cursor oldCursor = mCursor;
            mCursor = cursor;
    
            if (isStarted()) {
                super.deliverResult(cursor);
            }
    
            if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
                oldCursor.close();
            }
        }
    
        @Override
        protected void onStartLoading() {
            if (mCursor != null) {
                deliverResult(mCursor);
            }
            if (takeContentChanged() || mCursor == null) {
                forceLoad();
            }
        }
    
        @Override
        protected void onStopLoading() {
            cancelLoad();
        }
    
        @Override
        public void onCanceled(Cursor cursor) {
            if (cursor != null && !cursor.isClosed()) {
                cursor.close();
            }
        }
    
        @Override
        protected void onReset() {
            super.onReset();
    
            onStopLoading();
    
            if (mCursor != null && !mCursor.isClosed()) {
                mCursor.close();
            }
            mCursor = null;
        }
    }

    然后我们再接着定义我们最终的 不需要provider的loader实现类(注意你假如想写的比较完美的话 cursor记得用抽象类的,抽象类的那个就不要写成private的了,我这里为了图简单 直接用自己构造的)。

    package com.example.administrator.activeandroidtest3;
    
    import android.content.Context;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    
    /**
     * Created by Administrator on 2015/10/7.
     */
    public class SpecialLoader extends SimpleCursorLoader {
    
        ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
        private Context context;
    
        public SpecialLoader(Context context) {
            super(context);
            this.context = context;
    
        }
    
        @Override
        public Cursor loadInBackground() {
            DatabaseHelper dh = new DatabaseHelper(context, "Test.db");
            SQLiteDatabase database = dh.getReadableDatabase();
            String table = "Student";
            String[] columns = new String[]{"Name", "No"};
            //这个地方因为我用的是activeandroid 的orm 框架,所以默认的自增长主键是Id,但是SimpleCursorAdapter
            //需要的是_id 否则会报错,所以这里要重命名一下
            Cursor cursor = database.rawQuery("SELECT Id AS _id,Name,No FROM Student", null);
            if (database != null) {
                if (cursor != null) {
                    //注册一下这个观察者
                    cursor.registerContentObserver(mObserver);
                    //这边也要注意 一定要监听这个uri的变化。但是假如你这个uri没有对应的provider的话
                    //记得在你操作数据库的时候 通知一下这个uri
                    cursor.setNotificationUri(context.getContentResolver(), MainActivity.uri);
                }
    
            }
            return cursor;
        }
    }

    然后我们在简单看下activity 主类里的代码:

    package com.example.administrator.activeandroidtest3;
    
    import android.app.Activity;
    import android.app.LoaderManager;
    import android.content.Loader;
    import android.database.Cursor;
    import android.net.Uri;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.view.View;
    import android.widget.ListView;
    import android.widget.SimpleCursorAdapter;
    import android.widget.TextView;
    
    import com.activeandroid.query.Select;
    
    import java.util.List;
    import java.util.Random;
    
    public class MainActivity extends Activity implements LoaderManager.LoaderCallbacks {
    
        public static final Uri uri = Uri.parse("content://com.example.student");
        private TextView addTv;
        private ListView lv;
        private SimpleCursorAdapter adapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            addTv = (TextView) this.findViewById(R.id.add);
            addTv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Student student = new Student();
                    student.name = getRandomString(5);
                    student.no = (int) (Math.random() * 1000) + "";
                    student.sex = (int) (Math.random() * 1);
                    student.save();
                    //操作完数据库要notify 不然loader那边收不到哦
                    getContentResolver().notifyChange(uri, null);
    
                }
            });
            lv = (ListView) this.findViewById(R.id.lv);
            adapter = new SimpleCursorAdapter(MainActivity.this,
                    android.R.layout.simple_list_item_2, null,
                    new String[]{"Name", "No"},
                    new int[]{android.R.id.text1, android.R.id.text2}, 0);
            lv.setAdapter(adapter);
            getLoaderManager().initLoader(0, null, this);
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.menu_main, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
    
            //noinspection SimplifiableIfStatement
            if (id == R.id.action_settings) {
                return true;
            }
    
            return super.onOptionsItemSelected(item);
        }
    
        public static String getRandomString(int length) { //length表示生成字符串的长度
            String base = "abcdefghijklmnopqrstuvwxyz0123456789";
            Random random = new Random();
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < length; i++) {
                int number = random.nextInt(base.length());
                sb.append(base.charAt(number));
            }
            return sb.toString();
        }
    
        @Override
        public Loader onCreateLoader(int id, Bundle args) {
            SpecialLoader loader = new SpecialLoader(MainActivity.this);
            return loader;
        }
    
        @Override
        public void onLoadFinished(Loader loader, Object data) {
            adapter.swapCursor((Cursor) data);
        }
    
        @Override
        public void onLoaderReset(Loader loader) {
    
        }
    }

    最后我们看下运行的效果:

    好,那到这里 又有人要说了,你这个说来说去 还不是只能支持provider或者db类型的数据源吗?好 接着往下,我们给出另外一个例子,不过这个例子是谷歌官方的例子,我就取其中重要的部分给予注释讲解。

    http://developer.android.com/intl/zh-cn/reference/android/content/AsyncTaskLoader.html

    首先说一下 这个例子是干嘛的,他主要是监听手机里app list的变化,比如你删除了一个应用安装了一个应用,马上就能捕捉到你的手机里app list的变化 并显示在界面,大家都知道 监听app list是通过监听系统广播来完成的。 我就主要讲一下 这个官方demo里 是如何在监听到系统广播以后和loader结合起来然后自动回调方法的。

    /**
     * Helper class to look for interesting changes to the installed apps
     * so that the loader can be updated.
     */
    public static class PackageIntentReceiver extends BroadcastReceiver {
        final AppListLoader mLoader;
    
        //这个构造函数是很重要的 他接收的 就是自定义的loader
        public PackageIntentReceiver(AppListLoader loader) {
            mLoader = loader;
            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
            filter.addDataScheme("package");
            mLoader.getContext().registerReceiver(this, filter);
            // Register for events related to sdcard installation.
            IntentFilter sdFilter = new IntentFilter();
            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
            //在这个地方 直接用loader来注册这个广播接收器
            mLoader.getContext().registerReceiver(this, sdFilter);
        }
    
        //在收到广播以后 什么事情都没有做,而是调用了loader的onContentChanged方法
        @Override public void onReceive(Context context, Intent intent) {
            // Tell the loader about the change.
            mLoader.onContentChanged();
        }
    }

    你看这里的25-26行 调用了 loader的onContentChanged方法。继续看下面的loader

    /**
     * A custom Loader that loads all of the installed applications.
     */
    public static class AppListLoader extends AsyncTaskLoader<List<AppEntry>> {
        final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();
        final PackageManager mPm;
    
        List<AppEntry> mApps;
        PackageIntentReceiver mPackageObserver;
    
        public AppListLoader(Context context) {
            super(context);
    
            // Retrieve the package manager for later use; note we don't
            // use 'context' directly but instead the save global application
            // context returned by getContext().
            mPm = getContext().getPackageManager();
        }
    
        //实际上最重要的就是这个方法了,每当这个回调方法被调用的时候 就去取applist 然后将结果返回到
        //onLoadFinished 这个回调方法里面!
        @Override public List<AppEntry> loadInBackground() {
            // Retrieve all known applications.
            List<ApplicationInfo> apps = mPm.getInstalledApplications(
                    PackageManager.GET_UNINSTALLED_PACKAGES |
                    PackageManager.GET_DISABLED_COMPONENTS);
            if (apps == null) {
                apps = new ArrayList<ApplicationInfo>();
            }
    
            final Context context = getContext();
    
            // Create corresponding array of entries and load their labels.
            List<AppEntry> entries = new ArrayList<AppEntry>(apps.size());
            for (int i=0; i<apps.size(); i++) {
                AppEntry entry = new AppEntry(this, apps.get(i));
                entry.loadLabel(context);
                entries.add(entry);
            }
    
            // Sort the list.
            Collections.sort(entries, ALPHA_COMPARATOR);
    
            // Done!
            return entries;
        }
    
        /**
         * Called when there is new data to deliver to the client.  The
         * super class will take care of delivering it; the implementation
         * here just adds a little more logic.
         */
        @Override public void deliverResult(List<AppEntry> apps) {
            if (isReset()) {
                // An async query came in while the loader is stopped.  We
                // don't need the result.
                if (apps != null) {
                    onReleaseResources(apps);
                }
            }
            List<AppEntry> oldApps = mApps;
            mApps = apps;
    
            if (isStarted()) {
                // If the Loader is currently started, we can immediately
                // deliver its results.
                super.deliverResult(apps);
            }
    
            // At this point we can release the resources associated with
            // 'oldApps' if needed; now that the new result is delivered we
            // know that it is no longer in use.
            if (oldApps != null) {
                onReleaseResources(oldApps);
            }
        }
    
        /**
         * Handles a request to start the Loader.
         */
        @Override protected void onStartLoading() {
            if (mApps != null) {
                // If we currently have a result available, deliver it
                // immediately.
                deliverResult(mApps);
            }
    
            // Start watching for changes in the app data.
            if (mPackageObserver == null) {
                mPackageObserver = new PackageIntentReceiver(this);
            }
    
            // Has something interesting in the configuration changed since we
            // last built the app list 
            boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());
    
            if (takeContentChanged() || mApps == null || configChange) {
                // If the data has changed since the last time it was loaded
                // or is not currently available, start a load.
                forceLoad();
            }
        }
    
        /**
         * Handles a request to stop the Loader.
         */
        @Override protected void onStopLoading() {
            // Attempt to cancel the current load task if possible.
            cancelLoad();
        }
    
        /**
         * Handles a request to cancel a load.
         */
        @Override public void onCanceled(List<AppEntry> apps) {
            super.onCanceled(apps);
    
            // At this point we can release the resources associated with 'apps'
            // if needed.
            onReleaseResources(apps);
        }
    
        /**
         * Handles a request to completely reset the Loader.
         */
        @Override protected void onReset() {
            super.onReset();
    
            // Ensure the loader is stopped
            onStopLoading();
    
            // At this point we can release the resources associated with 'apps'
            // if needed.
            if (mApps != null) {
                onReleaseResources(mApps);
                mApps = null;
            }
    
            // Stop monitoring for changes.
            if (mPackageObserver != null) {
                getContext().unregisterReceiver(mPackageObserver);
                mPackageObserver = null;
            }
        }
    
        /**
         * Helper function to take care of releasing resources associated
         * with an actively loaded data set.
         */
        protected void onReleaseResources(List<AppEntry> apps) {
            // For a simple List<> there is nothing to do.  For something
            // like a Cursor, we would close it here.
        }
    }

    好,到这里流程就很明显了,在loader里 注册广播接收器,当广播接收器 收到广播以后 就调用loader的onContentChanged方法,这个方法一调用 AppListLoader里的loadInBackGround就会被调用,然后当loadInBackGround执行完毕以后 就会把结果传递给onLoadFinished方法了。 搞清楚这个流程 你就真正学会了使用loader这个大杀器了。当然了,我们并不满足于此,loader还有一个特性就是可以自动管理他自己的生命周期 等等。我们现在就去看看他的源码,是如何完成这一点的。 并且上述几个方法之间是如何相互调用的,顺序如何。

    首先 大家要搞清楚几个类之间的关系:

    public class CursorLoader extends AsyncTaskLoader<Cursor> {
    
    public abstract class AsyncTaskLoader<D> extends Loader<D> {
    
    public class Loader<D> {

    这样就很清晰。首先由一个实体类作为最基础的基类,Loader 注意他可以接受一个泛型为参数,然后有一个抽象类:AsyncTaskLoader 也是泛型作为参数。

    最后实际调用运作的类就是CursorLoader类了,这里就可以看出来 传进去的泛型是一个Cursor。你在自定义Loader的时候,这个泛型参数 当然是可以自己决定的,

    比如官方demo里 传的就是一个List。

    搞清楚 他们三者之间的关系,剩下的就简单多了。可以逐步分析了。

    在前面的3个demo里,我们分别演示了在fragment和activity里 调用loader的方法。 那我们就看看 这两者之间有什么异同点。先来看fragment。

    fragment里 我们是这样调用的:

     //这个地方初始化了我们的loader
     getLoaderManager().initLoader(0, null, this);

    直接get了一个manager 然后init他。我们进去看fragment的源码:

    //这边就能看出来一个fragment只能有一个loadermanager了。
    public LoaderManager getLoaderManager() {
    
            if (mLoaderManager != null) {
                return mLoaderManager;
            }
            //mHost很好理解 就是fragment的宿主,也就是跟fragment 相关联的activity。
            if (mHost == null) {
                throw new IllegalStateException("Fragment " + this + " not attached to Activity");
            }
            mCheckedForLoaderManager = true;
            mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, true);
            return mLoaderManager;
        }

    既然 我们知道 fragment的getLoaderManager也是通过activity的getLoader去调用的,那我们就去activity里的源码看看 :

    //在activty中最终实际上调用的就是他了 是这个方法
      LoaderManagerImpl getLoaderManagerImpl() {
            if (mLoaderManager != null) {
                return mLoaderManager;
            }
            mCheckedForLoaderManager = true;
            mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true /*create*/);
            return mLoaderManager;
        }
    
    //这个地方就能看到 主要的第一个参数 who,你到这就能发现 假如是activity自己调用的话,传进去的who的值就是root
    //也就是说一个actvity 只能有一个loadermanger 但是我们可以发现在fragment里 传进去的值是下面这个:
    // Internal unique name for this fragment;
    //String mWho;
    //也就是说每一个fragment的mWho的值都是唯一的,而在activty中,是维护了一个map,一个key 对应一个loadermanager
    //key就是fragment的那个唯一的标示,或者是activity自己,activity自己的标示就是(root)了
        LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
            if (mAllLoaderManagers == null) {
                mAllLoaderManagers = new ArrayMap<String, LoaderManager>();
            }
            LoaderManagerImpl lm = (LoaderManagerImpl) mAllLoaderManagers.get(who);
            if (lm == null) {
                if (create) {
                    lm = new LoaderManagerImpl(who, this, started);
                    mAllLoaderManagers.put(who, lm);
                }
            } else {
                lm.updateHostController(this);
            }
            return lm;
        }

    好 一直到这里 ,我们就可以下一个结论了,真正的loadermanager都是存储在activity中的,包括fragment的loadermanager也是,通过一个map来保证 get的时候取的manager是自己对应的,并且全局唯一。继续往下看:

    public abstract class LoaderManager {
        /**
         * Callback interface for a client to interact with the manager.
         */
        public interface LoaderCallbacks<D> {
            /**
             * Instantiate and return a new Loader for the given ID.
             *
             * @param id The ID whose loader is to be created.
             * @param args Any arguments supplied by the caller.
             * @return Return a new Loader instance that is ready to start loading.
             */
            public Loader<D> onCreateLoader(int id, Bundle args);
    
            /**
             * Called when a previously created loader has finished its load.  Note
             * that normally an application is <em>not</em> allowed to commit fragment
             * transactions while in this call, since it can happen after an
             * activity's state is saved.  See {@link FragmentManager#beginTransaction()
             * FragmentManager.openTransaction()} for further discussion on this.
             * 
             * <p>This function is guaranteed to be called prior to the release of
             * the last data that was supplied for this Loader.  At this point
             * you should remove all use of the old data (since it will be released
             * soon), but should not do your own release of the data since its Loader
             * owns it and will take care of that.  The Loader will take care of
             * management of its data so you don't have to.  In particular:
             *
             * <ul>
             * <li> <p>The Loader will monitor for changes to the data, and report
             * them to you through new calls here.  You should not monitor the
             * data yourself.  For example, if the data is a {@link android.database.Cursor}
             * and you place it in a {@link android.widget.CursorAdapter}, use
             * the {@link android.widget.CursorAdapter#CursorAdapter(android.content.Context,
             * android.database.Cursor, int)} constructor <em>without</em> passing
             * in either {@link android.widget.CursorAdapter#FLAG_AUTO_REQUERY}
             * or {@link android.widget.CursorAdapter#FLAG_REGISTER_CONTENT_OBSERVER}
             * (that is, use 0 for the flags argument).  This prevents the CursorAdapter
             * from doing its own observing of the Cursor, which is not needed since
             * when a change happens you will get a new Cursor throw another call
             * here.
             * <li> The Loader will release the data once it knows the application
             * is no longer using it.  For example, if the data is
             * a {@link android.database.Cursor} from a {@link android.content.CursorLoader},
             * you should not call close() on it yourself.  If the Cursor is being placed in a
             * {@link android.widget.CursorAdapter}, you should use the
             * {@link android.widget.CursorAdapter#swapCursor(android.database.Cursor)}
             * method so that the old Cursor is not closed.
             * </ul>
             *
             * @param loader The Loader that has finished.
             * @param data The data generated by the Loader.
             */
            public void onLoadFinished(Loader<D> loader, D data);
    
            /**
             * Called when a previously created loader is being reset, and thus
             * making its data unavailable.  The application should at this point
             * remove any references it has to the Loader's data.
             *
             * @param loader The Loader that is being reset.
             */
            public void onLoaderReset(Loader<D> loader);
        }

    一看就知道 loadermanger 其实是一个抽象类。就是定义了一些 我们需要的接口而已,这些接口方法的含义和用法 在那3个demo里 相信大家都有了解,不多说。

    我们去看看这个抽象类的实现类,为什么要看他,因为你在get到这个maganger以后 马上就去调用了他的init方法 我们就看看这部分的逻辑是怎么样的:

    public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
            if (mCreatingLoader) {
                throw new IllegalStateException("Called while creating a loader");
            }
    
            //这个就是先看看是否有活动的loader 有的话就取出来 没有的话 就创建一个
            LoaderInfo info = mLoaders.get(id);
    
            if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
    
            if (info == null) {
                // Loader doesn't already exist; create.
                info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
                if (DEBUG) Log.v(TAG, "  Created new loader " + info);
            } else {
                if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
                info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
            }
    
            if (info.mHaveData && mStarted) {
                // If the loader has already generated its data, report it now.
                info.callOnLoadFinished(info.mLoader, info.mData);
            }
    
            return (Loader<D>)info.mLoader;
        }
    
        //这个就是现在存活的loader
        final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>(0);
    
        //这个是已经运行结束的loader
        final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>(0);
    
        //其实这个创建loader的过程特别简单,我们主要看第三个参数,callback 这个参数
        //一想就明白,在前面3个demo里我们是直接在fragemet和activity里实现的callback
        //所以传进去的就是this,也就是说 回调就是在这个函数里 真正的和loader 发生了关联了
        private LoaderInfo createAndInstallLoader(int id, Bundle args,
                LoaderManager.LoaderCallbacks<Object> callback) {
            try {
                mCreatingLoader = true;
                LoaderInfo info = createLoader(id, args, callback);
                installLoader(info);
                return info;
            } finally {
                mCreatingLoader = false;
            }
        }

    你看 一直到这里,我们就明白了 callback是怎么和loadermageer本身发生关联的。 我们继续往下看。这次大家要搞明白当数据源发生变化的时候 是怎么一步步回调我们子类loader的方法的。

    我们先看Loader这个基类的主要方法:

    //这个是一个观察者 当发生变化的时候 他调用了onContentChanged方法
     public final class ForceLoadContentObserver extends ContentObserver {
            public ForceLoadContentObserver() {
                super(new Handler());
            }
    
            @Override
            public boolean deliverSelfNotifications() {
                return true;
            }
    
            @Override
            public void onChange(boolean selfChange) {
                onContentChanged();
            }
        }
    
    //下面这2个方法一看就明白 最终当数据源发生变化的时候 会通知这个观察者,然后这个观察者会最终调用
    //onForceLoad这个方法 而onForceLoad是交给子类去实现的 也就是AsyncTaskLoader的onForceLoad方法了
    public void onContentChanged() {
            if (mStarted) {
                forceLoad();
            } else {
                // This loader has been stopped, so we don't want to load
                // new data right now...  but keep track of it changing to
                // refresh later if we start again.
                mContentChanged = true;
            }
        }
    
     public void forceLoad() {
            onForceLoad();
        }
    
        /**
         * Subclasses must implement this to take care of requests to {@link #forceLoad()}.
         * This will always be called from the process's main thread.
         */
        protected void onForceLoad() {
        }

    然后看看AsyncTaskLoader的几个主要方法:

    //这边一目了然 asynacTaskLoader 里面 正好是有一个AsyncTask对象的!实现了runnabele接口
    //注意着参数d 这个d是干嘛的,这个d就是用来传递参数的一个泛型,可以是系统实现的loader里的cursor
    //也可以是我们自己实现的loader里的list类型
    final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
            private final CountDownLatch mDone = new CountDownLatch(1);
    
            // Set to true to indicate that the task has been posted to a handler for
            // execution at a later time.  Used to throttle updates.
            boolean waiting;
    
            /* Runs on a worker thread */
            @Override
            protected D doInBackground(Void... params) {
                if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
                try {
                    //这个地方就很明显了,他调用了自己的onLoadInBackGround方法
                    D data = AsyncTaskLoader.this.onLoadInBackground();
                    if (DEBUG) Log.v(TAG, this + "  <<< doInBackground");
                    return data;
                } catch (OperationCanceledException ex) {
                    if (!isCancelled()) {
                        // onLoadInBackground threw a canceled exception spuriously.
                        // This is problematic because it means that the LoaderManager did not
                        // cancel the Loader itself and still expects to receive a result.
                        // Additionally, the Loader's own state will not have been updated to
                        // reflect the fact that the task was being canceled.
                        // So we treat this case as an unhandled exception.
                        throw ex;
                    }
                    if (DEBUG) Log.v(TAG, this + "  <<< doInBackground (was canceled)", ex);
                    return null;
                }
            }
            //后面还有很多代码 略过
    }
    
    //你看这里下面的2个函数 一看就明白了 最终task里调用的是这个抽象方法,那这个抽象方法
    //就是留给我们子类自己去实现的,我们在自定义loader的时候最重要的就是重写这个方法。
     protected D onLoadInBackground() {
            return loadInBackground();
        }
    
     public abstract D loadInBackground();
    
    //你看这个地方 就是当数据源发生变化的时候 就会调用这个方法了,启动了我们的laodtask 
    //也是最终调用子类 也就是CursorLoader这样的子类的loadInBackground方法了
    @Override
        protected void onForceLoad() {
            super.onForceLoad();
            cancelLoad();
            mTask = new LoadTask();
            if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
            executePendingTask();
        }

    相信到这里 大家一定能搞明白数据源变化的时候 是怎么一步步调用我们的loader里的回调方法的,那有人肯定要继续问当你这个方法调用完毕的时候 是怎么通知最后updateUI呢,也就是当你background方法结束以后是怎么调用的onLoadFinished方法的呢?

    我们继续看AsyncTaskLoader这个类

    //在那个asynctask里面 走完是肯定要走这个方法的 相信大家都能理解。
            @Override
            protected void onPostExecute(D data) {
                if (DEBUG) Log.v(TAG, this + " onPostExecute");
                try {
                    AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
                } finally {
                    mDone.countDown();
                }
            }
    //实际上走的就是这个方法。看26行-
            void dispatchOnLoadComplete(LoadTask task, D data) {
            if (mTask != task) {
                if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
                dispatchOnCancelled(task, data);
            } else {
                if (isAbandoned()) {
                    // This cursor has been abandoned; just cancel the new data.
                    onCanceled(data);
                } else {
                    commitContentChanged();
                    mLastLoadCompleteTime = SystemClock.uptimeMillis();
                    mTask = null;
                    if (DEBUG) Log.v(TAG, "Delivering result");
                    deliverResult(data);
                }
            }
        }
    
    //这边一下就看出来是调用的mListtenr的回调方法
         public void deliverResult(D data) {
            if (mListener != null) {
                mListener.onLoadComplete(this, data);
            }
        }

    实际上这个Listener就是在Loader这个基类里:

    OnLoadCompleteListener<D> mListener;
    
    public interface OnLoadCompleteListener<D> {
            /**
             * Called on the thread that created the Loader when the load is complete.
             *
             * @param loader the loader that completed the load
             * @param data the result of the load
             */
            public void onLoadComplete(Loader<D> loader, D data);
        }
    
    //并且通过这个注册
     public void registerListener(int id, OnLoadCompleteListener<D> listener) {
            if (mListener != null) {
                throw new IllegalStateException("There is already a listener registered");
            }
            mListener = listener;
            mId = id;
        }

    那就好了 我们就是要看一下 是在哪个地方调用的registerlistener这个方法 注册他的

    //回到initLoader的这个方法 注意这个方法是在LoaderManger里面
      public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
            if (mCreatingLoader) {
                throw new IllegalStateException("Called while creating a loader");
            }
    
            LoaderInfo info = mLoaders.get(id);
    
            if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
    
            if (info == null) {
    //下面的代码跳转到30行
                info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
                if (DEBUG) Log.v(TAG, "  Created new loader " + info);
            } else {
                if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
                info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
            }
    
            if (info.mHaveData && mStarted) {
                // If the loader has already generated its data, report it now.
                info.callOnLoadFinished(info.mLoader, info.mData);
            }
    
            return (Loader<D>)info.mLoader;
        }
    
        private LoaderInfo createAndInstallLoader(int id, Bundle args,
                LoaderManager.LoaderCallbacks<Object> callback) {
            try {
                mCreatingLoader = true;
                LoaderInfo info = createLoader(id, args, callback);
                //这里跳转到43行
                installLoader(info);
                return info;
            } finally {
                mCreatingLoader = false;
            }
        }
    
        void installLoader(LoaderInfo info) {
            mLoaders.put(info.mId, info);
            if (mStarted) {
                //跳转到51行
                info.start();
            }
        }
    
       void start() {
                if (mRetaining && mRetainingStarted) {
                    // Our owner is started, but we were being retained from a
                    // previous instance in the started state...  so there is really
                    // nothing to do here, since the loaders are still started.
                    mStarted = true;
                    return;
                }
    
                if (mStarted) {
                    // If loader already started, don't restart.
                    return;
                }
    
                mStarted = true;
    
                if (DEBUG) Log.v(TAG, "  Starting: " + this);
                if (mLoader == null && mCallbacks != null) {
                   mLoader = mCallbacks.onCreateLoader(mId, mArgs);
                }
                if (mLoader != null) {
                    if (mLoader.getClass().isMemberClass()
                            && !Modifier.isStatic(mLoader.getClass().getModifiers())) {
                        throw new IllegalArgumentException(
                                "Object returned from onCreateLoader must not be a non-static inner member class: "
                                + mLoader);
                    }
                    if (!mListenerRegistered) {
                        //就是在这里注册的mloader里的回调了,注意这里的参数是this 也就是loaderInfo这个类 注意这个类就是loadermanger里的内部类了 再继续往下看
                        //我们前面说到 在asynctask里面最终调用的是mLoader里的onLoadComplete方法 所以我们就看看loaderInfo这个类里的这个方法做了什么看91行
                        mLoader.registerListener(mId, this);
                        mLoader.registerOnLoadCanceledListener(this);
                        mListenerRegistered = true;
                    }
                    mLoader.startLoading();
                }
            }
    
             @Override
            public void onLoadComplete(Loader<Object> loader, Object data) {
                if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);
    
                if (mDestroyed) {
                    if (DEBUG) Log.v(TAG, "  Ignoring load complete -- destroyed");
                    return;
                }
    
                if (mLoaders.get(mId) != this) {
                    // This data is not coming from the current active loader.
                    // We don't care about it.
                    if (DEBUG) Log.v(TAG, "  Ignoring load complete -- not active");
                    return;
                }
    
                LoaderInfo pending = mPendingLoader;
                if (pending != null) {
                    // There is a new request pending and we were just
                    // waiting for the old one to complete before starting
                    // it.  So now it is time, switch over to the new loader.
                    if (DEBUG) Log.v(TAG, "  Switching to pending loader: " + pending);
                    mPendingLoader = null;
                    mLoaders.put(mId, null);
                    destroy();
                    installLoader(pending);
                    return;
                }
    
                // Notify of the new data so the app can switch out the old data before
                // we try to destroy it.
                if (mData != data || !mHaveData) {
                    mData = data;
                    mHaveData = true;
                    if (mStarted) {
                        //继续往下 看第149行 
                        callOnLoadFinished(loader, data);
                    }
                }
    
                //if (DEBUG) Log.v(TAG, "  onLoadFinished returned: " + this);
    
                // We have now given the application the new loader with its
                // loaded data, so it should have stopped using the previous
                // loader.  If there is a previous loader on the inactive list,
                // clean it up.
                LoaderInfo info = mInactiveLoaders.get(mId);
                if (info != null && info != this) {
                    info.mDeliveredData = false;
                    info.destroy();
                    mInactiveLoaders.remove(mId);
                }
    
                if (mHost != null && !hasRunningLoaders()) {
                    mHost.mFragmentManager.startPendingDeferredFragments();
                }
            }
    
             void callOnLoadFinished(Loader<Object> loader, Object data) {
                if (mCallbacks != null) {
                    String lastBecause = null;
                    if (mHost != null) {
                        lastBecause = mHost.mFragmentManager.mNoTransactionsBecause;
                        mHost.mFragmentManager.mNoTransactionsBecause = "onLoadFinished";
                    }
                    try {
                        if (DEBUG) Log.v(TAG, "  onLoadFinished in " + loader + ": "
                                + loader.dataToString(data));
                        //到这里就真相大白了,最终callback是在这里调用的onLoadFinished方法也就是我们经常重写的方法
                        mCallbacks.onLoadFinished(loader, data);
                    } finally {
                        if (mHost != null) {
                            mHost.mFragmentManager.mNoTransactionsBecause = lastBecause;
                        }
                    }
                    mDeliveredData = true;
                }
            }

    好,到这里 我们就把Loader框架中的 数据传递 整个流程给摸清楚了。最后我们再来看看 他的生命周期是如何管理的吧。

    我们可以先看看activity的:

    //看activity的onStart方法
    protected void onStart() {
            if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
            mCalled = true;
            //继续看12行 这个地方mFragements 你就理解成activity本身即可,不多做解释 这个地方要搞清楚 又是另外一块了 有兴趣的可以自行谷歌activity和fragment如何建立关系
            mFragments.doLoaderStart();
    
            getApplication().dispatchActivityStarted(this);
        }
    
        //这个函数就很明显了 调用了manager的dostart函数
         void doLoaderStart() {
            if (mLoadersStarted) {
                return;
            }
            mLoadersStarted = true;
    
            if (mLoaderManager != null) {
                //跳转到30行
                mLoaderManager.doStart();
            } else if (!mCheckedForLoaderManager) {
                mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
            }
            mCheckedForLoaderManager = true;
        }
    
    //------------------注意上面的代码都在activity里,下面的开始 都在LoaderManger类里了
    
         void doStart() {
            if (DEBUG) Log.v(TAG, "Starting in " + this);
            if (mStarted) {
                RuntimeException e = new RuntimeException("here");
                e.fillInStackTrace();
                Log.w(TAG, "Called doStart when already started: " + this, e);
                return;
            }
    
            mStarted = true;
    
            // Call out to sub classes so they can start their loaders
            // Let the existing loaders know that we want to be notified when a load is complete
            for (int i = mLoaders.size()-1; i >= 0; i--) {
                //跳转到50行
                mLoaders.valueAt(i).start();
            }
        }
    
         void start() {
                if (mRetaining && mRetainingStarted) {
                    // Our owner is started, but we were being retained from a
                    // previous instance in the started state...  so there is really
                    // nothing to do here, since the loaders are still started.
                    mStarted = true;
                    return;
                }
    
                if (mStarted) {
                    // If loader already started, don't restart.
                    return;
                }
    
                mStarted = true;
    
                if (DEBUG) Log.v(TAG, "  Starting: " + this);
                if (mLoader == null && mCallbacks != null) {
                    //原来onCreateLoader这个回调方法 是在这里调用的 怪不得谷歌说这个方法是必定会被执行并且只会被执行一次的方法!
                   mLoader = mCallbacks.onCreateLoader(mId, mArgs);
                }
                if (mLoader != null) {
                    if (mLoader.getClass().isMemberClass()
                            && !Modifier.isStatic(mLoader.getClass().getModifiers())) {
                        throw new IllegalArgumentException(
                                "Object returned from onCreateLoader must not be a non-static inner member class: "
                                + mLoader);
                    }
                    if (!mListenerRegistered) {
                        mLoader.registerListener(mId, this);
                        mLoader.registerOnLoadCanceledListener(this);
                        mListenerRegistered = true;
                    }
                    //你看这里调用了startLoading方法 这个方法是属于mLoader的 跳转到88行
                    mLoader.startLoading();
                }
            }
    
    //88- 98行是loader这个类里的
        public final void startLoading() {
            mStarted = true;
            mReset = false;
            mAbandoned = false;
            onStartLoading();
        }
    
        //你看最终是调用的这个方法,注意他是空方法 是交给子类去实现的,我们去看看cursorloader这个子类是怎么实现的吧。
        protected void onStartLoading() {
        }
    //99-  112行 是cursorLoader这个类的代码
    
    //你看这个地方 直接调用了forceload方法 这个方法大家前面肯定有印象  他最终会启动那个asynctask 去执行background方法
    //这也就解释了 第一次我们的数据是怎么来的,比如说 假设我们的数据源还没有被更新的时候,为什么会自动去查找数据源 并返回数据
    //到这里就明白了,原来是activity的onStart函数为开端 一步步走到Loader的子类的onStartLoading方法里的,当然你假如觉得
    //Loader不需要初始加载 只要在有变化的时候再加载 那这个方法你就可以保持为空了。
         protected void onStartLoading() {
            if (mCursor != null) {
                deliverResult(mCursor);
            }
            if (takeContentChanged() || mCursor == null) {
                forceLoad();
            }
        }
    
    //114-139行 为 http://developer.android.com/intl/zh-cn/reference/android/content/AsyncTaskLoader.html 这个里面 AppListLoader  的一段源码
    //你看138行 也是直接调用的forceLoad 这样当我们的applist没有变化的时候 第一次也能显示出列表 
     /**
         * Handles a request to start the Loader.
         */
        @Override protected void onStartLoading() {
            if (mApps != null) {
                // If we currently have a result available, deliver it
                // immediately.
                deliverResult(mApps);
            }
    
            // Start watching for changes in the app data.
            if (mPackageObserver == null) {
                mPackageObserver = new PackageIntentReceiver(this);
            }
    
            // Has something interesting in the configuration changed since we
            // last built the app list 
            boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());
    
            if (takeContentChanged() || mApps == null || configChange) {
                // If the data has changed since the last time it was loaded
                // or is not currently available, start a load.
                forceLoad();
            }
        }

    start流程 我们分析完毕了 最后我们再看看stop流程吧 看完这个 其他生命周期 我们就不分析了留给读者自己感兴趣的话自己分析试试看。

    //我们来看看fragment的onDestroy方法 都做了什么
    public void onDestroy() {
            mCalled = true;
            //Log.v("foo", "onDestroy: mCheckedForLoaderManager=" + mCheckedForLoaderManager
            //        + " mLoaderManager=" + mLoaderManager);
            if (!mCheckedForLoaderManager) {
                mCheckedForLoaderManager = true;
                mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false);
            }
            if (mLoaderManager != null) {
                //跳转到16行
                mLoaderManager.doDestroy();
            }
        }
    //上面的代码 是在fragment里 下面的代码在loadermanger里
         void doDestroy() {
            if (!mRetaining) {
                if (DEBUG) Log.v(TAG, "Destroying Active in " + this);
                for (int i = mLoaders.size()-1; i >= 0; i--) {
                    mLoaders.valueAt(i).destroy();
                }
                mLoaders.clear();
            }
    
            if (DEBUG) Log.v(TAG, "Destroying Inactive in " + this);
            for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
                mInactiveLoaders.valueAt(i).destroy();
            }
            mInactiveLoaders.clear();
        }
    //下面这个destroy流程 可以清晰的看到很多东西 包括clear所有回调等
     void destroy() {
                if (DEBUG) Log.v(TAG, "  Destroying: " + this);
                mDestroyed = true;
                boolean needReset = mDeliveredData;
                mDeliveredData = false;
                if (mCallbacks != null && mLoader != null && mHaveData && needReset) {
                    if (DEBUG) Log.v(TAG, "  Reseting: " + this);
                    String lastBecause = null;
                    if (mHost != null) {
                        lastBecause = mHost.mFragmentManager.mNoTransactionsBecause;
                        mHost.mFragmentManager.mNoTransactionsBecause = "onLoaderReset";
                    }
                    try {
                        mCallbacks.onLoaderReset(mLoader);
                    } finally {
                        if (mHost != null) {
                            mHost.mFragmentManager.mNoTransactionsBecause = lastBecause;
                        }
                    }
                }
                mCallbacks = null;
                mData = null;
                mHaveData = false;
                if (mLoader != null) {
                    if (mListenerRegistered) {
                        mListenerRegistered = false;
                        mLoader.unregisterListener(this);
                        mLoader.unregisterOnLoadCanceledListener(this);
                    }
                    //在这调用了rest
                    mLoader.reset();
                }
                if (mPendingLoader != null) {
                    mPendingLoader.destroy();
                }
            }
    //最后我们来看看loader里的代码 就能明白了 当fragement destroy的时候最终的调用来到了子类的onReset方法
             public void reset() {
            onReset();
            mReset = true;
            mStarted = false;
            mAbandoned = false;
            mContentChanged = false;
            mProcessingChange = false;
        }
    
        /**
         * Subclasses must implement this to take care of resetting their loader,
         * as per {@link #reset()}.  This is not called by clients directly,
         * but as a result of a call to {@link #reset()}.
         * This will always be called from the process's main thread.
         */
        protected void onReset() {
        }
    
    //这里是cURSORLOADER的代码了 你看这里关闭了cursor
        @Override
        protected void onReset() {
            super.onReset();
    
            // Ensure the loader is stopped
            onStopLoading();
    
            if (mCursor != null && !mCursor.isClosed()) {
                mCursor.close();
            }
            mCursor = null;
        }
    
    //同样的 我们也能看到applistloader源码里面 也是在这个函数里清除了广播接收器。
    //所以读到这里 我们就知道 loader的强大了。你只需要搞清楚这些生命周期的函数的意义
    //就可以重写他们,至于什么时候调用 loader都帮你做好了 你只需要在里面实现你自己的逻辑即可!非常强大 非常好用
        @Override protected void onReset() {
            super.onReset();
    
            // Ensure the loader is stopped
            onStopLoading();
    
            // At this point we can release the resources associated with 'apps'
            // if needed.
            if (mApps != null) {
                onReleaseResources(mApps);
                mApps = null;
            }
    
            // Stop monitoring for changes.
            if (mPackageObserver != null) {
                getContext().unregisterReceiver(mPackageObserver);
                mPackageObserver = null;
            }
        }
    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    SQL汉字转换为拼音的函数

    windows 7系统无法运行Photoshop CS3的解决方法

    巧用MySQL加密函数对Web网站敏感数据进行保护

    MySQL基础知识简介

    Windows7和WinXP下如何实现不输密码自动登录系统的设置方法介绍

    windows 7系统ip地址冲突怎么办?windows 7系统IP地址冲突问题的

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。