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

2018-01-06 新增说明

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

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

提升app冷启动速度

在打开activity之前预加载数据

在Activity内部预加载数据

在显示Fragment之前预加载数据

为多ViewGroup分块管理的复杂页面统一预加载数据并分别处理数据

预加载数据接口的下拉刷新

分页加载时,为上拉加载更多提供预加载数据

阅读类app预加载下一章节内容

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

原文如下

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

传统的页面加载流程是:

c6eded78c99d07a46e5db38d9b27ceb5.png

问题:

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

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

所以,我们期望的页面加载流程是:

12342dd3318cb60b5f57488c30fc4bf2.png

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

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

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

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

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

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

启动一个HandlerThread并创建一个持有该线程Looper的handler

将加载数据的任务 (Task1) post到handler

给handler设置一个标记SyncBarrier,此标记移除之前,handler中在它之后的message将一直在messageQueue中不被执行

将数据展示的任务 (Task2) post到handler

Task1开始在HandlerThread线程中运行

UI布局初始化任务(Task3)开始在UI线程中运行

Task1运行结束,数据加载完成,等待移除handler的SyncBarrier

Task3运行结束,UI布局初始化完成,移除handler的SyncBarrier

Task2开始运行

Task2将自身放到UI线程中运行

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 {

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 loader, final Listener 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 PreLoader preLoad(final Loader loader, final Listener 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 load();

}

/**

* 数据监听

*/

public interface Listener {

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 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 loader = new PreLoader.Loader() {

@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 listener = new PreLoader.Listener() {

@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

ac7834839c92bc10ccc9ac51a8d09f77.png

通过预加载,让一部分异步任务提前执行,可以用来提高整体速度。

以上是以网络请求作为预加载的目的,它还可以用来预加载图片、预加载文件、读取数据库等

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值