Android異常之Service啟動Activity
在Activity中其中startActivity這個大家應該是非常熟悉的在塔;那么從service里面調(diào)用startActivity話郁季,會怎么樣呢芒率?
會出現(xiàn)下面的異常:
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity??context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
也就是在service里面啟動Activity的話卡啰,必須添加FLAG_ACTIVITY_NEW_TASK flag。
那么下面的話餐曼,我們將從下面幾個方面分析這個問題篮灼。
1.? ? 這個異常怎么產(chǎn)生的?
2.? ? 解決這個異常后會出現(xiàn)問題?
3.? ? 為什么Activity.startActivity()不會出現(xiàn)這個問題疾渴?
4.? ? Android 為什么要這么設計千贯?
下面,一一分析
一.? ? Context的繼承關系圖
首先來看一張圖搞坝, 這張圖表示了Context里面的基本繼承關系搔谴。
1.? ? 最上面的是Context.java,它其實是一個抽象類桩撮,它有兩個重要的子類ContextImpl和ContextWrapper
2.? ? ContextImpl敦第,是Context功能實現(xiàn)的主要類,
3.? ? ContextWrapper店量,顧名思義芜果,它只是一個包裝而已。主要功能實現(xiàn)都是通過調(diào)用ContextImpl去實現(xiàn)的融师。
4.? ? ContextThemeWrapper右钾,包括一些主題的包裝,由于Service沒有主題旱爆,所以直接繼承ContextWrapper舀射;但是Activity就需要繼承ContextThemeWrapper
二.? ? 異常如何產(chǎn)生
1.? ? 找到報錯的代碼
文件:
frameworks\base\core\java\android\app\ContextImpl.java
代碼: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);
}在下面的if條件判斷,如果不包含F(xiàn)LAG_ACTIVITY_NEW_TASK就會報這個錯誤if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
...
}那么service.startActivity(Intent intent)怎么會調(diào)用這里來的呢怀伦?
要回答這個問題脆烟,我們分析下service.startActivity()做了什么,其實房待,service.startActivity調(diào)用的是ContextWrapper.startActivity()邢羔,因為service繼承自ContextWrapper
2.??代碼文件
frameworks\base\core\java\android\content\ContextWrapper.java
代碼:public void startActivity(Intent intent, Bundle options) {
mBase.startActivity(intent, options);
}ContextWrapper.startActivity的話,是直接調(diào)用的
mBase.startActivity(intent, options);
那么這個mBase是什么呢吴攒?又是什么時候賦值的呢张抄?其實mBase是在ContextWrapper的attachBaseContext的時候初始化的。如下:protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}那又是誰調(diào)用attachBaseContext的呢洼怔?
是在service創(chuàng)建的時候署惯,在ActivityThread里面調(diào)用,如下:
3. 代碼文件
frameworks\base\core\java\android\app\ActivityThread.java
代碼:private void handleCreateService(CreateServiceData data) {
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
....
}
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
service.onCreate();
mServices.put(data.token, service);
....
} catch (Exception e) {
...
}
}抽出主要代碼分析ActivityThread. handleCreateService()方法里面主要做這幾件事
3.1 通過pms找到要啟動的Service配置信息镣隶,然后通過反射生成Service對象
3.2 創(chuàng)建ContextImpl對象极谊,然后調(diào)用service.attach方法設置到ContextWrapper.java的mBaseContext變量里面。
那現(xiàn)在就明白了安岂,service.startActivity()->ContextWrapper.startActivity()->ContextImpl.startActivity()
然后再ContextImpl.startActivity里面會檢查Intent的參數(shù)是否包含F(xiàn)LAG_ACTIVITY_NEW_TASK轻猖,從而出現(xiàn)這個異常。
三.? ? 解決這個異常后會出現(xiàn)問題域那?
有些同學就會說了咙边,在Service里面啟動Activity必須要有FLAG_ACTIVITY_NEW_TASK參數(shù),那么我們添加上不就可以了?如下:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
那么這樣會帶來什么問題呢败许?
這樣帶來的問題就是在最近任務列表里面會出現(xiàn)兩個相同的應用程序王带,比如你是在電話本里面啟動的,那么最近任務列表就會出現(xiàn)兩個電話本市殷;因為有兩個Task嘛愕撰!
那怎么解決呢?其實也非常好解決醋寝,只要在新的Task里面的Activity里面配置android:excludeFromRecents="true"就可以了搞挣。表示這個Activity不會顯示在最近列表里面。
四.? ? Activity.startActivity()為什么不出現(xiàn)這個異常呢音羞?
要回答這個問題囱桨,需要看下Activity.startActivity()調(diào)用到哪里去了
代碼文件:
frameworks\base\core\java\android\app\Activity.java
代碼:public void startActivity(Intent intent) {
this.startActivity(intent, null);
}接下來會調(diào)用startActivityForResult()->然后一路調(diào)用到Ams去啟動Activity;
原來如此,Activity重寫了startActivity()方法...
五.? ? Android 為什么要這么設計?
那現(xiàn)在來回答這個問題黄选,為什么Android在Service 里面啟動Activity要強制規(guī)定使用參數(shù)FLAG_ACTIVITY_NEW_TASK呢蝇摸?
我們可以來做這樣一個假設,我們有這樣一個需求:
我們在電話本里面啟動一個Service办陷,然后它執(zhí)行5分鐘后貌夕,啟動一個Activity
那么很有可能用戶在5分鐘后已經(jīng)不在電話本程序里面操作了,有可能去上網(wǎng)民镜,打開瀏覽器程序了啡专。
5分鐘后,此時當前的Task是瀏覽器的task制圈,那么彈出Activity们童,如果這個Activity在當前Task的話,也就是瀏覽器的Task鲸鹦;那么用戶就會覺得莫名其妙慧库;因為彈出的Activity和瀏覽器在一個Task,本來這個Activity應該屬于電話本的馋嗜。
所以齐板,對于Service而言,干脆強制定義啟動的Activity要創(chuàng)建一個新的Task.
這種設計葛菇,我覺得還是比較合理的甘磨。
借鑒自http://bbs.51cto.com/thread-1133875-1.html