Android系统源码分析:从一次WebView的loadUrl报NullPointerException说起

异常及初步分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java.lang.NullPointerException
at android.webkit.WebViewClassic.loadUrlImpl(WebViewClassic.java:2740)
at android.webkit.WebViewClassic.loadUrlImpl(WebViewClassic.java:2756)
at android.webkit.WebViewClassic.loadUrl(WebViewClassic.java:2749)
at android.webkit.WebView.loadUrl(WebView.java:843)
at com.fbmodule.moduleother.lyric.LyricFragment$5$1.run(TbsSdkJava:243)
at android.os.Handler.handleCallback(Handler.java:800)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5392)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:851)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
at dalvik.system.NativeStart.main(Native Method)

这个异常出现频率非常低,异常涉及到LyricFragment,而在这个fragment中的这一行执行了loadUrl的操作,这个操作是在onPageFinished()回调中进行的,目的是加载完成一个网页后加载一段内置的JS方法。

先去搜了一波Android8.0源码,意料之中找不到WebViewClassic.java这个文件了,想到应该是新版本使用了Chrome内核的问题,于是先去查询了这个异常的对应系统版本。
一查果然是Android4.2,嗯。。。

先找到WebViewClassic.java中的loadUrlImpl方法,可以看到这个方法如下:

1
2
3
4
5
6
7
//WebViewClassic.java
private void loadUrlImpl(String url) {
if (url == null) {
return;
}
loadUrlImpl(url, null);
}

而他最终是调用loadUrlImpl(url, null)的,可以看到上面的url是有判空的,所以肯定不是url为空导致的空指针(废话,报异常这里url都是hardcode的怎么会为空)。

下面是loadUrlImpl方法的最终实现:

1
2
3
4
5
6
7
8
9
//WebViewClassic.java
private void loadUrlImpl(String url, Map<String, String> extraHeaders) {
switchOutDrawHistory();
WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData();
arg.mUrl = url;
arg.mExtraHeaders = extraHeaders;
mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);
clearHelpers();
}

switchOutDrawHistory();这个方法的调用只是重置绘制历史和重新渲染view,这个过程不会导致上述空指针的发生,继续往下看。
at android.webkit.WebViewClassic.loadUrlImpl(WebViewClassic.java:2740)来看,基本确定控制针发生在loadUrlImpl中的mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);这一行代码,那么,这个时候,引用的对象mWebviewCore如果为空,那就会导致这个异常。

关注点从现在开始就转移了,什么时候mWebviewCore会为空?

mWebviewCore什么时候会是null

要关注mWebviewCore什么时候是null,直接检索“mWebViewCore = null;”这句代码,果然如我所想,很快就找到了,在destoryJava()方法中会将mWebviewCore对象置为空:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//WebViewClassic.java
private void destroyJava() {
mCallbackProxy.blockMessages();
if (mAccessibilityInjector != null) {
mAccessibilityInjector.destroy();
mAccessibilityInjector = null;
}
if (mWebViewCore != null) {
// Tell WebViewCore to destroy itself
synchronized (this) {
WebViewCore webViewCore = mWebViewCore;
mWebViewCore = null; // prevent using partial webViewCore
webViewCore.destroy();
}
// Remove any pending messages that might not be serviced yet.
mPrivateHandler.removeCallbacksAndMessages(null);
}
}

下面关注点就转移到什么时候调用destroyJava()身上了,继续搜索他的调用栈,找到destroy()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
//WebViewClassic.java
/**
* See {@link WebView#destroy()}
*/
@Override
public void destroy() {
if (mWebView.getViewRootImpl() != null) {
Log.e(LOGTAG, "Error: WebView.destroy() called while still attached!");
}
ensureFunctorDetached();
destroyJava();
destroyNative();
}

注意上面的注释“See {@link WebView#destroy()}”,嗯,这应该是哪里调用了WebViewdestory()之后又使用webview来loadUrl操作了,到这里还只是猜测,下面去实锤。

这里既然他叫我们看WebViewdestory()了,我们就转头去看这块东西。

1
2
3
4
5
//WebView.java
public void destroy() {
checkThread();
mProvider.destroy();
}

checkThread();检查当前是否在UI线程操作,这个没啥好说的。
因为WebView,他只是一个View,所以destory()操作由WebViewProvider来提供就不足为奇了。

WebViewProvider是一个接口,定义了一系列webview中可以使用的方法,但这个我们不关心,我们关心的是他的实现类,因为我们要找他destroy()有什么骚操作。

mProvider不是石头里蹦出来的

要找mProvider的具体实现类,就先找他是怎么创建出来的,直接command+F搜索“mProvider = ”关键字(为啥?赋值呗),找到以下方法:

1
2
3
4
5
6
7
8
9
//WebView.java
private void ensureProviderCreated() {
checkThread();
if (mProvider == null) {
// As this can get called during the base class constructor chain, pass the minimum
// number of dependencies here; the rest are deferred to init().
mProvider = getFactory().createWebView(this, new PrivateAccess());
}
}

看到这里顺便把getFactory()也看了吧:

1
2
3
4
5
6
7
//WebView.java
private static synchronized WebViewFactoryProvider getFactory() {
// For now the main purpose of this function (and the factory abstration) is to keep
// us honest and minimize usage of WebViewClassic internals when binding the proxy.
checkThread();
return WebViewFactory.getProvider();
}

可以看到getFactory()里面通过WebViewFactory.getProvider();静态方法获取WebViewFactoryProvider实例,这个方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//WebView.java
static WebViewFactoryProvider getProvider() {
synchronized (sProviderLock) {
// For now the main purpose of this function (and the factory abstraction) is to keep
// us honest and minimize usage of WebViewClassic internals when binding the proxy.
if (sProviderInstance != null) return sProviderInstance;

// For debug builds, we allow a system property to specify that we should use the
// Chromium powered WebView. This enables us to switch between implementations
// at runtime. For user (release) builds, don't allow this.
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("webview.use_chromium", false)) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
sProviderInstance = loadChromiumProvider();
if (DEBUG) Log.v(LOGTAG, "Loaded Chromium provider: " + sProviderInstance);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}

if (sProviderInstance == null) {
if (DEBUG) Log.v(LOGTAG, "Falling back to default provider: "
+ DEFAULT_WEBVIEW_FACTORY);
sProviderInstance = getFactoryByName(DEFAULT_WEBVIEW_FACTORY,
WebViewFactory.class.getClassLoader());
if (sProviderInstance == null) {
if (DEBUG) Log.v(LOGTAG, "Falling back to explicit linkage");
sProviderInstance = new WebViewClassic.Factory();
}
}
return sProviderInstance;
}
}

上面代码是不是一大坨,好,我们不要在意这些细节,只需要关心这个:

1
sProviderInstance = new WebViewClassic.Factory();

到此,我们再回到WebViewClassic类,找到Factory这个内部类,因为这个类实现了WebViewFactoryProvider接口,我们直接找这个类的destory()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
//WebViewClassic.java
/**
* See {@link WebView#destroy()}
*/
@Override
public void destroy() {
if (mWebView.getViewRootImpl() != null) {
Log.e(LOGTAG, "Error: WebView.destroy() called while still attached!");
}
ensureFunctorDetached();
destroyJava();
destroyNative();
}

哇哈哈哈哈,是不是似曾相识。

就是前面提到的destory(),现在实锤了。

那么应该怎么解决

因为他报空指针,所以肯定有简单粗暴的解决方案,那就是try-catch,捕获空指针异常,那就fix掉了。
但是既然我们知道了为什么他会包空指针,那解决起来当然要优雅一点,利用一个boolean变量来记录webview的destroy状态,再决定要不要loadUrl。

中场休息,整理思绪

由前面源码里绕来绕去可能有点晕,没关系,现在来理一下思绪。

为什么报空指针?

因为loadUrl方法中引用的mWebviewCore为null,所以就报NullPointerException异常了。

为什么mWebviewCore会为null

因为调用了webview.destroy()

为什么会调用webview.destroy()

前面留下一个问题,因为调用了webview.destroy(),而导致了@$#T$#$%#@$#$@78……%¥……@#%¥……%,嗯。。。。就这样。

那么现在要搞清楚,为什么会调用?

调用的代码在这里,这个是在Activity中调用的:

1
2
3
4
5
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
fragment.destroyWebviewView();
}

为什么会有这个操作?这里先交代一下作案动机:

Activity(Fragment(WebView))

嗯,机智的人应该理解,更机智的人看文字解释吧:

架构是这样的,Fragment里面来负责View的操作,那么webview自然就是在Fragment里面的,而Activity只是作为一个Fragment的容器所在,想着在Activity销毁的时候把webview处理掉(怕内存泄漏嘛。。。。)

这个时候,转向看看Activity的onDetachedFromWindow()是在什么时候调用的吧。

1
2
3
4
5
6
7
8
9
10
 //Activity.java
/*
* Called when the main window associated with the activity has been
* detached from the window manager.
* See {@link View#onDetachedFromWindow() View.onDetachedFromWindow()}
* for more information.
* @see View#onDetachedFromWindow
*/
public void onDetachedFromWindow() {
}

可以看到,源码里提到叫我们看ViewonDetachedFromWindow()方法,但有Android开发基础的应该都知道,Activity虽然继承的Context,看似跟view无关,其实还有个WindowManager,启动Activity的时候,会将DecorView添加到这里面,光说无凭,我们还是来看一下他是怎么来的。

Activity的启动过程

说到Activity启动,自然就想到Context里面的startActivity()方法,下面我们从这个方法入手,研究下view怎么跟Activity关联起来的。

先看一眼Activity跟他老爸与老爸的老爸之间的关系(一时找不到空心箭头啥的大概看一下)

图1 Activity

Activity本身是没有startActivity(),我们直接看他的父类,在ContextWrapper中我们找到了这个方法如下:

1
2
3
4
5
//ContextWrapper.java
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}

可以看到最终调用的还是mBase里的startActivity()方法,这个mBase就是Context,我们直接找Context里的startActivity()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
//Context.java
/**
* Same as {@link #startActivity(Intent, Bundle)} with no options
* specified.
*
* @param intent The description of the activity to start.
*
* @throws ActivityNotFoundException
*
* @see {@link #startActivity(Intent, Bundle)}
* @see PackageManager#resolveActivity
*/
public abstract void startActivity(Intent intent);

因为Context是抽象类,这个方法是抽象的也就不足为奇,注意上面的图,还有个ContextImpl,正常来说实现就在那里的,下面去找它出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//ContextImpl.java
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}

@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intent, -1, options);
}

其中的warnIfCallingFromSystemProcess()在这里我们可以无视,他只是判断是不是系统进程来调用这个方法而给出警告的。
我们只需要注意下面这句代码:

1
2
3
mMainThread.getInstrumentation().execStartActivities(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intents, options);

上面是先通过ActivityThread获取Instrumentation,再通过Instrumentation里的execStartActivity()方法来执行Activity的启动,这个方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//Instrumentation.java
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.setAllowFds(false);
intent.migrateExtraStreamToClipData();
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
}
return null;
}

注意这句:

1
2
3
4
5
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options);

看到上面的ActivityManagerNative了咩,这个是用来跟ActivityManagerService通信的Binder对象。

其中ActivityManagerNativegetDefault()代码如下:

1
2
3
4
5
6
/**
* Retrieve the system's default/global activity manager.
*/
static public IActivityManager getDefault() {
return gDefault.get();
}

getDefault()中获取到一个IActivityManager对象,这个对象的实现类就是ActivityManagerProxy,下面直接看ActivityManagerProxy中的startActivity()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//ActivityManagerProxy.java
public int startActivity(IApplicationThread caller, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeStrongBinder(resultTo);
data.writeString(resultWho);
data.writeInt(requestCode);
data.writeInt(startFlags);
data.writeString(profileFile);
if (profileFd != null) {
data.writeInt(1);
profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
data.writeInt(0);
}
if (options != null) {
data.writeInt(1);
options.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
reply.recycle();
data.recycle();
return result;
}

看到序列化打包数据,你就应该能想到这里的通信机制一二了,注意mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);这里,这是通过AIDL方式与ActivityManagerNative(Stub)进行通信,然后代码就会走到下面的方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//ActivityManagerNative.java
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
case START_ACTIVITY_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
String resultWho = data.readString();
int requestCode = data.readInt();
int startFlags = data.readInt();
String profileFile = data.readString();
ParcelFileDescriptor profileFd = data.readInt() != 0
? data.readFileDescriptor() : null;
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
int result = startActivity(app, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags,
profileFile, profileFd, options);
reply.writeNoException();
reply.writeInt(result);
return true;
}
//省略N行代码

注意这里的:

1
2
3
int result = startActivity(app, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags,
profileFile, profileFd, options);

这个是在系统进程执行的,也就是会调用到ActivityManagerService中的方法,这个方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//ActivityManagerService.java
public final int startActivity(IApplicationThread caller,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags,
String profileFile, ParcelFileDescriptor profileFd, Bundle options) {
return startActivityAsUser(caller, intent, resolvedType, resultTo, resultWho, requestCode,
startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId());
}

public final int startActivityAsUser(IApplicationThread caller,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags,
String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivity");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false, true, "startActivity", null);
return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
null, null, options, userId);
}

关注点落在这句:

1
2
3
return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
null, null, options, userId);

接着,我们去找ActivityStack中的startActivityMayWait(),看看它做了什么:

这个方法和接下来调用的方法都很长,而且很复杂,我直接贴上核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//ActivityStack.java
final int startActivityMayWait(IApplicationThread caller, int callingUid,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, String profileFile,
ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,
Bundle options, int userId) {
//省略N行代码
int res = startActivityLocked(caller, intent, resolvedType,
aInfo, resultTo, resultWho, requestCode, callingPid, callingUid,
startFlags, options, componentSpecified, null);
//省略N行代码
return res;
}
}

顺势,进入startActivityLocked()方法,同样这个方法巨长巨复杂,也直接找出来我们这里要的那部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
//ActivityStack.java
final int startActivityLocked(IApplicationThread caller,
Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo,
String resultWho, int requestCode,
int callingPid, int callingUid, int startFlags, Bundle options,
boolean componentSpecified, ActivityRecord[] outActivity) {
//省略N行代码
int err = ActivityManager.START_SUCCESS;
err = startActivityUncheckedLocked(r, sourceRecord,
startFlags, true, options);
//省略N行代码
return err;
}

可以看到这里调用的startActivityUncheckedLocked()方法,这个方法前面是一些判断Task Flag的代码,直接跳过,因为我们这里找的是Activity最终动起来那部分:

1
2
3
4
5
6
7
8
9
//ActivityStack.java
final int startActivityUncheckedLocked(ActivityRecord r,
ActivityRecord sourceRecord, int startFlags, boolean doResume,
Bundle options) {
//省略N行代码
logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
startActivityLocked(r, newTask, doResume, keepCurTransition, options);
return ActivityManager.START_SUCCESS;
}

接着,又转移到了startActivityLocked()这个方法,而这个方法里面,嗯。。。讲道理这篇文章不是要分析Activity的启动过程,因为这个过程分析十篇也不为多,这里为了实锤DecorView添加进Activity的过程容易咩。。。

1
2
3
4
5
6
7
8
//ActivityStack.java
private final void startActivityLocked(ActivityRecord r, boolean newTask,
boolean doResume, boolean keepCurTransition, Bundle options) {
//省略N行代码
if (doResume) {
resumeTopActivityLocked(null);
}
}

之后到了resumeTopActivityLocked()方法:

1
2
3
4
5
6
7
8
9
10
//ActivityStack.java
final boolean resumeTopActivityLocked(ActivityRecord prev) {
return resumeTopActivityLocked(prev, null);
}

final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
//省略N行代码
startSpecificActivityLocked(next, true, true);
return true;
}

惯例,继续看startSpecificActivityLocked()吧,需要提一下的是,在startSpecificActivityLocked()里面,要分两种情况,一个是app进程未启动并且没向AMS注册,另一个是app进程已启动,这里我们分析的情况肯定是app进程已经启动了的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//ActivityStack.java
private final void startSpecificActivityLocked(ActivityRecord r,
boolean andResume, boolean checkConfig) {
// Is this activity's application already running?
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid);

if (r.launchTime == 0) {
r.launchTime = SystemClock.uptimeMillis();
if (mInitialStartTime == 0) {
mInitialStartTime = r.launchTime;
}
} else if (mInitialStartTime == 0) {
mInitialStartTime = SystemClock.uptimeMillis();
}

if (app != null && app.thread != null) {
try {
app.addPackage(r.info.packageName);
realStartActivityLocked(r, app, andResume, checkConfig);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting activity "
+ r.intent.getComponent().flattenToShortString(), e);
}

// If a dead object exception was thrown -- fall through to
// restart the application.
}

mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
"activity", r.intent.getComponent(), false, false);
}

如上所说,app进程已经启动,那就直接分析realStartActivityLocked(r, app, andResume, checkConfig);:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//ActivityStack.java
final boolean realStartActivityLocked(ActivityRecord r,
ProcessRecord app, boolean andResume, boolean checkConfig)
throws RemoteException {
//省略N行代码
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info,
new Configuration(mService.mConfiguration),
r.compat, r.icicle, results, newIntents, !andResume,
mService.isNextTransitionForward(), profileFile, profileFd,
profileAutoStop);
//省略N行代码
return true;
}

可以看到在上面方法里面做的工作就是通知app的进程启动Activity,这个启动过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//ActivityThread.java
// we use token to identify this activity without having to send the
// activity itself back to the activity manager. (matters more with ipc)
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
ActivityClientRecord r = new ActivityClientRecord();

r.token = token;
r.ident = ident;
r.intent = intent;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;

r.pendingResults = pendingResults;
r.pendingIntents = pendingNewIntents;

r.startsNotResumed = notResumed;
r.isForward = isForward;

r.profileFile = profileName;
r.profileFd = profileFd;
r.autoStopProfiler = autoStopProfiler;

updatePendingConfiguration(curConfig);

queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
}

ActivityThread中的scheduleLaunchActivity()方法前面做的一些工作是保存AMS携带过来的参数信息,之后通过queueOrSendMessage()来向主线程发送消息,这个消息的处理情况如下:

1
2
3
4
5
6
7
8
9
10
//ActivityThread.java
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;

r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;

下面我们把上面代码中的handleLaunchActivity(r, null);揪出来,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//ActivityThread.java
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();

if (r.profileFd != null) {
mProfiler.setProfiler(r.profileFile, r.profileFd);
mProfiler.startProfiling();
mProfiler.autoStopProfiler = r.autoStopProfiler;
}

// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);

if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
Activity a = performLaunchActivity(r, customIntent);

if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);

if (!r.activity.mFinished && r.startsNotResumed) {
// The activity manager actually wants this one to start out
// paused, because it needs to be visible but isn't in the
// foreground. We accomplish this by going through the
// normal startup (because activities expect to go through
// onResume() the first time they run, before their window
// is displayed), and then pausing it. However, in this case
// we do -not- need to do the full pause cycle (of freezing
// and such) because the activity manager assumes it can just
// retain the current state it has.
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
// We need to keep around the original state, in case
// we need to be created again. But we only do this
// for pre-Honeycomb apps, which always save their state
// when pausing, so we can not have them save their state
// when restarting from a paused state. For HC and later,
// we want to (and can) let the state be saved as the normal
// part of stopping the activity.
if (r.isPreHoneycomb()) {
r.state = oldState;
}
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onPause()");
}

} catch (SuperNotCalledException e) {
throw e;

} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to pause activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
}
}
r.paused = true;
}
} else {
// If there was an error, for any reason, tell the activity
// manager to stop us.
try {
ActivityManagerNative.getDefault()
.finishActivity(r.token, Activity.RESULT_CANCELED, null);
} catch (RemoteException ex) {
// Ignore
}
}
}

上面方法看着很长,其实只有两个比较重要的点,一个是Activity a = performLaunchActivity(r, customIntent);这个方法内部会通过Java反射机制创建一个目标Activity,然后调用Activity的onCreateonStart方法,但这个不是我们在这里关心的,所以略过这个方法。
另一点是:

1
2
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);

handleResumeActivity这个方法进去后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//ActivityThread.java
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();

ActivityClientRecord r = performResumeActivity(token, clearHide);

if (r != null) {
final Activity a = r.activity;

if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);

final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}

//省略N行代码
}

那么,注意这一块:

1
2
3
4
5
6
7
8
9
10
11
12
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}

上面这一块就实锤了,我前面所说的:

源码里提到叫我们看ViewonDetachedFromWindow()方法,但有Android开发基础的应该都知道,Activity虽然继承的Context,看似跟view无关,其实还有个WindowManager,启动Activity的时候,会将DecorView添加到这里面

那么!!!!!!

好,绕了地球两圈只为证明这句话,之后我们就去看ViewonDetachedFromWindow()方法。

ViewonDetachedFromWindow()方法

前面终于证明了类似先有鸡还是先有蛋的问题,终于回到熟悉的onDetachedFromWindow(),下面先来看看View里面的这个方法做了什么骚操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//View.java
protected void onDetachedFromWindow() {
mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;

removeUnsetPressCallback();
removeLongPressCallback();
removePerformClickCallback();
removeSendViewScrolledAccessibilityEventCallback();

destroyDrawingCache();

destroyLayer(false);

if (mAttachInfo != null) {
if (mDisplayList != null) {
mAttachInfo.mViewRootImpl.enqueueDisplayList(mDisplayList);
}
mAttachInfo.mViewRootImpl.cancelInvalidate(this);
} else {
// Should never happen
clearDisplayList();
}

mCurrentAnimation = null;

resetRtlProperties();
onRtlPropertiesChanged(LAYOUT_DIRECTION_DEFAULT);
resetAccessibilityStateChanged();
}

当然,这个方法有什么骚操作不是我们关心的,我们关心的是这个方法在什么时候被调用。

这个方法的调用是在ViewdespatchedFromWindow()中进行的,这个方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//View.java
void dispatchDetachedFromWindow() {
AttachInfo info = mAttachInfo;
if (info != null) {
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(GONE);
}
}

onDetachedFromWindow();

ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewDetachedFromWindow(this);
}
}

if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
mAttachInfo.mScrollContainers.remove(this);
mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
}

mAttachInfo = null;
}

那么,接下来我们要找在哪里调用了dispatchDetachedFromWindow()这个方法,顺着方法找下去容易,逆着来就很难,首先你得知道在哪里调用了View这个类,然后哪里再调用了这个类对应对象的这个方法。虽然这些在Android Studio里面是按住command + 鼠标左键就能看到的事情,但毕竟现在是在androidxref上阅读源码。
用你们的android开发经验判断,detached类的方法,应该在Activity的onDestroy生命周期中调用到,那我们直接找Activity生命周期onDestroy看看吧(其实我已经知道了哇哈哈哈哈)。

Activity的onDestroy()及它之前的那些骚操作

Activity的onDestroy()其实只是一个类似通知和做最后清理对话框、游标等的回调,这里可以抛弃那些清理对话框等过程,因为我们需要找到这篇文章想要知道的dispatchDetachedFromWindow()调用栈。
我们都知道在Application中使用ActivityLifecycleCallbacks可以观察Activity的生命周期,那么这个onDestroy()其实只是一个告知该回调说:我死了。

所以我们要找onDestroy()之前做了什么。

找onDestroy()之前,很自然可以想到一个入手点——finish()

没错,这个就是我们主动调用Activity中的关闭的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//Activity.java
/**
* Call this when your activity is done and should be closed. The
* ActivityResult is propagated back to whoever launched you via
* onActivityResult().
*/
public void finish() {
if (mParent == null) {
int resultCode;
Intent resultData;
synchronized (this) {
resultCode = mResultCode;
resultData = mResultData;
}
if (false) Log.v(TAG, "Finishing self: token=" + mToken);
try {
if (resultData != null) {
resultData.setAllowFds(false);
}
if (ActivityManagerNative.getDefault()
.finishActivity(mToken, resultCode, resultData)) {
mFinished = true;
}
} catch (RemoteException e) {
// Empty
}
} else {
mParent.finishFromChild(this);
}
}

(这……确实跟Android6.0的方法有些许区别的,因为6.0源码的finish还有一个boolean参数是否涉及相关联task的)
一般情况下,mParent是为空的,所以我们直接关注下面这行代码:

1
2
3
4
if (ActivityManagerNative.getDefault()
.finishActivity(mToken, resultCode, resultData)) {
mFinished = true;
}

前面分析Activity启动过程的时候,说过ActivityManagerNative.getDefault()这个东西,通过这个方法会得到一个ActivityManagerProxy,那么我们去看看ActivityManagerProxy里面的finishActivity方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//ActivityManagerNative.java
public boolean finishActivity(IBinder token, int resultCode, Intent resultData)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
data.writeInt(resultCode);
if (resultData != null) {
data.writeInt(1);
resultData.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
mRemote.transact(FINISH_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
data.recycle();
reply.recycle();
return res;
}

看到mRemote.transact(FINISH_ACTIVITY_TRANSACTION, data, reply, 0);,直接找onTransact方法中对应的case吧(为啥?请看前面Activity启动流程分析里提到的ActivityManagerNative(Stub)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//ActivityManagerNative.java
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
//省略N行代码
case FINISH_ACTIVITY_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
Intent resultData = null;
int resultCode = data.readInt();
if (data.readInt() != 0) {
resultData = Intent.CREATOR.createFromParcel(data);
}
boolean res = finishActivity(token, resultCode, resultData);//注意看这里
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
}
//省略N行代码
}
}

上面的我们只需要关注boolean res = finishActivity(token, resultCode, resultData);这里,所以接下来去把ActivityManagerService里的这个方法揪出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//ActivityManagerService.java
/**
* This is the internal entry point for handling Activity.finish().
*
* @param token The Binder token referencing the Activity we want to finish.
* @param resultCode Result code, if any, from this Activity.
* @param resultData Result data (Intent), if any, from this Activity.
*
* @return Returns true if the activity successfully finished, or false if it is still running.
*/
public final boolean finishActivity(IBinder token, int resultCode, Intent resultData) {
// Refuse possible leaked file descriptors
if (resultData != null && resultData.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}

synchronized(this) {
if (mController != null) {
// Find the first activity that is not finishing.
ActivityRecord next = mMainStack.topRunningActivityLocked(token, 0);
if (next != null) {
// ask watcher if this is allowed
boolean resumeOK = true;
try {
resumeOK = mController.activityResuming(next.packageName);
} catch (RemoteException e) {
mController = null;
}

if (!resumeOK) {
return false;
}
}
}
final long origId = Binder.clearCallingIdentity();
boolean res = mMainStack.requestFinishActivityLocked(token, resultCode,
resultData, "app-request", true);//注意看这里
Binder.restoreCallingIdentity(origId);
return res;
}
}

可以看到,上面的代码里会去调用ActivityTask中的requestFinishActivityLocked()方法,而这个方法会继续调用其内部的带上ActivityRecord参数的finishActivityLocked()方法,这两个方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//ActivityTask.java    
/**
* @return Returns true if the activity is being finished, false if for
* some reason it is being left as-is.
*/
final boolean requestFinishActivityLocked(IBinder token, int resultCode,
Intent resultData, String reason, boolean oomAdj) {
int index = indexOfTokenLocked(token);
if (DEBUG_RESULTS || DEBUG_STATES) Slog.v(
TAG, "Finishing activity @" + index + ": token=" + token
+ ", result=" + resultCode + ", data=" + resultData
+ ", reason=" + reason);
if (index < 0) {
return false;
}
ActivityRecord r = mHistory.get(index);

finishActivityLocked(r, index, resultCode, resultData, reason, oomAdj);
return true;
}
/**
* @return Returns true if this activity has been removed from the history
* list, or false if it is still in the list and will be removed later.
*/
final boolean finishActivityLocked(ActivityRecord r, int index,
int resultCode, Intent resultData, String reason, boolean oomAdj) {
return finishActivityLocked(r, index, resultCode, resultData, reason, false, oomAdj);//这里继续调用下面的方法
}

/**
* @return Returns true if this activity has been removed from the history
* list, or false if it is still in the list and will be removed later.
*/
final boolean finishActivityLocked(ActivityRecord r, int index, int resultCode,
Intent resultData, String reason, boolean immediate, boolean oomAdj) {
if (r.finishing) {
Slog.w(TAG, "Duplicate finish request for " + r);
return false;
}

r.makeFinishing();
EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY,
r.userId, System.identityHashCode(r),
r.task.taskId, r.shortComponentName, reason);
if (index < (mHistory.size()-1)) {
ActivityRecord next = mHistory.get(index+1);
if (next.task == r.task) {
if (r.frontOfTask) {
// The next activity is now the front of the task.
next.frontOfTask = true;
}
if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
// If the caller asked that this activity (and all above it)
// be cleared when the task is reset, don't lose that information,
// but propagate it up to the next activity.
next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
}
}
}

r.pauseKeyDispatchingLocked();
if (mMainStack) {
if (mService.mFocusedActivity == r) {
mService.setFocusedActivityLocked(topRunningActivityLocked(null));
}
}

finishActivityResultsLocked(r, resultCode, resultData);

if (mService.mPendingThumbnails.size() > 0) {
// There are clients waiting to receive thumbnails so, in case
// this is an activity that someone is waiting for, add it
// to the pending list so we can correctly update the clients.
mService.mCancelledThumbnails.add(r);
}

if (immediate) {
return finishCurrentActivityLocked(r, index,
FINISH_IMMEDIATELY, oomAdj) == null;
} else if (mResumedActivity == r) {
boolean endTask = index <= 0
|| (mHistory.get(index-1)).task != r.task;
if (DEBUG_TRANSITION) Slog.v(TAG,
"Prepare close transition: finishing " + r);
mService.mWindowManager.prepareAppTransition(endTask
? WindowManagerPolicy.TRANSIT_TASK_CLOSE
: WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE, false);

// Tell window manager to prepare for this one to be removed.
mService.mWindowManager.setAppVisibility(r.appToken, false);

if (mPausingActivity == null) {
if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r);
if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false");
startPausingLocked(false, false);
}

} else if (r.state != ActivityState.PAUSING) {
// If the activity is PAUSING, we will complete the finish once
// it is done pausing; else we can just directly finish it here.
if (DEBUG_PAUSE) Slog.v(TAG, "Finish not pausing: " + r);
return finishCurrentActivityLocked(r, index,
FINISH_AFTER_PAUSE, oomAdj) == null;//注意看这里
} else {
if (DEBUG_PAUSE) Slog.v(TAG, "Finish waiting for pause of: " + r);
}

return false;
}

上面代码可以看到下面又走到了finishCurrentActivityLocked这个方法,但前面Activity启动流程告诉我们,肯定没那么简单。
其实真没那么简单,接下来在finishCurrentActivityLocked里面又调用了内部实现带有index的方法,下面贴出这两个方法的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//ActivityTask.java
private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r,
int mode, boolean oomAdj) {
final int index = indexOfActivityLocked(r);
if (index < 0) {
return null;
}

return finishCurrentActivityLocked(r, index, mode, oomAdj);
}

private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r,
int index, int mode, boolean oomAdj) {
// First things first: if this activity is currently visible,
// and the resumed activity is not yet visible, then hold off on
// finishing until the resumed one becomes visible.
if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) {
if (!mStoppingActivities.contains(r)) {
mStoppingActivities.add(r);
if (mStoppingActivities.size() > 3) {
// If we already have a few activities waiting to stop,
// then give up on things going idle and start clearing
// them out.
scheduleIdleLocked();
} else {
checkReadyForSleepLocked();
}
}
if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPING: " + r
+ " (finish requested)");
r.state = ActivityState.STOPPING;
if (oomAdj) {
mService.updateOomAdjLocked();
}
return r;
}

// make sure the record is cleaned out of other places.
mStoppingActivities.remove(r);
mGoingToSleepActivities.remove(r);
mWaitingVisibleActivities.remove(r);
if (mResumedActivity == r) {
mResumedActivity = null;
}
final ActivityState prevState = r.state;
if (DEBUG_STATES) Slog.v(TAG, "Moving to FINISHING: " + r);
r.state = ActivityState.FINISHING;

if (mode == FINISH_IMMEDIATELY
|| prevState == ActivityState.STOPPED
|| prevState == ActivityState.INITIALIZING) {
// If this activity is already stopped, we can just finish
// it right now.
boolean activityRemoved = destroyActivityLocked(r, true,
oomAdj, "finish-imm");//注意看这里
if (activityRemoved) {
resumeTopActivityLocked(null);
}
return activityRemoved ? null : r;
} else {
// Need to go through the full pause cycle to get this
// activity into the stopped state and then finish it.
if (localLOGV) Slog.v(TAG, "Enqueueing pending finish: " + r);
mFinishingActivities.add(r);
resumeTopActivityLocked(null);
}
return r;
}

由上面的代码可以看到,下面又去到了destroyActivityLocked,接下来去到这个方法里面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//ActivityTask.java
final boolean destroyActivityLocked(ActivityRecord r,
boolean removeFromApp, boolean oomAdj, String reason) {
//省略N行代码
try {
if (DEBUG_SWITCH) Slog.i(TAG, "Destroying: " + r);
r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,
r.configChangeFlags);//注意看这里
} catch (Exception e) {
// We can just ignore exceptions here... if the process
// has crashed, our death notification will clean things
// up.
//Slog.w(TAG, "Exception thrown during finish", e);
if (r.finishing) {
removeActivityFromHistoryLocked(r);
removedFromHistory = true;
skipDestroy = true;
}
}
//省略N行代码
return removedFromHistory;
}

可能你看到了scheduleDestroyActivity()似曾相识,再想想看想起来什么?
没错就是前面我们说Activity启动流程时有提到的scheduleLaunchActivity()类似(好生硬的转折哇哈哈哈哈),那这里就不重复说,直接单刀直入,找到DESTROY_ACTIVITY这个case:

1
2
3
4
5
6
case DESTROY_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
msg.arg2, false);//注意看这里
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;

之后我们找到handleDestroyActivity()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//ActivityThread.java
private void handleDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
ActivityClientRecord r = performDestroyActivity(token, finishing,
configChanges, getNonConfigInstance);
if (r != null) {
cleanUpPendingRemoveWindows(r);
WindowManager wm = r.activity.getWindowManager();
View v = r.activity.mDecor;
if (v != null) {
if (r.activity.mVisibleFromServer) {
mNumVisibleActivities--;
}
IBinder wtoken = v.getWindowToken();
if (r.activity.mWindowAdded) {
if (r.onlyLocalRequest) {
// Hold off on removing this until the new activity's
// window is being added.
r.mPendingRemoveWindow = v;
r.mPendingRemoveWindowManager = wm;
} else {
wm.removeViewImmediate(v);//注意看这里
}
}
if (wtoken != null && r.mPendingRemoveWindow == null) {
WindowManagerGlobal.getInstance().closeAll(wtoken,
r.activity.getClass().getName(), "Activity");
}
r.activity.mDecor = null;
}
//省略N行代码
}

上面代码中通过performDestroyActivity方法去找到当前需要destroy的ActivityClientRecord,清理mPendingRemoveWindowManager中的mPendingRemoveWindow对象,之后获取WindowManager和对应的DecorView,onlyLocalRequest一般跟RelaunchActivity有关,所以这里正常情况下是直接走到了 wm.removeViewImmediate(v);

下面我们再进去wm.removeViewImmediate(v);方法:

1
2
3
4
5
//WindowManagerImpl.java
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}

WindowManagerImpl里面的mGlobal对应的是WindowManagerGlobal对象,下面再进去WindowManagerGlobal查看removeView()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//WindowManagerGlobal.java
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}

synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = removeViewLocked(index, immediate);//注意看这里
if (curView == view) {
return;
}

throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}

接着会调用removeViewLocked()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//WindowManagerGlobal.java
private View removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots[index];
View view = root.getView();

if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
if (imm != null) {
imm.windowDismissed(mViews[index].getWindowToken());
}
}
root.die(immediate);//注意看这里

final int count = mViews.length;

// remove it from the list
View[] tmpViews = new View[count-1];
removeItem(tmpViews, mViews, index);
mViews = tmpViews;

ViewRootImpl[] tmpRoots = new ViewRootImpl[count-1];
removeItem(tmpRoots, mRoots, index);
mRoots = tmpRoots;

WindowManager.LayoutParams[] tmpParams
= new WindowManager.LayoutParams[count-1];
removeItem(tmpParams, mParams, index);
mParams = tmpParams;

if (view != null) {
view.assignParent(null);
// func doesn't allow null... does it matter if we clear them?
//view.setLayoutParams(null);
}
return view;
}

上面代码中会调用ViewRootImpl中的die()方法,这个方法里面再调用doDie()方法(死和作死,嗯。。。):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//ViewRootImpl.java
public void die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
doDie();
} else {
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
}
}

void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mAdded) {
dispatchDetachedFromWindow();//注意看这里
}

if (mAdded && !mFirst) {
destroyHardwareRenderer();

if (mView != null) {
int viewVisibility = mView.getVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
if (mWindowAttributesChanged || viewVisibilityChanged) {
// If layout params have been changed, first give them
// to the window manager to make sure it has the correct
// animation info.
try {
if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
}
}

mSurface.release();
}
}

mAdded = false;
}
}

而在doDie()方法中,有一句代码特别明显,那就是dispatchDetachedFromWindow();,兜兜转转,终于梳理完了,正常情况下dispatchDetachedFromWindow()调用栈就很直白展现在我们面前了。

总结

好,到这里,我们再回头看一下,为了了解我们在什么情况下回调用onDetachedFromWindow(),去梳理了一下Activity的finish()方法的流程,但是有一个问题还是有疑惑的,那就是正常finish掉Activity的话,也不会出现loadUrl时发现方法中引用的mWebviewCore为null导致的NullPointerException,那么什么时候才会导致这个异常的发生?

回过头看看一开始说的:

这个异常出现频率非常低,异常涉及到LyricFragment,而在这个fragment中的这一行执行了loadUrl的操作,这个操作是在onPageFinished()回调中进行的,目的是加载完成一个网页后加载一段内置的JS方法。

其实我打算说”还有下半场“,但是我用印象笔记Mac beta版本、markdown写这篇东西的时候写到这已经非常十分特别超级卡,而且后续如果从WebViewClient讲开来可能还有差不多到这里的大半篇幅,所以就简单梳理一下好了,有兴趣的也可以按照下面梳理的顺序去阅读源码,将会(应该会吧哇哈哈哈哈哈)大大提升源码的阅读效率。

在第一次进行loadUrl()操作的时候,进过的各个类的路径如下(C++层面使用Native表示):

WebView->WebViewClassic->WebCore->WebCore.EventHub->BrowserFrame->Native

onPageFinish()回调的时候如下:

Native->BrowserFrame->CallbackProxy->CallbackProxy.Handler->WebViewClassic->WebViewClient

最后,因为这里没有详细去一点点从源码分析,只能说是猜想:因为webview的loadurl使用的handler机制,有可能在其负载的Activity回调onDetachedFromWindow()后消息才到达,之后回调onPageFinish(),导致此时空指针异常,因为是时机导致的异常,所以实际上出现的频率非常低。大家如果有证明了的或者别的任何想法也欢迎联系我一起讨论。

文章作者: Kevin Wu
文章链接: https://kevinwu.cn/p/10716945/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 KevinWu.CN
支付宝打赏