Android之Loader和LoaderManager使用

1.Loader作用

顾名思义即是指加载器,用于异步加载数据,Android 3.0 中引入了加载器,支持轻松在 Activity 或片段(Fragment)中异步加载数据;

a.在单独的线程中读取数据(耗时操作),不用阻塞UI线程的执行;

b.监听数据源是否发生变化传递新的结果(观察者模式);

2.Loader和LoaderManager使用示例

/**
 * Loader测试类
 */

public class CursorLoaderActivity extends ToolbarActivity implements LoaderManager.LoaderCallbacks<Cursor>,
        TextWatcher {

    private static final int CURSOR_LOADER_ID = 1;//Loader的唯一标识符
    private SimpleCursorAdapter adapter = null;//一个简单的适配器,用于将列从Cursor映射到XML文件中定义的TextViews或ImageViews;您可以指定您想要哪些列显示到对应的xml文件中的那个视图上;

    @Bind(R.id.listView)
    ListView listView;
    @Bind(R.id.edit_text)
    EditText edit_text;

    @Override
    protected int provideContentViewId() {
        return R.layout.activity_cursor_loader;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ButterKnife.bind(this);
        //绑定编辑框的文本变化事件
        edit_text.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                return false;
            }
        });
        edit_text.addTextChangedListener(this);

        //创建Adapter
        adapter = new SimpleCursorAdapter(this,
                android.R.layout.simple_list_item_2,
                null,//默认设置游标Cursor为空
                new String[]{ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.CONTACT_STATUS},
                new int[]{android.R.id.text1, android.R.id.text2},//指定游标中的哪些列对应哪些视图控件
                0);
        listView.setAdapter(adapter);//设置游标适配器

//        LoaderManager lm = getSupportLoaderManager();
        LoaderManager lm = getLoaderManager();
        //查询全部联系人
        Bundle args = new Bundle();
        args.putString("filter", null);

        /**
         *  id Loader的惟一标识符(在LoaderManager容器中的唯一标识符),可以任意整型值,
         *  标识符(identifier)的范围用在一个特定的LoaderManager实例中(当前Activity或者Fragment中的LoaderManager实例)。
         *
         *  args 在创建Loader的过程中提供给Loader的可选参数。
         *  如果Loader已经存在(不需要创建一个新的加载程序),则
         *  参数将被忽略,最后的参数将继续使用。
         *
         *  callback Loader中的内部接口,回调传送Loader状态的变化
         *
         *  public abstract <D> Loader<D> initLoader(int id, Bundle args,
         *  LoaderManager.LoaderCallbacks<D> callback);
         */
        lm.initLoader(CURSOR_LOADER_ID, args, this);//a.初始化Loader
    }

    /**
     * 要检索联系人的哪些字段数据
     */
    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME,
            ContactsContract.Contacts.CONTACT_STATUS,
            ContactsContract.Contacts.CONTACT_PRESENCE,
            ContactsContract.Contacts.PHOTO_ID,
            ContactsContract.Contacts.LOOKUP_KEY,
    };
    //b.回调接口方法
    /*
     *  当需要创建一个新的Loader时调用。这个例子中只有一个Loader,所以我们不关心ID。
     *  首先,根据我们当前过滤是否选择使用相应的URI
     */
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        Uri baseUri;
        String filter = args != null ? args.getString("filter") : null;
        if(!TextUtils.isEmpty(filter)){
            baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
                    Uri.encode(filter));
        }else{
            baseUri = ContactsContract.Contacts.CONTENT_URI;
        }

        //现在创建并且返回一个CursorLoader, 它将负责为正在显示的数据创建游标。
        String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
        return new CursorLoader(this, baseUri, CONTACTS_SUMMARY_PROJECTION,
                select, null, ContactsContract.Contacts.DISPLAY_NAME+" COLLATE LOCALIZED ASC");
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        //切换新的cursor(游标),一旦返回,框架会关闭旧的游标
        adapter.swapCursor(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        //当上面提供给onLoadFinished()的最后一个光标即将被关闭时,
        //这个方法将被调用。我们需要确保我们没有使用它。
        adapter.swapCursor(null);
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        String filter = edit_text.getText().toString();
        Bundle args = new Bundle();
        args.putString("filter", filter);
        LoaderManager lm = getLoaderManager();
        lm.restartLoader(CURSOR_LOADER_ID, args, this);
    }
}

我们在AndroidManifest.xml中需要加入android.permission.READ_CONTACTS权限以读取联系人信息。

3.使用示例分析

a.lm(LoaderManager).initLoader(CURSOR_LOADER_ID, args, this);

//initLoader源码片段

public abstract <D> Loader<D> initLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<D> callback);

initLoader方法的作用:

LoaderManager中的initLoader负责根据ID查找LoaderManager容器中是否已经存在Loader,若不存在则创建一个Loader放到LoaderManager容器中,存在与ID关联的Loader,则Loader保持不变,只是将Loader回掉callback替换成新的回调;

id:用于标识加载器(Loader)的唯一 ID。可以是任意整型值。

args:在构建时提供给加载器的可选参数(在此示例中为 null),主要用于传递参数给回调接口public Loader<Cursor> onCreateLoader(int id, Bundle args)方法使用,onCreateLoader方法被通知回调通知客户端(Activity或者Fragment)通知去创建Loader,同时回传ID和args给客户端,ID用于区分是那个Loader的回调,上面例子中只有一个Loader,则不用关心ID参数,args用于执行操作所需要的参数值,例如上面的例子参数表示查询联系人的搜索条件;

callback:给客户端(Activity或者Fragment)设置回调LoaderManager.LoaderCallbacks接口,用于监听Loader的各种状态,例如Loader创建,或者Loader加载完成等;

有时候我们可能需要重新启动的对应的ID关联的Loader,这样就可以丢弃旧的数据,调用lm.restartLoader(CURSOR_LOADER_ID, args, this);就可以完成该操作,丢弃旧的Loader,重新创建一个新的Loader;

源码分析:

每个客户端(Actvity或者Fragment)只有一个LoaderManager,而LoaderManager中会有多个Loader,如下是LoaderManager创建过程;

Activity
创建FragmentController,并返回LoaderManager
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
//通过构造函数发现FragmentController对象是单例
private FragmentController(FragmentHostCallback<?> callbacks) {
    mHost = callbacks;
}
/**
 * Return the LoaderManager for this activity, creating it if needed.
 */
public LoaderManager getLoaderManager() {
    return mFragments.getLoaderManager();
}

FragmentController
通过代码会发现返回的是LoaderManager实现类LoaderManagerImpl
/**
 * Returns a {@link LoaderManager}.
 */
public LoaderManager getLoaderManager() {
    return mHost.getLoaderManagerImpl();
}
//证明客户端(Activity或者Fragment)只存在一个LoaderManager
LoaderManagerImpl getLoaderManagerImpl() {
    if (mLoaderManager != null) {
        return mLoaderManager;
    }
    mCheckedForLoaderManager = true;
    mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true /*create*/);
    return mLoaderManager;
}
不存在则创建LoaderManager实例
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 && create) {
        lm = new LoaderManagerImpl(who, this, started);
        mAllLoaderManagers.put(who, lm);
    } else if (started && lm != null && !lm.mStarted){
        lm.doStart();
    }
    return lm;
}

LoaderManager中Loader初始化操作分析,代码如下;

LoaderManagerImpl
实际调用LoaderManagerImpl类对象的
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback)
{}方法初始化创建Loader;
LoaderInfo:用于存放创建Loader的相关信息;
首先检测是否Loader已存在
不存在则
createAndInstallLoader()创建并安装Loader
createLoader()创建Loader
callback.onCreateLoader(id, args);创建完Loader回调通知客户端(Activity或者Fragment)Loader已经创建了
installLoader()将Loader保存在LoaderManagerImpl中
mLoaders.put(info.mId, info);
开启Loader
info.start();
mLoader.startLoading();
onStartLoading();

b.回调接口方法

使用 LoaderManager 回调LoaderManager.LoaderCallbacks 是一个支持客户端与 LoaderManager 交互的回调接口。

public class CursorLoaderActivity extends ToolbarActivity implements LoaderManager.LoaderCallbacks<Cursor>,

        TextWatcher {}

onCreateLoader():

当LoaderInfo被创建好以后,回调通知客户端(Activity或者Fragment)调用onCreateLoader()方法创建并返回Loader,然后将Loader赋值给LoaderInfo中的mLoader变量;

LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
        Loader<Object> loader = callback.onCreateLoader(id, args);
        info.mLoader = (Loader<Object>)loader;

public Loader<D> onCreateLoader(int id, Bundle args);

例子中创建的是CursorLoader类,CursorLoader继承自AsyncTaskLoader类;

CursorLoader一个查询ContentResolver并返回游标(Cursor)的加载器。该类以一种标准的方法来实现游标(Cursor)协议,用于查询游标(Cursor),构建在AsyncTaskLoader上,以在后台线程上执行游标查询(Cursor),从而不会阻塞应用程序的UI。

onLoadFinished()

当先前创建的加载器完成加载时,将调用此方法。该方法必须在为此加载器提供的最后一个数据释放之前调用。 此时,您应移除所有使用的旧数据(因为它们很快会被释放),但不要自行释放这些数据,因为这些数据归其加载器所有,其加载器会处理它们。

当Loader完成加载时会调用该方法回调,然后调用adapter.swapCursor(data)的切换ListView控件上显示的UI数据,同时会处理旧数据,不需要开发人员管理;

onLoaderReset()

此方法将在先前创建的加载器重置且其数据因此不可用时调用。 通过此回调,您可以了解何时将释放数据,因而能够及时移除其引用。  

当上面提供给onLoadFinished()的最后一个光标即将被关闭时,这个方法将被调用。我们需要确保我们没有使用它。

        adapter.swapCursor(null);


参考:

Android开发之Loader与LoaderManager

https://www.jianshu.com/p/8b8197dc2e04

https://developer.android.google.cn/guide/components/loaders


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值