分析下面app的错误,
顺便
认识一下
Choreographer
可好?
问题来源:使用WindowManager在Service中addView/removeView,快速做一系列复杂动画交互,销毁的时候出现的问题.
想看
系统源码
可以点击这里👉👉
Android Code Search官网
下面是我们拿来分析的异常日志
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.ThreadedRenderer.setFrameCompleteCallback(android.view.ThreadedRenderer$FrameCompleteCallback)' on a null object reference
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3354)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2706)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1599)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7652)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1034)
at android.view.Choreographer.doCallbacks(Choreographer.java:839)
at android.view.Choreographer.doFrame(Choreographer.java:768)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1020)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7096)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:604)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:928)
这里报错的地方是『
ThreadedRenderer.setFrameCompleteCallback』
出现空指针问题。
那么为什么这里会出现这个问题呢?先不急,我们来补一补一些知识,为了后面更好的分析问题。
一、WindowManagerImpl在哪里初始化?
有人知道,Activity是如何启动的吗?细节就不在这里说了,介绍的话又是一篇文章,太长了,我们就挑一个简单的入口ActivityThread#handleLaunchActivity 来看一看里面发生了什么:
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
WindowManagerGlobal.initialize();
final Activity a = performLaunchActivity(r, customIntent);
return a;
再来看一下performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
} catch (Exception e) {
return activity;
继续看里面的Activity#attach方法
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
这里我们看初始化了一个PhoneWindow,在setWindowManager里面初始化了WindowManagerImpl
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
原来WindowManagerImpl是在Activity#attatch中初始化的
二、ViewRootImpl在哪里初始化?
我们看一下源码中的注释:
The top of a view hierarchy, implementing the needed protocol between View and the WindowManager.
This is for the most part an internal implementation detail of WindowManagerGlobal.
普通话大概是下面这个意思:
它是视图层次结构的顶部,实现了View和WindowManager之间的通信协议。
具体实现的细节在WindowManagerGlobal这个类当中。
我们简单看一下下面的类图:
classDiagram
ViewManager <|-- WindowManager
WindowManager <|-- WindowManagerImpl
class ViewManager{
+addView(View view, ViewGroup.LayoutParams params)
+updateViewLayout(View view, ViewGroup.LayoutParams params)
+removeView(View view)
class WindowManager{
+getDefaultDisplay()
+removeViewImmediate(View view)
class WindowManagerImpl{
-WindowManagerGlobal:mGlobal
<<interface>> ViewManager
<<interface>> WindowManager
看到这里了,内心疑问三连,这些玩意就算看了源码,过段时间也会忘,那么为什么要看源码?
当然是为了上面的错误啦,出错了找不到原因就先看源码里面到底做了什么事情,再来结合自己的项目来分析。
我们在目录一,已经分析过WindowManagerImpl在哪初始化的了,我们再来看一下addView方法
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
再次默默的打开了WindowManagerGlobal源码
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
原来ViewRootImpl是在WindowManagerGlobal的addView方法中初始化的
我们简单看一眼ViewRootImpl构造函数里面初始化了什么
public ViewRootImpl(Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
mCompatibleVisibilityInfo = new SystemUiVisibilityInfo();
mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager, mHandler);
mHighContrastTextManager = new HighContrastTextManager();
mAccessibilityManager.addHighTextContrastStateChangeListener(
mHighContrastTextManager, mHandler);
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
mChoreographer = useSfChoreographer
? Choreographer.getSfInstance() : Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
三、Choreographer是什么东西?
协调动画、输入和绘图的时间,每个Looper线程都有自己的Choreographer,
Choreographer通过接收显示系统的时间脉冲(如垂直同步信号), 来完成下一帧的渲染工作;
我们在目录二看了ViewRootImpl是在哪初始化的,初始化ViewRootImpl的时候同时初始化了Choreographer,这两个类是有关联的。
1.Choregrapher四个常用方法
在ViewRootImpl类中,发现使用了Choregrapher的四个方法:
void postCallback(int callbackType, Runnable action, Object token)
void removeCallbacks(int callbackType, Runnable action, Object token)
void postFrameCallback(FrameCallback callback)
void removeFrameCallback(FrameCallback callback)
查看之后发现上面两个post方法最终都会执行到Choreographer#postCallbackDelayedInternal 里面
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);
2.scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
如何触发scheduleTraversals调用的呢?
我们看一下View在调用invalidate之后触发的调用链:
graph TD
View#invalidate --> ViewGroup#invalidateChild --> ViewGroup#onDescendantInvalidated --递归调用--> ViewGroup#onDescendantInvalidated --> ViewRootImpl#onDescendantInvalidated --> ViewRootImpl#invalidate --> ViewRootImpl#scheduleTraversals --开启消息同步屏障--> Choreographer#postCallback --> Choreographer#postCallbackDelayedInternal --> Choreographer#scheduleFrameLocked --> 请求VSYNC垂直同步信号 --> Choreographer.FrameDisplayEventReceiver#run --> Choreographer#doFrame --> Choreographer#doCallbacks --> Choreographer.CallbackRecord#run --> ViewRootImpl.TraversalRunnable#run --> ViewRootImpl#doTraversal --移除消息同步屏障--> ViewRootImpl#performTraversals -.-> |省略部分方法|View#draw
3.scheduleFrameLocked
我们可以从上面的调用链看到是从哪里执行到scheduleFrameLocked方法的
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
我们看一下上面的scheduleVsyncLocked:请求VSYNC垂直同步信号
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
public void scheduleVsync() {
nativeScheduleVsync(mReceiverPtr);
android_view_DisplayEventReceiver.cpp
static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
sp<NativeDisplayEventReceiver> receiver =
reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
status_t status = receiver->scheduleVsync();
DisplayEventDispatcher.cpp
status_t DisplayEventDispatcher::scheduleVsync() {
if (!mWaitingForVsync) {
status_t status = mReceiver.requestNextVsync();
return OK;
刚刚上面看到scheduleVsyncLocked里面使用了FrameDisplayEventReceiver 发起VSYNC请求
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
onVsync(timestampNanos, physicalDisplayId, frame);
scheduleFrameLocked逻辑如下:
(1). USE_VSYNC,默认是true
如果Looper是FrameHandler对应的Looper,则执行下一个VSYNC信号;
否则发送异步消息,最终都会执行到scheduleVsyncLocked()
(2). 非USE_VSYNC
定时向FrameHandler发送类型为MSG_DO_FRAME的异步消息,
执行doFrame从而刷新一帧数据;
4.doFrame
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return;
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
frameTimeNanos = startNanos - lastFrameOffset;
if (frameTimeNanos < mLastFrameTimeNanos) {
scheduleVsyncLocked();
return;
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();
return;
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
doFrame逻辑如下:
(1). 掉帧检查
如果掉帧数量超过阈值,触发Log日志警告开发者,修正VSYNC信号回调的时间;
如果跳帧或者时间间隔小于执行时间,都需要请求下一个VSYNC信号;
(2).渲染当前帧
绘制每一帧,依次执行下面的doCallbacks
CALLBACK_INPUT
CALLBACK_ANIMATION
CALLBACK_INSETS_ANIMATION
CALLBACK_TRAVERSAL
CALLBACK_COMMIT
5.doCallbacks
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
mCallbacksRunning = true;
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
if (jitterNanos >= 2 * mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ mFrameIntervalNanos;
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(frameTimeNanos);
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
doCallbacks逻辑如下:
(1). 获取CallbackRecord内部的dueTime小于当前时间戳的callbacks
(2). 如果帧延迟超过2帧,需要更新帧时间,保证下一帧时间永远要大于前一帧
(3). 遍历CallbackRecord链表,执行run方法
(4). 回收CallbackRecord链表
6.CallbackRecord#run
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
CallbackRecord#run逻辑如下:
(1). 如果是Choreographer#postFrameCallback
的调用会执行到第一个分支里面去
(2). 如果是Choreographer.postCallback(CALLBACK_TRAVERSAL, mTraversalRunnable, null)
调用就会执行到第二个分支,执行里面的run方法之后,会触发ViewRootImpl#doTraversal
方法的调用
四、堆栈信息异常分析
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.ThreadedRenderer.setFrameCompleteCallback(android.view.ThreadedRenderer$FrameCompleteCallback)' on a null object reference
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3354)
.....
在这个方法里面执行到ThreadedRenderer.setFrameCompleteCallback
出现空指针问题。
我们有下面两个疑问点,需要查看代码进行进一步的分析
1.ThreadedRenderer在哪初始化?
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
if (mSurfaceHolder == null) {
enableHardwareAcceleration(attrs);
我们看到setView的时候在surfaceHolder为null的时候会调用enableHardwareAcceleration
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
mAttachInfo.mHardwareAccelerated = false;
mAttachInfo.mHardwareAccelerationRequested = false;
if (mTranslator != null) return;
final boolean hardwareAccelerated =
(attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
if (hardwareAccelerated) {
if (!ThreadedRenderer.isAvailable()) {
return;
if (!ThreadedRenderer.sRendererDisabled
|| (ThreadedRenderer.sSystemRendererDisabled && forceHwAccelerated)) {
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.destroy();
mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
attrs.getTitle().toString());
mAttachInfo.mThreadedRenderer.setWideGamut(wideGamut);
updateForceDarkMode();
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mHardwareAccelerated =
mAttachInfo.mHardwareAccelerationRequested = true;
enableHardwareAcceleration逻辑如下:
(1). 如果处于兼容模式则不启用硬件加速
(2). 非兼容模式,判断是否支持硬件加速,如果支持启用,ThreadedRenderer.isAvailable()不可用,会拦截执行
(3). 先销毁mAttachInfo里面的mThreadedRenderer,再使用OpenGL创建并初始化ThreadRenderer
2.ThreadedRenderer何时为null?
我们上面分析到ThreadedRenderer在setView里面的enableHardwareAcceleration方法中初始化的,那么有初始化启用的地方,一定要对应禁用销毁的地方
void dispatchDetachedFromWindow() {
destroyHardwareRenderer();
private void destroyHardwareRenderer() {
ThreadedRenderer hardwareRenderer = mAttachInfo.mThreadedRenderer;
if (hardwareRenderer != null) {
if (mView != null) {
hardwareRenderer.destroyHardwareResources(mView);
hardwareRenderer.destroy();
hardwareRenderer.setRequested(false);
mAttachInfo.mThreadedRenderer = null;
mAttachInfo.mHardwareAccelerated = false;
destroyHardwareRenderer逻辑如下:
(1). hardwareRenderer不为null
如果mView不为null,则需要:销毁此视图与硬件关联的资源
(2). 销毁HardwareRenderer
(3). 重置mThreadedRenderer
3.ViewRootImpl#performDraw
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
} else if (mView == null) {
return;
final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
mFullRedrawNeeded = false;
boolean usingAsyncReport = false;
boolean reportNextDraw = mReportNextDraw;
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
ArrayList<Runnable> commitCallbacks = mAttachInfo.mTreeObserver.captureFrameCommitCallbacks();
final boolean needFrameCompleteCallback = mNextDrawUseBLASTSyncTransaction ||
(commitCallbacks != null && commitCallbacks.size() > 0) ||
mReportNextDraw;
usingAsyncReport = mReportNextDraw;
if (needFrameCompleteCallback) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(...);
try {
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
finishBLASTSync(true );
} finally {
我们看一下可疑点2️⃣上面的draw(fullRedrawNeeded)
private boolean draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return false;
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
curScrollY = mScroller.getCurrY();
} else {
curScrollY = mScrollY;
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
return false;
if (fullRedrawNeeded) {
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
mAttachInfo.mTreeObserver.dispatchOnDraw();
int xOffset = -mCanvasOffsetX;
int yOffset = -mCanvasOffsetY + curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
xOffset -= surfaceInsets.left;
yOffset -= surfaceInsets.top;
dirty.offset(surfaceInsets.left, surfaceInsets.right);
mAttachInfo.mDrawingTime =
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
boolean useAsyncReport = false;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
mInvalidateRootRequested = false;
mIsAnimating = false;
if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
mHardwareYOffset = yOffset;
mHardwareXOffset = xOffset;
invalidateRoot = true;
if (invalidateRoot) {
mAttachInfo.mThreadedRenderer.invalidateRoot();
dirty.setEmpty();
final boolean updated = updateContentDrawBounds();
if (mReportNextDraw) {
mAttachInfo.mThreadedRenderer.setStopped(false);
if (updated) {
requestDrawWindow();
useAsyncReport = true;
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
if (mAttachInfo.mThreadedRenderer != null &&
!mAttachInfo.mThreadedRenderer.isEnabled() &&
mAttachInfo.mThreadedRenderer.isRequested() &&
mSurface.isValid()) {
try {
mAttachInfo.mThreadedRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
mFullRedrawNeeded = true;
scheduleTraversals();
return false;
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
return useAsyncReport;
这个draw(boolean fullRedrawNeeded) 方法内部大致执行了如下流程:
点击查看大图
这个时候能不能分析出来上面那个空指针可能是什么原因导致的呢?
我们可以大胆的猜测一下:
1.draw(boolean fullRedrawNeeded) 返回false的情况,可能会导致异常崩溃
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
finishBLASTSync(true );
2.performDraw() 什么条件下触发调用的?
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw) {
performDraw();
五、猜想验证
刚刚上面分析了2个可疑点,虽然上面的错误只是某某用户操作某个功能出现的一种错误,但是不得不引起我们的重视,为什么会出现ThreadRenderer为null了,我们还有要绘制的任务再继续执行?
打个比方,我们给即将销毁
的View设置setVisibility为GONE,大家可以猜想一下,可能会出现什么问题?
假设
我们removeView的代码如下:
fun WindowManager.removeLayout(layout: View?) {
if (null != layout) {
layout.post{
layout.visibility = View.GONE
try {
removeView(layout)
}catch (e:Exception){
我们可以直接打开ViewRootImpl
,看一下里面的performTraversals
方法
private void performTraversals() {
.....
final int viewVisibility = getHostVisibility();
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded
|| mAppVisibilityChanged);
mAppVisibilityChanged = false;
.....
if (viewVisibilityChanged) {
.....
if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
endDragResizing();
destroyHardwareResources();
.....
如果执行到方法最后一行,这个时候刚好mThreadRenderer被设置为null,那么还是会出现空指针
void destroyHardwareResources() {
final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer;
if (renderer != null) {
if (Looper.myLooper() != mAttachInfo.mHandler.getLooper()) {
mAttachInfo.mHandler.postAtFrontOfQueue(this::destroyHardwareResources);
return;
renderer.destroyHardwareResources(mView);
renderer.destroy();
所以最好的做法是,在我们执行视图销毁之前:不要出现
触发刷新视图的动作,包括设置可见性等。
有些不规范的写法,比如:某某自定义View,
在onDetachedFromWindow里面判断了当前View的layerType,然后调用了setLayerType(LAYER_TYPE_NONE),这种的是不应该在这里调用的,需要删除,一般动画开始设置LAYER_TYPE_HARDWARE,动画结束或者取消再去设置LAYER_TYPE_NONE
因为在做一些复杂的场景的时候,如果无法做不到控制,到处刷新,会造成一些莫名其妙的错误。
希望这篇文章的分析,能对大家有那么一点点的帮助!