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


    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

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

  • 相关阅读:
    Vmware 虚拟硬盘 合并多个分割文件
    一步步带你做vue后台管理框架(三)——登录功能
    一步步带你做vue后台管理框架(二)——上手使用
    webpack教程(六)——分离组件代码
    webpack教程(五)——图片的加载
    webpack教程(四)——css的加载
    input输入框自动填充黄色背景解决方案
    webpack教程(三)——热刷新
    webpack教程(二)——webpack.config.js文件
    webpack教程(一)——初体验
  • 原文地址:https://www.cnblogs.com/wukong1688/p/10702858.html
Copyright © 2020-2023  润新知