Android 异步UI
之前有分析过子线程中直接更新ui
众所周知CalledFromWrongThreadException是检查original thread
,也就是创建ui的线程。那么在子线程中创建ui,自然也可以在此线程中更新ui。
要维护一个子线程,首先想到的就是HandlerThread
下面写个demo
class MainActivity : AppCompatActivity() {
private val handlerThread = HandlerThread("AsyncHandlerThread")
class H(looper: Looper, private val weak: WeakReference<MainActivity>) : Handler(looper) {
var tvMain: TextView? = null
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
10086 -> {
val root = LayoutInflater.from(weak.get())
.inflate(R.layout.activity_main, null)
val wm: WindowManager =
weak.get()?.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val param = WindowManager.LayoutParams()
param.width = WindowManager.LayoutParams.MATCH_PARENT
param.height = 300
wm.addView(root, param)
tvMain = root.findViewById(R.id.tvMain)
tvMain?.setOnClickListener {
tvMain?.text = "${Thread.currentThread()}"
}
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handlerThread.start()
val handler = H(handlerThread.looper, WeakReference(this@MainActivity))
handler.sendEmptyMessage(10086)
}
override fun onDestroy() {
handlerThread.quitSafely()
super.onDestroy()
}
}
Run,点击TextView验证一下
onClick回调在HandlerThread所在线程。通过这个思路,可以将部分ui挪到子线程中,减少主线程耗时。
追本溯源,WindowManager的实现类是WindowManagerImpl,
WindowManagerImpl.addView
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
WindowManagerGlobal.addView
...
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView, userId);
...
ViewRootImpl构造方法中为mThread赋值、初始化Choreographer
mThread = Thread.currentThread();
mChoreographer = useSfChoreographer
? Choreographer.getSfInstance() : Choreographer.getInstance();
Choreographer.getSfInstance()
,从ThreadLocal中取对应线程的Choreographer
public static Choreographer getSfInstance() {
return sSfThreadInstance.get();
}
private static final ThreadLocal<Choreographer> sSfThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
return new Choreographer(looper, VSYNC_SOURCE_SURFACE_FLINGER);
}
};
回看ViewRootImpl.setView()
...
requestLayout();
...
ViewRootImpl.requestLayout()
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
ViewRootImpl.scheduleTraversals()
final ViewRootHandler mHandler = new ViewRootHandler();
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
mHandler调用Handler无参构造方法初始化取的是当前线程looper,如此一来mHandler、mChoreographer都在demo中HandlerThread线程所在的事件循环。
mTraversalRunnable
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
doTraversal大家都懂,后面就是绘制流程了。
继续跟一下Choreographer相关逻辑,回看Choreographer.postCallback()
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
...
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
Choreographer.scheduleFrameLocked()
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
到这里就差不太多。上述注释说明,在有Looper的线程立即发出vsync;否则post 一个带vsync的message到ui线程。我们的HandlerThread中当然是有Looper事件循环的啦。
Choreographer.scheduleVsyncLocked()
private final FrameDisplayEventReceiver mDisplayEventReceiver;
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable{}
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
调用父类DisplayEventReceiver.scheduleVsync()
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
nativeScheduleVsync(),VSync信号最终调到C层。笔者比较懒,就不跟C层代码了,搜索一番得到结论,VSync信号接受回调的方法是onVsync()
FrameDisplayEventReceiver.onVsync()
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
...
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
Message.obtain(mHandler, this),this也就是FrameDisplayEventReceiver这个Runnable,那么就调到了run方法。
FrameDisplayEventReceiver.run()
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
doFrame()最终就是执行前面ViewRootImpl.mTraversalRunnable
验证一下,profile运行record一次看调用栈。
绘制流程确实都在AsyncHandlerThread这个子线程中了。