相信Android開發都遇到過這樣一個報錯信息
04-09 15:55:08.165: E/AndroidRuntime(3403): android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
解決辦法也很簡單,就是在非Activity的Context調用startActivity方法時,Intent添加一個FLAG_ACTIVITY_NEW_TASK的flag,代碼如下
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
但是細心的同學會發現Android7.0及以上的機型即使沒有添加以上代碼,也不會崩潰。這是怎麼回事呢?
這就涉及到activity的startActivity和context的startActivity方法之間的區別,Activity也是Context的子類,但是Activity本身實現了startActivity方法,所以兩個是不同的。
activity的startActivity方法最終會調用Activity類的startActivityForResult方法,代碼如下
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
// If this start is requesting a result, we can avoid making
// the activity visible until the result is received. Setting
// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
// activity hidden during this time, to avoid flickering.
// This can only be done when a result is requested because
// that guarantees we will get information back when the
// activity is finished, no matter what happens to it.
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
// TODO Consider clearing/flushing other event sources and events for child windows.
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
context的 startActivity方法最終會調用ContextImpl的startActivity方法,代碼如下
@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);
}
7.0及以下
@Override
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.
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?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
從上面代碼可以看出,Activity的startActivity方法是不會拋出文章開始說的那個錯的;問題集中在非Activity的Context調用startActivity方法,在7.0之前如果Intent沒有添加flag:FLAG_ACTIVITY_NEW_TASK,程序是必崩的,7.0及以上如果調用startActivity(intent)方法,是不會崩的,因爲此時options爲空。一般爲了兼容7.0以下版本,我們還是得添加flag的。
startActivity(intent, null);
注意7.0增加了兩個判斷條件
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?");
}
到這裏基本上文章就該結束了,但是我們項目在線上還是遇到文章開始說的那個問題,有一些崩潰,都是7.0以下的機型。經過一番尋找,終於定位到有問題的代碼,原因是我們用到了Intent的一個方法createChooser(Intent target, CharSequence title)
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, text);
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
shareIntent.setType("text/plain");
Utils.getContext().startActivity(Intent.createChooser(shareIntent, "分享到"));
從代碼上看到我們明明給Intent添加了flag,還是會崩潰
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
我們跟進Intent.createChooser方法裏看看
public static Intent createChooser(Intent target, CharSequence title, IntentSender sender) {
Intent intent = new Intent(ACTION_CHOOSER);
intent.putExtra(EXTRA_INTENT, target);
if (title != null) {
intent.putExtra(EXTRA_TITLE, title);
}
if (sender != null) {
intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender);
}
// Migrate any clip data and flags from target.
int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
| FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| FLAG_GRANT_PREFIX_URI_PERMISSION);
if (permFlags != 0) {
ClipData targetClipData = target.getClipData();
if (targetClipData == null && target.getData() != null) {
ClipData.Item item = new ClipData.Item(target.getData());
String[] mimeTypes;
if (target.getType() != null) {
mimeTypes = new String[] { target.getType() };
} else {
mimeTypes = new String[] { };
}
targetClipData = new ClipData(null, mimeTypes, item);
}
if (targetClipData != null) {
intent.setClipData(targetClipData);
intent.addFlags(permFlags);
}
}
return intent;
}
首先這個方法先把原Intent存到新Intent的extra上,
intent.putExtra(EXTRA_INTENT, target);
然後permFlags值爲0,就這樣,新Intent的flag就沒了,所以返回來的Intent的flag值爲0,雖然extra裏的Intent的flag值爲Intent.FLAG_ACTIVITY_NEW_TASK,但是沒有,取flag值時不是從extra裏的Intent取值。
int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
| FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| FLAG_GRANT_PREFIX_URI_PERMISSION);
有點坑吧,所以如果使用了Intent.createChooser()方法後還得加上addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
Utils.getContext().startActivity(Intent.createChooser(shareIntent, "分享到").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
建議以後如果有非Activity的Context調用startActivity()方法,一定要用7.0以下的機型測試一下,我們手上的測試大部分是7.0的,加上那段代碼的使用場景很少,所以導致了線上出現這個bug。