android 利用Handler机制中SyncBarrier的特性实现预加载

2018-01-06 新增说明


本文中所采用的方案需要持有Handler对象,并且需要反射,在Activity或Fragment内部UI初始化之前预加载数据比较适合,在打开Activity之前预加载数据就不太合适。

最新方案PreLoader(github地址)适用于:

  • 提升app冷启动速度
  • 在打开activity之前预加载数据
  • 在Activity内部预加载数据
  • 在显示Fragment之前预加载数据
  • 为多ViewGroup分块管理的复杂页面统一预加载数据并分别处理数据
  • 预加载数据接口的下拉刷新
  • 分页加载时,为上拉加载更多提供预加载数据
  • 阅读类app预加载下一章节内容

详情请移步 github,要是喜欢别忘记点star哦,持续优化更新中,欢迎watch

原文如下


在进行android客户端界面开发时,我们常常会需要将从服务端获取的数据展示到页面布局上,由于数据显示到布局的前置条件是页面布局已初始化完成,否则会出现空指针异常,所以一般我们需要将网络请求放在布局初始化完成之后。
传统的页面加载流程是:

传统的页面加载流程

问题:

如果加载的UI布局比较复杂,或者初始化逻辑执行的时间比较多,那么网络请求开始执行的时间就比较晚,最终完成页面加载的时间就比较长。

如果页面初始化和网络加载能同时进行,等两者都执行结束后,再在布局上展示网络数据,这样我们就可以缩短整个页面的加载时间了。
所以,我们期望的页面加载流程是:

预加载的流程

这个流程我们称之为:预加载

预加载的目标任务可以是一个网络请求,也可以是其它一些耗时操作,例如:加载一张图片到控件上展示

在实现预加载方案之前,我们需要了解一下Handler工作机制中的SyncBarrier概念,对Barrier概念了解可以看这篇文章中对“同步分割栏”的介绍, 此处我们简单理解为:

在MessageQueue中添加一个特殊的msg,将这个msg作为一个标记,在这个标记被移除之前,当前MessageQueue队列中排在它后面的其它(非async) 的message不会被handler处理。

我们可以先不理会什么是 非async 的message,若需要了解更多,这篇文章中对“同步分割栏”的介绍中也有相关介绍。

利用这个特性,我们可以按如下步骤实现预加载:

  1. 启动一个HandlerThread并创建一个持有该线程Looper的handler
  2. 将加载数据的任务 (Task1) post到handler
  3. 给handler设置一个标记SyncBarrier,此标记移除之前,handler中在它之后的message将一直在messageQueue中不被执行
  4. 将数据展示的任务 (Task2) post到handler
  5. Task1开始在HandlerThread线程中运行
  6. UI布局初始化任务(Task3)开始在UI线程中运行
  7. Task1运行结束,数据加载完成,等待移除handler的SyncBarrier
  8. Task3运行结束,UI布局初始化完成,移除handler的SyncBarrier
  9. Task2开始运行
  10. Task2将自身放到UI线程中运行
  11. Task2运行结束

步骤7和步骤8的顺序是可互换的,不影响Task2的最终执行。

在android api 22及之前,设置标记SyncBarrier可以由

HandlerThread.getLooper().postSyncBarrier();

在android api 23以后,需要调用的方法为:

HandlerThread.getLooper().getQueue().postSyncBarrier();

同样的,移除标记的方法分别为:

HandlerThread.getLooper().removeSyncBarrier(token);
HandlerThread.getLooper().getQueue().removeSyncBarrier(token);

不幸的是:这些方法都是@hide的,无法直接调用。
幸运的是:我们还有反射


封装工具类如下: PreLoader.java

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.MessageQueue;
import android.support.annotation.Nullable;

import java.lang.reflect.Method;

/**
 * 使用Handler方式实现的预加载
 * @author billy.qi
 * @since 17/1/22 11:05
 */
public class PreLoader<T> {

    private int token;
    private Handler mainThreadHandler;//用于将结果处理的task放在主线程中执行
    private HandlerThread handlerThread;//提供一个异步线程来运行预加载任务
    private Handler handler;//预加载使用的handler
    private Method methodPostSyncBarrier;
    private T data;
    private boolean destroyed;

    private PreLoader(final Loader<T> loader, final Listener<T> listener) {
        final Runnable loaderWrapper = new Runnable() {
            @Override
            public void run() {
                data = loader.load();
            }
        };
        final Runnable listenerWrapper = new Runnable() {
            @Override
            public void run() {
                if (destroyed) {
                    return;
                }
                if (Looper.myLooper() == Looper.getMainLooper()) {
                    listener.onDataArrived(data);
                    //数据被listener处理后,PreLoader自行销毁
                    destroy();
                } else {
                    mainThreadHandler.post(this);
                }
            }
        };

        methodPostSyncBarrier = getHideMethod("postSyncBarrier");

        mainThreadHandler = new Handler(Looper.getMainLooper());
        handlerThread = new HandlerThread("pre-loader") {
            @Override
            protected void onLooperPrepared() {
                super.onLooperPrepared();
                handler = new Handler(handlerThread.getLooper());
                handler.post(loaderWrapper);
                //设置同步分割,后面post进来的sync为true的message将暂停执行
                Looper looper = handlerThread.getLooper();
                if (methodInQueue()) {
                    postSyncBarrier(looper.getQueue());
                } else {
                    postSyncBarrier(looper);
                }
                handler.post(listenerWrapper);
            }
        };
        handlerThread.start();
    }

    /**
     * 开启预加载
     * 比如:开始网络请求(在HandlerThread中执行)
     * @param loader 预加载任务
     * @param listener 加载完成后执行的任务
     */
    public static <T> PreLoader<T> preLoad(final Loader<T> loader, final Listener<T> listener) {
        return new PreLoader<>(loader, listener);
    }

    /**
     * 可以开始执行预加载结果处理
     */
    public void readyToGetData() {
        if (destroyed || handlerThread == null) {
            return;
        }
        Looper looper = handlerThread.getLooper();
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            removeSyncBarrier(looper.getQueue());
        } else {
            removeSyncBarrier(looper);
        }
    }

    /**
     * 数据加载
     */
    public interface Loader<DATA> {
        DATA load();
    }

    /**
     * 数据监听
     */
    public interface Listener<DATA> {
        void onDataArrived(DATA data);
    }

    @Nullable
    private Method getHideMethod(String name) {
        Method method = null;
        try{
            if (methodInQueue()) {
                method = MessageQueue.class.getMethod(name);
            } else {
                method = Looper.class.getMethod(name);
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
        return method;
    }

    //postSyncBarrier 和 removeSyncBarrier 方法是否在MessageQueue类中(api23及以上)
    private static boolean methodInQueue() {
        return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M;
    }

    private void postSyncBarrier(Object obj) {
        try{
            methodPostSyncBarrier = obj.getClass().getMethod("postSyncBarrier");
            token = (int) methodPostSyncBarrier.invoke(obj);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    private void removeSyncBarrier(Object obj) {
        try{
            Method method = MessageQueue.class.getMethod("removeSyncBarrier", int.class);
            method.invoke(obj, token);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public void destroy() {
        if (destroyed) {
            return;
        }
        destroyed = true;
        handlerThread.quit();
        handlerThread = null;
        handler.removeCallbacksAndMessages(null);
        handler = null;
        mainThreadHandler.removeCallbacksAndMessages(null);
        mainThreadHandler = null;
    }

}

在activity中使用实例:

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

/**
 * 使用预加载方式
 */
public class PreLoadActivity extends AppCompatActivity {

    private PreLoader<String> preLoader;
    private TextView textView;
    TimeWatcher allTime;
    private TextView logTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //开始总计时
        allTime = TimeWatcher.obtainAndStart("total");
        //启动预加载
        preLoader = PreLoader.preLoad(loader, listener);
        //开始布局初始化的计时
        TimeWatcher timeWatcher = TimeWatcher.obtainAndStart("init layout");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("使用PreLoader");
        try {
            //模拟耗时
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(PreLoadActivity.this, RxPreLoadActivity.class));
            }
        });
        textView = (TextView)findViewById(R.id.textView);
        logTextView = (TextView)findViewById(R.id.log);
        //UI布局初始化计时结束
        String s = timeWatcher.stopAndPrint();
        logTextView.append(s + "\n");
        //初始化完成,可以在UI上显示加载的数据
        preLoader.readyToGetData();
    }

    /**
     * 数据加载
     */
    PreLoader.Loader<String> loader = new PreLoader.Loader<String>() {
        @Override
        public String load() {
            TimeWatcher timeWatcher = TimeWatcher.obtainAndStart("load data");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String time = timeWatcher.stopAndPrint();
            return "result:" + time;
        }
    };
    /**
     * 数据显示
     */
    PreLoader.Listener<String> listener = new PreLoader.Listener<String>() {
        @Override
        public void onDataArrived(String s) {
            textView.setText(s);
            //总耗时结束
            String total = allTime.stopAndPrint();
            logTextView.append(s + "\n" + total + "\n");
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        preLoader.destroy();
    }
}

运行效果:
UI初始化耗时(Task1):34ms
数据加载耗时(Task3):500ms
总耗时(Task1 + Task2 + Task3):503ms

PreLoader

通过预加载,让一部分异步任务提前执行,可以用来提高整体速度。
以上是以网络请求作为预加载的目的,它还可以用来预加载图片、预加载文件、读取数据库等

原文地址:http://blog.csdn.net/cdecde111/article/details/54670136
在RxJava环境下实现预加载的方案,请看下一篇文章

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值