【Bugly安卓開發乾貨分享】Android APP 快速 Pad 化實現

騰訊bugly發表於2016-03-18

專案背景

採用最新版本手機 APP(之後稱為 MyApp)程式碼,實現其 Pad 化,為平板和大屏手機使用者提供更好的體驗。為實現 MyApp 的 Pad 化工作,需要我們首先來了解一下 MyApp 專案經典頁面的構成以及 Pad 化後的頁面結構的變化。

1.MyApp 頁面經典構成

現在主流手機 APP 主頁通常採用標籤欄加標籤內容方式顯示。而通過主頁進入的二級頁面全部採用全屏方式展示。比如手機 QQ,微信,支付寶等等都是採用 Tab 欄方式為主,進入一個具體功能後,全屏開啟。我們專案也是如此。下面看一下 MyApp 專案手機端的頁面構成圖。


左側是一個 Tab 欄(區域1)加 Tab Content(區域2)構成的頁面,右側是在 TabContent 中點選具體功能後進入的一個功能詳情頁面(全屏區域3)。
檢視程式碼,發現除 TabContent 區域2,從主頁開始到其他全屏顯示的頁面全部採用 Android Activity 元件實現。經統計得出大概有幾百個 Activity。這些 Activity還包含比如Web程式,peak 程式(圖片選擇檢視)等其他非主程式 Activity。

1.MyApp pad 化的設計圖

瞭解了手機 MyApp 頁面構成後,還要來看 Pad 化後 UI 結構的變化,通過對比來探索 Pad 化最佳的實現方案。下面是我們的 PAD 版本頁面結構圖。

由於 Pad 平板的空間要遠大於手機空間,所以,在主頁中 Pad 所展示的內容要比手機更多。通過觀察設計圖發現,整個頁面分為了3塊區域,與手機端頁面的1,2,3區域一一對應。Tab 欄被移到了左側1區,Tab Content 被移到了中間2區,而在2區開啟的 Details 頁面則要求在3區展示,而不再是像手機 APP 一樣全屏展示。

手機 APP Pad 化道路的探索過程

通過了解 MyApp 專案經典頁面構成和 pad 版頁面結構的變化,以及快速Pad 化的原則,我們開始了對手機 APP pad 化實現方案的漫漫探索。
首先想到的是,既然手機APP頁面主要是由Activity構成,那麼我們能不能把 Activity 縮小,讓多個 Activity 在同一螢幕顯示呢,很快我們的方案1出來了。

方案1,如果把設計圖的整個頁面稱為主 Activity,主 Activity 全屏顯示不變,在主 Activiy 中開啟的新 Activity (稱為A)縮小顯示在設計圖3區,我們就可以實現 Pad 設計的要求。那麼我們具體實現步驟為:
1,A類 Activity 繼承 Base Activity
2,修改 Base Activity 的 window 的起始座標x和寬度 width,讓其剛好位於3區。
3,A類 Activity 背景改為透明
4,讓在A類 Activity 繼續開啟的 Activity,重複1,2,3,4步驟。

但是很快就發現了問題。

  1. 在當前 Tab 開啟 Activity A,切換 Tab 後 A Activity 仍然顯示。
  2. 每個 Tab 開啟的 Activity,都處於同一個 Activity 棧中,按開啟先後順序新增,點選返回鍵也是順序退出的。這樣每個 Tab 中開啟的Activity 都混在一起了,而不是彼此獨立。導致 back 鍵出現問題。

既然直接顯示 Actvity 有問題,想想反正都是顯示UI佈局,能不能把 A 類 Activity 的根佈局拉出來掛載在主 Activity 右側?從而我們推匯出了方案2。

方案2:在主 Activity 啟動 A 類 Activity 時,獲取 A 的根佈局,新增到主 Activity 在右側3區預留的一個空佈局中。具體實現步驟為:
1,重寫主 Activity 的 startActivity 方法。
2,使用 LocalActivityManager 啟動 A 類 Aactivity 返回 window 物件。
3,通過 Window 物件 window.getDecorView()返回開啟 Activity 的佈局並 Add 到主 Actvity 上。
4,重寫主 Activity 的 Back 邏輯,在點選返回鍵時 remove 掉掛載的 decorView。

但是在 Demo 上一測試,就發現了很多問題,

  1. 用 mat 檢視到 A 類 Activity 是怎麼也釋放不掉的,因為 LocalActivityManager 已經抓住了 A 類 Activity 的 parent。
  2. 直接拿出 A 類 Activity 的 decorView,已經讓A類 Activity 喪失了 Activity 的一切特性,包括生命週期,返回邏輯,ActivityResult,以及啟動其他 Activity 等功能。會導致之前正常執行的A類 Activity 出現大量問題。

既然直接拿到根檢視沒有用,那該怎麼做才好呢?怎麼做才能使 A 類 Activity 的頁面掛載在主 Activity 右側,又能保證 A 的生命週期和 Activity 行為呢?經過大家一番思考討論後,能不能利用外掛的思想,把 A 類 Activity 中的生命週期方法以及繼承自 Activity 類的方法都拿出來了,在適當時候自己呼叫呢?這樣就保證了原來 A 中的程式碼不會出現問題,不需要修改 A 中的任何程式碼。但是用什麼做容器(代理)比較好了?之後我們想到了用 Fragment,,因為 Fragment 可以作為所屬 Activity 的一個塊存在於任何位置,並且 Fragment 有自己的生命週期,並受所屬 Activity 的生命週期影響,它就像一個子 Activity 一樣。簡直是絕佳容器。而且 Fragment 比較輕量,本身由 Activity 來管理(而不像 Activity 由 Android 系統服務管理),在不同的佈局結構中重用 Fragment 可以優化螢幕空間和使用者體驗。
注意,下面所說的把 Activity 轉換為 Fragment 並不是直接把 Activity 變為 Fragment,這會付出巨大代價,而是以一個空的 Fragment 為容器來承載 Activity。

終於方案3順利出爐。

方案3,把 Activity 轉換為 Fragment,使用 Fragment 模擬 Activity 的方法。然後把 Fragment 直接新增到主 Activity 的右側佈局中。實現的具體步驟為:
1,新增 BasePadActivity,讓所有 Activity 繼承 BasePadActivity,重寫 StartActivity 方法,在該方法中手動 New 出A類 Activity,並把主 Activity 的上下文物件 Context 傳遞給它。
2,建立 MyFragment,持有A類 Activity 例項引用,在 MyFragment 生命週期中直接呼叫A類 Activity 生命週期方法,並把A類 Activity 的檢視傳遞給 Fragment 使用。
3,A類 Activity 中繼承自 Activity 的方法全部重寫,具體實現由步驟1中的得到的主 Activity 上下文 context 處理。這樣A類 Activity 已經成為一個普通例項化物件,不再由 Android 系統管理。

該方案有較多優點,由於繼承 base,不僅能迅速將大量 Activity 快換為 Fragment,而且轉換後,使原來A類 Activity 的功能邏輯維持正常。並且由於A類 Activity 的上下文其實使用了主 Activity 的上下文物件,需要在A類 Activity 獲取 Resouce,Window,Asset 物件等都能通過主 Activity 的 context 進行獲取。
該方案實現後,最初測試好像一切正常,但是不就後也發現了若干問題:

  1. 原 Activity 自定義 TitleBar 出現問題。
  2. 每個 Tab 標籤中開啟的 Fragment,由於都屬於一個主 Activity,導致它們只有一個 Fragment 棧,Back 返回時會出現與方案1類似的問題。
  3. 儘管 Activity 轉換為 Fragment 後,大部分行為都進行了模擬,但是還有一些重要行為沒有做處理,比如說 Activity 的啟動模式,Back 鍵,onActivityResult 等等,這些還要進行完善。
  4. 對於宣告為多程式的 Activity,轉換為 Fragment 後失去了多程式的特性。因為這些 Fragment 屬於主 Activity,主 Activity 是屬於手Q程式的。

該方案雖然也有諸多問題,但是經過調研和測試,發現基本都是能解決的。出於該方案的優點,以及對其出現的問題的解決難易評估,最終決定在該方案基礎上進行優化和完善。那麼對上述出現的若干問題該如何解決呢?

問題1,Activity 替換成 Fragment 之後,如何實現自定義 TitleBar?

設定自定義 TitleBar,是 Activity 所提供的介面,檢視手機APP程式碼,大部分 Activity 都繼承了 TitleBarActivity,通過

getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,
            R.layout.custom_commen_title);  

來定義 TitleBar 的樣式。所以改為 Fragment 之後導致大量 Activity titlebar 顯示不出,甚至 crash。那麼能不能實現一個自定義的 window 物件繼承 android.view.Window,通過 getWindow()得到的是我們自定義的 Window 物件,它可以處理自定義 Titlebar 的使用。

問題2,如何確保每個標籤頁中 Fragment 的操作互不干擾?

Pad 版本主頁也是分為多個 Tab 標籤欄的,每個標籤欄中對 Fragment 的操作應該是相互獨立的。Android中Fragment都是由 FragmentManager 來管理的。Fragment 的新增,替換,移除等操作都是由 FragmentManager 中物件 FragmentTransaction 來記錄和執行。每個 Activity 中只有一個 FragmentManager 例項,通過程式碼
Activity.java

final FragmentManagerImpl mFragments = new FragmentManagerImpl();
public FragmentManager getFragmentManager() {
    return mFragments;
}  

獲取。如果把設計圖中的整個頁面稱為主 Activity,用主 Activity 中一個 FragmentManager 來管理所有標籤欄的 Fragment 顯然會引起混亂,那麼能否實現每個標籤頁中都有一個 FragmentManager 的例項來管理當前標籤中所有 Activity 轉換的 Fragment?

問題3:Activity 替換成 Fragment 之後,完善 Fragment 模擬 Activity 的行為。

完善 Fragment 的 Activity 行為,比如還需要模擬 Activity 的啟動模式、Activity result、startActivity、finish、onBackPressed 等等。

問題4,如何處理多程式 Activity 的顯示?

在回答這個問題之前,要先問一個問題,為什麼不都轉換為 Fragment 呢?
之前研究手機 APP 專案程式碼發現,許多Activity都是設計成屬於其他程式,比如 Web 程式。這樣設計的原因:

其一是這類 Activity 功能都是屬於同一模組,出現 crash 也不會讓整個QQ崩潰。
另外一個重要原因是,Android 平臺對每個程式都有記憶體限制,使用多程式就可以使APP所使用的記憶體加大幾倍。其他程式可以分擔主程式的記憶體壓力,大大降低記憶體溢位導致的 crash。

所以不能輕易把這些 Activity 轉換為 Fragment,因為轉換為 Fragment,就失去了 Activity 多程式的特性,違背了之前設計初衷,大大增加了APP的記憶體壓力。那麼這種情況下是否能讓多個 Activity 在同一螢幕顯示,能不能讓從主 Activity 開啟的新 Activity 變為透明,並且讓其大小和位置剛好覆蓋設計圖的區域3,同時讓屬於主 Activity 的區域1,和區域2接收事件。這樣既讓 Acitvity 擁有多程式的特性,又讓他們看起來就像是在同一個 Activity 中操作。咦,這不是我們的方案1嗎? 對的,由於以上種種原因,對於多程式的 Activity,我們還是要按照方案1來處理。

那麼如何解決解決方案1中的問題。

問題5,多程式的 Activity 在切換標籤後如何處理?Back 鍵如何處理?

在每個標籤頁開啟的多程式 Activity,應該只與本標籤頁有關聯,在切換到其他標籤後,這些 Activity 應該隱藏起來,重新再切換 Tab 回到該標籤時,之前在該標籤開啟的這些 Activity 應該重新顯示。而且每個 Tab 的 Activitys 都應該有一個 Activity 棧來管理。
這該如何實現呢?通過閱讀http://developer.android.com/guide/components/tasks-and-back-stack.html瞭解到
一個 app 中通常包含若干個 Activitys,我們可以把這些 Activity 分為若干類,讓每一類都屬於同一個 Task,以多工的方式把這些 Activity 分為若干組。比如把在 Tab1欄內開啟的多程式 Activity 放入一個Task中,把Tab2中開啟的多程式 Activity 放入另外一個 Task 中,切換 tab 時,只需要讓兩個 task 交替移到前臺顯示或後臺隱藏即可,而且每個Task中都維護著一個 Activity 棧。該想法似乎能解決這個問題。

那麼看到這裡大家又會有另外一個疑問了?既然能解決方案1中的問題,為什麼不直接全部使用方案1呢?還要把Activity轉為Fragment幹嘛?
1,實現的問題,使用多Task的實現方式,在Android中需要宣告Activity的TaskAffinity,而 TaskAffinity 不能在程式碼中動態宣告,而只能寫在配置檔案中,導致不同Tab開啟的同一個Activity可能需要在配置檔案中宣告兩次,因為它們的 TaskAffinity 要不一樣,而同一個Activity是不能宣告兩次的,所以只有寫一個空的 Activity 繼承它,導致大量空Activity產生,而且在程式碼中啟動 Activity 前還要重定向到繼承的Activity,比較麻煩。多程式 Activity 畢竟還是少數,所以可以這麼做。但全部這樣實現明顯不太可取。

2,體驗的問題,當切換 Tab,把 Task 移入前臺,會有一個延時,而且這個延時並不確定,導致切回 tab,會先顯示底部的頁面,然後 task 中Activity 才覆蓋上來。

3,機型的問題,極少數機型可能是由於廠家定製的原因,在多個 Activity 顯示在同一螢幕時會有一個問題,在接收左側主 Activity 的事件時,A類 Activity 會消失。經過原因查詢,發現A類 Activity 的 task 自動回到了後臺。應該是系統原始碼被修改了。這樣的話基本沒法用了。

經過大家若干分析討論,我們基本理清楚了方案3所遇到問題的大致解決版本,經過均衡考慮,使用以下解決方案是目前 Pad 化最好的解決方案。

APP 快速 Pad 化實現架構方案

對 MyApp pad 化的整個流程以及會遇到的問題理清楚之後,經過思考和大家的討論確定了我們手機端 APP Pad 化的架構方案:

1,轉換 APP 主程式的 Activity 為 Fragment,轉換過程儘量不修改原來 Activity 的任何程式碼。
2,讓轉換後的 Fragment 模擬 Activity 的行為,保持 Fragment 和原來 Activity 的行為一致。
3,使用 LocalActivityManager 實現每個 Tab 標籤 Fragments 操作的獨立性。
4,多程式 Activity(包括外掛 Activity)不轉換為 Fragment,實現多工分屏顯示。

1,巧妙轉化 Activity 為 Fragment

實現 BasePadActivity 為所有 Activity 基類,對 Activity 轉換為 Fragment 的操作在 BasePadActivity 中實現,利用類似外掛的想法,以Fragment為殼,把真正具體的實現從 Activity 中搬入 Fragment,而不需要修改原 Activity 的任何程式碼。通過重寫 StartActivity 方法,讓原來去 startActivity 的動作變為新增一個新的 Fragment 動作,並呼叫 addToBackStatck()方法新增到 Fragment 棧中。
下面給出部分虛擬碼的實現 ,注意以下程式碼都是虛擬碼,很多隻有方法而無實現, 這裡主要是講思路

BasePadActivity.java  

public class BasePadActivity extends Activity{
    /**
     * 通過重寫startActivityForResult來將Fragment轉換為Activity
     */
    @Override
    public void startActivityForResult(Intent intent) {
        if (isfragment && !isNewProcessActivty()){ //多程式Activity不轉換為Fragment
                //初始化activity,注意該Activity是我們自己例項化出來的
                BasePadActivity activity =(BasePadActivity)newInstance(intent);
                activity.attachBaseContext(getBaseContext()) 
                activity.onCreate(intent.getExtras());  //模擬Activity的onCreate
                activity.addMyFragment();
            }
        }
    }

    public class MyFragment extends Fragment {
        @Override
        public View onCreateView() {
            View contentView = getWindow().getDecorView();   //Fragment的View為Activity的decorView
            return contentView;
        }

        @Override
        public void onResume() {
            super.onResume();
            BasePadActivity.this.onResume();  //模擬Activity 
        }

        @Override
        public void onPause() {
            super.onPause();
            BasePadActivity.this.onPause(); //模擬Activity onPause
        }
        ........
        ........
    }
}  

程式碼巧妙實現了所有繼承 BasePadActivity 的 Activity 轉換為Fragment 的過程,當然這裡只展示了轉換一小部分,其他細節問題並沒有在程式碼中列出來。當開啟 Activity 的過程轉換為開啟 Fragment 的過程後,我們需要讓 Fragment 模擬 Activity 的行為。

2,Fragment 模擬 Activity 的行為

模擬 Activity 的 finish 方法

 BasePadActivity.java    

 public void finish() {  
    if (bActivityToFragment) {
        removeTopFragment(); //移除Fragment棧中最頂層Fragment
    } 
}  

模擬 Activity 的 onActivityResult,在當前 Fragment 被 finish 去觸發

private void removeTopFragment() {
    if(popBackStackImmediate()){     //成功把頂層Fragment,從Fragment棧中移出。
        handleSetResult(requestCode,false);
    }
}

private void handleSetResult(int requestCode){
     //觸發上層Fragment的onActivityResult
    topFragment.onActivityResult(requestCode, resultCode, data);  
}

模擬 Activity 的返回事件,當把 Activity 轉換為 Fragment 時,其返回事件已經由該 Fragment 所屬的 Activity 接收,因此需要處理其所屬真正 Activity 的返回事件。通過 FragmentManager 可以管理該 Activity 中所有 Fragment。

BasePadActivity.java     

public void onBackPressed() {
    if(isActivityForFragment()){  //,為Fragment所屬Activity
        if(!handleFragmentBackEvent()){  //先處理Fragment本身的返回事件,比如想先關閉當前Fragment選單。
            finishTopFragment();  //最後finish該Fragment
        }
    }
}  

模擬 Activity 的啟動方式,通過獲取 intent.getFlags()值,來判斷 Activity 的啟動模式,通過

public boolean hasFlagClearTop(int flags){
     return  (flags & Intent.FLAG_ACTIVITY_CLEAR_TOP )!=0  ;
}

public boolean hasFlagNewTask(int flags){
    return  (flags & Intent.FLAG_ACTIVITY_NEW_TASK )!=0  ;
}  

來判斷 Flag 中是否包含相應啟動方式值,來對 Fragment 的開啟做相應處理。這裡會稍微麻煩一點,故不作程式碼說明了。

3,Fragment 實現自定義的 TitleBar

在不改變原來 Activity 程式碼的情況下,通過改變 Window 物件,自己實現對 Fragment 佈局的控制。實現自定義 Titlebar。
BasePadActivity.java

public Window getWindow() {
    if(customWindow==null){   //自定義的window,主要為了解決很多Activity繼承IphoneTitleBar的問題
            customWindow = new Window4FragmentTitle(mContext); 
        }
        return customWindow;
}  

Window4FragmentTitle.java    

public class Window4FragmentTitle extends Window{
    @Override
    public void setContentView(View view, LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        } 
        mContentParent.addView(view, params);
    }

    @Override
    public void setFeatureInt(int featureId, int value) {
         if(featureId != FEATURE_CUSTOM_TITLE){
             return;
         }
         FrameLayout titleContainer = (FrameLayout) findViewById(android.R.id.title);
         if (titleContainer != null) {
             mLayoutInflater.inflate(value, titleContainer);
         }
    }
}  

通過 Window4FragmentTitle 呼叫 setFeatureInt(int)方法,會把自定義的 Title 佈局嵌入到我們建立的佈局 mDecor 中,然後把 mDecor 放入 Fragment,實現自定義 Titlebar 在 Fragment 中的顯示。

4,使用 LocalActivityManager,實現對每個 Tab 中 Fragments 的獨立管理。

使用 LocalActivityManager 實現標籤佈局,使每個 Tab 中都有一個 Acitvity 物件,而每個 Activity 中都會有一個 FragmentManager 對該 Tab 的 Frament 棧進行管理,這樣每個 Tab 的 Fragments 相互獨立,互不影響。
主Activity

    addFrame(Tab1Content.class, mTabs[1]);   //Tab1
    addFrame(Tab2Content.class, mTabs[2]);    //Tab2
    .....
    public void addFrame(){
        mTabHost.setup(getLocalActivityManager());
        TabSpec tabSpec = mTabHost.newTabSpec("").setIndicator(tab).setContent(new Intent(this, clz));
        mTabHost.addTab(tabSpec);

    }

通過 setContent(new Intent(this, clz) ,把每個標籤欄內容以子 Activity 的方式新增進來。

5,多工分屏顯示。

前面說過,對於多程式的 Activity,為了保持其模組化以及分擔主程式記憶體壓力的特點,經過大家討論,不把他們轉換為 Fragment,那麼就需要解決多個 Activity 一起展示的問題。經過研究,得出的有效實現方式是:讓在每個標籤欄內開啟的 Activity 透明化,並且讓其大小和位置剛好居於設計圖3區,同時能讓處於該 Activity 下方的左側區域的主Activity 接收點選事件

1,Activity 透明化實現,在配置檔案中宣告 Activity 的 theme 為透明
2,定義 Activity 的大小和位置。
在 Activity 的 onCreate 方法中實現

WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.gravity = Gravity.RIGHT | Gravity.TOP;  //位置居於右側
layoutParams.width =  mActivity.getRightPanelWidth(); //寬度為右側區域寬度    

3,讓左側主 Activity 接收事件
通過設定 window 的 WindowManager.LayoutParams 的 flag 為 FLAG_NOT_TOUCH_MODAL 可以讓顯示在透明 Activity 左側下方的主 Activity 接收事件。
新開啟的 Activity 位於右區,左區為主 Activity 顯示區域。左區和右區能同時接收使用者點選事件,看起來就好像同一個 Activity 一樣。

但是由於在當前 Tab 開啟的位於右區的 Activity,是跟隨當前Tab的,在切換 Tab 後,應該消失,比如 Tab1中開啟的 Actvity,切換到Tab2時應該隱藏掉,重新再切換回 Tab1時讓其重新顯示,保留現場。該功能要如何實現呢?經過對 Android 特性的理解以及思考,發現可以是用多工分屏顯示方式實現不同 Tab 多程式 Activitys 的顯示和隱藏。讓不同Tab開啟的 Activitys 分屬於不同 Task,每個 task 擁有一個 Activity 棧來管理其中 Activity,切換 tab 要做的就是不同 Task 的切換。這樣邏輯非常清晰,也符合快速 Pad 化的原則。那麼具體該怎麼實現呢?
為每個 Tab 開啟的第一個 Activity 提供一個不同的 TaskAffinity
首先我們來了解,什麼是 TaskAffinity
在某些情況下,Android 需要知道一個 Activity 屬於哪個 Task,這是通過任務共用性(TaskAffinity)完成的,TaskAffinity 為執行一個或多個Activity 的 task 提供一個獨特的名稱,當使用 Intent.FLAG_ACTIVITY_NEW_TASK 標誌的 Activity,併為該 Activity 宣告一個獨特的 TaskAffinity 時,該 Activity 不再執行在啟動它的 Task 裡,而是會重新啟動一個新的 Task,新的 task 管理一個新的 Activity 棧,而開啟的這個 Activity 則位於棧底。
瞭解了 TaskAffinity,我們在配置檔案中為開啟的多程式 Activity 設定相關 tab 的 TaskAffinity 值
下面展示對 web 程式 Activity 的處理

<activity
  android:name=" BrowserActivity1"
  android:process=":web"
  android:taskAffinity="com.tab1. BrowserActivity1" 
  android:theme="@style/Default.Transparent" />

<activity
  android:name=" BrowserActivity2"
  android:process=":web"
  android:taskAffinity="com.tab2.BrowserActivity2" 
  android:theme="@style/Default.Transparent"/>

........  

在不同 Tab 開啟的 BrowserActivity,都為它們設定了不同的 TaskAffinity,在程式碼中當發現開啟的頁面是 Web 頁面時,則在哪個Tab開啟,頁面重定向到設定了相應 TaskAffinity 的 Activity上。

public void startActivityForResult(Intent intent) {
  if(isBrowserActivity(intent)){   //純開啟QQBrowserActivity
    int curTab = getTabIndex();  //獲取當前Tab索引
    Class<?> c = getBrowserMap().get(curTab);  //取出相應開啟的Activity
    redirectAndOpenInNewTask()    //重定向    
  }
  ......
  ......
}  

這樣就為在不同 Tab 開啟的 Activity 建立了不同的 Task。然後在切換Tab時通過傳送廣播動態的顯示和隱藏 Task,

public void onTabSelected(int curTabIndex) {
    Intent i = new Intent("action");
    i.putExtra("cur_Tab_Id",curTabIndex);    //切換到當前Tab的索引
    sendBroadcast(i);
}

在 Task 的根 Activity 中接收廣播,處理 Task 顯示和隱藏邏輯

public void onReceive(Context context, Intent intent) {
        int tab = intent.getIntExtra(CUR_TAB_ID,-1);
            if(tabIndex>=0 && tabIndex == tab){
                 moveTaskToFront(mActivity.getTaskId());   //移動當前Task移入後臺
            }else{
                 moveTaskToBack();  //把該Activity所屬Task移動到前臺

            }
    }  

到這裡基本上解決了多程式 Activity 與主 Activity 同屏顯示所帶來的問題。

總結

通過上述方案,以及一些問題的巧妙解決,終於實現了 MyApp Pad 化的快速開發,而且 MyApp for Pad 第一個版本的釋出上線,到現在6到7個版本的迭代,一直都是穩定執行的。該方案的優點是,只要維護好架構,其他開發者在對單個頁面改造時,不需要管它是真正 Activity 還是 Fragment,只要知道這些頁面表現的都是 Activity 的行為,就和在手機 APP 上開發是一樣的。

擴充套件

通過 MyApp Pad 化開發方案的實現,我們想我們這個方案是否可以寫成一個通用的手機 App Pad 化元件,為公司的其他Android產品Pad化提供技術支援。就算你的 APP 佈局並不是 Tab 方式,我們開放出一個 Base Activity,其他 App 的 Activity 通過繼承 Base,就能自動轉換為Fragment,並且能為多程式 Activity 提供處理方案。但這肯定需要考慮更多的情況,和解決更多問題。路漫漫其修遠兮,吾將上下而求索。期待完善的 Android App PAD 元件能與大家見面。

如果你覺得內容意猶未盡,如果你想了解更多相關資訊,請掃描以下二維碼,關注我們的公眾賬號,可以獲取更多技術類乾貨,還有精彩活動與你分享~

 

 

騰訊 Bugly是一款專為移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的情況以及解決方案。智慧合併功能幫助開發同學把每天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響使用者數最多的崩潰,精準定位功能幫助開發同學定位到出問題的程式碼行,實時上報可以在釋出後快速的瞭解應用的質量情況,適配最新的 iOS, Android 官方作業系統,鵝廠的工程師都在使用,快來加入我們吧!

 

相關文章