Android Bug分析系列:第三方平臺安裝app啟動後,home鍵回到桌面後點擊app啟動時會再次啟動入口類bug的原因剖析

来源:http://www.cnblogs.com/lizhilin2016/archive/2017/11/07/7797908.html
-Advertisement-
Play Games

首先感謝 http://www.cnblogs.com/net168/p/5722752.html 前言 前些天,測試MM發現了一個比較奇怪的bug。 具體表現是: 1、將app包通過電腦QQ傳送到手機QQ上面,點擊安裝,安裝後選擇打開app (此間的應用邏輯應該是要觸發 【閃屏頁Activity】 ...


首先感謝 http://www.cnblogs.com/net168/p/5722752.html 

前言

  前些天,測試MM發現了一個比較奇怪的bug。

  具體表現是:

  1、將app包通過電腦QQ傳送到手機QQ上面,點擊安裝,安裝後選擇打開app (此間的應用邏輯應該是要觸發 【閃屏頁Activity】, 然後跳轉 【主頁Activity】)

  2、然後MM在 【主頁Activity】 時按下了 【Home鍵】,回到桌面

  3、再點擊app的icon圖標,原諒耿直的我們都是覺得應該直接回到【主頁Activity】,但是結果卻是又一次觸發 【閃屏頁Activity】,亮瞎了24K鈦合金狗眼的我們覺得這玩法不對吧?

  4、然後,收拾收拾心情開始定位之路吧~

 

現象分析

  先說說項目結構吧,我們這邊的項目需求邏輯是 先進入 【閃屏頁Activity】(普通的Activity,啟動模式為standard),然後根據一堆初始化操作和判斷,一般是接著進入【主頁Activity】(Activity的啟動模式為singleTask);點擊home鍵不做任何攔截處理,按照系統預設邏輯返回Lanuch桌面。

  也就是說,app的整體交互邏輯並沒有特殊之處,並非業務邏輯導致的bug。那麼回顧下不同的地方,也就是啟動App的入口的區別了,一者是平常的桌面Icon圖標啟動,一者是QQ安裝這類第三方平臺啟動。我們都知道,桌面啟動的話也是通過startActivity這個api通過特定的Intent向ActivityManagerServer發起啟動任務;所以我們可以推導出QQ安裝啟動這類方式也是通過Intent啟動對應的App。

  再往下分析的話,可能需要一些前置知識需要瞭解才能更好的理解。

 

前置知識

1、Activity的Task管理

  一般來說,整個Android系統的App啟動與切換管理依賴於相關Activity的Task的管理。一個Task之中可能含有若幹個Activity,為了簡便起見,我們這裡記錄【Task A】的Activity分別為 【A1】 、【A2】等,【Task B】的Activity分別為 【B1】 、【B2】。

那麼我們來分析下App之間是怎麼切換的。

 

  假設應用都是單Task應用(相對於大部分的普通App來說,都是採用單一Task來管理的)

  桌面程式App:【TaskA】 ---- 存在Activity有【A1】 ----  其棧的結構為 A1

  應用程式B:【TaskB】 ---- 存在Activity有【B1】【B2】 ---- 其棧的結構為 B1B2

  應用程式C: 【TaskC】 ---- 存在Activity有【C1】【C2】 ---- 其棧的結構為 C1C2

 

a、那麼我們進入桌面時:Task之間的結構是 A1 ---- 也就是只有一個【TaskA】棧(桌面Task),並且位於最前端(這裡表現為最後添加的末端)

b、然後我們點擊應用程式B的圖標,啟動B :Task之間的結構是 A1B1B2  ---- 添加了一個【TaskB】,而且【TaskB】也是位於最前端,現在顯示的是【TaskB】的B2的Activity的界面

c、接著點擊home鍵: Android對於home做了特殊預設處理,就是會把桌面Task挪到所以Task最前端,Task結構應該變成  B1B2A1 ---- 【TaskA】挪到隊列最前端,現在顯示的是【TaskA】的A1的Activity的界面,也就是桌面

d、我們再在桌面點擊應用程式C的圖標,啟動C : Task之間的結構變成 B1B2A1C1C2 ---- 添加了一個【TaskC】,而且【TaskC】也是位於最前端,現在顯示的是【TaskC】的C2的Activity的界面

 

從上面的例子,我們可以大致瞭解到Android是怎麼管理不同app之間切換的邏輯:

  我們編寫任何一個Activity的時候,都可以在AndroidManifest裡面顯式指定一個taskAffinity的屬性,也就是說該Activity歸屬於對應taskAffinity的棧;如果沒有指定任何taskAffinity,那麼該Activity將會直接歸屬於包名所在的Task之下。而我們啟動一個Activity時(這裡只討論standard啟動模式),那麼回去先搜尋對應的Task是否存在,如果不存在,新建一個Task並將Activity入棧,如果已經存在對應的Task,那麼直接在對應Task入棧即可。

那麼問題來了:如果我們在上面第d步點擊的圖片並不是程式C的圖標,而是重新點擊了程式B的圖標,此時【TaskB】是已經存在的了,那麼為了不會講B的入口activity(B1)直接在【TaskB】入棧,而是將【TaskB】挪到前臺並不做任何Activity啟動的操作呢?

 

2、桌面的啟動管理:

  回頭研究下AndroidManifest這個文件,我們輕而易舉發現,但凡是App入口Activity,那麼一定會包含 

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

這幾行代碼。這裡到底有什麼玄機呢?其實這個就是跟桌面約定好的啟動攔截過濾器。因為桌面有一個很明顯的需求就是,如果我們再次點擊已經在後臺的App圖標時,是應該將該後臺任務挪到前臺而不是再次啟動該App程式。

 

而從柯元旦所著的《android內核剖析》一書中有記錄如下規則:

  每次啟動Intent導致新創建Task的時候,該Task會記錄導致其創建的Intent;而如果後續需要有一個新的與創建Intent完全一致(完全一致定位為:啟動類,action、category等等全部一樣,不可多項也不可缺少),那麼該Intent並不會觸發Activity的新建啟動,而只會將已經存在的對應Task移到前臺;這也就是為什麼桌面會在再次點擊圖標時將後臺任務挪到前臺而不是重新啟動App的實現。

 

  那麼為啥要指定入口Activity特定的action和category呢,有一個原因我們可以確定,就是為了讓桌面啟動app所用的Intent具有特殊性,也就是添加了特別的攔截器,避免其他應用內或者應用間的Intent對於這個啟動方式的干擾。

說了這麼多,我們可以著手分析上續bug的產生原因了。

 

原理剖析

  從此我們可以知道QQ安裝器其實也就是使用Intent來啟動其剛剛安裝的那個App,但是問題所在的是:他們的啟動Intent並沒有跟桌面的啟動Intent完全一致!

我們將桌面的Task記為【TaskL】,QQ安裝器的Task記為【TaskQ】,我們應用的Task記為【TaskA】,那麼分析如下:

 

進入桌面: L1 ---- L1是單純的桌面

打開QQ: L1Q1Q2 ---- Q2是安裝完畢後詢問是否啟動對應程式的Activity

點擊打開: L1Q1Q2A1A2 ---- A1是入口閃屏頁,A2是主頁Activity

返回桌面: Q1Q2A1A2L1 ---- 回到桌面頁,也就是L1前置

點擊A的圖標: Q1Q2L1A1A2A1 ---- 找到【TaskA】,挪到前臺,由於比對Intent並不是完全一致,所以該請求是新啟動Activity,那麼把A1添加到對應的【TaskA】中

所以bug出現了,出現了再一次的閃屏頁【A1】,問題定位成功!

 

PS:這裡我稍微變種一下,因為一般我們閃屏頁都是在啟動主頁後finish的,而主頁一般是singleTask模式

 

打開QQ: L1Q1Q2 ---- Q2是安裝完畢後詢問是否啟動對應程式的Activity

點擊打開: L1Q1Q2A2 ---- A1是入口閃屏頁,A2是主頁Activity,啟動後A1業務邏輯應該finish掉,所以從【TaskA】中挪去

返回桌面: Q1Q2A2L1 ---- 回到桌面頁,也就是L1前置

點擊A的圖標: Q1Q2L1A2A1 -> Q1Q2L1A2A1 ---- 找到【TaskA】,挪到前臺,由於比對Intent並不是完全一致,所以該請求是新啟動Activity,那麼把A1添加到對應的【TaskA】中,然後A1所再一次觸發啟動主頁,但是主頁是singleTask模式,所以又回到了上次對應的A2主頁,所以現象為再一次出現閃屏頁,然後回到原先的主頁界面。

 

解決思路

  1、讓騰訊那些第三方平臺修正其啟動Intent的設置,使其與原聲桌面啟動Intent保持完全一致。(PS:基本不可能)

  2、自身業務代碼規避,我們可以知道,如果是多餘的閃屏頁入口Activity的話,其基本不可能位於Task的根部,而如果正常啟動的話,閃屏頁入口Activity必定在多對應的Task的根部位置,那麼我們可以從這個地方對於這個bug進行規避,方法就是在閃屏頁入口Activity的onCreate代碼加入如下一段代碼:

複製代碼
// 避免從桌面啟動程式後,會重新實例化入口類的activity
if (!this.isTaskRoot()) {
    Intent intent = getIntent();
    if (intent != null) {
        String action = intent.getAction();
        if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(action)) {
            finish();
            return;
        }
    }
}
複製代碼

 問題解決!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Bootstrap,來自 Twitter,是目前最受歡迎的前段框架。Bootstrap是基於HTML、CSS、JAVASCRIPT的,它簡潔靈活,使得Web開發更加快捷Bootstrap特點:優雅,靈活,可擴展為什麼使用Bootstrap:1.移動設備優先 :自從Bootstrap3起,框架包含了貫 ...
  • 起因 使用HTML5開發Android應用時,少不了調試WebView。做前端的還是習慣Chrome的開發者工具,以前都是輸入Chrome://inspect就可以調試WebView了,太方便了。 最近老是出現空白頁面,各種搜索,最後還是Fan牆解決了。好在翻一次能用一段時間,鬱悶的是當你需要調試的 ...
  • json本身是字元串,即 json字元串 js使用 要把 json字元串 轉為 javascript對象 json字元串轉為js對象的方法:jquery的parseJSON var str='[{"name":"cxh","sex":"man"},{"name":"cxh1","sex":"man1 ...
  • 1、vue 路由 如果傳遞 params 定義路由的時候是 獲取的時候 最後形如 傳參的時候 2、如果傳遞query ?id=str.... 定義路由的時候直接是 獲取的時候 傳參的時候 ...
  • <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>for與each性能比較</title> </head> <body><div id="test"></div> <input type="button" value="for" o ...
  • 比如在做下拉刷新的時候,切記在下拉刷新的函數中要加 這行代碼,否則下拉載入之後一直顯示載入中,而不會載入完成。 ...
  • 這是一個將滑鼠移入圖片,顯示圖片該區域的放大效果。 該效果中,使用透明遮罩的span標簽觸發滑鼠的移入移出以及ommousemove事件並且將圖片放大。 利用百分比實現放大效果。 ...
  • 1、JS 異常之 missing ) after argument list 錯誤釋疑報錯原因:不是字元串就輸出啦 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...