1. 程式人生 > >QtAndroid詳解(2):startActivity和它的小夥伴們

QtAndroid詳解(2):startActivity和它的小夥伴們

    上一篇,“QtAndroid詳解(1):QAndroidJniObject”,我們做了好多好多準備工作,目的就是為使用 QtAndroid 名字空間裡的 startActivity() 方法呼叫 Android 系統功能奠定基礎。那這次呢,我們就要來研究如何使用 startActivity 方法了。

    在我的書Qt on Android核心程式設計中,講解 JNI ,介紹如何使用 JNI 擴充套件 Qt 應用時,是通過重寫 QtActivity ,為我們的 Activity 加入靜態方法來實現的,實際上從 Qt 5.3.0 以後,有更方便的方法,那就是接下來要介紹的 startActivity 了。

    startActivity ,方法名字已經道出了它的功能:啟動一個活動(Activity)。簡單的說,在 Android 裡,Activity就是佈滿整個視窗或者懸浮於其他視窗上的互動介面。通常使用者可見的功能,都是與 Activity 關聯的,比如我們用微信、微博、美團,我們看到的那些介面,可操作的東東,都是與 Activity 息息相關的。

    我們先詳細介紹一下 startActivity 方法,然後再舉例來看使用 startActivity 呼叫一個活動的兩種情況。

startActivity方法詳解

    startActivity 方法原型如下:

void	startActivity(const QAndroidJniObject & intent, int receiverRequestCode, QAndroidActivityResultReceiver * resultReceiver = 0);

    如你所見,startActivity 有三個引數。

    第一個是 intent ,我們在“QtAndroid詳解(1):QAndroidJniObject”中舉例時已經介紹了 Android 提供的 Intent 類,這裡的 intent 引數實際上就是一個 Java Intent 物件。

    第二個引數是 receiverRequestCode ,實際上是一個識別符號,用來標記一次 startActivity 呼叫,當你啟動一個 Activity 時,這個 request code 會傳遞過去,當你呼叫的 Activity 結束時,你會得到一個通知,這個通知裡又把你傳的 request code 帶回給你,同時呢,這個通知還帶回來你呼叫的那個 Activity 執行的結果,叫作 result code 。更厲害的是,你呼叫的 Activity ,還可以傳遞更多的資料回來,而這些資料呢,又是通過一個 Intent 攜帶的。

    為了處理你調起的 Activity 的返回結果,在 Qt 程式碼中,我們需要一個 QAndroidActivityResultReceiver 物件,這就是第三個引數了。

    實際上, Qt 提供的 startActivity 方法是個混搭,根據引數的不同,分別對應了 Android Activity 類的 startActivity(Intent) 和 startActivityForResult(Intent, int) 兩個方法。追本溯源,還是來看看 Android 的文件吧,這樣更清楚些。

Android 中的 startActivity 和 startActivityForResult

    首先看 startActivity ,它是 Activity 類的方法,原型如下:

void startActivity(Intent intent);

    在 Android 文件中有兩處對 startActivity 的描述:

1. Launch a new activity. You will not receive any information about when the activity exits.

2. The startActivity(Intent) method is used to start a new activity, which will be placed at the top of the activity stack. It takes a single argument, an Intent, which describes the activity to be executed.

    我翻譯一下,中文如下:

1. 啟動一個新活動。當新活動結束時,你收不到任何訊息。

2. startActivity(Intent) 方法用來啟動一個新活動,這個新活動將被放在活動棧的頂端。這個方法接受一個意圖(Intent)作為引數,而作為引數的意圖,描述了要執行的活動的相關資訊。

    哦嘛,就是這啦,startActivity 啟動一個與呼叫者分離的活動,新活動起來後就自生自滅,呼叫者既不關心它的死活也不關心它幹嘛有什麼後果。

    再來看 startActivityForResult 方法,它也是 Activity 的方法。原型如下:

public void startActivityForResult (Intent intent, int requestCode);

    從名字就可以看出來,startActivityForResult ,啟動一個新活動的目的就是為了拿到新活動的結果。就是說,它對新活動很 care ,你要幹個事兒,還得帶個結果回來給我。比方說你從通訊錄裡挑個人兒給我,那你結束時,如果誰也沒挑,那就告訴我失敗;如果你挑了個人兒,就告訴我成功,並且還得讓我有地方獲得你挑的那個人兒的資訊。

    intent 引數與 startActivity 的引數含義完全一樣,不必再提。

    requestCode 引數,用來標識一次呼叫,當新 Activity 結束後,這個 requestCode 會通過 onActivityResult() 方法返回給呼叫方。這裡的 requestCode ,與 QtAndroid 提供的 startActivity 的第二個引數是一致的。

    那接下來該說到怎麼接收新 Activity 的返回結果了。

Android中 Activity 類的 onActivityResult 方法

    當我們使用 startActivityForResult 啟動一個新的 Activity 時,目的是為了做點什麼事兒獲取一些資訊,那如何知道新 Activity 結束了、執行情況如何、帶回什麼東東呢? Android 為 Activity 類設計了 onActivityResult 方法,我們在 extends Activity 時可以重寫這個方法,根據 requestCode 來區分一次呼叫,判斷呼叫結果,抽取新 Activity 攜帶的資料。

    onActivityResult 方法原型如下:

protected void onActivityResult (int requestCode, int resultCode, Intent data);

    如你所見,它有三個引數。

    第一個引數 requestCode 是我們呼叫 startActivityForResult 時傳遞的。

    第二個引數 resultCode 是新 Activity 的執行結果,有兩種,一種是 RESULT_OK(實際上為 -1 ),一種是 RESULT_FAILED(實際上是0)。 這兩個值有點詭異啊,不符合我的習慣。當你在 C++ 中來程式設計時,不使用 RESULT_OK 和 RESULT_FAILED 兩個常量,直接使用數字字面量,就容易搞反(C的習慣,0是OK,-1是FAILED)。

    第三個引數是一個 Intent 物件,攜帶了新 Activity 返回的資料。

Android中的Intent

    前面不止一次提到 Android 的 Intent ,對 Android 開發人員來講 Intent 是很熟悉的一種存在,對 Qt 開發人員來講 Intent 可能還是有些陌生,我們再花點篇幅介紹一下。

    Intent 在 android.content 包中,全路徑類名為 android.content.Intent ,我們在“QtAndroid詳解(1):QAndroidJniObject”中舉例時也提到過。

    Intent 類是 Android 提供的、用於元件間通訊的一種機制。它是待執行操作的一個抽象描述,它可以與 startActivity() 配合來啟動一個活動,與 broadcastIntent() 配合來發送一個廣播,也可以與 startService() 或 bindService() 配合以便與後臺服務進行通訊。

    Intent 最常見的用途就是啟動活動,通過 Intent ,你可以呼叫其它的系統功能或第三方提供的功能,比如你可以呼叫撥打電話的功能,可以顯示聯絡人,也可以呼叫相機。

    我們在使用 Intent 時可以指定一個 action ,action 代表你要做的動作,也就是你想幹啥;還可以在 Intent 中攜帶資料給被呼叫的一方。而被調起的一方(Activity或Service)則可以通過 Intent 的 getData() 、 getBundle() 、 getXxxExtra() 等方法來獲取呼叫者傳遞過來的資料。Intent 就像 Android 元件之間的信使,可以告訴我們要做什麼,以及有哪些資料可用。

    Intent 有好幾個建構函式,我們可能會用到下面兩個:

  • Intent(String action)
  • Intent(String action, Uri uri)

    Intent 還提供了很多方法,允許我們設定 action 和 data ,下面列出的僅僅是一小部分:

  • setAction(String action)
  • putExtra(String name, int value)
  • putExtra(String name, CharSequence value)
  • putExtra(String name, Bundle value)
  • setData(Uri data)

    要想用 Intent 呼叫某個元件,就需要指定 action ,那 action 到底是什麼玩意兒呢?

    action 實際上是一個字串,代表了某個元件,當你傳遞一個 Intent 物件給 startActivity() 等方法時, Android 框架會來解析這個 Intent 的 action ,找到 action 代表的元件並呼叫它。解析的過程比較複雜,也有很多規則,我們可以簡單的理解為 Android 有一張表,登記了系統中的各種元件,當你給出"com.android.settings.SETTINGS"這樣的action時,Android就會在這個表中查詢到設定元件並啟動它。Intent預定義了很多 action ,感興趣的可以到 Android 線上幫助中檢視 Intent 的文件。

    好啦,Android 中與 startActivity 相關的背景知識就介紹到這裡吧,是時候回過頭來看我們的 QtAndroid 了。

QAndroidActivityResultReceiver

    Qt 使用 startActivity 一個方法對應了 Android 的 startActivity 和 startActivityForResult 兩個方法。當我們提供 requestCode 和 resultReceiver 兩個引數時,就對應 Android Activity 的方法 startActivityForResult ,而 resultReceiver 引數就是與 Android Activity 的 onActivityResult 方法適配的,它的型別是 QAndroidActivityResultReceiver 。

    QAndroidActivityResultReceiver 是一個純虛類,定義了一個介面,原型如下:

virtual void	handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject & data) = 0;

    看到了吧,handleActivityResult 方法和 Android Activity 的 onActivityResult 是完全匹配的。唯一不同的是,第三個引數轉換為了 QAndroidJniObject ,實際上就是一個 Intent 物件。

    為了處理 Activity 的返回結果,我們需要實現 QAndroidActivityResultReceiver 介面,比如:

class ResultReceiver: public QAndroidActivityResultReceiver
{
public:
    int requestId;
    
    void	handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject & data)
    {
        if(receiverRequestCode == requestId)
        {
            if(resultCode == RESULT_OK)
            {
                //some code here
            }
            else
            {
                //some code here
            }
        }
    }
};

    需要注意的是,QAndroidActivityResultReceiver 是被非同步呼叫的,因此當你提供一個物件用於接收新調起的 Activity 的返回結果時,這個物件最好是從堆上構造(new),如果你把 QAndroidActivityResultReceiver 放在棧上,很可能帶來災難性的後果,當 Qt 收到被調起的 Activity 的返回結果時,你提供的 receiver 可能已經析構了,你就悲劇了,崩潰吧你。

呼叫Android活動

    我乖,終於來了嗎……

    OK,要說到如何呼叫 Android Activity 了,真不容易啊,鋪墊忒長。

    在 Qt 中呼叫 Android Activity ,根據是否關注結果來劃分,有兩種方式:

  • 告知式呼叫,不管結果
  • 追問式呼叫,要求結果

    不管結果的呼叫可能是醬紫的:

    QAndroidJniObject action = QAndroidJniObject::fromString("android.settings.SETTINGS");
    QAndroidJniObject intent("android/content/Intent","(Ljava/lang/String;)V", action.object<jstring>());
    startActivity(intent, 0);

    關注結果的呼叫可能是醬紫的:
    ResultReceiver *receiver = new ResultReceiver(1);
    QAndroidJniObject action; 
    QAndroidJniObject intent("android/content/Intent","(Ljava/lang/String;)V", action.object<jstring>());
    startActivity(intent, 1, receiver);

    不知不覺又寫了這麼長了,看來例項要等到下一次了……

------------

    回顧一下: