欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android之Loader和LoaderManager使用

程序员文章站 2024-02-11 11:45:16
...

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