✏️
Android应用开发
  • 简介
  • 目录
  • 第一章 熟悉Android Studio开发环境
    • 第一个项目——Hello World!
    • 第二个项目——计数器
    • 第三个项目——新闻阅读器
  • 第二章 活动、组件及布局
    • 第四个项目——消息发送
    • 第五个项目——计数器2
    • 第六个项目——登陆界面
    • 第七个项目——新闻列表
  • 第三章 数据持久化
    • 第八个项目——登陆界面2
    • 第九个项目——新闻列表2
  • 第四章 内容提供器与数据共享
    • 第十个项目——音乐播放器1
  • 第五章 多媒体与服务
    • 第十一个项目——音乐播放器2
    • 第十二个项目——音乐播放器3
  • 第六章 绑定服务与自定义广播
    • 第十三个项目——音乐播放器4
  • 第七章 网络与多线程
    • 第十四个项目——新闻列表3
  • 附录 扩展项目
    • 第十五个项目——使用ViewHolder提升ListView显示性能
    • 第十六个项目——使用SwipeRefreshLayout实现下拉刷新
    • 第十七个项目——使用接口回调实现删除ListView列表中的Item数据
    • 第十八个项目——使用RecyclerView显示列表数据
    • 第十九个项目——使用Fragment组织UI页面
    • 第二十个项目——使用CursorAdapter进行数据绑定及渲染
    • 第二十一个项目——使用Loader实现异步数据加载操作
    • 第二十二个项目——使用TimerTask及Handler实现秒表计时器
    • 第二十三个项目——使用AsyncTask进行网络异步请求
Powered by GitBook
On this page
  • 实验目的
  • 实验要求
  • 实验内容
  • 步骤一,打开第九个项目
  • 步骤二,定义NewsQueryCursorLoader类
  • 步骤三,在MainActivity中实现LoaderManager.LoaderCallbacks接口
  • 步骤五,编译并运行App
  • 实验小结

Was this helpful?

  1. 附录 扩展项目

第二十一个项目——使用Loader实现异步数据加载操作

Previous第二十个项目——使用CursorAdapter进行数据绑定及渲染Next第二十二个项目——使用TimerTask及Handler实现秒表计时器

Last updated 4 years ago

Was this helpful?

实验目的

  • 掌握Loader框架的基本原理;

  • 掌握AsyncTaskLoader、CursorLoader及LoaderManager等组件的使用方法;

实验要求

  • 掌握使用Loader进行异步数据加载的方法;

实验内容

在中,我们实现了通过读取SQLite数据库加载新闻列表,并为了简化读取数据库的操作定义了NewsCursorAdapter适配器,用于直接将数据库查询得到的Cursor游标对象作为适配器的数据源。

为了简便起见,这类数据读取操作放在MainActivity的活动组件中。然而实际项目开发中,无论数据是从数据库或是 云端服务器进行读取,这类数据操作通常会比较耗时,这势必会占用UI主线程资源,从而影响App与用户的交互体验。

如果通过AsyncTask异步活动在子线程中进行数据读取,则开发人员还需要在Activity或Fragment生命周期中 管理主线程、子线程的状态,这增加了应用开发的复杂度。

本实验中使用Android提供的Loader异步加载框架解决上述问题,将数据读取等操作放入Loader框架中进行管理, 又可降低在Activity或Fragment中管理数据读取子线程状态的复杂度。

在Loader异步加载框架中主要需要熟悉两个组件的使用方法,其一是LoaderManager,它用于在Activity或Fragement中管理多个Loader实例,每个Loader实例可对应一个AsyncTaskLoader对象;其二是AsyncTaskLoader,它在AsyncTask的基础上做了封装,每一个AsyncTaskLoader实例会启动一个AsyncTask,从而启动一个子线程执行数据异步操作。

步骤一,打开第九个项目

打开,在该项目基础上完成本次实验。

步骤二,定义NewsQueryCursorLoader类

在Android Studio中新建一个名为NewsQueryCursorLoader的Java类,该类继承至CursorLoader (CursorLoader继承至AsyncTaskLoader)。在该类中,有两个方法需要进行重写,分别是 onStartLoading()及loadInBackground()方法,其中onStartLoading()方法调用了forceLoad()方法强制NewsQueryAsyncCursorLoader类实例重新执行loadInBackground()方法;

而loadInBackground()方法则是实际放入子线程执行的代码,在该方法中把数据库的查询操作代码放入其中,而该方法的返回值为Cursor类型,则是从子线程中返回的数据。loadInBackground()方法的返回值将作为LoaderManager.LoaderCallbacks接口的onLoadFinished()方法的参数回传给调用LoaderManager的组件。

以下为NewsQueryAsyncCursorLoader类定义:

import android.content.Context;
import android.content.CursorLoader;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

public class NewsQueryAsyncCursorLoader extends CursorLoader {
    private Context mContext;

    public NewsQueryAsyncCursorLoader(Context context) {
        super(context);
        mContext = context;
    }

    @Override
    protected void onStartLoading() {
        forceLoad();
    }

    @Override
    public Cursor loadInBackground() {
        MyDbOpenHelper dbOpenHelper = new MyDbOpenHelper(mContext);
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        Cursor cursor = db.query(
                NewsContract.NewsEntry.TABLE_NAME,
                null,
                null,
                null,
                null,
                null,
                NewsContract.NewsEntry._ID +" DESC");

        return cursor;
    }
}

步骤三,在MainActivity中实现LoaderManager.LoaderCallbacks接口

在Activity或Fragement中使用LoaderManager需要做的工作主要包括两部分:

  • 实现LoaderManager.LoaderCallbacks接口,该接口需要实现onCreateLoader()、onLoaderFinished()、onLoaderReset()方法;分别用于当Loader实例被创建、完成、重置时执行的回调方法;

  • 获取LoaderManager实例并调用其initLoader()或restartLoader()方法启动Loader实例;

通过在MainActivity中实现LoaderManager.LoaderCallbacks接口,该接口需要一个泛型参数,其指明接口中的三个方法返回值或参数类型:

  • Loader onCreateLoader(int i, Bundle bundle);

  • void onLoadFinished(Loader loader, D obj);

  • void onLoaderReset(Loader loader);

1. 实现LoaderManager.LoaderCallbacks接口

由于本实验中,数据从数据库进行查询,其返回值类型为Cursor,因此指明LoaderManager.LoaderCallbacks泛型参数为Cursor,具体代码见本文最后一段代码。

接下来逐一讲解LoaderManager.LoaderCallbacks接口的三个重写方法。

首先,重写Loader onCreateLoader(int i, Bundle bundle)方法,该方法的返回值为Loader类型,而第一个参数i表示的是Loader的编号,通常由LoaderManager的initLoader()或restartLoader()方法指定,开发者可根据该参数决定实例化哪个Loader类实例(实际开发中,可能需要多个Loader实例对数据进行不同操作,例如查询、更新、插入、删除等操作都可能由不同的自定义Loader子类完成;具体而言,本实验中NewsQueryAsyncCursorLoader实现对数据的查询操作,而插入操作则需要NewsInsertAsyncCursorLoader来完成);第二个参数是传递给Loader的Bundle类型值,在本实验中并未使用到。 onCreateLoader()方法在LoaderManager调用initLoader()或restartLoader()方法后被回调执行。

可以看到在MainActivity类中,为了简便起见,直接返回了一个匿名NewsQueryAsyncCursorLoader类对象。实际开发中通常需要通过switch或if语句判断参数i的值,从而实例化不同的Loader子类。

其次,重写void onLoadFinished(Loader loader, Cursor cursor)方法,该方法第一个参数为在onCreateLoader()函数返回的Loader(实际为NewsQueryAsyncCursorLoader)实例,第二个参数为Loader实例执行完成loadInBackground()函数后的返回值Cursor对象(也就是NewsQueryAsyncCursorLoader中查询到的数据库结果集Cursor对象)。该方法在对应的Loader实例的loadInBackground()方法执行完成后被回调执行。

在该方法中,通过cursorAdapter.swapCursor()方法切换了最新的数据库查询结果集Cursor对象,并调用notifyDataSetChanged()方法通知ListView刷新列表数据。

最后,重写void onLoaderReset(Loader loader)方法。该方法在对应的Loader被重置时或LoaderManager调用destroyLoader()方法或Activity、Fragement被销毁时被回调执行。通常需要在该方法内将引用到某一个Loader数据的对象进行删除。

2. 调用LoaderManager.initLoader()方法启动Loader

通过调用LoaderManager对象调用initLoader()方法启动Loader从而实现数据的异步读取操作,该方法接受3个参数,分别是:

  • int i,表示要初始化的Loader编号,开发人员可自定义每个Loader对应的整型编号,从而在onStartLoading()、onLoadFinished()等方法内进行判断执行相应的业务代码;

  • Bundle bundle,表示传递给onStartLoading()方法的Bundle类型参数,通常用于初始化Loader,具体视Loader子类实现而异;

  • Context context,表示上下文参数;

在Activity、Fragement等组件中,通过getLoaderManager()方法可获得LoaderManager实例。在MainActivity活动的onCreate()方法中,当ListView、NewsCursorAdapter等对象实例化完成后,调用了getLoaderManager()方法获取了LoaderManager实例,接着调用initLoader()方法初始化Loader实现新闻列表数据的异步读取操作。

以下为修改后的MainActivity类代码:

 public class MainActivity extends AppCompatActivity 
         implements LoaderManager.LoaderCallbacks<Cursor> {
    ...
    private CursorAdapter cursorAdapter =null;
    private ListView lvNewsList;
    ...

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

        lvNewsList = findViewById(R.id.lv_news_list);
        myDbHelper = new MyDbOpenHelper(MainActivity.this);
        db = myDbHelper.getWritableDatabase();

        cursorAdapter = new NewsCursorAdapter(MainActivity.this);
        cursorAdapter.swapCursor(null);

        lvNewsList.setAdapter(cursorAdapter);
        getLoaderManager().initLoader(0, null, this);
        ...
    }

    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
        return new NewsQueryAsyncCursorLoader(MainActivity.this);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        cursorAdapter.swapCursor(cursor);
        cursorAdapter.notifyDataSetChanged();
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    }
}

步骤五,编译并运行App

请思考如何结合Loader框架与SwipeRefreshLayout控件实现新闻列表的异步下拉刷新。

实验小结

通过本次实验,你应该掌握了如下知识内容:

  • 使用AsyncTaskLoader、CursorLoader及LoaderManager等组件进行异步数据加载;

编译本项目,成功后在AVD上或物理机上运行App,其运行效果与相同。

第九个项目
第九个项目
第九个项目