Android百问百答-《那些年被问过的Handler原理》

Android百问百答-《那些年被问过的Handler原理》

关于Handler,安卓面试最热门的知识点之一。本篇文章将围绕3点展开:

可以提问哪些Questions?

面试官会怎样follow up?

以及怎样寻找答案。

文章快速索引

Handler常见提问

  1. 哪些场景使用到了Handler?用Handler做什么业务?
  2. 用Handler遇到什么问题?怎么解决这些问题的?
  3. 说一说Handler原理?
  4. 能自己实现一个Handler吗?
  5. 说一说Handler延时原理?
  6. Handler延时有哪些缺陷?造成这些缺陷的原因?
  7. 你知道Handler#handleMessage原理吗?
  8. Handler的post与sendMessage有哪些区别?
  9. 子线程能使用Handler吗?
  10. 子线程能创建Handler吗?
  11. 了解过HandlerThread吗?
  12. 了解过IdleHandler吗?

Handler常见Follow Up

  1. 你刚才提到了Message,消息屏障听过吗?有几种Message?
  2. Message有什么用?存储了哪些信息?以什么数据结构存储?
  3. APP内最多能有几个Handler?
  4. App内最多能有多少Message?
  5. App内最多能有几个Looper?
  6. App内最多能有几个MessageQueue?
  7. Message如何知道发给哪一个MessageQueue?发给哪个Handler?
  8. MessageQueue如何存储消息,以什么结构存储?
  9. 你提到了Looper,请问子线程如何获取Looper?
  10. 你提到了Looper,说一说Looper的消息队列模型?
  11. 主线程Looper为什么不会阻塞?为什么不会ANR?
  12. 子线程跟主线程如何通过Handler通信?
  13. 子线程创建Handler这么麻烦,有什么替代方法吗?
  14. 主线程Looper什么时候启动的?
  15. 对Handler做过哪些优化?

Handler源码分析

Handler#构造函数原理

Handler有7个构造函数

Handler()
Handler(Handler.Callback callback)
Handler(Looper looper)
Handler(Looper looper, Handler.Callback callback)
Handler(boolean async)
Handler(Callback callback, boolean async)
Handler(Looper looper, Callback callback, boolean async)

先从其中一个构造函数看起:
在这里插入图片描述

提到了MessageQueue、Looper,Message,CallBack暂且记下。

我们根据经验及面试题,关注Handler几个关键API

  • obtainMessage
  • post
  • sendMessage
  • postDelayed
  • sendMessageDelayed

接着我们关注这些API的底层实现,一个一个分析吧!

Handler#obtainMessage 原理

Handler#obtainMessage 调用了Message#obtain()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T7alw6p4-1597413794308)(C:\Users\lenovo\AppData\Local\Temp\1597329263919.png)]

可以看到obtain函数从Message链表中获取message,这是一种内存复用,节省了频繁创建内存,如果Message链表为空,则创建一个Message。如果你对Message是个链表有疑问,那么请继续看下面的内容吧!

Message源码分析

Message有如下公有属性,供程序员调用:

public int what;//消息标示,
public int arg1; //简单int类型数据
public int arg2;//简单int类型数据
public Object obj;//简单Object类型数据
public Messenger replyTo;//跨进程信使
public int sendingUid = -1;//Messenger消息标示
    

Message有如下私有属性,用途如下:

/*package*/ static final int FLAG_IN_USE = 1 << 0;//正在使用中
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;//消息同步标识
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;//
/*package*/ int flags;//消息执行标识
/*package*/ long when;//执行时间
/*package*/ Bundle data;//装载的数据
/*package*/ Handler target;//目标载体
/*package*/ Runnable callback;//任务线程
/*package*/ Message next;//消息链表,下一个消息

private static final Object sPoolSync = new Object();//锁对象
private static Message sPool;//消息池
private static int sPoolSize = 0;//消息池大小
private static final int MAX_POOL_SIZE = 50;//消息池最大消息数常量
private static boolean gCheckRecycle = true;//循环检查

Message的源码,我们可以得出如下结论,Message是一种链表结构,每个Message持有以下信息:

  1. 用于传递的数据,如what、arg1、arg2、obj
  2. 用于执行当前Message的Handler
  3. 用于执行当前Message的回调接口CallBack、子线程Runnable
  4. 当前Message的属性,如延时时间、执行标识、Bundle数据,下一个Message引用。这种结构构成了链表。

Handler#post的原理

post函数入口接收一个子线程Runnable对象

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

getPostMessage()做了如下工作:

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

sendMessageDelayed做了如下工作:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

sendMessageAtTime做了如下工作:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

最终走下了MessageQueue#enqueueMessage

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

这一步总结如下

  1. getPostMessage将Message与Handler绑定
  2. 通过SystemClock.uptimeMillis() + delayMillis计算延时时间,delayMillis默认为0
  3. 将Message与计算得出的时间值,传递给MessageQueue#enqueueMessage,交由MessageQueue处理Message。

Handler#sendMessage原理

入口接收Message对象

public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

后续执行过程与post相同,最终都将Message交由MessageQueue#enqueueMessage处理。

Handler#postDelayed原理

与post相比,postDelayed函数入口除了接收Runnable子线程对象,还接收一个时间戳,用于延时时间的计算。其他过程与post相同。

public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

Handler#sendMessageDelayed原理

与sendMessage类似,多了一个时间戳,用于计算延时时间。其他过程与sendMessage、相同。

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

看完源码,我们得出几个结论:

  1. 无论是Handler#post或者是Handler#sendMessage,Messag都会交由MessageQueue#enqueueMessage执行
  2. MessageQueue#enqueueMessage接收两个参数Message和long型的时间戳
  3. 时间戳计算方式是SystemClock.uptimeMillis() + delayMillis
  4. post与sendMessage的区别是入参参数不一样,post接收Runnable子线程,将子线程绑定到Message上;sendMessage持有的是主线程

那么我们心中很自然会产生疑问,MessageQueue#enqueueMessage是如何执行的?

MessageQueue源码分析

源码分析的思路是构造函数和enqueueMessage

MessageQueue#构造函数原理

首先来看看构造函数

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

构造函数之上定义了很多native方法

 private native static long nativeInit();
    private native static void nativeDestroy(long ptr);// 阻塞
    private native static void nativeWake(long ptr); // 唤醒
    private native static boolean nativeIsPolling(long ptr);
    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

native之上定义了几类数据结构,Message、ArrayList、SparseArray、数组

 Message mMessages; // 头结点
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
    private IdleHandler[] mPendingIdleHandlers;

接着我们来看看enqueueMessage是如何处理Message的吧

MessageQueue#enqueueMessage原理

在这里插入图片描述

  • 图中 1处会判断如果 Message 中的 target 没有被设置,则直接抛出异常;
  • 图中2和 3 处会按照 Message 的时间 when 来有序得插入 MessageQueue 中,可以看出 MessageQueue 实际上是一个链表维护的有序队列,只不过是按照 Message 的执行时间来排序。

看到这里,思路似乎终止了,我们跟随Handler、MessQueue的脚步,只看到了Message被插入到MessageQueue的私有队列中。那我们产生的Message什么时候会背消费呢?

视角再次回到一开始的地方——Handler的构造函数原理,在那一节我们提到了Handler构造函数初始化Looper.myLooper(),mLooper.mQueue,接下来我们看看Looper吧!

Looper源码分析

查看源码可知,Looper是final类型的,禁止被外部继承修改。

Looper#子线程用例

首先在Looper类的注释上,我们看到了如下信息,提示我们在子线程中用个Looper.prepare()+Looper.looper()的方式使用Handler
在这里插入图片描述

为什么需要用这种方式开启Looper呢?

答案是在任何线程要开启Loop,都要用Looper.prepare()+Looper.looper()的方式。以APP主进程为例,APP进程启动入口的main方法,也是通过这种方式开启loop的。与子线程细微不同的是,主线程开启looper用的是prepareMainLooper。

ActivityThread #main方法

在这里插入图片描述

带着以下疑问,我们去追看源码:Looper构造函数做了什么?prepare做了什么?loop做了什么?

Looper#构造函数原理

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

Looper构造函数做了两件事情,初始化消息队列MessageQueue对象,记录当前线程信息。

Looper#myLooper()原理

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

可以看到myLooper是从threadLocal中取出Looper对象。在Looper类中定义了如下变量sThreadLocal、mQueue、sMainLooper、mThread

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class

final MessageQueue mQueue;
final Thread mThread;

Looper#prepare原理

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

prepare就是 new 出一个 Looper。核心之处在于将 new 出的 Looper 设置到了线程本地变量 sThreadLocal 中。也就是说创建的 Looper 与当前线程发生了绑定。

Looper#prepareMainLooper原理

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

prepareMainLooper只有在APP进程启动的时候有用,并不推荐开发者调用这个函数。

Looper#loop原理

在这里插入图片描述

图1 取出Looper对象

图2 校验当前线程是否持有Looper,是否启动而来Looper.prepare

图3 从Looper中取出对应的MessageQueue,主线程Looper就取出主线程的MessageQueue,子线程就取出子线程MessageQueue

图4 从MessageQueue中取出Message

图5 Message#target属性,即handler,调用Message绑定好的handler#dispatchMessage,处理消息。

也就是说,Message最终交由与Message绑定的Handler处理。Looper只是负责无限循环+从MessageQueue中读取。

Handler#dispatchMessage

   /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);// 1
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {// 2
                    return;
                }
            }
            handleMessage(msg);// 3
        }
    }

可以看到有3处可以处理Message

图 1触发了Message#Runnable的run方法,要知道callback就是个Runnable子线程

private static void handleCallback(Message message) {
    message.callback.run();
}

图2 触发了 Handler#Callback接口,Callback是Handler构造函数初始化的时候传递进来的。参考Handler#构造函数原理

public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public boolean handleMessage(Message msg);
}

图3 触发了Handler的handleMessage方法,这是个空实现,一般由开发者复写实现。

/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message msg) {
}

在Looper这一节,我们暂停脚步总结一下:

  1. 主线程和子线程都可以使用Handler,Handler使用方式都是要Looper.prepare+Lopper.loop,

  2. 子线程Handler用法

     class LooperThread extends Thread {
       public Handler mHandler;
       public void run() {
    	   Looper.prepare();
    
    	   mHandler = new Handler() {
    		   public void handleMessage(Message msg) {
    			   // process incoming messages here
    		   }
    	   };
    
    	   Looper.loop();
       }
    
      }
     new LooperThread().start();
    

回答这些面试题吧

面试题解析

哪些场景使用到了Handler?用Handler做什么业务?

最简单的消息发送

主线程使用Handler, 主线程里或子线程里发送消息,或延迟发送消息的方式更新UI如,启动应用时Splash页面的延迟2,3秒后,跳转到主页面

 private static final int WHAT_UPDATE_ICON = 1;

 private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case WHAT_UPDATE_ICON:
                    Log.e(Tag, "receive message:" + msg.obj);
                    break;
            }
        }
  };
    
    Message msg = handler.obtainMessage(WHAT_UPDATE_ICON);
    msg.obj = "update the imageview";
    handler.sendMessage(msg);

使用消息的时候,尽量使用 obtainMessage 的方式来获取Message,避免多次创建Message对象,消耗内存,效率低下。

记住:消息不一定是更新UI的消息,可以再handlerMessage中做很多事情!

结合HandlerThread处理耗时任务

结合HandlerThread,串行的处理单个耗时任务,如单任务下载

class DownloadOneByOne extends HandlerThread {
    public DownloadOneByOne() {
        super(DownloadOneByOne.class.getSimpleName());
    }

    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        // 初始化下载组件
    }
}

private HandlerThread mHandlerThread;

private Handler downloadHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        String url = (String) msg.obj;
        // 使用下载组件开始下载
        
    }
};

public void initHandler() {
    // 初始化Handler
    mHandlerThread = new DownloadOneByOne();
    mHandlerThread.start();
    
    downloadHandler = new Handler(mHandlerThread.getLooper());
}

private void sendDownloadTask(String downloadUrl) {
    // 发送下载任务
    Message msg = downloadHandler.obtainMessage(WHAT_DOWNLOAD_TASK);
    msg.obj = downloadUrl;
    downloadHandler.sendMessage(msg);
}
倒计时View的简易实现

通过Handler我们还可以快速简易,并且不占用太多性能的实现一个简易的倒计时View。

public class CountDownView extends AppCompatTextView {
    /**
     * 总时间
     */
    private long seconds;
    /**
     * 当前分钟
     */
    private long minutes;
    /**
     * 当前秒数
     */
    private int second = 60;

    private static final int SECONDS_PER_MINUTE = 60;
    private static final int MILLS_PER_SECOND = 1000;
    private static final int MILLS_PER_MINUTE = SECONDS_PER_MINUTE * 1000;

    private static final int WHAT_DONE = 2;
    private static final int WHAT_TICK = 1;

    private int marginEnd;

    private StringBuilder content = new StringBuilder();

    public CountDownView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        DeviceProfile deviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
        int size = (int) (MeasureSpec.getSize(widthMeasureSpec) / deviceProfile.inv.numColumns);
        marginEnd = marginEnd == 0 ? (size - deviceProfile.iconSizePx) / 2 : marginEnd;

        setMarginEnd(marginEnd);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private void setMarginEnd(int marginEnd) {
        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
        layoutParams.setMarginEnd(marginEnd);
        layoutParams.resolveLayoutDirection(layoutParams.getLayoutDirection());
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (handler.hasMessages(WHAT_TICK)) {
            handler.removeMessages(WHAT_TICK);
        }
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case WHAT_DONE:
                    setVisibility(View.GONE);
                    break;
                default:
                    setText(content.toString());
                    handler.post(runnable);
                    break;
            }
        }
    };

    /***
     * 设置倒计时
     * @param millis
     */
    public void setCountDownMills(long millis) {
        seconds = (long) Math.floor(millis / MILLS_PER_SECOND);
        minutes = (long) Math.floor(millis / MILLS_PER_MINUTE) - 1;
        // start after one second
        handler.postDelayed(runnable, MILLS_PER_SECOND);
    }

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            if (seconds <= 0) {
                handler.sendEmptyMessage(WHAT_DONE);
                return;
            }
            seconds--;
            if (second <= 0) {
                second = SECONDS_PER_MINUTE;
                minutes = (long) Math.floor(seconds / SECONDS_PER_MINUTE);
            }
            second--;
            content.delete(0, content.length());

            appendZeroWhenLower10(minutes);
            content.append(":");
            appendZeroWhenLower10(second);

            if (handler.hasMessages(WHAT_TICK)) {
                handler.removeMessages(WHAT_TICK);
            }
            handler.sendEmptyMessageDelayed(WHAT_TICK, MILLS_PER_SECOND);
        }
    };

    private StringBuilder appendZeroWhenLower10(long value) {
        if (value < 10) {
            content.append("0").append(value);
        } else {
            content.append(value);
        }
        return content;
    }
}
结合IntentService的使用

使用IntentService处理耗时的任务相对比较简单,我们来个有难度的,结合AlarmManager的调度,息屏唤醒IntentService定时处理任务的案例来讲

  private static final String ACTION_WAKE_UP = "com.doze.cpu.wakeup";

  public static void registerAlarm(Context context, int wakeType) {
    type = wakeType;
    if (alarmManager == null)
        alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

    if (operation != null) alarmManager.cancel(operation);

    schedule(context);
}
    
private static void schedule(Context context) {
    Intent intent = new Intent();
    intent.setAction(ACTION_WAKE_UP);
    operation = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    switch (type) {
        case 0:
            AlarmUtils.setRTCWakeup(alarmManager, AlarmUtils.DEFAULT_TRIGGER_AT_MILLIS, operation);
            break;
        case 1:
            AlarmUtils.setElapsedWakeup(alarmManager, AlarmUtils.DEFAULT_TRIGGER_AT_MILLIS, operation);
            break;
    }
}

定时5分钟发送一个Action为com.doze.cpu.wakeup的广播,我们的广播需要继承 WakefulBroadcastReceiver, 在onReceive里,调用startWakefulService方法,会创建一个1分钟的WakeLock,唤醒cpu处理我们的任务,我们的任务IntentService处理最好不过了,处理完就销毁,不会有多余的占用

public class WakeCPUReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        Intent wakefulIntent = new Intent(context, WorkService.class);
        startWakefulService(context, wakefulIntent);
        schedule(context);
    }

startWakefulService的源码

public static ComponentName startWakefulService(Context context, Intent intent) {
        synchronized (mActiveWakeLocks) {
            int id = mNextId;
            mNextId++;
            if (mNextId <= 0) {
                mNextId = 1;
            }

            intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
            ComponentName comp = context.startService(intent);
            if (comp == null) {
                return null;
            }

            PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
            PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    "wake:" + comp.flattenToShortString());
            wl.setReferenceCounted(false);
            wl.acquire(60*1000);
            mActiveWakeLocks.put(id, wl);
            return comp;
        }
    }

在IntentService里,我们在onHandleIntent处理我们的任务后,再调用WakefulBroadcastReceiver的静态方法completeWakefulIntent,释放WakeLock,减少电量的消耗

public class WorkService extends IntentService {
    ...
    @Override
    protected void onHandleIntent(Intent intent) {
        Log.e(WakeCPUReceiver.TAG, "WorkService is working");
        // TODO 处理我们的任务
        WakeCPUReceiver.completeWakefulIntent(intent);
    }
}

completeWakefulIntent源码

public static boolean completeWakefulIntent(Intent intent) {
        final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
        if (id == 0) {
            return false;
        }
        synchronized (mActiveWakeLocks) {
            PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
            if (wl != null) {
                wl.release();
                mActiveWakeLocks.remove(id);
                return true;
            }
            // We return true whether or not we actually found the wake lock
            // the return code is defined to indicate whether the Intent contained
            // an identifier for a wake lock that it was supposed to match.
            // We just log a warning here if there is no wake lock found, which could
            // happen for example if this function is called twice on the same
            // intent or the process is killed and restarted before processing the intent.
            Log.w("WakefulBroadcastReceiver", "No active wake lock id #" + id);
            return true;
        }
    }
IdleHandler 用于UI性能优化

先计算任务放在Activity绘制结束完成之后,节省了90Ms计算时间。参考面试题你了解过IdleHandler 吗?
在这里插入图片描述

HandlerThread用于单线程消息通知器

在用户操作某些界面元素的时候,如收藏、点赞、转发,有一个小的问题,就是如果有一个操作生成10个快速连续的增删改查操作,那么我们的UI就会收到10次回调,而这种场景下我们其实只需要最后一次回调就够了,中间操作其实不用刷新UI的。如何合并这些频繁操作,只在最后一次操作结束时候响应UI更新呢。

答:HandlerThread+反射MessageQueue+idelHandler
在这里插入图片描述

用Handler遇到什么问题?怎么解决这些问题的?

问题:Handler延时不准确,经常到了时间不响应业务

解决:SystemClock.uptimeMillis()表示系统开机到当前的时间总数,单位是毫秒,但是,当系统进入深度睡眠(CPU休眠、屏幕休眠、设备等待外部输入)时间就会停止,但是不会受到时钟缩放、空闲或者其他节能机制的影响。

使用其他延时方式

  1. 用concurrent包的TimeUnit类延时sleep()方法延时
  2. Timer+TimeTask
  3. AlarmManager
  4. ScheduledExecutorService
问题:子线程创建Handler失败

解决:参考Looper#子线程用例部分

问题:非静态类导致的内存泄漏

解决:static+WeakReference

static class MyHandler extends Handler{
     WeakReference<MainActivity> mActivity;
     MyHandler(MainActivity activity){
         mActivity = new WeakReference<MainActivity>(activity);
     }
   @Override
    public void handleMessage(Message msg) {

      int cuttent = msg.what;

        MainActivity activity = mActivity.get();

        if(activity.currentlayout!=null){
            Animation set2 = new TranslateAnimation(0,0,0,-100);
            set2.setDuration(500);
            activity.currentlayout.setAnimation(set2);
        }

        activity.linearLayout.removeAllViews();

        activity.currentlayout = activity.initView(cuttent);

        Animation set1 = new TranslateAnimation(0,0,100,0);
        set1.setDuration(500);
        activity.currentlayout.setAnimation(set1);

        activity.linearLayout.addView(activity.currentlayout);

        super.handleMessage(msg);
    }
}

说一说Handler原理?

原理的定义是:某个类的提供哪些功能,这些功能是如何实现的?

我认为回答这个问题包含三步:Handler是什么,关键API是什么,关键对象是什么?

第一步:回答Handler是什么,有哪些场景。

答:Handler是Android消息通信组件,用于线程间通信,收发消息,更新UI等参考面试题目1。

第二步:Handler的关键API是什么,用途是什么,如何实现的。

答:关键API有:

构造函数:用于绑定MessageQueue、Looper、Message、Runnable、CallBack等

obtainMessage:用于复用Message

post、sendMessage:不同的执行Message方式,前者是接收Runnable参数,后者是当前线程

sendMessageAtTime:消息延时的实现入口,调用MessageQueue#enqueueMessage

第三步:Handler的关键对象是什么,提供哪些功能,这些功能如何实现的。

答:关键对象有:

Message

Message是一种链表结构的子节点,作为载体可以存储的信息有:公开信息arg1、arg2、handler、Message,私有信息when、Runnable。Message有2种Flag,使用中、同步中,三种消息类型,如普通消息、异步消息、消息屏障。

MessageQueue

MessageQueue提供一种链表数据结构,包括头结点信息,插入节点的方式是按照 Message 的时间 when 顺序,时间小的先插入 。

Looper

开启无限循环,不断从 MessageQueue 中取出 Message,然后处理 Message 中指定的任务。典型的Looper是主线程Looper,在 ActivityThread 的 main 方法中,除了调用 Looper.prepareMainLooper 初始化 Looper 对象之外,还调用了 Looper.loop 方法开启无限循环,Looper 的主要功能就是在这个循环中完成的。

Looper提供了一些native方法用于唤醒阻塞状态如nativePollOnce

Looper不断loop的结果,就是调用msg.target.handleMessage,即执行开发者定义好的Handler#handleMessage方法体中的业务。

能自己实现一个Handler吗?

根据Handler的类图,我们可以抽象出Handler消息组件的基本架构。
在这里插入图片描述

简版Handler

概要设计:

首先我们仿照Android的Handler定义了:阻塞队列、处理消息的回调、分发和发送消息的方法
其次然后在创建Handler时,我们获取了当前线程的Looper和MessageQueue

最后,当我们发送消息的时候,将消息添加进之前得到的MessageQueue

public class MyHandler {

    private MyMessageQueue queue;// 用于进行线程间通信的阻塞队列
    private CallBack callBack; // 处理消息的回调

    public MyHandler(CallBack callBack) {
        MyLooper looper = MyLooper.myLooper();
        if (looper == null) {
            throw new RuntimeException("在新开的线程中。创建MyHandler对象需要先调用MyLooper.prepare()方法。");
        }
        queue = looper.queue;
        this.callBack = callBack;
    }

    //消息接收的回调
    public interface CallBack {
        void handleMessage(MyMessage msg);
    }

    //发送消息
    public void sendMessage(MyMessage msg) {
        msg.target = this;
        queue.enqueueMessage(msg);
    }

    //派发消息
    public void dispatchMessage(MyMessage msg) {
        callBack.handleMessage(msg);
    }

}
简版Looper
  1. 在Looper中,我们用一个ThreadLocal存储当前Looper的相关数据
  2. 定义了一个消息队列,用来管理消息
  3. 在prepare()时,用ThreadLocal存储Looper的数据;在myLooper时,读取ThreadLocal存储的Looper数据
  4. 在loop()时,用一个死循环来不断的接受和分发消息
public class MyLooper {

    private static ThreadLocal<MyLooper> threadLocal = new ThreadLocal<>();
    private static MyLooper myLooper;
    public MyMessageQueue queue;//一个线程对应一个阻塞队列

    private MyLooper() {
        queue = new MyMessageQueue();
    }

    //获取当前线程相对应的Looper对象
    public static MyLooper myLooper() {
        return threadLocal.get();//当未调用prepare()方法时。ThreadLocal.get()方法返回的为null;
    }

    //为本线程准备对应的MyLooper对象
    public static void prepare() {
        if (threadLocal.get() != null) {
            throw new RuntimeException( "Only one MyLooper may be created per thread");
        }
        threadLocal.set(new MyLooper());
    }

    //这里启动消息循环
    public static void loop() {
        while (true) {
            myLooper = myLooper();
            MyMessageQueue mQueue = myLooper.queue;
            senior.thread_concurrent.handler.MyMessage msg = mQueue.next();// take()方法是个阻塞方法。线程运行到此会阻塞住。以准备接收发过来的消息
            msg.target.dispatchMessage(msg);
        }
    }
}
简版MessageQueue
public class MyMessageQueue {

    private BlockingQueue<MyMessage> queue;
    private boolean quit = false;

    public MyMessageQueue() {
        queue = new LinkedBlockingQueue<>();
        queue.clear();
    }

    //入队
    public boolean enqueueMessage(MyMessage msg) {
        if (msg.target == null) {
            throw new RuntimeException("消息必须有一个消息处理者");
        }
        try {
            queue.put(msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

    //出队
    public MyMessage next() {
        MyMessage msg = null;
        if (quit) {
            return null;
        }
        try {
            msg = queue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return msg;
    }

    //销毁
    public synchronized void quit() {
        quit = true;
    }
}
简版Message
public class MyMessage {
    public int msg1;
    public int msg2;
    public int what;
    public Object obj;
    public MyHandler target;
    public Runnable runnable;
}
写一个网络请求的测试用例
public class TestClient {

    private senior.thread_concurrent.handler.MyHandler mainHandler;

    public static void main(String[] args) {
        new TestClient().test();
    }

    private void test() {
        //初始化主线程Looper
        MyLooper.prepare();
        mainHandler = new senior.thread_concurrent.handler.MyHandler(new senior.thread_concurrent.handler.MyHandler.CallBack() {
            @Override
            public void handleMessage(senior.thread_concurrent.handler.MyMessage msg) {
                // 刷新界面
                String obj = (String) msg.obj;
                LogUtil.print("刷新界面:" + obj);
            }
        });
        //发起网络请求
        LogUtil.print("在主线程发起一个网络请求");
        NetThread netThread = new NetThread("http://baidu.com");
        netThread.start();
        LogUtil.print("在主线程继续其它操作");

        //开始消息循环
        MyLooper.loop();
    }


    //网络线程类
    private class NetThread extends Thread {
        private String url;

        public NetThread(String url) {
            this.url = url;
        }

        @Override
        public void run() {
            String body = getWebData(url);
            senior.thread_concurrent.handler.MyMessage msg = new senior.thread_concurrent.handler.MyMessage();
            msg.obj = body;
            mainHandler.sendMessage(msg);
        }
    }

    //执行网络请求
    private String getWebData(String url) {
        LogUtil.print("执行请求网络:" + url);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String body = "这是" + url + "的响应值";
        LogUtil.print("请求网络成功:" + body);
        return body;
    }
}

说一说Handler延时原理?

首先Handler无论是post还是sendMessage方式处理Message过程中,都会产生一个时间戳,计算方式是SystemClock.uptimeMillis() + delayMillis,这个时间戳会赋值给Message.when,影响Message在MessageQueue链表中的位置。时间戳值越大,越晚执行。

Handler延时存在时间不准的问题,问题产生原因以及解决办法以及在面试题2问题:Handler延时不准确,经常到了时间不响应业务提到。

Handler延时有哪些缺陷?造成这些缺陷的原因?

参考面试题2问题:Handler延时不准确,经常到了时间不响应业务

Handler的post与sendMessage有哪些区别?

post需要指定Runnable参数,将传入的Runnable绑定至Handler默认的Message,很多值都为默认值,换言之post方法只是为了执行Runnable子线程的任务。

sendMessage需要传入开发者自定义的Message参数,将Message中的信息载体传递下去,sendMessage方法是为了传递消息。

两者最终都会将Message传递下去,区别是Message中的数据信息赋值数量的不同。

子线程能使用Handler吗?

能,可以使用handler对象以及对应的方法。区别是Handler的创建位置,如果Handler在主线程创建,那么只能在主线程中处理消息。如果在子线程创建Handler,那么才能在子线程处理消息。

子线程能创建Handler吗?

能,前提是需要Looper.prepare+Looper.loop

Looper.prepare是将当前线程添加到sThreadLocal中,Looper.loop是开启无限循环,不断执行Message

子线程创建Handler这么麻烦,有什么替代方法吗?了解过HandlerThread吗?

HandlerThread的run方法中替我们做了Looper.prepare+Looper.loop

HandlerThread handlerThread = new HandlerThread("handler-thread");
handlerThread.start(); // 必须在Handler创建前调用,因为线程start后才会创建Looper

Handler threadHandler = new Handler(handlerThread.getLooper()) {

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        // 处理消息,因为这个方法是在子线程调用,所以可以在这执行耗时任务
    }
};

了解过IdleHandler吗?

IdleHandler 用途:

  1. IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机;
  2. 当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;
  3. Activity界面绘制结束的回调时机

IdleHandler 缺点:

  1. 但它执行的时机依赖消息队列的情况,那么如果 MessageQueue 一直有待执行的消息时,IdleHandler 就一直得不到执行,也就是它的执行时机是不可控的,不适合执行一些对时机要求比较高的任务。

IdleHandler场景

如果我们想在界面绘制出来后做点什么,那么在onResume里面是不合适的,它先于measure等流程了**, 有人可能会说在onResume里面post一个runnable可以吗?还是不行,因为那样就会变成这个样子

在这里插入图片描述

所以你的行为一样会在绘制之前执行,这个时候我们的主角IdleHandler就发挥作用了,我们前面说了,它是在looper里面message暂时执行完毕了就会回调,顾名思义嘛,Idle就是队列为空的意思,那么我们的onResume和measure, layout, draw都是一个个message的话,这个IdleHandler就提供了一个它们都执行完毕的回调了,大概就是这样
在这里插入图片描述

也就是说IdleHandler可以再界面绘制的消息回调之后执行。
在这里插入图片描述

优化前:

这个是我们地图的公交详情页面, 进入之后产品要求左边的页卡需要展示,可以看到左边的页卡是一个非常复杂的布局,那么进入之后的效果可以明显看到头部的展示信息是先显示空白再100毫秒左右之后才展示出来的,原因就是这个页卡的内容比较复杂,用数据向它填充的时候花了较长时间,代码如下:
在这里插入图片描述

可以看到这个detailView就是这个侧滑的页卡了,填充里面的数据花了90ms,如果这个时间是用在了界面view绘制之前的话,就会出现以上的效果了,view先是白的,再出现,这样就体验不好了。

优化后:如果我们把它放到IdleHandler里面呢?

结果非常明显:顶部的页卡先展示出来了,这样体验是不是会更好一些呢。虽然只有短短90ms,不过我们做app也应该关注这种细节优化的,是吧~ 这个做法也提供了一种思路,android本身提供的activity框架和fragment框架并没有提供绘制完成的回调,如果我们自己实现一个框架,就可以使用这个IdleHandler来实现一个onRenderFinished这种回调了。

代码如下:

在这里插入图片描述


特别参考

https://wetest.qq.com/lab/view/352.html

https://blog.csdn.net/u013718120/article/details/51945490

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值