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
转载请著名出处!谢谢~~