谷歌Android開發人員的手誤bug

我們啟動Activity,都會用到Intent

Intent需要傳入Context參數,通常我們在Activity中使用Intent時,都會傳入Activity的實例作為Context。但由於考慮到內存洩漏的風險以及在非Acitivty中啟動Activity(比如Service中),大都提倡使用ApplicationContext,所以很多同學就把Activity類型的Context換成了ApplicationContext,這時就發現,突然不能正常啟動Activity了,並且收到如下的錯誤。

<code>Calling startActivity() from outside of an Activity  
context requires the FLAG_ACTIVITY_NEW_TASK flag.
Is this really what you want?/<code>

意思就是,啟動外部的Activity,需要FLAG_ACTIVITY_NEW_TASK標識。

但是,又有很多同學說,我這裡不加這個標識,不會報錯,可以正常運行。

說的都對,關鍵問題是Google的安卓工程師出了錯誤,在7.0(Android N)之前,不加這個標識是不行的。我們來看看代碼是如何寫的。

getApplicationContext().startActivity執行的是ContextWrapper的方法。

<code>    public void startActivity(Intent intent) {
mBase.startActivity(intent);
}/<code>

mBaseContextImpl,所以startActivityContextImplstartActivity

我們來看7.0之前的代碼。如果沒有FLAG_ACTIVITY_NEW_TASK,就會報錯。

<code>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?");
}
......

}/<code>

我們來看看7.0以後的代碼。

<code>public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();

// 這裡有判斷
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
......
}/<code>

發現有什麼不同了嗎,7.0以後多了options!=null的判斷,而實際應用中,這個條件普遍的被滿足了,所以FLAG_ACTIVITY_NEW_TASK約束就不那麼有效了,這就導致我們startActivity總能成功。

安卓工程師們貌似發現了這個問題,所以在9.0(API28)的時候,開始修復這個問題。看9.0代碼。

<code>public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
//開發人員很有意思,說明了情況
// Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
// generally not allowed, except if the caller specifies the task id the activity should
// be launched in. A bug was existed between N and O-MR1 which allowed this to work. We

// maintain this for backwards compatibility.
final int targetSdkVersion = getApplicationInfo().targetSdkVersion;

if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& (targetSdkVersion < Build.VERSION_CODES.N
|| targetSdkVersion >= Build.VERSION_CODES.P)
&& (options == null
|| ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
......
}/<code>

開發人員在註釋中做了說明,承認在7.0,8.0版本中存在bug,但是呢為了兼容這個問題,又不能一棍子打死,所以在這裡運行7.0和8.0版本繼續使用原來的邏輯,所以我沒看到,判斷條件中加入了對版本的判斷。

大牛不是神,和我們一樣。


分享到:


相關文章: