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环境下实现预加载的方案,请看下一篇文章
相关文章推荐
- Android腾讯微博客户端开发5:利用FootView实现ListView滑动动态加载实现分页
- Android利用V4包中的SwipeRefreshLayout实现上拉加载
- Android中利用动态加载实现手机淘宝的节日特效
- Android自定义view利用Xfermode实现动态文字加载动画
- android利用定时器实现应用开头加载图片或者静态广告的显示
- Android利用V4包中的SwipeRefreshLayout实现上拉加载
- Android利用glide加载圆形图片,头像的实现
- Android 5.X新特性之为RecyclerView添加下拉刷新和上拉加载及SwipeRefreshLayout实现原理
- Android 利用反射实现不安装直接运行APK(动态加载)
- 一起学android之利用回调函数onCreateDialog实现加载对话框(23)
- Android利用V4包中的SwipeRefreshLayout实现上拉加载
- JAVAWEB开发之Servlet3.0新特性的使用以及注解的详细使用和自定义注解的方法、动态代理的使用、利用动态代理实现细粒度的权限控制以及类加载和泛型反射
- Android腾讯微博客户端开发5:利用FootView实现ListView滑动动态加载实现分页
- Android利用Gallery和ImageSwitcher实现在线相册图片预览功能(异步加载图片)
- Android开发之图片处理专题(二):利用AsyncTask和回调接口实现图片的异步加载和压缩
- Android开发之图片处理专题(三):利用ThreadPoolExcutor线程池实现多图片的异步加载
- vue+vue-router+vuex,利用vue-router2.2.0新增特性addRoutes实现路由动态加载,菜单动态加载,运用于后台管理系统,路由数据取自数据库
- Android 5.X新特性之为RecyclerView添加下拉刷新和上拉加载及SwipeRefreshLayout实现原理
- Android开发之ListView利用OnScrollListener实现分页加载数据
- Android利用V4包中的SwipeRefreshLayout实现上拉加载