您的位置:首页 > 移动开发 > Android开发

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

2017-01-22 18:20 671 查看

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,若需要了解更多,这篇文章中对“同步分割栏”的介绍中也有相关介绍。

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

启动一个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<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



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

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

原文地址:http://blog.csdn.net/cdecde111/article/details/54670136

在RxJava环境下实现预加载的方案,请看下一篇文章
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐