異常資訊
AndroidRuntime: FATAL EXCEPTION: main Process: com.aspook.contexttest,
PID: 22578
android.util.AndroidRuntimeException: Calling startActivity() from
outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK
flag. Is this really what you want?
at android.app.ContextImpl.startActivity(ContextImpl.java:672)
at android.app.ContextImpl.startActivity(ContextImpl.java:659)
at
android.content.ContextWrapper.startActivity(ContextWrapper.java:331)
at android.widget.TextView.shareSelectedText(TextView.java:9493)
at android.widget.TextView.onTextContextMenuItem(TextView.java:9211)
at
android.widget.Editor TextActionModeCallback.onActionItemClicked(Editor.java:3249)atcom.android.internal.policy.PhoneWindow DecorView ActionModeCallback2Wrapper.onActionItemClicked(PhoneWindow.java:3540)atcom.android.internal.view.FloatingActionMode 3.onMenuItemSelected(FloatingActionMode.java:85)
at
com.android.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:761)
at
com.android.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:152)
at
com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:904)
at
com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:894)
at
com.android.internal.view.FloatingActionMode 4.onMenuItemClick(FloatingActionMode.java:111)atcom.android.internal.widget.FloatingToolbar FloatingToolbarMainPanel 1.onClick(FloatingToolbar.java:1015)atandroid.view.View.performClick(View.java:5204)atandroid.view.View PerformClick.run(View.java:21155)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5422)
at java.lang.reflect.Method.invoke(Native Method)
at
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
上面是整個異常的堆棧資訊,我們先來看主要的部分:
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的上下文去startActivity且沒有添加FLAG_ACTIVITY_NEW_TASK這個flag。而我遇到的這個異常卻不是由于人為使用錯誤的Context去startActivity造成的,而是系統自己調用startActivity引起的。
原因分析
其實從用代碼去啟動Activity的層面上講,自己寫的話不會犯上述錯誤。情況是這樣的,對于一個EditText,長按其内容可以彈出系統的上下文菜單,支援複制、全選等,然後在新的系統裡面開始支援分享了。當選中一些文本,點選分享的時候,本應該調起系統的分享界面,而這時候就報此異常了,異常堆棧資訊也指出了該事實:
at android.widget.TextView.shareSelectedText(TextView.java:9493)
由于老版本的Android不支援分享功能,是以一直沒有發現該異常,最根本原因還是由于代碼寫的有問題。
下面就來複現一下這個異常。
- 界面很簡單,就一個EditText
- 界面布局利用LayoutInflater引入,代碼如下
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LayoutInflater mLayoutInflater = LayoutInflater.from(getApplicationContext()); View root = mLayoutInflater.inflate(R.layout.activity_second, null); setContentView(root); }
最終效果如下圖:
上述代碼乍一看沒什麼問題,如果不使用長按分享功能,也一切正常。但選中文本後進行分享,當調起系統分享界面時就發生了崩潰。我們可能有以下經驗:
在Activity中通過startActivity(intent)可以正常啟動另一Activity,而通過getApplicationContext().startActivity(intent)則會報上述相同錯誤(如果沒設定FLAG_ACTIVITY_NEW_TASK的話)。是以可以判斷原因必定是由于使用了錯誤的Context引起。仔細檢查上述代碼,并沒有顯式調用startActivity的代碼,隻有
LayoutInflater mLayoutInflater = LayoutInflater.from(getApplicationContext());
這一句引入了Context對象,是以可以推斷是通過LayoutInflater引入布局時錯用了Application的Context,而這裡應該使用Activity的上下文對象。
将上面錯誤代碼改寫如下後,果然不再報錯。
LayoutInflater mLayoutInflater = LayoutInflater.from(this);
關于Context的分析及使用,網上已有太多文章,這裡就不再分析,請參考Context都沒弄明白,還怎麼做Android開發?或者自己去看源碼,下面也是來自這篇文章的一張總結圖檔:
刨根問底
既然我們知道原因是由于LayoutInflater時傳入了錯誤的Context,那麼LayoutInflater中的Context是怎樣傳到TextView(EditView繼承自TextView)中的呢?
從錯誤日志資訊
at android.widget.TextView.shareSelectedText(TextView.java:9493)
定位到TextView的源碼:
private void shareSelectedText() {
String selectedText = getSelectedText();
if (selectedText != null && !selectedText.isEmpty()) {
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
getContext().startActivity(Intent.createChooser(sharingIntent, null));
stopTextActionMode();
}
}
倒數第二行
getContext().startActivity(Intent.createChooser(sharingIntent, null));
就是這裡調用的系統分享界面。
接着看getContext(),定位到View.java源碼中
/**
* Returns the context the view is running in, through which it can
* access the current theme, resources, etc.
*
* @return The view's Context.
*/
@ViewDebug.CapturedViewProperty
public final Context getContext() {
return mContext;
}
繼續跟蹤發現,mContext是在View的構造函數中指派的:
/**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
*/
public View(Context context) {
mContext = context;
……
}
那麼我們接下來就是要分析LayoutInflater生成View的過程了,主要代碼都在LayoutInflater.java中,邏輯細節比較繁瑣,但最終是通過反射調用View的構造函數建立一個View對象,是以Context就傳到了View中。如果對LayoutInflater生成View的流程感興趣,可以參考LayoutInflater&LayoutInflaterCompat源碼解析,這裡不再分析,對于LayoutInflater生成View的需求,建議使用Activity的上下文對象,除了可以避免上述隐藏的崩潰外,還可以讓視圖的主題跟Activity保持一緻,如果使用Application的上下文,則視圖的主題為應用的主題,可能會造成不一緻。