前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解--Android Loader

深入理解--Android Loader

作者头像
Anymarvel
发布2020-12-16 16:43:22
7450
发布2020-12-16 16:43:22
举报
文章被收录于专栏:Android开发实战Android开发实战

点击蓝字关注我们哟~

开发 漫品 客户端 本地图书导入页面 的过程中,需要获取到手机目录中所有的txt文件进行展示用于提供给的用户进行

如果使用Java读取目录,目前想到的是递归的方式进行文件获取,但获取过程其实是比较缓慢的,基于获取到的文件再向下进行传递。如果手机文件较多,内容较多的话,这并不是一个好的选中,

也许查找时间会非常的长。

代码语言:javascript
复制
private void searchBook(ObservableEmitter<File> e, File parentFile) {
    if (null != parentFile && parentFile.listFiles() != null &&parentFile.listFiles().length > 0) {
        File[] childFiles = parentFile.listFiles();
        for (int i = 0; i < childFiles.length; i++) {
            if (childFiles[i].isFile() && childFiles[i].getName().substring(childFiles[i].getName().lastIndexOf(".") + 1).equalsIgnoreCase("txt") && childFiles[i].length() > 100 * 1024) {   //100kb
                e.onNext(childFiles[i]);
                continue;
            }
            if (childFiles[i].isDirectory() && childFiles[i].listFiles().length > 0) {
                searchBook(e, childFiles[i]);
            }
        }
    }}

一. Loader是什么,有什么用。

Loader 顾名思义 就是 加载器。

借助 Loader API,您可以从内容提供程序或其他数据源中加载数据,以便在 FragmentActivity 或 Fragment 中显示。如果您不理解为何需要 Loader API 来执行这个看似无关紧要的操作,请首先考虑没有加载器时可能会遇到的一些问题:

  • 如果直接在 Activity 或片段中获取数据,由于通过界面线程执行查询的速度可能较慢,响应能力的不足将影响您的用户。
  • 如果从另一个线程获取数据(方法可能是使用 AsyncTask),则您需负责通过各种 Activity或片段生命周期事件(例如 onDestroy() 和配置变更)来管理线程和界面线程。

加载器不仅能解决这些问题,同时还具备其他优势。例如:

  • 加载器在单独的线程上运行,以免界面出现卡顿或无响应问题。
  • 加载器可在事件发生时提供回调方法,从而简化线程管理。
  • 加载器会保留和缓存配置变更后的结果,以免出现重复查询问题。
  • 加载器可实现观察器,从而监控基础数据源的变化。例如,CursorLoader 会自动注册 ContentObserver,以在数据变更时触发重新加载。

上面是官方的介绍,其实总结下就是以下两点:

  • 1)在单独的线程中读取数据,不会阻塞UI线程
  • 2)监视数据的更新

二. Loader API 总结

在应用中使用加载器时,可能会涉及到多个类和接口。下表对其进行了总结:


  • LoaderManager

一种与 FragmentActivity 或 Fragment 相关联的抽象类,用于管理一个或多个 Loader 实例。每个 Activity 或片段只有一个 LoaderManager,但 LoaderManager 可管理多个加载器。

如要获取 LoaderManager,请从 Activity 或片段调用 getSupportLoaderManager()。

如要从加载器开始加载数据,请调用 initLoader() 或 restartLoader()。系统会自动确定是否已存在拥有相同整型 ID 的加载器,并将创建新加载器或重复使用现有的加载器。


  • LoaderManager.LoaderCallbacks

此接口包含加载器事件发生时所调用的回调方法。接口定义三种回调方法:

  • onCreateLoader(int, Bundle) - 系统需要创建新加载器时调用。您的代码应创建 Loader 对象并将其返回系统。
  • onLoadFinished(Loader<D>, D) - 加载器在完成数据加载时调用。一般来说,您的代码应向用户显示数据。
  • onLoaderReset(Loader<D>) - 重置之前创建的加载器时调用(当您调用 destroyLoader(int) 时),或由于系统销毁 Activity 或片段而使其数据不可用时调用。您的代码应删除其对加载器数据的任何引用。

此接口一般由您的 Activity 或片段实现,并在您调用 initLoader() 或 restartLoader() 时进行注册。


  • Loader

Loader 类执行数据的加载。此类属于抽象类,并且是所有加载器的基类。您可以直接创建 Loader 的子类,或使用以下某个内置子类来简化实现:

  • AsyncTaskLoader - 抽象加载器,可通过提供 AsyncTask 在单独的线程上执行加载操作。
  • CursorLoader - AsyncTaskLoader 的具体子类,用于异步加载 ContentProvider 的数据。该类会查询 ContentResolver 并返回 Cursor。

三. 如何使用Loader

使用loader的几个必备条件如下:

  1. 一个Activity 或者 一个Fragment。LoaderManager获取需要传递Owner,这里必须是Activity 或者fragment
  2. 获取一个LoaderManager的实例。LoaderManager.getInstance(activity).initLoader(LoaderCreator.ALL_BOOK_FILE, null, new MediaLoaderCallbacks(activity, resultCallback));
代码语言:javascript
复制
/**
 * Gets a LoaderManager associated with the given owner, such as a {@link androidx.fragment.app.FragmentActivity} or
 * {@link androidx.fragment.app.Fragment}.
 *
 * @param owner The owner that should be used to create the returned LoaderManager
 * @param <T> A class that maintains its own {@link android.arch.lifecycle.Lifecycle} and
 *           {@link android.arch.lifecycle.ViewModelStore}. For instance,
 *           {@link androidx.fragment.app.FragmentActivity} or {@link androidx.fragment.app.Fragment}.
 * @return A valid LoaderManager
 */@NonNullpublic static <T extends LifecycleOwner & ViewModelStoreOwner> LoaderManager getInstance(
        @NonNull T owner) {
    return new LoaderManagerImpl(owner, owner.getViewModelStore());}
  1. 一个CursorLoader,从一个ContentProvider里加载数据。
  2. 一个LoaderManager.LoaderCallbacks的实现。在这创建新的loader,和管理已经存在的loaders。

LoaderManager.LoaderCallbacks<D>接口是LoaderManager用来向客户返回数据的方式。

每个Loader都有自己的回调对象供与LoaderManager进行交互。

该回调对象在实现LoaderManager中地位很高,告诉LoaderManager如何实例化Loader(onCreateLoader),以及当载入行为结束或者重启(onLoadFinished或者onLoadReset)之后执行什么操作。

大多数情况,你需要把该接口实现为组件的一部分,比如说让你的Activity或者Fragment实现

LoadManager.LoaderCallbacks<D>接口。

代码语言:javascript
复制
public interface LoaderCallbacks<D> {
    @MainThread
    @NonNull
    Loader<D> onCreateLoader(int id, @Nullable Bundle args);


    @MainThread    void onLoadFinished(@NonNull Loader<D> loader, D data);

    @MainThread    void onLoaderReset(@NonNull Loader<D> loader);}

一旦实现该接口,客户端将回调对象(本例中为“this”)作为LoaderManager的initLoader函数的第三个参数传输。

总的来说,实现回调接口非常直接明了。每个回调方法都有各自明确的与LoaderManager进行交互的目的:

  1. onCreateLoader是一个工厂方法,用来返回一个新的Loader。LoaderManager将会在它第一次创建Loader的时候调用该方法。
  2. onLoadFinished方法将在Loader创建完毕的时候自动调用。典型用法是,当载入数据完毕,客户端(译者注:调用它的Activity之类的)需要更新应用UI。客户端假设每次有新数据的时候,新数据都会返回到这个方法中。记住,检测数据源是Loader的工作,Loader也会执行实际的同步载入操作。一旦Loader载入数据完成,LoaderManager将会接受到这些载入数据,并且将将结果传给回调对象的onLoadFinished方法,这样客户端(比如Activity或者Fragment)就能使用该数据了。
  3. 最后,当Loader们的数据被重置的时候将会调用onLoadReset。该方法让你可以从旧的数据中移除不再有用的数据。

  • (可选)一种数据源,例如一个Conterprovider(当使用CursorLoader)。
  • (可选)一种显示loader加载数据的方式,例如SimpleCursorAdapter。

四. 获取媒体库中所有的书籍文件(手机中所有的.txt文件)

源码地址: https://github.com/AnyMarvel/ManPinAPP

路径 app/src/main/java/com/mp/android/apps/monke/monkeybook/utils/media

代码语言:javascript
复制
package com.mp.android.apps.monke.monkeybook.utils.media;import android.content.Context;import android.database.Cursor;import android.os.Bundle;import androidx.fragment.app.FragmentActivity;import androidx.loader.app.LoaderManager;import androidx.loader.content.Loader;import java.io.File;import java.lang.ref.WeakReference;import java.util.List;/**
 * 获取媒体库的数据。
 */public class MediaStoreHelper {

    /**
     * 获取媒体库中所有的书籍文件
     * <p>
     * 暂时只支持 TXT
     *
     * @param activity
     * @param resultCallback
     */
    public static void getAllBookFile(FragmentActivity activity, MediaResultCallback resultCallback) {
        // 将文件的获取处理交给 LoaderManager。
        LoaderManager.getInstance(activity)
                .initLoader(LoaderCreator.ALL_BOOK_FILE, null, new MediaLoaderCallbacks(activity, resultCallback));
    }

    public interface MediaResultCallback {
        void onResultCallback(List<File> files);
    }

    /**
     * Loader 回调处理
     */
    static class MediaLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
        protected WeakReference<Context> mContext;
        protected MediaResultCallback mResultCallback;

        public MediaLoaderCallbacks(Context context, MediaResultCallback resultCallback) {
            mContext = new WeakReference<>(context);
            mResultCallback = resultCallback;
        }

        @Override        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            return LoaderCreator.create(mContext.get(), id, args);
        }

        @Override        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            LocalFileLoader localFileLoader = (LocalFileLoader) loader;
            localFileLoader.parseData(data, mResultCallback);
        }

        @Override        public void onLoaderReset(Loader<Cursor> loader) {
        }
    }}

LoaderCreator.java

代码语言:javascript
复制
package com.mp.android.apps.monke.monkeybook.utils.media;import android.content.Context;import android.os.Bundle;import androidx.loader.content.CursorLoader;public class LoaderCreator {
    public static final int ALL_BOOK_FILE = 1;

    public static CursorLoader create(Context context, int id, Bundle bundle) {
        LocalFileLoader loader = null;
        switch (id){
            case ALL_BOOK_FILE:
                loader = new LocalFileLoader(context);
                break;
            default:
                loader = null;
                break;
        }
        if (loader != null) {
            return loader;
        }

        throw new IllegalArgumentException("The id of Loader is invalid!");
    }}

LocalFileLoader.java

代码语言:javascript
复制
package com.mp.android.apps.monke.monkeybook.utils.media;import android.content.Context;import android.database.Cursor;import android.net.Uri;import android.provider.MediaStore;import android.text.TextUtils;import androidx.annotation.NonNull;import androidx.loader.content.CursorLoader;import java.io.File;import java.sql.Blob;import java.util.ArrayList;import java.util.List;public class LocalFileLoader extends CursorLoader {
    private static final String TAG = "LocalFileLoader";

    private static final Uri FILE_URI = Uri.parse("content://media/external/file");
    private static final String SELECTION = MediaStore.Files.FileColumns.DATA + " like ?";
    private static final String SEARCH_TYPE = "%.txt";
    private static final String SORT_ORDER = MediaStore.Files.FileColumns.DISPLAY_NAME + " DESC";
    private static final String[] FILE_PROJECTION = {
            MediaStore.Files.FileColumns.DATA,
            MediaStore.Files.FileColumns.DISPLAY_NAME    };

    public LocalFileLoader(Context context) {
        super(context);
        initLoader();
    }

    /**
     * 为 Cursor 设置默认参数
     */
    private void initLoader() {
        setUri(FILE_URI);
        setProjection(FILE_PROJECTION);
        setSelection(SELECTION);
        setSelectionArgs(new String[]{SEARCH_TYPE});
        setSortOrder(SORT_ORDER);
    }

    public void parseData(Cursor cursor, final MediaStoreHelper.MediaResultCallback resultCallback) {
        List<File> files = new ArrayList<>();
        // 判断是否存在数据
        if (cursor == null) {
            // TODO:当媒体库没有数据的时候,需要做相应的处理
            // 暂时直接返回空数据
            resultCallback.onResultCallback(files);
            return;
        }
        // 重复使用Loader时,需要重置cursor的position;
        cursor.moveToPosition(-1);
        while (cursor.moveToNext()) {
            String path;

            path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA));
            // 路径无效
            if (TextUtils.isEmpty(path)) {
                continue;
            } else {
                File file = new File(path);
                if (file.isDirectory() || !file.exists()){
                    continue;
                }
                else {
                    files.add(file);
                }
            }
        }
        if (resultCallback != null) {
            resultCallback.onResultCallback(files);
        }
    }}

以上是漫品 客户端加载本地文件的方式,欢迎有更好方式的童鞋留言。

https://github.com/AnyMarvel/ManPinAPP

欢迎各位大大 start

漫品 客户端小说模块:

  • 基于mvp架构进行代码布局,降低代码耦合度。
  • 采用 sql 数据库对数据进行存储。
  • 支持小说更新提示。

阅读页支持:

  • 支持翻页动画:仿真翻页、覆盖翻页、上下滚动翻页等翻页效果。
  • 支持页面定制:亮度调节、背景调节、字体大小调节
  • 支持全屏模式(含有虚拟按键的手机)、音量键翻页
  • 支持页面进度显示、页面切换、上下章节切换。
  • 支持在线章节阅读、本地书籍查找。
  • 支持本地书籍加载到页面(支持本地书籍分章、加载速度快、耗费内存少)

夯实基础,关注前沿,娱乐生活

掌握更多前沿技术,获取更多笑点

请关注--------喘口仙氣

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 喘口仙氣 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. Loader是什么,有什么用。
  • 二. Loader API 总结
  • 三. 如何使用Loader
  • 四. 获取媒体库中所有的书籍文件(手机中所有的.txt文件)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档