Loader 知识梳理(2) - initLoader和restartLoader的区别

一、概述

在前面的一篇文章中,我们分析了LoaderManager的实现,里面涉及到了很多的细节问题,我们并不需要刻意地记住每个流程,之所以需要分析,以后在使用的过程中,如果遇到问题了,再去查看相关的源代码就好了。
对于Loader框架的理解,我认为掌握以下四个方面的东西就可以了:

  • LoaderManager的实现原理有一个大概的印象。
  • LoaderManager的三个关键方法initLoader/restartLoader/destroyLoader的使用场景。
  • 学会自定义Loader来实现数据的异步加载。
  • 总结一些App开发中常用的场景。

第一点可以参考前面的这篇文章:

Loader框架 - LoaderManager初探

今天这篇,我们将专注于分析第二点:initLoader/restartLoader的区别

二、基本概念的区别

首先,我们回顾一下,对于init/restart的定义:

  • initLoader

  • 通过调用这个方法来初始化一个特定IDLoader,如果当前已经有一个和这个ID关联的Loader,那么并不会去回调onCreateLoader来通知使用者传入一个新的 Loader实例替代那个旧的实例,仅仅是替代Callback,也就是说,上面的Bundle参数被丢弃了;而假如不存在一个关联的实例,那么会进行初始化,并启动它。

  • 这个方法通常是在Activity/Fragment的初始化阶段调用,因为它会判断之前是否存在相同的Loader,这样我们就可以复用之前已经加载过的数据,当屏幕装转导致Activity重建的时候,我们就不需要再去重新创建一个新的Loader,也免去了重新读取数据的过程。

  • restartLoader

  • 调用这个方法,将会重新创建一个指定IDLoader,如果当前已经有一个和这个ID关联的Loader,那么会对它进行canceled/stopped/destroyed等操作,之后,使用新传入的Bundle参数来创建一个新的Loader,并在数据加载完毕后递交给调用者。

  • 并且,在调用完这个函数之后,所有之前和这个ID关联的Loader都会失效,我们将不会收到它们传递过来的任何数据。

总结下来就是:

  • 当调用上面这两个方法时,如果不存在一个和ID关联的Loader,那么这两个方法是完全相同的。
  • 如果已经存在相关联的Loader,那么init方法除了替代Callback,不会做任何其它的事情,包括取消/停止等。而restart方法将会创建一个新的Loader,并且重新开始查询。

三、代码的区别

为了方便大家更直观地理解,我们截取一部分的源码来看一下:

  • initLoader
LoaderInfo info = mLoaders.get(id);
if (info == null) {
    //如果不存在关联的Loader,那么创建一个新的Loader
    info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
   //如果已经存在,那么仅仅替代Callback,传入的Bundle参数会被丢弃。
   info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
  • restartLoader
LoaderInfo info = mLoaders.get(id);
//如果已经存在一个相关联的Loader,那么执行操作。
if (info != null) {
    //mInactiveLoaders列表就是用来跟踪那些已经被抛弃的Loader
    LoaderInfo inactive = mInactiveLoaders.get(id);
    if (inactive != null) {
        //对跟踪列表进行一系列的操作。
    } else {
        //取消被抛弃的Loader,并加入到跟踪列表当中,以便在新的Loader完成任务之后销毁它。
        info.mLoader.abandon();
        mInactiveLoaders.put(id, info);
    }
}
//通知调用者,创建一个新的Loader,这个Loader将会和新的Bundle和Callback相关联。
info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);

通过上面的代码,就印证了前面第二节我们的说话,我们根据这些区别可以知道它们各自的职责:

  • initLoader是用来确保Loader能够被初始化,如果已经存在相同IDLoader,那么它会复用之前的。
  • restartLoader的应用场景则是我们的查询条件发生了改变。因为LoaderManager是用ID关联的,当这个Loader已经获取到了数据,那么就不需要再启动它了。因此当我们的需求发生了改变,就需要重新创建一个Loader

也就是说:

  • 查询条件一直不变时,使用initLoader
  • 查询条件有可能发生改变时,采用restartLoader

五、对于屏幕旋转的情况

5.1 重建

当我们在Manifest.xml没有给Activity配置configChanged的时候,旋转屏幕会导致的Activity/Fragment重建,这时候有两点需要注意的:

  • 由于此时我们的查询条件并不会发生改变,并且LoaderManager会帮我们恢复Loader的状态。因此,我们没有必要再去调用restartLoader来重新创建Loader来执行一次耗时的查询操作。

  • LoaderManager虽然会恢复Loader,但是它不会保存Callback实例,因此,如果我们希望在旋转完之后获得数据,那么至少要调用一次initLoader来传入一个新的Callback进行监听。

在这种情况下,假如我们在旋转之前Loader已经加载数据完毕了,那么onLoadFinished会立即被会调

5.2 没有重建

当没有重建时,不会走onCreate方法,因此需要在别的地方来初始化Loader

5.3 LoaderId

针对上面的这两种情况,我们都需要自己去保存LoaderId,在组件恢复之后,通过这个保存的id去调用init/restart方法,一般情况下,是通过savedInstanceState来保存的。

六、示例

现在,我们通过一个很简单的例子,来看一下,initLoader/restartLoader的区别,我们的Demo中有一个EditText和一个TextView,当EditText发生改变时,我们将当前EditText的内容作为查询的Key,查询任务就是调用Loader,延时2s,并将这个key作为查询的结果展示在TextView上。

6.1 采用initLoader来查询:

public class MainActivity extends Activity {

    private static final String LOADER_TAG = "loader_tag";
    private static final String QUERY = "query";

    private MyLoaderCallback mMyLoaderCallback;
    private TextView mResultView;
    private EditText mEditText;

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

    private void init() {
        mEditText = (EditText) findViewById(R.id.loader_input);
        mResultView = (TextView) findViewById(R.id.loader_result);
        mEditText.addTextChangedListener(new MyEditTextWatcher());
        mMyLoaderCallback = new MyLoaderCallback();
    }

    private void startQuery(String query) {
        if (query != null) {
            Bundle bundle = new Bundle();
            bundle.putString(QUERY, query);
            getLoaderManager().initLoader(0, bundle, mMyLoaderCallback);
        }
    }

    private void showResult(String result) {
        if (mResultView != null) {
            mResultView.setText(result);
        }
    }

    private static class MyLoader extends BaseDataLoader<String> {

        public MyLoader(Context context, Bundle bundle) {
            super(context, bundle);
        }

        @Override
        protected String loadData(Bundle bundle) {
            Log.d(LOADER_TAG, "loadData");
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bundle != null ? bundle.getString(QUERY) : "empty";
        }
    }

    private class MyLoaderCallback implements LoaderManager.LoaderCallbacks {

        @Override
        public Loader onCreateLoader(int id, Bundle args) {
            Log.d(LOADER_TAG, "onCreateLoader");
            return new MyLoader(getApplicationContext(), args);
        }

        @Override
        public void onLoadFinished(Loader loader, Object data) {
            Log.d(LOADER_TAG, "onLoadFinished");
            showResult((String) data);
        }

        @Override
        public void onLoaderReset(Loader loader) {
            Log.d(LOADER_TAG, "onLoaderReset");
            showResult("");
        }
    }

    private class MyEditTextWatcher implements 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) {
            Log.d(LOADER_TAG, "onTextChanged=" + s);
            startQuery(s != null ? s.toString() : "");
        }

        @Override
        public void afterTextChanged(Editable s) {}
    }

}

当我们输入a时,成功地获取到了数据:


之后,我们继续输入b,发现onLoadFinished立即被回调了,但是结果还是之前地a

我们通过AS的断电发现,整个调用过程如下:

也就是在initLoader之后,立即执行了:

    public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
        //按照前面的分析,此时的info不为null。
        if (info.mHaveData && mStarted) {
            info.callOnLoadFinished(info.mLoader, info.mData);
        }
        return (Loader<D>)info.mLoader;
    }

而之后,callOnLoadFinished就会把之前的数据传回给调用者,因此没有执行onCreateLoader,也没有进行查询操作:

        void callOnLoadFinished(Loader<Object> loader, Object data) {
            //传递数据给调用者.
            if (mCallbacks != null) {
                mCallbacks.onLoadFinished(loader, data);
            }
        }

假如,我们在a触发的任务还没有执行时就输入b,我们看看会发生什么,可以看到,它并不会考虑后来的任务:

6.2 采用restartLoader查询

还是按照前面的方式,我们先输入a


这时候,和采用initLoader的结果完全相同,接下来再输入b

可以清楚地看到,在执行完restartLoader之后,LoaderManager回调了onCreateLoader方法让我们传入新的Loader,并且之后重新进行了查询,并成功地返回了结果。
接下来,我们看一下连续输入的情况:

可以看到,虽然a触发的任务已经开始了,但是当我们输入b的时候,最终得到的时ab这个结果,并且a所触发的任务的结果并没有返回给调用者,这也是我们所希望的,因为我们的结果一定是要以用户最后输入的为准。

6.3 加上保证正确初始化的代码

最后,我们再加上保证Loader能够正确初始化的代码,一个简单的联想输入 - 加载框架就搭建好了。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        restore(savedInstanceState);
    }
    
    private void save(Bundle outState) {
        if (mEditText != null) {
            outState.putString(QUERY, mEditText.getText().toString());
        }
    }
    
    private void restore(Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            Bundle bundle = new Bundle();
            String query = bundle.getString(QUERY);
            bundle.putString(QUERY, query);
            getLoaderManager().initLoader(0, bundle, mMyLoaderCallback);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        save(outState);
        super.onSaveInstanceState(outState);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 1 背景## 在Android中任何耗时的操作都不能放在UI主线程中,所以耗时的操作都需要使用异步实现。同样的,在...
    我是昵称阅读 1,207评论 0 3
  • 一、概述 刚开始学习Loader的时候,只是使用CursorLoader把它当作加载封装在ContentProvi...
    泽毛阅读 10,121评论 4 8
  • Android开发者都经历过APP UI开发不当 会造成overDraw,导致APP UI渲染过慢,但是很多人却没...
    Tamic阅读 15,901评论 30 104
  • 我不会老去 在未见到你之前 云淡风轻 天色也不晚 我可以先泡上一杯青茶 在茶色氤氲中 翻开一座城 城中人相见,欢笑...
    仲童阅读 245评论 0 5