android进阶3step3:Android 常用框架——Loader异步加载框架

  • 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可以异步加载(更快),还可以注册观察者进行实时更新数据

  1. 获得LoaderManager对象
  2. 通过LoaderManager初始化Loader
  3. 实现LoaderCallbacks接口
  4. 在onCreateLoader方法中,创建CursorLoader
  5. 在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>类型,改成你需要的数据类型

  1. 实现LoaderManager.LoaderCallbacks类,T是代表你希望返回的数据是咋样的,可以是string,boolean等基本数据类型,也可以是cursor等等。 
  2. 当cursorLoader被初始化之后,会首先执行onCreateLoader()方法,执行完之后,会返回T类型的数据。 
  3. 当onCreateLoader()方法执行完毕,就该执行onLoadFinished()方法了,在这里你就可以进行数据的获取了。 
  4. 其它的一些方法,比如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
步骤:

  1.  继承AsyncTaskLoader类
  2.  实现loadInBackground方法
  3. 使用LoaderManager初始化Loader
  4. 在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来实现

猜你喜欢

转载自blog.csdn.net/qq_17846019/article/details/84582770
今日推荐