首页 > 技术文章 > [Android] Android 用于异步加载 ContentProvider 中的内容的机制 -- Loader 机制 (LoaderManager + CursorLoader + LoaderManager.LoaderCallbacks)

wukong1688 原文

Android 用于异步加载 ContentProvider 中的内容的机制 -- Loader 机制 (LoaderManager + CursorLoader + LoaderManager.LoaderCallbacks)

关于Android Loader 的文章,百度一搜搜出了一大把。笔者看了好多篇,都吧唧吧唧讲了很多 异步 的好处。但笔者看完后,还是一头雾水,实现异步加载的方式

不是已经有了 Thread + Handle 或者 AsyncTask 等很多机制了吗?(可参考: https://www.cnblogs.com/wukong1688/p/10657659.html

为啥又要搞出一个新的东东出来???

后来终于查阅了很多资料,终于找到  Loader 机制 相比其他 异步加载更适合使用的场景:

Loader 机制一般用于数据加载,特别是用于加载 ContentProvider 中的内容,比起 Handler + Thread 或者 AsyncTask 的实现方式,Loader 机制能让代码更加的简洁易懂,而且是 Android 3.0 之后最推荐的加载方式。

Loader 机制的 使用场景 有:

  • 展现某个 Android 手机有多少应用程序

  • 加载手机中的图片和视频资源

  • 访问用户联系人

好了,既然明白了 Loader 机制使用的场景,

下面用一个加载手机中的图片文件夹的例子,看看在实际开发中如何运用 Loader 机制进行高效加载。

我们接下来就来看如何实现吧!

 一、实现自己的CursorLoader 加载器

 加载器是我们加载数据的工具,通过将对应的 URI 以及其他的查询条件传递给加载器,便可让加载器在后台高效地加载数据,等数据加载完成了便会返回一个 Cursor.

AlbumLoader.java

package com.jack.testmd.loader;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v4.content.CursorLoader;

public class AlbumLoader extends CursorLoader {
    public static final String COLUMN_COUNT = "count";

    /**
     * content://media/external/file
     */
    private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");

    private static final String[] COLUMNS = {
            MediaStore.Files.FileColumns._ID,
            "bucket_id",
            "bucket_display_name",
            MediaStore.MediaColumns.DATA,
            COLUMN_COUNT};

    private static final String[] PROJECTION = {
            MediaStore.Files.FileColumns._ID,
            "bucket_id",
            "bucket_display_name",
            MediaStore.MediaColumns.DATA,
            "COUNT(*) AS " + COLUMN_COUNT};

    /**
     * (media_type=? OR media_type =?) AND _size>0) GROUP BY (bucket_id
     */
    private static final String SELECTION =
            "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                    + " OR "
                    + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
                    + " AND " + MediaStore.MediaColumns.SIZE + ">0"
                    + ") GROUP BY (bucket_id";

    private static final String[] SELECTION_ARGS = {
            String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
            String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)
    };

    private static final String BUCKET_ORDER_BY = "datetaken DESC";

    private AlbumLoader(Context context, String selection, String[] selectionArgs) {
        super(context, QUERY_URI, PROJECTION, SELECTION, SELECTION_ARGS, BUCKET_ORDER_BY);
    }

    public static CursorLoader newInstance(Context context) {
        String selection = SELECTION;
        String[] selectionArgs = SELECTION_ARGS;
        return new AlbumLoader(context, selection, selectionArgs);
    }

    @Override
    public Cursor loadInBackground() {
        return super.loadInBackground();
    }

}

二、实现 LoaderCallbacks 进行客户端的交互

为了降低代码的耦合度,继承 LoaderManager.Loadercallbacks 实现 AlbumLoader 的管理类,将 Loader 的各种状态进行管理。

通过外部传入 Context,采用弱引用的方式防止内存泄露,获取 LoaderManager,并在 AlbumCollection 内部定义了相应的接口,将加载完成后返回的 Cursor 回调出去,让外部的 Activity 或 Fragment 进行相应的处理。

AlbumCollection.java
package com.jack.testmd.loader;

import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;

import java.lang.ref.WeakReference;

public class AlbumCollection  implements LoaderManager.LoaderCallbacks<Cursor> {
    private static final int LOADER_ID = 1;
    private WeakReference<Context> mContext;
    private LoaderManager mLoaderManager;
    private AlbumCallbacks mCallbacks;

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        Context context = mContext.get();
        if(context == null){
            return null;
        }

        return AlbumLoader.newInstance(context);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        Context context = mContext.get();
        if(context == null){
            return;
        }

        mCallbacks.onAlbumLoad(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        Context context = mContext.get();
        if(context == null){
            return;
        }
        mCallbacks.onAlbumReset();
    }

    public void onCreate(FragmentActivity activity, AlbumCallbacks callbacks){
        mContext = new WeakReference<Context>(activity);
        mLoaderManager = activity.getSupportLoaderManager();
        mCallbacks = callbacks;
    }

    public void loadAlbums(){
        mLoaderManager.initLoader(LOADER_ID, null, this);
    }

    public interface AlbumCallbacks{

        void onAlbumLoad(Cursor cursor);

        void onAlbumReset();
    }

}

三、填充数据到 RecyclerList 中
AlbumAdapter.java

package com.jack.testmd.loader;

import android.database.Cursor;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.jack.testmd.R;

public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.AlbumViewHolder> {
    private Cursor mCursor;

    public AlbumAdapter(Cursor cursor) {
        this.mCursor = cursor;
    }

    @Override
    public AlbumAdapter.AlbumViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_rv_album,null);
        return new AlbumViewHolder(view);
    }

    @Override
    public void onBindViewHolder(AlbumAdapter.AlbumViewHolder holder, int position) {
        if(mCursor != null && mCursor.moveToNext()){
            String albumCoverPath = mCursor.getString(mCursor.getColumnIndex("_data"));
            String albumName = mCursor.getString(mCursor.getColumnIndex("bucket_display_name"));
            String amount = mCursor.getString(mCursor.getColumnIndex("count"));

            holder.tvAlbumName.setText(albumName);
            holder.tvAlbumAmount.setText(amount);
            Glide.with(holder.ivAlbum.getContext())
                    .load(albumCoverPath)
                    .centerCrop()
                    .into(holder.ivAlbum);
        }
    }

    @Override
    public int getItemCount() {
        return mCursor.getCount();
    }

    public static class AlbumViewHolder extends RecyclerView.ViewHolder {

        private ImageView ivAlbum;
        private TextView tvAlbumName;
        private TextView tvAlbumAmount;

        public AlbumViewHolder(View itemView) {
            super(itemView);
            ivAlbum = (ImageView) itemView.findViewById(R.id.album_iv_album);
            tvAlbumName = (TextView) itemView.findViewById(R.id.album_tv_album_name);
            tvAlbumAmount = (TextView) itemView.findViewById(R.id.album_tv_amount);
        }
    }
}

四、主界面中的逻辑

看到这代码是不是觉得特别简洁,让 MainActivity 中继承了 AlbumCollection 中的 AlbumCallback 接口,接着 onCreate() 中实例化了 AlbumCollection,然后让 AlbumCollection 开始加载数据。

等数据加载完成后,便将包含数据的 Cursor 回调在 onAlbumLoad() 方法中,我们便可以进行 UI 的更新。

可以看到采用 Loader 机制,可以让我们的 Activity 或 Fragment 中的代码变得相当的简洁、清晰,而且代码耦合程度也相当低。

package com.jack.testmd;

import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;

import com.jack.testmd.loader.AlbumAdapter;
import com.jack.testmd.loader.AlbumCollection;

public class TestLoaderActivity extends AppCompatActivity implements AlbumCollection.AlbumCallbacks {
    private AlbumCollection mCollection;
    private AlbumAdapter mAdapter;
    private RecyclerView mRvAlbum;

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

        initLoad();
    }

    private void initLoad() {
        if (ContextCompat.checkSelfPermission(TestLoaderActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            mCollection = new AlbumCollection();
            mCollection.onCreate(this, this);
            mCollection.loadAlbums();
        } else {
            ActivityCompat.requestPermissions(TestLoaderActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
        }
    }

    @Override
    public void onAlbumLoad(Cursor cursor) {
        mRvAlbum = (RecyclerView) findViewById(R.id.main_rv_album);
        mRvAlbum.setLayoutManager(new LinearLayoutManager(this));
        mAdapter = new AlbumAdapter(cursor);
        mRvAlbum.setAdapter(mAdapter);

    }

    @Override
    public void onAlbumReset() {

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mCollection = new AlbumCollection();
                    mCollection.onCreate(this, this);
                    mCollection.loadAlbums();
                }
                break;
            default:
                break;
        }
    }

}

附:
笔者在测试的时候,还以为自动在相册中添加图片文件后,需要我们手动刷新。后来再次测试,发现确实是可以自动刷新数据,而不是我们手动去维护刷新!

本博客地址: wukong1688

本文原文地址:https://www.cnblogs.com/wukong1688/p/10702858.html

转载请著名出处!谢谢~~

推荐阅读