欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

android非RxJava环境下使用Handler实现预加载

程序员文章站 2024-02-18 18:34:28
在进行android客户端界面开发时,我们常常会需要将从服务端获取的数据展示到页面布局上,由于数据显示到布局的前置条件是页面布局已初始化完成,否则会出现空指针异常,所以一般...

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

android非RxJava环境下使用Handler实现预加载

问题:

如果加载的ui布局比较复杂,或者初始化逻辑执行的时间比较多,那么网络请求开始执行的时间就比较晚,最终完成页面加载的时间就比较长。
如果页面初始化和网络加载能同时进行,等两者都执行结束后,再在布局上展示网络数据,这样我们就可以缩短整个页面的加载时间了。
所以,我们期望的页面加载流程是:

android非RxJava环境下使用Handler实现预加载

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

预加载的目标任务可以是一个网络请求,也可以是其它一些耗时操作,例如:加载一张图片到控件上展示
在实现预加载方案之前,我们需要了解一下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。
这种情况下我们可以用这种方式来解决。

最后,源码下载地址