学而实习之 不亦乐乎

Android:Loader和LoaderManager

2021-09-22 21:29:53

一、简介

Loader 负责在一个单独线程中执行查询,监控数据源改变,当探测到改变时将查询到的结果集发送到注册的监听器上。

1.LoaderManager

LoaderManager 就是加载器的管理器,负责管理与Activity或者Fragment联系起来的一个或多个Loaders对象,一个Activity或者Fragment都有一个唯一的LoadManager(通过getLoaderManager()方法获得),用来启动,停止,保持,重启,关闭与它关联的所有的 Loader。这些功能可通过调用initLoader()/restartLoader()/destroyLoader()方法来实现.

LoaderManager并不知道数据如何装载以及何时需要装载.相反,它只需要控制它的Loaders们开始,停止,重置他们的Load行为,在配置变换或数据变化时保持loaders们的状态,并使用接口来返回load的结果.

LoaderManager 用于管理与 Activity 和Fragment 关联的 Loader 实例,LoaderManager 负责根据的 Activity 的生命周期对 Loader 的数据加载器进行调度,所以这里分工明确,Loader 负责数据加载逻辑,LoaderManager 负责 Loader 的调度,开发者只需要自定义自己的 Loader,实现数据的加载逻辑,而不再关注数据加载时由于 Activity 销毁引发的问题。

2.Loader

Loades负责在一个单独线程中执行查询,监控数据源改变,当探测到改变时将查询到的结果集发送到注册的监听器上.Loader是一个强大的工具,具有如下特点:

(1)它封装了实际的数据载入

Activity或Fragment不再需要知道如何载入数据.它们将该任务委托给了Loader,Loader在后台执行查询要求并且将结果返回给Activity或Fragment.

(2)客户端不需要知道查询如何执行.Activity或Fragment不需要担心查询如何在独立的线程中执行,Loder会自动执行这些查询操作.

这种方式不仅减少了代码复杂度,同时也消除了线程相关bug的潜在可能.

(3)它是一种安全的事件驱动方式

Loader检测底层数据,当检测到改变时,自动执行并载入最新数据.
这使得使用Loader变得容易,客户端可以相信Loader将会自己自动更新它的数据.
Activity或Fragment所需要做的就是初始化Loader,并且对任何反馈回来的数据进行响应.除此之外,所有其他的事情都由Loader来解决.

加载器,简单来说,Loader做了2件事:

  • 在单独的线程中读取数据,不会阻塞UI线程
  • 监视数据的更新


二、异步Loader的实现原理

1.Loader 与 AsyncTaskLoader

Loader 类用于数据的加载 ,类型参数D用于指定Loader加载的数据类型

public class Loader<D> {
}

一般我们不直接继承Loader,而是继承 AsyncTaskLoader,因为Loader的加载工作并不是在异步线程中。而AsyncTaskLoader实现了异步线程,加载流程在子线程中执行。

注意:对该类的调用应该在主线程中完成。

(1)执行异步载入的任务.为了确保在一个独立线程中执行载入操作,Loader的子类必须继承AsyncTaskLoader<D>而不是Loader<D>类.

AsyncTaskLoader<D>是一个抽象Loader,它提供了一个AsyncTask来做它的执行操作,定义子类时,通过实现抽象方法loadInBackground方法来实现异步task.该方法将在一个工作线程中执行数据加载操作。

(2)在一个注册监听器中接收载入完成返回的结果.

 对于每个Loader来说,LoaderManager注册一个OnLoadCompleteListener<D>,该对象将通过调用onLoadFinished(Loader<D> loader, D result)方法使Loader将结果传送给客户端.
 Loader通过调用Loader#deliverResult(D result),将结果传递给已注册的监听器.

2.Loader三种不同状态

已启动: 处于已启动状态的Loader会执行载入操作,并在任何时间将结果传递到监听器中.已启动的Loader将会监听数据改变,当检测到改变时执行新的载入.
一旦启动,Loader将一直处在已启动状态,一直到转换到已停止和重置,这是唯一一种onLoadFinished永远都会调用的状态。

已停止: 处于已停止状态的Loader将会继续监听数据改变,但是不会将结果返回给客户端.在已停止状态,Loader可能被启动或者重启.

重置: 当Loader处于重置状态时,将不会执行新的载入操作,也不会发送新的结果,也不会检测数据变化.
当一个Loader进入重置状态,它必须解除对应的数据引用,方便垃圾回收(客户端也必须确定,在Loader无效之后,移除了所有对该数据的引用)。通常,重置Loader不会两次调用.然而,在某些情况下他们可能会启动,所以如果必要的话,它们必须能够适时重置.

3.接收Loader数据改变的通知

必须有一个观察者接受数据源改变的通知。 Loader必须实现这些 Observer 其中之一(ContentObserver,BroadcastReceiver等),来检测底层数据源的改变。当检测到数据改变,观察者必须调用Loader.onContentChanged().在该方法中执行两种不同操作:

(1)如果Loader已经处于启动状态,就会执行一个新的载入操作。
(2)设置一个flag标识数据源有改变,这样当Loader再次启动时,就知道应该重新载入数据了。

4.CursorLoader

CursorLoader 实现 LoaderManager.LoaderCallbacks 接口方法,接口声明及使用如下:

public interface LoaderCallbacks<D> {
    public Loader<D> onCreateLoader(int id, Bundle args);
    public void onLoadFinished(Loader<D> loader, D data);
    public void onLoaderReset(Loader<D> loader);
}

onCreateLoader 方法将在创建Loader时候调用,此时需要提供查询的配置,如监听一个URI。这个方法会在loader初始化的时调用,即调用下面的代码时调用: 

getLoaderManager().initLoader(id, bundle, loaderCallbacks);

 initLoader函数原型为:

  <D> Loader<D> android.app.LoaderManager.initLoader(int id, Bundle bundle, LoaderCallbacks<D> loaderCallbacks);
  第1个参数loader的ID,可自定义一个常量值,便于实现多个Loader;
  第2个参数一般置null;
  第3个参数是实现了LoaderManager.LoaderCallbacks实现类对象。

onLoadFinished方法,在Loader完成任务后调用,一般在此读取结果。
onLoaderReset方法是在配置发生变化时调用,一般调用下面的代码后调用:
getLoaderManager().restartLoader(id, bundle, loaderCallbacks);
restartLoader方法参数同initLoader,重新初始化loader之后,需要用来释放对前面loader查询到的结果引用.

三、实例

使用Loader来加载手机中的音乐

1、主布局,就是一个ListView

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#d9d9d9"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/music_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none" />

</RelativeLayout>

2、ListView的Item布局,主要显示歌曲名称和歌手信息

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/music_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/music_singer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

3、MainActivity,注释比较详细,使用比较简单

/**
 * 实现LoaderCallbacks 重写三个方法
 *
 * @author yungfan
 *
 */
@SuppressLint("NewApi")
public class MainActivity extends Activity implements LoaderCallbacks<Cursor> {

    private ListView listView;

    // 使用SimpleCursorAdapter来填充数据
    private SimpleCursorAdapter mAdapter;

    // 使用CursorLoader来获取数据
    private CursorLoader loader;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = (ListView) findViewById(R.id.music_list);

        initMusic();
    }

    private void initMusic() {

        // 这里创建Adapter时 注意不传递数据
        mAdapter = new SimpleCursorAdapter(MainActivity.this, R.layout.item,
                null, new String[] { MediaStore.Audio.Media.TITLE,
                        MediaStore.Audio.Media.ARTIST }, new int[] {
                        R.id.music_name, R.id.music_singer }, 0);

        listView.setAdapter(mAdapter);

        // 通过异步的方式加载数据
        LoaderManager manager = getLoaderManager();
        // 第一个参数为id 第二个位Bundle数据 第三个为LoaderCallbacks
        manager.initLoader(0, null, this);

    }

    // 首先检查指定的id是否存在,如果不存在才会触发该方法,通过该方法才能创建一个loader。
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {

        // 查询音乐数据库 获取音乐数据 并排序
        loader = new CursorLoader(this,
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,
                MediaStore.Audio.Media.DEFAULT_SORT_ORDER);

        return loader;
    }

    // 完成对Ui控件的更新,如果不再使用,将自动释放loader的数据,不需要使用close();
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        mAdapter.swapCursor(data);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        mAdapter.swapCursor(null);
    }
}

注意:必须加上读SD卡的权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>