- Loader简介
- Loader的基本用法
- 自定义Loader的用法
- Loader的原理简介
一、Loader是什么?
Android的设计之中,任何耗时的操作都不能放在UI主线程之中。所以类似于网络操作等等耗时的操作都需要使用异步的实现。而在ContentProvider之中,也有可能存在耗时的操作(当查询的数据量很大的时候),这个时候我们也需要使用异步的调用来完成数据的查询。
• Loaders机制在Android 3.0版本后引入。
• Loaders提供了异步加载数据的方法,能解决长时间数据加载的问题。
• 特点:
– 适用于任何Activity和Fragment;
– 提供了异步加载数据的机制;
– 检测数据源,当数据源内容改变时它们能够传递新的结果。
二、相关API
• LoaderManager
– 管理Loader,每个Activity或Fragment对应一个LoaderManager
• LoaderCallbacks
– 包含和Loader相关的回调方法
• AsyncTaskLoader
– 抽象类,提供异步加载的方法
• Cursors Loader
– AsyncTaskLoader的子类,提供游标数据的加载。
三、使用Loader加载联系人
当手机有大量的联系人的时候,时候loader可以异步加载(更快),还可以注册观察者进行实时更新数据
- 获得LoaderManager对象
- 通过LoaderManager初始化Loader
- 实现LoaderCallbacks接口
- 在onCreateLoader方法中,创建CursorLoader
- 在onLoadFinished方法中,获得加载数据,更新UI
第一、二步:获得LoaderManager对象并初始化
添加联系人的权限:android6.0之后要进行动态申请 :请参考——>动态获取权限的封装
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
//1.获得LoaderManager对象
LoaderManager loaderManager = getLoaderManager();
//2.初始化
/**
* 参数1:loader的ID
* 参数2:给Loader传递的参数
* 参数3:LoaderCallback接口
* */
//传入this让类实现这个接口
loaderManager.initLoader(0, null, this);
。
第三步:实现LoaderCallbacks接口
LoaderCallbacks接口有三个方法:
1.创建loader对象
- public Loader<Object> onCreateLoader(int id, Bundle args)
当初始化成功之后会调用这个方法。参数对应initLoader的参数
2.加载数据完成
- public void onLoadFinished(Loader<Object> loader, Object data)
当数据加载完返回数据
3.重置loader
- public void onLoaderReset(Loader<Object> loader)
当数据发送改变,可以在这里变换之前的loader为新的loader以便更新数据
注意:onCreateLoader()相当于一个被观察者,onLoadFinished()相当于一个观察者,只要被观察者的数据有改变,那么观察者就能得到通知,并进行相应的响应
首先设置游标适配器将加载到数据显示上来:
因为是要使用游标数据,所以将LoaderCallbacks里面的泛型<Object>改成<Cursor>类型,改成你需要的数据类型
- 实现LoaderManager.LoaderCallbacks类,T是代表你希望返回的数据是咋样的,可以是string,boolean等基本数据类型,也可以是cursor等等。
- 当cursorLoader被初始化之后,会首先执行onCreateLoader()方法,执行完之后,会返回T类型的数据。
- 当onCreateLoader()方法执行完毕,就该执行onLoadFinished()方法了,在这里你就可以进行数据的获取了。
- 其它的一些方法,比如loader对象被重置了,就会执行onLoaderReset()方法。
mLv = findViewById(R.id.lv_contacts);
//设置游标适配器
/**
* 参数1:上下文
* 参数2:系统自带item的布局
* 参数3:cursor数据源(默认不给先,需要loader加载后再添加)
* 参数4:显示列的名字
* 参数5:item控件的id(系统的)
* 参数6:flag 标识位 FLAG_REGISTER_CONTENT_OBSERVER
* 注册内容观察者模式,数据源发生更新随之更新
*/
mCursorAdapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1,
null,
new String[]{ContactsContract.Contacts.DISPLAY_NAME},
new int[]{android.R.id.text1},
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER
);
mLv.setAdapter(mCursorAdapter);
在回调方法中:
LoaderManager初始化完成之后
进入onCreateLoader,新建游标loader进行异步获取数据,将数据返回
回调到onLoadFinished,(此时是在主线程)进行数据的更新,游标适配器替换原来的cursor对象
观察者模式:立马更新ListView上的数据
/**
* 创建loader对象
*
* @param id
* @param args
* @return
*/
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
/**
* 参数1:上下文 参数2:Uri 参数3:布局筛选 参数4:条件 参数5:列的值 :参数6:排序
*/
CursorLoader cursorLoader = new CursorLoader(
this,
ContactsContract.Contacts.CONTENT_URI,
null,
null,
null,
null);
//将加载到的数据进行返回到onLoadFinished
return cursorLoader;
}
/**
* 加载数据完成
*
* @param loader
* @param data
*/
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
//主线程UI线程完成,更新UI
//当数据加载成功后返回数据源,将游标适配器的数据源进行切换,即可显示
//该方法,回返回之前的使用的游标对象
Cursor oldCursor = mCursorAdapter.swapCursor(data);
if (oldCursor != null) {
oldCursor.close();
}
}
/**
* 重置loader
*
* @param loader
*/
@Override
public void onLoaderReset(Loader<Cursor> loader) {
//如果loader不使用要进行释放
Cursor oldCursor = mCursorAdapter.swapCursor(null);
if (oldCursor != null) {
oldCursor.close();
}
}
完整的代码:
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
/**
* 1.获得LoaderManager对象
* 2.通过LoaderManager初始化Loader
* 3.实现LoaderCallbacks接口
* 4.在onCreateLoader方法中,创建CursorLoader
* 5.在onLoadFinished方法中,获得加载数据,更新UI
*/
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
private ListView mLv;
private SimpleCursorAdapter mCursorAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLv = findViewById(R.id.lv_contacts);
//设置游标适配器
/**
* 参数1:上下文
* 参数2:系统自带item的布局
* 参数3:cursor数据源(默认不给先,需要loader加载后再添加)
* 参数4:显示列的名字
* 参数5:item控件的id(系统的)
* 参数6:flag 标识位 FLAG_REGISTER_CONTENT_OBSERVER
* 注册内容观察者模式,数据源发生更新随之更新
*/
mCursorAdapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1,
null,
new String[]{ContactsContract.Contacts.DISPLAY_NAME},
new int[]{android.R.id.text1},
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER
);
mLv.setAdapter(mCursorAdapter);
//1.获得LoaderManager对象
LoaderManager loaderManager = getLoaderManager();
//2.初始化
/**
* 参数1:loader的ID
* 参数2:给Loader传递的参数
* 参数3:LoaderCallback接口
* */
//传入this让类实现这个接口
loaderManager.initLoader(0, null, this);
}
/**
* 创建loader对象
*
* @param id
* @param args
* @return
*/
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
/**
* 参数1:上下文 参数2:Uri 参数3:布局筛选 参数4:条件 参数5:列的值 :参数6:排序
*/
CursorLoader cursorLoader = new CursorLoader(
this,
ContactsContract.Contacts.CONTENT_URI,
null,
null,
null,
null);
//将加载到的数据进行返回到onLoadFinished
return cursorLoader;
}
/**
* 加载数据完成
*
* @param loader
* @param data
*/
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
//主线程UI线程完成,更新UI
//当数据加载成功后返回数据源,将游标适配器的数据源进行切换,即可显示
//该方法,回返回之前的使用的游标对象
Cursor oldCursor = mCursorAdapter.swapCursor(data);
if (oldCursor != null) {
oldCursor.close();
}
}
/**
* 重置loader
*
* @param loader
*/
@Override
public void onLoaderReset(Loader<Cursor> loader) {
//如果loader不使用要进行释放
Cursor oldCursor = mCursorAdapter.swapCursor(null);
if (oldCursor != null) {
oldCursor.close();
}
}
}
效果:加载速度很快!
四、联系人的筛选
大致流程:
加一个EditText来输入内容,筛选联系人(使用Uri筛选的模式,当内容发生改变,返回新的uri对象)
代码根据上面完整的代码来
增加1:
String mFilterName ;
//联系人筛选文本框内容改变后,重新加载loader的数据
mEt = findViewById(R.id.et_search);
mEt.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//将发生改变的内容赋值给类属性
mFilterName = s.toString();
//重新创建Loader //与之前的初始化的loader参数一致
loaderManager.restartLoader(0, null, MainActivity.this);
}
@Override
public void afterTextChanged(Editable s) {
}
});
修改1 onCreateLoader:
当mFilterName就是搜索框的内容是否为空时,不为空,利用该值来拼接uri来筛选结果,返回新的uri
/**
* 创建loader对象
*
* @param id
* @param args
* @return
*/
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
/**
* 参数1:上下文 参数2:Uri 参数3:布局筛选 参数4:条件 参数5:列的值 :参数6:排序
*/
Uri uri = ContactsContract.Contacts.CONTENT_URI;
if (!TextUtils.isEmpty(mFilterName)) {
//如果搜索框中有内容,就将原来uri进行拼接根据输入的内容进行筛选
//返回新的uri
uri = Uri.withAppendedPath(
ContactsContract.Contacts.CONTENT_FILTER_URI,
Uri.decode(mFilterName));
}
CursorLoader cursorLoader = new CursorLoader(
this,
uri,
null,
null,
null,
null);
//将加载到的数据进行返回到onLoadFinished
return cursorLoader;
}
三、自定义Loader
上面的loader处理了Cursor数据,那如果是加载其他类型怎么办呢?
问题:如果从网络中加载数据,返回的格式可能不是Cursor类型的,怎么办?
使用自定义Loader
步骤:
- 继承AsyncTaskLoader类
- 实现loadInBackground方法
- 使用LoaderManager初始化Loader
- 在LoaderCallbacks接口的onCreateLoader方法中返回自定义Loader
第一步:创建布局文件,自定义实体类:(你想要的数据的格式)
activity_custom_loader.xml 主布局文件
<?xml version="1.0" encoding="utf-8"?>
<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"
tools:context="com.demo.loaderdemo.custom.CustomLoaderActivity">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
user_item.xml listviewitem的布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/username_tv"
android:text="username"
android:gravity="center"
android:textSize="30sp"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/password_tv"
android:gravity="center"
android:text="password"
android:textSize="30sp"/>
</LinearLayout>
自定义的bean类 UserBean.java
**
* 自定义实体类
*/
public class UserBean {
private String userName;
private String password;
public UserBean(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
第二步:创建适配器UserAdapter.java 数据显示桥梁
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.demo.loaderdemo.R;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义数据适配
*/
public class UserAdapter extends BaseAdapter {
private final LayoutInflater inflater;
private Context context;
private List<UserBean> users = new ArrayList<>();
public UserAdapter(Context context) {
this.context = context;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return users == null ? 0 : users.size();
}
@Override
public Object getItem(int position) {
return users.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
public void addUsers(List<UserBean> userList) {
users.addAll(userList);
notifyDataSetChanged();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
ViewHolder holder = null;
if (view == null) {
view = inflater.inflate(R.layout.user_item, null);
holder = new ViewHolder(view);
} else {
holder = (ViewHolder) view.getTag();
}
UserBean userBean = users.get(position);
holder.usernameTv.setText(userBean.getUserName());
holder.passwordTv.setText(userBean.getPassword());
return view;
}
class ViewHolder {
TextView usernameTv;
TextView passwordTv;
public ViewHolder(View view) {
usernameTv = (TextView) view.findViewById(R.id.username_tv);
passwordTv = (TextView) view.findViewById(R.id.password_tv);
view.setTag(this);
}
}
}
第四步:自定义loader来实现你想要的数据格式 CustomLoader.java
继承了AsyncTaskLoader<List<UserBean>> 泛型的数据是你想要得到的数据
AsyncTaskLoader其实就是AsyncTask的封装类
在loadInBackground(子线程中)执行耗时操作
在onPostExecute中将数据返回到 LoaderCallbacks<List<UserBean>> 的 onLoadFinished(主线程)方法中
下面的loadInBackground中,只是进行了模拟网络操作而已。具体实现根据自己的要求
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义Loader,加载UserBean数据集合
*/
public class CustomLoader extends AsyncTaskLoader<List<UserBean>> {
public CustomLoader(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
super.onStartLoading();
//Loader开始执行后,强制调用loadInBackground()方法
if(isStarted()){
forceLoad();
}
}
/**
* 在子线程加载数据
* @return
*/
@Override
public List<UserBean> loadInBackground() {
List<UserBean> users = new ArrayList<>();
users.add(new UserBean("zhangsan","123456"));
users.add(new UserBean("lisi","123456"));
users.add(new UserBean("wangwu","123456"));
users.add(new UserBean("zhaoliu","123456"));
return users;
}
}
第五步:主界面接收数据
和上面的cursorLoader一样,也是先进行获取LoaderManager 然后初始化,实现LoaderManager.LoaderCallbacks的接口
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import com.demo.loaderdemo.R;
import java.util.List;
/**
* 调用自定义Loader的界面
*/
public class CustomLoaderActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<List<UserBean>> {
private LoaderManager loaderManager;
private ListView listView;
private UserAdapter userAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_loader);
listView = findViewById(R.id.list_view);
userAdapter = new UserAdapter(this);
listView.setAdapter(userAdapter);
//getSupportLoaderManager()是v4的包可以适配各个版本,getLoaderManager要3.0之后才能使用
loaderManager = getSupportLoaderManager();
loaderManager.initLoader(888, null, this);
}
@Override
public Loader<List<UserBean>> onCreateLoader(int id, Bundle args) {
//使用自定义Loader
if (id == 888) {
return new CustomLoader(this);
}
return null;
}
@Override
public void onLoadFinished(Loader<List<UserBean>> loader, List<UserBean> data) {
userAdapter.addUsers(data);
}
@Override
public void onLoaderReset(Loader<List<UserBean>> loader) {
}
}
完成啦!对了别忘记权限, 如果要进行网络操作!
总结
• Loader是3.0后引入的异步数据机制
• Loader相关的API有:
- – LoaderManager
- – LoaderCallbacks
- – AsyncTaskLoader
- – CursorsLoader
• 可以用CursorsLoader加载数据库、ContentProvider中的数据
• 对于自定义类型的数据,可以自定义Loader来实现