博客主页

接下来讲解的类有:Messenger、IdleHandler、Looper.Observer、MessageLogging

1. Messenger

Messenger可以翻译为信使,可以通过它在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以实现数据进程间的传递了。

Messenger是一个轻量级的IPC方案,底层实现是AIDL。

下面看下Messenger的两个构造方法:

// Messenger.java 的构造方法
    private final IMessenger mTarget;

    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }

Messenger对AIDL做了封装,且它一次处理一个请求,在服务端不用考虑线程同步问题,因为服务端不存在并发执行的情形。

一起看下Messenger使用步骤:
1、服务端进程
创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind方法中返回这个Messenger对象底层的Binder即可。

public class MessengerService extends Service {

    private static final String TAG = "Messenger";
   
    // 处理客户端发送的消息
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case 111:
                    Log.d(TAG, "handleMessage: receive msg from client: " + msg.getData().getString("msg"));

                    // 回复一条消息给客户端
                    Message replyMsg = Message.obtain(null, 112);

                    Bundle replyData = new Bundle();
                    replyData.putString("replyMsg", "hello, this is service.");
                    replyMsg.setData(replyData);
                    try {
                        // 拿到客户端的回复的Messenger
                        Messenger client = msg.replyTo;
                        client.send(replyMsg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    // 这个Messenger作用:将客户端发送来的消息传递给MessengerHandler处理
    private final Messenger mMessenger = new Messenger(new MessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

需要在Manifest清单文件中注册Service,让它运行在单独的进程中:

        <service
            android:name=".MessengerService"
            android:process=":messenger_remote" />

2、客户端进程
首先绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger向服务端发送消息,消息的类型是Message对象。

如果需要服务端能够回应客户端,就和服务端一样,还需要创建一个Handler并创建一个Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。

public class MessengerActivity extends AppCompatActivity {

    private static final String TAG = "Messenger";

    private Messenger mMessenger;

    // 创建一个接受服务端回复消息的Handler
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 112:
                    Log.d(TAG, "handleMessage: receive msg from service: " + msg.getData().getString("replyMsg"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
    
    // 创建一个回复的Messenger对象
    private Messenger mReplyMessenger = new Messenger(new MessengerHandler());

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 2. 绑定成功后,根据服务端返回的binder对象创建Messenger对象
            mMessenger = new Messenger(service);

            Message msg = Message.obtain(null, 111);
            Bundle data = new Bundle();
            data.putString("msg", "hello , this is client.");
            msg.setData(data);
            // 把服务端回复的Messenger通过Message的replyTo参数传递给服务端


            msg.replyTo = mReplyMessenger;

            try {
                // 3.通过Messenger对象向服务端发送消息
                mMessenger.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mMessenger = null;
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
         
        // 1. 绑定远程进程MessengerService
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        super.onDestroy();
    }
}

在Messenger中进行数据传递必须将数据放入Message中,Messenger和Message都实现了Parcelable接口,可以跨进程传输。Message中所支持的数据类型就是Messenger所传输类型。

Message中能使用的载体有:what、arg1、arg2、Bundle、replyTo
Message中还有一个字段Object obj,但是在Android 2.2之前obj字段不支持跨进程传输,之后也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。

意味着:我们自定义的Parcelable接口对象是无法通过obj字段来传输的。

下面这张图可以更好的理解Messenger的工作原理:

2. IdleHandler

页面启动优化,可以通过IdleHandler实现。看下IdleHandler源码,可知,当消息队列没有消息的时候调用

// MessageQueue.java

    /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }

MessageQueue中还提供了addIdleHandler和removeIdleHandler方法

// MessageQueue.java

  private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }

    public void removeIdleHandler(@NonNull IdleHandler handler) {
        synchronized (this) {
            mIdleHandlers.remove(handler);
        }
    }

重点分析下IdleHandler什么时候被执行,首先看下MessageQueue的next 方法

// MessageQueue.nex() 源码

    Message next() {
       
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // ...
                // 没有消息了...

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                // toArray会自动扩容
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                // 如果queueIdle方法返回false,只会被执行一次,因为当前的IdleHandler会被移除
                // 如果queueIdle方法返回true,当前的IdleHandler下次还会被执行
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

由于Looper的getQueue方法在Android API 23版本才添加,23以下版本需要通过反射获取MessageQueue对象。下面看下IdleHandler使用方式:

    MessageQueue queue = null;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        queue = Looper.getMainLooper().getQueue();
    } else {
        // 可以通过反射拿到MessageQueue
        try {
            Method getQueue = Looper.class.getDeclaredMethod("getQueue");

            queue = (MessageQueue) getQueue.invoke(Looper.getMainLooper());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    if (queue != null) {
        queue.addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                Log.d(TAG, "queueIdle: 执行了");
                return false;
            }
        });
    }

3. Looper.Observer

这个是在Android API 29新增加的,但是是隐藏的API,@hide

// Looper.java

  /**
     * Set the transaction observer for all Loopers in this process.
     *
     * @hide
     */
    public static void setObserver(@Nullable Observer observer) {
        sObserver = observer;
    }

4. MessageLogging

Looper中提供了mLogging,可以用来监控卡顿。这个监控卡顿的方法是基于消息队列实现,通过替换Looper的Printer实现。

// Looper.java 

    public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }

通过Looper中的loop()监控日志的输出

// Looper的loop() 方法部分代码

  // This must be in a local variable, in case a UI event sets the logger
  final Printer logging = me.mLogging;
  if (logging != null) {
        logging.println(">>>>> Dispatching to " + msg.target + " " +
                msg.callback + ": " + msg.what);
  }

  if (logging != null) {
      logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
  }

但这种方式有一个缺点,当我们快速滑动时帧率起码降低5帧,可以通过Traceview查看,导致这个原因是因为大量字符串拼接导致性能损耗严重。

如何解决呢?
可以通过一个监控线程,每隔1秒向主线程消息队列的头部插入一条空消息。假设1秒后这个消息并没有被主线程消耗掉,说明堵塞消息运行的时间在0~1秒之间。换句话说,如果我们需要监控3秒卡顿,那在第4次轮询中头部消息依然没有被消耗的话,就可以确定主线程出现了一次3秒以上的卡顿。

这个方案也存在误差,就是发送空消息的间隔时间,但这个间隔时间也不能太小,因为监控线程和主线程处理空消息都会带来一些性能损耗,但基本影响不大。

补充:向消息队列的头部插入消息方式

boolean sendMessageAtFrontOfQueue(@NonNull Message msg)

boolean postAtFrontOfQueue(@NonNull Runnable r)

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)


小兵兵同学
56 声望23 粉丝

Android技术分享平台,每个工作日都有优质技术文章分享。从技术角度,分享生活工作的点滴。