在上一篇文章中,讲解了 Loader 的基本概念。这一篇将会用实战的方式来探寻 Android Loader的内部机制。我们准备做一个 读取手机短信的例子。废话不多说,直接上效果图:
实例源码
- 首先 SmsActivity 的源码
package com.app.loader.sms;
import android.app.LoaderManager;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import com.app.loader.R;
public class SmsActivity extends AppCompatActivity {
private int loaderId = 0 ;
private ListView lv;
private SimpleCursorAdapter adapter;
private LoaderManager.LoaderCallbacks loaderCallbacks ;
private EditText editText ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sms );
Log.e( "loader", "activity onCreate: ");
lv = (ListView) findViewById( R.id.listview );
adapter = new SimpleCursorAdapter(this,
R.layout.sms_listview_item ,
null,
new String[]{"address","body"},
new int[]{R.id.address, R.id.body},
SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
lv.setAdapter(adapter);
loaderCallbacks = new MyCallback() ;
//初始化,并且创建Loader 实例,并且开始执行
getLoaderManager().initLoader( loaderId , null, loaderCallbacks );
editText = (EditText) findViewById( R.id.editText );
findViewById( R.id.start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//开始查询
String tag = editText.getText().toString() ;
Bundle bundle = new Bundle();
bundle.putString("key", tag );
getLoaderManager().restartLoader( loaderId , bundle, loaderCallbacks );
}
});
findViewById( R.id.init).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//初始化,并且创建Loader 实例,并且开始执行
getLoaderManager().initLoader( loaderId , null, loaderCallbacks );
}
});
}
class MyCallback implements LoaderManager.LoaderCallbacks<Cursor> {
@Override
public Loader onCreateLoader(int id, Bundle args) {
SmsLoader loader = new SmsLoader( SmsActivity.this , args);
Log.e( "loader", "onCreateLoader: ");
return loader ;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Log.e( "loader", "onLoadFinished: ");
adapter.changeCursor( data );
}
@Override
public void onLoaderReset(Loader loader) {
Log.e( "loader", "onLoaderReset: ");
//当 Activity OnDestory() , 系统会回调这个方法
adapter.swapCursor(null);
}
}
@Override
protected void onResume() {
Log.e( "loader", "activity onResume: ");
super.onResume();
}
@Override
protected void onPause() {
Log.e( "loader", "activity onPause: ");
super.onPause();
}
@Override
protected void onDestroy() {
Log.e( "loader", "activity onDestroy: ");
super.onDestroy();
}
}
- activity_sms 布局
<?xml version="1.0" encoding="utf-8"?>
<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"
tools:context="com.app.loader.MainActivity">
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="50dp"
android:hint="请你输入过滤关键词"
/>
<Button
android:id="@+id/start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开始查询"
/>
<Button
android:id="@+id/init"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开始执行 init方法"
/>
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
- SmsLoader 类继承 AsyncTaskLoader
package com.app.loader.sms;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
/**
* Created by ${zhaoyanjun} on 2017/4/19.
*/
public class SmsLoader extends AsyncTaskLoader<Cursor> {
private Bundle bundle;
private Uri uri = Uri.parse("content://sms");
private String []colums = {"_id","address","body"};
public SmsLoader(Context context , Bundle bundle ) {
super(context);
this.bundle = bundle;
}
@Override
public Cursor loadInBackground() {
String selection = null;
String[] selectionArgs = null;
if (bundle!=null) {
selection = "body like ? ";
selectionArgs = new String[]{"%"+bundle.getString("key")+"%"};
}
Cursor cursor = getContext().getContentResolver().query(uri, colums, selection , selectionArgs, null);
Log.e( "loader", "loadInBackground: ");
return cursor;
}
@Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
Log.e( "loader", "onStartLoading: ");
//这里一定要执行 forceLoad(); 否则 Loader 不会正常工作
// onStartLoading() --> forceLoad --> 调用 AsyncTaskLoader 里面的 forceLoad()
// --> 开始创建 AsyncTask 对象实例,并且运行 AsyncTask 的 doInBackground --> SmsLoader 类中 loadInBackground()
// --> 开始把结果 回调给主线程 AsyncTask onPostExecute() --> AsyncTaskLoader dispatchOnLoadComplete()
// -- AsyncTaskLoader deliverResult() 把最后的结果回调给 LoaderManager.LoaderCallbacks
}
}
测试
- 第一次 Activity 启动 Log 日志分析 :
com.app.loader E/loader: activity onCreate:
com.app.loader E/loader: onCreateLoader:
com.app.loader E/loader: onStartLoading:
com.app.loader E/loader: loadInBackground:
com.app.loader E/loader: onLoadFinished:
可以看到 第一次调用 initLoader() 方法后
//初始化,并且创建Loader 实例,并且开始执行
getLoaderManager().initLoader( loaderId , null, loaderCallbacks );
首先 调用 LoaderCallbacks
中的 onCreateLoader
来创建一个 Loader 对象,然后调用 SmsLoader
中的 onStartLoading
方法。然后调用 SmsLoader
中的 loadInBackground
开始执行 异步任务。最后在 LoaderCallbacks
中 onLoadFinished
方法中回调。
- 点击 “开始执行 Init 方法” 按钮,Log 分析
点击 “开始执行 Init 方法”按钮后,开始执行
getLoaderManager().initLoader( loaderId , null, loaderCallbacks );
Log 日志为:
com.app.loader E/loader: onLoadFinished:
这里只回调了 onLoadFinished
的方法,把异步操作产生的数据给传递出来。请注意,这里没有走 loadInBackground
方法,说明此时 onLoadFinished
回传的数据,是旧数据,也就是上一次异步产生的数据。
但是有时我们想丢弃旧数据然后重新开始创建一个新Loader,这可怎么办呢?别担心,要丢弃旧数据调用restartLoader()即可。
- restartLoader 方法探究。
点击 开始查询按钮
,会执行下面的代码
//开始查询
String tag = editText.getText().toString() ;
Bundle bundle = new Bundle();
bundle.putString("key", tag );
getLoaderManager().restartLoader( loaderId , bundle, loaderCallbacks );
Log 日志:
com.app.loader E/loader: onCreateLoader:
com.app.loader E/loader: onStartLoading:
com.app.loader E/loader: loadInBackground:
com.app.loader E/loader: onLoadFinished:
通过日志可以看出 restartLoader
重新执行了 onCreateLoader
创建了一个新的 Loader
对象; loadInBackground
丢弃了旧数据,重新加载了新数据 , 并且回调 onLoadFinished
。
- 当前 Activity 从后台到前台
在测试的时候,我发现当前 Activity 从后台到前台的时候,调用顺序如下:
com.app.loader E/loader: onStartLoading:
com.app.loader E/loader: activity onResume:
com.app.loader E/loader: loadInBackground:
com.app.loader E/loader: onLoadFinished:
可以看到当前界面从后台到前台的过程中,Loader 会自动调异步任务,并且回调新的数据。
- 当前 Activity 销毁
当前 Activity 销毁的时候,调用顺序如下:
com.app.loader E/loader: activity onPause:
com.app.loader E/loader: activity onDestroy:
com.app.loader E/loader: onLoaderReset:
可以看到 LoaderCallbacks
的 onLoaderReset
方法会回调。
当 onLoaderReset
方法被调用的时候,代表 这个 Loader 正在被重置,此时的数据不可用。应用程序应该在这一点上删除对Loader数据的任何引用。
比如:
@Override
public void onLoaderReset(Loader loader) {
Log.e( "loader", "onLoaderReset: ");
//当 Activity OnDestory() , 系统会回调这个方法
adapter.swapCursor(null);
}