1. 程式人生 > >Android 在 Service 啟動 Activity 和 Dialog

Android 在 Service 啟動 Activity 和 Dialog

Service 啟動 Activity

在 Activity 中其中 startActivity 這個大家應該是非常熟悉的。那麼從 Service 裡面呼叫 startActivity 話,會怎麼樣呢?

會出現下面的異常:

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 “FLAG_ACTIVITY_NEW_TASK”。

那麼下面的話,我們將從下面幾個方面分析這個問題。
1. 這個異常怎麼產生的?
2. 解決這個異常後會出現問題?
3. 為什麼 Activity.startActivity() 不會出現這個問題?

Context的繼承關係圖

首先來看一張圖, 這張圖表示了 Context 裡面的基本繼承關係。

這裡寫圖片描述

  1. 最上面的是 Context.java,它其實是一個抽象類,它有兩個重要的子類 ContextImpl 和 ContextWrapper
  2. ContextImpl,是 Context 功能實現的主要類,
  3. ContextWrapper,顧名思義,它只是一個包裝而已。主要功能實現都是通過呼叫 ContextImpl 去實現的。
  4. ContextThemeWrapper,包括一些主題的包裝,由於 Service 沒有主題,所以直接繼承 ContextWrapper;但是 Activity 就需要繼承 ContextThemeWrapper

異常如何產生

找到報錯的程式碼

檔案:
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條件判斷,如果不包含 FLAG_ACTIVITY_NEW_TASK 就會報這個錯誤

if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
    ...
}  

那麼 service.startActivity(Intent intent) 怎麼會呼叫這裡來的呢?
要回答這個問題,我們分析下 service.startActivity() 做了什麼,其實, service.startActivity 呼叫的是 ContextWrapper.startActivity(),因為 Service 繼承自 ContextWrapper

程式碼檔案

frameworks\base\core\java\android\content\ContextWrapper.java
程式碼:

public void startActivity(Intent intent, Bundle options) {
    mBase.startActivity(intent, options);
}

ContextWrapper.startActivity 的話,是直接呼叫的
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;

    }

那又是誰呼叫 attachBaseContext 的呢?
是在 Service 建立的時候,在 ActivityThread 裡面呼叫,如下:

程式碼檔案
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() 方法裡面主要做這幾件事:
1. 通過 pms 找到要啟動的 Service 配置資訊,然後通過反射生成 Service 物件
2. 建立 ContextImpl 物件,然後呼叫 service.attach 方法設定到 ContextWrapper.java的 mBaseContext 變數裡面。

那現在就明白了,service.startActivity()->ContextWrapper.startActivity()->ContextImpl.startActivity()
然後再 ContextImpl.startActivity 裡面會檢查 Intent 的引數是否包含FLAG_ACTIVITY_NEW_TASK,從而出現這個異常。

解決這個異常後會出現問題?

有些同學就會說了,在 Service 裡面啟動 Activity 必須要有 FLAG_ACTIVITY_NEW_TASK 引數,那麼我們新增上不就可以了?如下:

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

那麼這樣會帶來什麼問題呢?
這樣帶來的問題就是在最近任務列表裡面會出現兩個相同的應用程式,比如你是在電話本里面啟動的,那麼最近任務列表就會出現兩個電話本;因為有兩個Task嘛!
那怎麼解決呢?其實也非常好解決,只要在新的Task裡面的Activity裡面配置android:excludeFromRecents="true"就可以了。表示這個 Activity 不會顯示在最近列表裡面。

Activity.startActivity()為什麼不出現這個異常呢?

要回答這個問題,需要看下 Activity.startActivity() 呼叫到哪裡去了
程式碼檔案:
frameworks\base\core\java\android\app\Activity.java
程式碼:

public void startActivity(Intent intent) {
   this.startActivity(intent, null);
}

接下來會呼叫startActivityForResult()->然後一路呼叫到Ams去啟動Activity;
原來如此,Activity 重寫了 startActivity() 方法

Service 啟動 Dialog

由於 Dialog 是依賴於 Activity 存在的,所以對於從 Service 啟動 Dialog 主要有兩種方法:

  1. 首先啟動一個 半透明的 Activity,然後在 Activity 裡啟動 Dialog。(或者直接使用 Activity 來仿寫一個 Activity)
  2. 使用 WindowManager 實現

使用 WindowManager 時需要注意,此時的 Dialog 是 SYSTEM 級別的,如果程式在後臺時啟動這個 Dialog,Dialog 會浮在桌面上。(使用小米等有自己許可權管理的系統時,需要申請一定許可權才可以在桌面顯示這個 Dialog,否則只能在自己 APP 前臺時才顯示)

使用 WindowManager 實現

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("是否接受檔案?")
.setPositiveButton("是", new DialogInterface.OnClickListener() {
@Override
publicvoid onClick(DialogInterface dialog, int which) {
}
}).setNegativeButton("否", new OnClickListener() {
@Override
publicvoid onClick(DialogInterface dialog, int which) {
}
});
AlertDialog ad = builder.create();
// ad.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); //系統中關機對話方塊就是這個屬性
ad.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
ad.setCanceledOnTouchOutside(false); //點選外面區域不會讓dialog消失
ad.show();

許可權

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

使用 Activity 實現

Activity 半透明主題

<style name="DialogTransparent" parent="@android:style/Theme.Dialog">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowAnimationStyle">@android:style/Animation</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowIsFloating">false</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>

或者直接使用

@android:style/Theme.Dialog

相關推薦

AndroidService 啟動 Activity Dialog

Service 啟動 Activity 在 Activity 中其中 startActivity 這個大家應該是非常熟悉的。那麼從 Service 裡面呼叫 startActivity 話,會怎麼樣呢? 會出現下面的異常: android.util.A

如何找到Android app啟動activity頁面元素信息

dump ref adg 按鈕 配置環境變量 好的 too 啟動app ace 在實施app自動化的時候,我們需要知道app 的啟動activity和頁面元素信息,以此啟動app和定位頁面元素,那麽如何在沒有源碼的情況下找打他們呢?當然是有好的工具啦,有Android sd

Android】【ServiceActivityService是否同一程序

如果Service沒有設定屬性android:process=”:remote” Service會和Activity是在同一個程序中的,而且都是主執行緒 如果Service設定屬性android:process=”:remote” 那麼就會建立新程序,這時

adb shell start中啟動activityservice

命令視窗通過adb shell 進入android 的Linux命令介面,輸入am -help看到如下資訊: 我們可以通過命令啟動android中的Activity,Service,BroadcastReceiver 等元件     撥打一個電話:     am start

Android中自定義ActivityDialog的位置大小背景透明度等

1.自定義Activity顯示樣式 先在res/values下建colors.xml檔案,寫入: <?xmlversion="1.0"encoding="utf-8"?> <resources>     <!-- 設定透明度為56%

Android ServiceActivity傳引數資料

作為Android開發人員來說,Activity 向Service傳資料很容易,用Intent跳轉的時候攜帶資料,但是Service向Activity傳資料對於剛接觸可能相對有點難度,所以,此篇部落格記錄下Service向Activity用廣播傳值。 An

Android app啟動activity並調用onCreate()方法時都默默地幹了什麽?

AR 其中 保存狀態 位置 mod con 會同 語句 Go Android app啟動activity並調用onCreate() 方法時都默默地幹了什麽? 在AndroidManifest.xml文件中的<intent-filter>元素

android service啟動AlertDialog

專案中是接收系統廣播啟動service,然後在service中彈出列表樣式的AlertDialog。 1.廣播啟動service,intent傳遞路徑 Intent i = new Intent(context, UpdateSystemService.class);i.putExtra("

Android App 啟動 Activity 建立解析

繼承實現類關係:   ActivityThread  thread = new ActivityThread();   Context->ContextImpl   ContextImpl context = new ContextImpl(null, mainThre

Android App 啟動 Activity 創建解析

ram name token ger spl 分享 con cti image 繼承實現類關系:   ActivityThread thread = new ActivityThread();   Context->ContextImpl ContextImpl

轉:Android 外部啟動activity,自定義action,action常量大全

https://www.cnblogs.com/guop/p/5067342.html Android 外部啟動activity,自定義action,action常量大全 從任意app,啟動另外一個app的activity: 1.   Intent i =

Android學習筆記:ActivityFragment——建立Activity

建立一個activity需要的三步 新建類繼承activity或者子類 在AndroidManifest裡面宣告 建立佈局xml檔案,並在activity的onCreate裡面設定 第一步: 在java下面com.....的包裡面新建一個class,繼承自AppC

Android 外部啟動activity,自定義action,action常量大全

https://www.cnblogs.com/guop/p/5067342.html 從任意app,啟動另外一個app的activity: 1.   Intent i = new Intent();        &

Android ServiceActivity的互動

Android中有時候需要在Service中改變Activity的UI,或者在Activity中修改Service中的數值。首先必須使用與Activity繫結的Service,有三種方式可以實現。第一,是使用介面回撥的方式在Activty中實現Service中的介面;第二,使用廣播的方式傳遞;第三,則是用觀察

Android ServiceActivity的交互

create ces per andro 時機 子線程 lse tag ins Android中有時候需要在Service中改變Activity的UI,或者在Activity中修改Service中的數值。首先必須使用與Activity綁定的Service,有三種方式可以實現

Android:Service通知Activity更新介面

Android有四大元件,其中包括service和activity,那麼在使用的過程中,我們最常遇到的問題是他們之間的通訊問題。 1.首先Activity呼叫Service 這個是比較基礎的,它有兩種常見的方法; 1. 通過Intent 可以指定pack

Android ServiceActivity之間通訊:通過Binder物件、Broadcast廣播

From:http://blog.csdn.net/xiaanming/article/details/9750689 From:http://blog.csdn.net/ameyume/article/details/6290137 From:http://blog.c

Android外掛化技術之旅 1 開篇 - 實現啟動外掛與呼叫外掛中的ActivityService

前言 Android技術如今已很成熟了,元件化、外掛化、熱修復等等框架層出不窮,如果只停留在單純的會用框架上,技術永遠得不到成長,只有懂得其原理,能夠婉婉道來,能夠自己手動寫出,技術才會得到成長,與其焦慮未來,不如把握現在。本篇將手寫教大家寫出外掛化框架,外掛化技術是Android高階工程師必備的技術之一,

android Service啟動Dialog

ams onclick finish ble 實現 dsm new app params 在Service 中彈出Dialog與在Activity中彈出Dialog的方式一樣,可是activity finish後,dialog也會隨著關閉。他是依附著

手把手教你_怎麽找android應用的包名啟動activity

color don dsm too key 包名 ani 一個 col 自己主動化測試中常常遇到這個問題,關於這個題目,方法眾多,咱的目的是找個比較簡單靠譜的: 方法一: 先進入cmd窗體,adb shell 後: cd /data/d