Android 异步加载神器Loader全解析

Posted 星辰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 异步加载神器Loader全解析相关的知识,希望对你有一定的参考价值。

在之前呢,我们经常会有这种需求,比如在某个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里 相信大家都有了解,不多说。

我们去看看这个抽象类的

以上是关于Android 异步加载神器Loader全解析的主要内容,如果未能解决你的问题,请参考以下文章

Android-Image-Loader 图片异步加载类库的使用超(详细配置)

第三方的图片加载( Android-Universal-Image-Loader)

Android 图片加载框架Universal-Image-Loader源码解析

Android 开源框架Universal-Image-Loader完全解析---源代码解读

Android异步载入全解析之大图处理

Android异步载入全解析之使用多线程