編輯:關於Android編程
隨著公司新業務的起步由於原有APP_A的包已經很大了,所以上邊要求另外開發一款APP_B,要求是APP_A和APP_B賬號通用且兩個APP可以相互打開。賬號通用也就是說在APP_A上登錄了那麼打開APP_B也就默認是登錄狀態,這個實現也不復雜就不介紹了;APP相互打開本來也不是難事,但是在測試的過程中發現了一個之前沒有遇到的問題,現象如下圖的demo所示:
運行現象是在APP_A中打開了APP_B後,這時候在APP_B中進行任何操作都是沒問題的,在APP_B不退出的情況下若摁了HOME鍵切換到桌面後此時再點擊APP_A的icon圖標打開APP_A時,發現界面竟然是APP_B的界面,當時感覺很詭異,是什麼原因導致出現這種現象呢?當時就琢磨著可能是APP_B運行在了APP_A的任務棧中了,於是開始排查代碼,在APP_B中響應APP_A的代碼如下所示:
由於我們APP_A和APP_B約定了相互打開采用scheme的形式,所以響應代碼看起來是沒有問題的,接著查看在APP_A中打開APP_B的代碼,如下所示:
public void openAPP_B1() { Uri uri = Uri.parse("llew://"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); }這段就是打開我們APP_B的代碼,看上去也沒有什麼問題,但是為什麼會出現上述現象呢?然後我就嘗試在openAPP_B中采用另外的方式,代碼如下:
public void openAPP_B2() { Intent intent = getPackageManager().getLaunchIntentForPackage("packageName"); if(null != intent) { startActivity(intent); } }
方式二以前使用過並看過這塊相關源碼,所以首先就想到了通過PackageManager來獲取Intent來啟動我們的APP_B,運行程序後發現第二種方式是沒問題的,那也就是說在第二種中采用PackageManager獲取到的Intent肯定是和采用第一種方式獲取到的Intent是有區別的,那他們的區別在哪呢?先不說結論我們接著往下看,運行程序通過debug模式分別查看這兩種方式獲取到的Intent的不同之處:
方式一的intent截圖如下所示:
方式二的intent截圖如下所示:
通過對比這兩種方式的Intent對象可以發現方式二中的intent對象包含了flg屬性,而該flg屬性的值恰好是Intent.FLAG_ACTIVITY_NEW_TASK的值,這時候豁然開朗了,原來方式二中的Intent添加了FLAG_ACTIVITY_NEW_TASK標記,也就是說采用方式一開打APP_B時的頁面是運行在APP_A的任務棧中,而通過方式二打開APP_B的頁面運行在了新的任務棧中。為了證明通過方式一打開的APP_B的頁面是運行在APP_A的任務棧中,我們可以使用adb shell dumpsys activity activities 命令來查看Activity任務棧的情況,截圖如下:
然後我們在方式一中的Intent也添加FLAG_ACTIVITY_NEW_TASK標記在運行一下,使用adb shell dumpsys activity activities 命令查看一下,截圖如下:
出現以上問題的原因就是APP_B運行在了APP_A的任務棧中,解決方法也就是在啟動APP_B的時候讓APP_B運行在新的任務棧中,接下來順帶進入源碼看一看通過PackageManager獲取到的Intent對象在哪賦值的flag標記吧,在Activity中調用getPackageManager()輾轉調用的是其間接父類ContextWrapper的getPackageManager()的方法,源碼如下所示:
@Override public PackageManager getPackageManager() { // mBase為Context類型,其實現類為ContextImpl return mBase.getPackageManager(); }ContextWrapper的getPackageManager()方法中調用的是Context的getPackageManager()同名方法,而mBase的實現類為ContextImpl,所以我們直接查看ContextImpl的getPackageManager()方法,源碼如下:
@Override public PackageManager getPackageManager() { // 如果mPackageManager非空就直接返回 if (mPackageManager != null) { return mPackageManager; } // 通過ActivityThread獲取IPackageManager對象pm IPackageManager pm = ActivityThread.getPackageManager(); if (pm != null) { // 新建ApplicationPackageManager對象並返回 // Doesn't matter if we make more than one instance. return (mPackageManager = new ApplicationPackageManager(this, pm)); } return null; }通過源碼我們知道getPackageManger()方法獲取的是ApplicationPackageManager對象,獲取Intent對象就是調用該對象的getLaunchIntentForPackage()方法,源碼如下:
@Override public Intent getLaunchIntentForPackage(String packageName) { // First see if the package has an INFO activity; the existence of // such an activity is implied to be the desired front-door for the // overall package (such as if it has multiple launcher entries). Intent intentToResolve = new Intent(Intent.ACTION_MAIN); intentToResolve.addCategory(Intent.CATEGORY_INFO); intentToResolve.setPackage(packageName); Listris = queryIntentActivities(intentToResolve, 0); // Otherwise, try to find a main launcher activity. if (ris == null || ris.size() <= 0) { // reuse the intent instance intentToResolve.removeCategory(Intent.CATEGORY_INFO); intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); intentToResolve.setPackage(packageName); ris = queryIntentActivities(intentToResolve, 0); } if (ris == null || ris.size() <= 0) { return null; } // 運行到這裡是查找到了符合條件的Intent了,新建Intent Intent intent = new Intent(intentToResolve); // 在這裡給Intent添加了我們期待的FLAG_ACTIVITY_NEW_TASK標簽 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setClassName(ris.get(0).activityInfo.packageName, ris.get(0).activityInfo.name); // 返回新建的Intent對象 return intent; }
通過源碼我們看到在ApplicationPackageManager的getLaunchIntentForPackage()方法中給符合條件的Intent添加了FLAG_ACTIVITY_NEW_TASK標簽,而該標簽的作用就是為目標Activity開啟新的任務棧並把目標Activity放到棧底。
開始講解Activity的launchMode之前我們先提一下任務和返回棧的概念,以下部分內容參考自官方文檔。
任務和返回棧好了,用了不小篇幅介紹了任務和返回棧的概念,若要改變返回棧的默認行為,可通過Activity的launchMode以及Intent的Flag標簽,我們今天主要講解的是通過launchMode來改變任務棧的默認行為,Android系統為launchMode提供了四種機制,分別是standard,singleTop,singleTask,singleInstance,為了方便查看任務棧的相關信息,這裡給大家說一個命令:adb shell dumpsys activity,如果有對該命令不熟悉的,請自行查閱並掌握。下面我們來逐一講解launchMode的各個屬性值。
standardif( 發現一個 Task 的 affinity == Activity 的 affinity ){ if(此 Activity 的實例已經在這個 Task 中){ 這個 Activity 啟動並且清除頂部的 Acitivity ,通過標識 CLEAR_TOP } else { 在這個 Task 中新建這個 Activity 實例 } } else { // Task 的 affinity 屬性值與 Activity 不一樣 新建一個 affinity 屬性值與之相等的 Task 新建一個 Activity 的實例並且將其放入這個 Task 之中 }singleInstance
根據以上結果我們已經大致掌握了launchMode的各種特性了,為了深刻理解需要小伙伴們自己動手實驗嘗試各種情況下的Activity的打開方式。本篇文章到此就結束了,感謝觀看(*^__^*) ……
第1節 線程概述安卓應用只有一個主線程-各個組件都是在這個線程中運行。作為組件的之一的Activity就是在這個線程中更新應用界面的,例如,用戶點擊界面上的一個按鈕,按鈕
小米手環需要安卓4.4及以上系統,於是很多朋友購買之後進行手機系統升級,但是卻發現小米手環充不了電。遇到這種情況要怎麼辦呢?那麼小米手環充不了電怎麼辦呢?小
做開發的,最基本的調試要會,今天簡單做個步驟,希望對小白有幫助。網上很多教程講的都是使用這個按鈕進行調試今天我只講個簡單的吧。簡單流程:正常Run app也就是用&ldq
Q:安卓Android-x86 4.4 VMware虛擬機休眠後怎麼喚醒?按一下右Alt鍵旁邊的菜單鍵就可以喚醒系統了。Q:安卓Android-x86 4.