在进行Android客户端界面开发时,我们常常会需要将从服务端获取的数据展示到页面布局上,由于数据显示到布局的前置条件是页面布局已初始化完成,否则会出现空指针异常,所以一般我们需要将网络请求放在布局初始化完成之后。
传统的页面加载流程是:
问题:
如果加载的UI布局比较复杂,或者初始化逻辑执行的时间比较多,那么网络请求开始执行的时间就比较晚,最终完成页面加载的时间就比较长。
如果页面初始化和网络加载能同时进行,等两者都执行结束后,再在布局上展示网络数据,这样我们就可以缩短整个页面的加载时间了。
所以,我们期望的页面加载流程是:
这个流程我们称之为:预加载
预加载的目标任务可以是一个网络请求,也可以是其它一些耗时操作,例如:加载一张图片到控件上展示
在实现预加载方案之前,我们需要了解一下Handler工作机制中的SyncBarrier概念,对Barrier概念了解可以看这篇文章中对“同步分割栏”的介绍, 此处我们简单理解为:
在MessageQueue中添加一个特殊的msg,将这个msg作为一个标记,在这个标记被移除之前,当前MessageQueue队列中排在它后面的其它(非async) 的message不会被handler处理。
我们可以先不理会什么是 非async 的message,若需要了解更多,这篇文章中对“同步分割栏”的介绍中也有相关介绍。
利用这个特性,我们可以:
启动一个HandlerThread来异步执行网络请求
设置一个标记SyncBarrier,此后在message将一直在messageQueue中不被执行
网络请求成功后,post一个任务来执行展示数据
布局初始化成功后,移除SyncBarrier
将展示数据的任务post到ui线程来执行
步骤3和步骤4的先后顺序可以交换
其中,在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 java.lang.reflect.Method;
/**
* 使用Handler方式实现的预加载
* @author billy.qi
*/
public class PreLoader {
private int token;
private Handler mainThreadHandler;//用于将结果处理的task放在主线程中执行
private HandlerThread handlerThread;//提供一个异步线程来运行预加载任务
private Handler handler;//预加载使用的handler
private PreLoader(final Runnable task) {
mainThreadHandler = new Handler(Looper.getMainLooper());
handlerThread = new HandlerThread("pre-loader") {
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
handler = new Handler();
handler.post(task);
//设置同步分割,后面post进来的sync为true的message将暂停执行
Looper looper = handlerThread.getLooper();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
postSyncBarrier(looper.getQueue());
} else {
postSyncBarrier(looper);
}
}
};
handlerThread.start();
}
/**
* 开启预加载
* 比如:开始网络请求(在HandlerThread中执行)
* @param task 预加载任务
*/
public static PreLoader preLoad(Runnable task) {
return new PreLoader(task);
}
/**
* 处理加载结果, 一般在预加载任务处理完成后调用
* 由于有handler所在的messageQueue设置了同步分割(SyncBarrier),该task
* @param task 在主线程中执行的任务
*/
public void performResultTask(final Runnable task) {
if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
mainThreadHandler.post(task);
}
});
}
}
/**
* 可以开始执行预加载结果处理
*/
public void readyToGetData() {
Looper looper = handlerThread.getLooper();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
removeSyncBarrier(looper.getQueue());
} else {
removeSyncBarrier(looper);
}
}
private void postSyncBarrier(Object obj) {
try{
Method method = MessageQueue.class.getMethod("postSyncBarrier");
token = (int) method.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() {
handlerThread.quit();
handlerThread = null;
handler = null;
mainThreadHandler = null;
}
}
在activity中使用实例:
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class PreLoaderActivity extends AppCompatActivity {
private PreLoader preLoader;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
preLoad();//启动预加载
//进行页面布局加载及其它初始化工作
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.textView);
//布局初始化完成
preLoader.readyToGetData();
}
private void preLoad() {
//预加载的任务
preLoader = PreLoader.preLoad(new Runnable() {
//将得到的请求结果展示到布局控件上
@Override
public void run() {
//模拟网络请求耗时
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
final String result = "result";
//在UI线程执行的显示任务
preLoader.performResultTask(new Runnable() {
//将得到的请求结果展示到布局控件上
@Override
public void run() {
textView.setText(result);
}
});
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
//在onDestroy()中进行销毁
preLoader.destroy();
}
}
通过预加载,让一部分异步任务提前执行,可以用来提高整体速度。
以上都是以网络请求作为预加载的目的,它同时还可以用来预加载图片、预加载文件、读取数据库等;
除了预加载外,还可以用来作为多个任务并行,并全部执行完之后,再执行另一个任务对之前所有任务执行的结果进行处理。
总结:
在多线程开发时,经常会遇到以下情况:
任务A、任务B(甚至更多任务)是任务C的充要条件,为了提高效率,我们希望AB同时执行,当AB都完成之后,开始执行任务C。
这种情况下我们可以用这种方式来解决。
最后,源码下载地址
# android
# RxJava
# Handler
# 预加载
# Android开发RecyclerView性能优化之异步预加载
# android使用RxJava实现预加载
# Android 开发中fragment预加载问题
# android 预加载进程的实现方法
# 加载
# 的是
# 都是
# 放在
# 移除
# 还可以
# 我们可以
# 这篇文章
# 中对
# 就比
# 是一个
# 也有
# 充要条件
# 在这个
# 多个
# 下载地址
# 可以用
# 作为一个
# 分别为
# 不被
相关文章:
Thinkphp 中 distinct 的用法解析
网站制作外包价格怎么算,招聘网站上写的“外包”是什么意思?
公司网站建设制作费用,想建设一个属于自己的企业网站,该如何去做?
在线制作视频网站免费,都有哪些好的动漫网站?
如何在IIS7中新建站点?详细步骤解析
如何基于云服务器快速搭建网站及云盘系统?
江苏网站制作公司有哪些,江苏书法考级官方网站?
网站专业制作公司,网站编辑是做什么的?好做吗?工作前景如何?
如何设计高效校园网站?
建站主机选虚拟主机还是云服务器更好?
香港服务器选型指南:免备案配置与高效建站方案解析
常州自助建站:操作简便模板丰富,企业个人快速搭建网站
建站之星价格显示格式升级,你的预算足够吗?
美食网站链接制作教程视频,哪个教做美食的网站比较专业点?
浅谈Javascript中的Label语句
学校免费自助建站系统:智能生成+拖拽设计+多端适配
香港服务器网站推广:SEO优化与外贸独立站搭建策略
,石家庄四十八中学官网?
如何用y主机助手快速搭建网站?
建站之星Pro快速搭建教程:模板选择与功能配置指南
小捣蛋自助建站系统:数据分析与安全设置双核驱动网站优化
如何续费美橙建站之星域名及服务?
如何解决VPS建站LNMP环境配置常见问题?
油猴 教程,油猴搜脚本为什么会网页无法显示?
零基础网站服务器架设实战:轻量应用与域名解析配置指南
如何用手机制作网站和网页,手机移动端的网站能制作成中英双语的吗?
早安海报制作网站推荐大全,企业早安海报怎么每天更换?
c# 在ASP.NET Core中管理和取消后台任务
如何通过云梦建站系统实现SEO快速优化?
制作公司内部网站有哪些,内网如何建网站?
香港服务器网站生成指南:免费资源整合与高速稳定配置方案
如何高效配置IIS服务器搭建网站?
建站之星免费模板:自助建站系统与智能响应式一键生成
建站之星如何快速更换网站模板?
宿州网站制作公司兴策,安徽省低保查询网站?
魔毅自助建站系统:模板定制与SEO优化一键生成指南
高端企业智能建站程序:SEO优化与响应式模板定制开发
北京网站制作公司哪家好一点,北京租房网站有哪些?
武汉外贸网站制作公司,现在武汉外贸前景怎么样啊?
网站海报制作教学视频教程,有什么免费的高清可商用图片网站,用于海报设计?
成都响应式网站开发,dw怎么把手机适应页面变成网页?
建站OpenVZ教程与优化策略:配置指南与性能提升
临沂网站制作企业,临沂第三中学官方网站?
杭州银行网站设计制作流程,杭州银行怎么开通认证方式?
如何使用Golang table-driven基准测试_多组数据测量函数效率
详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)
已有域名如何免费搭建网站?
高端建站如何打造兼具美学与转化的品牌官网?
建站之星后台密码如何安全设置与找回?
定制建站方案优化指南:企业官网开发与建站费用解析
*请认真填写需求信息,我们会在24小时内与您取得联系。