深入解析Android系統(tǒng)中應(yīng)用程序前后臺切換的實現(xiàn)要點
在介紹程序?qū)崿F(xiàn)之前,我們先看下Android中Activities和Task的基礎(chǔ)知識。
我們都知道,一個Activity 可以啟動另一個Activity,即使這個Activity是定義在別一個應(yīng)用程序里的,比如說,想要給用戶展示一個地圖的信息,現(xiàn)在已經(jīng)有一個Activity可以做這件事情,那么現(xiàn)在你的Activity需要做的就是將請求信息放進(jìn)一個Intent對象里,并且將這個Intent對象傳遞給startActivity(),那么地圖就可顯示出來了,但用戶按下Back鍵之后,你的Activity又重新出現(xiàn)在屏幕上。
對用戶來講,顯示地圖的Activity和你的Activity好像在一個應(yīng)用程序中的,雖然是他們是定義在其他的應(yīng)用程序中并且運(yùn)行在那個應(yīng)有進(jìn)程中。Android將你的Activity和借用的那個Activity被放進(jìn)一個Task中以維持用戶的體驗。那么Task是以棧的形式組織起來一組相互關(guān)聯(lián)的Activity,棧中底部的Activity就是開辟這個Task的,通常是用戶在應(yīng)用程序啟動器中選擇的Activity。棧的頂部的Activity是當(dāng)前正在運(yùn)行的Activity--用戶正在交互操作的Activity。
當(dāng)一個Activity啟動另一個Activity時,新啟動的Activity被壓進(jìn)棧中,成為正在運(yùn)行的Activity。舊的Activity仍然在棧中。當(dāng)用戶按下BACK鍵之后,正在運(yùn)行的Activity彈出棧,舊的Activity恢復(fù)成為運(yùn)行的Activity。棧中包含對象,因此如果一個任務(wù)中開啟了同一個Activity子類的的多個對象——例如,多個地圖瀏覽器——則棧對每一個實例都有一個單獨的入口。棧中的Activity不會被重新排序,只會被、彈出。Task是一組Activity實例組成的棧,不是在manifest文件里的某個類或是元素,所以無法設(shè)定一個Task的屬性而不管它的Activity,一個Task的所有屬性值是在底部的Activity里設(shè)置的,這就需要用于Affinity。關(guān)于Affinity這里不再詳述,大家可以查詢文檔。
一個Task里的所有Activity作為一個整體運(yùn)轉(zhuǎn)。整個Task(整個Activity堆棧)可以被推到前臺或被推到后臺。假設(shè)一個正在運(yùn)行的Task中有四個Activity——正在運(yùn)行的Activity下面有三個Activity,這時用戶按下HOME鍵,回到應(yīng)有程序啟動器然后運(yùn)行新的應(yīng)用程序(實際上是運(yùn)行了一個新的Task),那么當(dāng)前的Task就退到了后臺,新開啟的應(yīng)用程序的root Activity此時就顯示出來了,一段時間后,用戶又回到應(yīng)用程序器,又重新選擇了之前的那個應(yīng)用程序(先前的那個Task),那么先前的那個Task此時又回到了前臺了,當(dāng)用戶按下BACK鍵時,屏幕不是顯示剛剛離開的那個新開啟的那個應(yīng)用程序的Activity,而是被除回到前臺的那個Task的棧頂Activity,將這個Task的下一個Activity顯示出來。 上述便是Activity和Task一般的行為,但是這個行為的幾乎所有方面都是可以修改的。Activity和Task的關(guān)系,以及Task中Activity的行為,是受啟動該Activity的Intent對象的標(biāo)識和在manifest文件中的Activity的<Activity>元素的屬性共同影響的。
以上是關(guān)于Activity和Task的描述。
在開發(fā)Android項目時,用戶難免會進(jìn)行程序切換,在切換過程中,程序?qū)⑦M(jìn)入后臺運(yùn)行,需要用時再通過任務(wù)管理器或是重新點擊程序或是通過點擊信息通知欄中的圖標(biāo)返回原來的界面。這種效果類似于騰訊QQ的效果,打開QQ后顯示主界面,在使用其他的程序時,QQ將以圖標(biāo)的形式顯示在信息通知欄里,如果再用到QQ時再點擊信息通知欄中的圖標(biāo)顯示QQ主界面。
先看下本示例實現(xiàn)效果圖:
在上圖第二個圖中,我們點擊時將會返回到的原來的Activity中。
當(dāng)我們的程序進(jìn)入后臺運(yùn)作時,在我們的模擬器頂部將以圖標(biāo)形式出現(xiàn),如下圖:
對于這種效果一般的做法是在Activity中的onStop()方法中編寫相應(yīng)代碼,因為當(dāng)Activity進(jìn)入后臺時將會調(diào)用onStop()方法,我們可以在onStop()方法以Notification形式顯示程序圖標(biāo)及信息,其中代碼如下所示:
@Override protected void onStop() { // TODO Auto-generated method stub super.onStop(); Log.v("BACKGROUND", "程序進(jìn)入后臺"); showNotification(); }
以上的showNotification()方法就是Notification。
然后點擊信息通知欄的Notification后再返回到原來的Activity。
當(dāng)然,我們也可以捕捉HOME鍵,在用戶按下HOME鍵時顯示Notification, 以下是代碼示例:
// 點擊HOME鍵時程序進(jìn)入后臺運(yùn)行 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub // 按下HOME鍵 if(keyCode == KeyEvent.KEYCODE_HOME){ // 顯示Notification notification = new NotificationExtend(this); notification.showNotification(); moveTaskToBack(true); return true; } return super.onKeyDown(keyCode, event); }
這里的NotificationExtend是對顯示Notification的一個封裝,類中的代碼如下:
package com.test.background; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Intent; import android.graphics.Color; /** * Notification擴(kuò)展類 * @Description: Notification擴(kuò)展類 * @File: NotificationExtend.java * @Package com.test.background */ public class NotificationExtend { private Activity context; public NotificationExtend(Activity context) { // TODO Auto-generated constructor stub this.context = context; } // 顯示Notification public void showNotification() { // 創(chuàng)建一個NotificationManager的引用 NotificationManager notificationManager = ( NotificationManager)context.getSystemService( android.content.Context.NOTIFICATION_SERVICE); // 定義Notification的各種屬性 Notification notification = new Notification( R.drawable.icon,"閱讀器", System.currentTimeMillis()); // 將此通知放到通知欄的"Ongoing"即"正在運(yùn)行"組中 notification.flags |= Notification.FLAG_ONGOING_EVENT; // 表明在點擊了通知欄中的"清除通知"后,此通知自動清除。 notification.flags |= Notification.FLAG_AUTO_CANCEL notification.flags |= Notification.FLAG_SHOW_LIGHTS; notification.defaults = Notification.DEFAULT_LIGHTS; notification.ledARGB = Color.BLUE; notification.ledOnMS = 5000; // 設(shè)置通知的事件消息 CharSequence contentTitle = "閱讀器顯示信息"; // 通知欄標(biāo)題 CharSequence contentText = "推送信息顯示,請查看……"; // 通知欄內(nèi)容 Intent notificationIntent = new Intent(context,context.getClass()); notificationIntent.setAction(Intent.ACTION_MAIN); notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER); PendingIntent contentIntent = PendingIntent.getActivity( context, 0, notificationIntent,PendingIntent.FLAG_UPDATE_CURRENT); notification.setLatestEventInfo( context, contentTitle, contentText, contentIntent); // 把Notification傳遞給NotificationManager notificationManager.notify(0, notification); } // 取消通知 public void cancelNotification(){ NotificationManager notificationManager = ( NotificationManager) context.getSystemService( android.content.Context.NOTIFICATION_SERVICE); notificationManager.cancel(0); } }
這里需要在配置文件中設(shè)置每個Activity以單任務(wù)運(yùn)行,否則,每次返回原Activity時會新增加一個Activity,而不會返回到原Activity。
在使用FLAG_ACTIVITY_NEW_TASK控制標(biāo)識時也會出現(xiàn)不會返回到原Activity的現(xiàn)象。如果該標(biāo)識使一個Activity開始了一個新的Task,然后當(dāng)用戶按了HOME鍵離開這個Activity,在用戶按下BACK鍵時將無法再返回到原Activity。一些應(yīng)用(例如Notification)總是在一個新的Task里打開Activity,而從來不在自己的Task中打開,所以它們總是將包含F(xiàn)LAG_ACTIVITY_NEW_TASK的Intent傳遞給startActivity()。所以如果有一個可以被其他的東西以這個控制標(biāo)志調(diào)用的Activity,請注意讓應(yīng)用程序有獨立的回到原Activity的方法。 代碼如下:
<activity android:name="ShowMessageActivity" android:launchMode="singleTask"></activity>
Android應(yīng)用前后臺切換的判斷
Android中沒有提供一個應(yīng)用前后臺切換的回調(diào)或廣播,這個功能只能我們自己來處理。以前遇到這個問題的處理方式是,實現(xiàn)一個BaseActivity,然后讓其他所有Activity都繼承自它,然后在生命周期函數(shù)中做相應(yīng)的檢測。具體檢測方法如下:
在Activity的onStart和onStop方法中進(jìn)行計數(shù),計數(shù)變量為count,在onStart中將變量加1,onStop中減1,假設(shè)應(yīng)用有兩個Activity,分別為A和B。
情況一、首先啟動A,A再啟動B:啟動A,count=1,A啟動B,生命周期的順序為B.onStart->A.onStop,count的計數(shù)仍然為1。
情況二、首先啟動A,然后按Home鍵返回桌面:啟動A,count=1,按Home鍵返回桌面,會執(zhí)行A.onStop,count的計數(shù)變位0。
從上面的兩種情況看出,可以通過對count計數(shù)為0,來判斷應(yīng)用被從前臺切到了后臺。同樣的,從后臺切到前臺也是類似的道理。具體實現(xiàn)看后面的代碼。
但是如果項目中不是所有的Activity都繼承自同一個BaseActivity,就無法實現(xiàn)這個功能了。幸運(yùn)的是,Android在API 14之后,在Application類中,提供了一個應(yīng)用生命周期回調(diào)的注冊方法,用來對應(yīng)用的生命周期進(jìn)行集中管理,這個接口叫registerActivityLifecycleCallbacks,可以通過它注冊自己的ActivityLifeCycleCallback,每一個Activity的生命周期都會回調(diào)到這里的對應(yīng)方法。其實這個注冊方法的本質(zhì)和我們實現(xiàn)BaseActivity是一樣的,只是將生命周期管理移到了Activity本身的實現(xiàn)中。
具體使用方法如下:
public class MyApplication extends Application{ public int count = 0; @Override public void onCreate() { super.onCreate(); registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityStopped(Activity activity) { Log.v("viclee", activity + "onActivityStopped"); count--; if (count == 0) { Log.v("viclee", ">>>>>>>>>>>>>>>>>>>切到后臺 lifecycle"); } } @Override public void onActivityStarted(Activity activity) { Log.v("viclee", activity + "onActivityStarted"); if (count == 0) { Log.v("viclee", ">>>>>>>>>>>>>>>>>>>切到前臺 lifecycle"); } count++; } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { Log.v("viclee", activity + "onActivitySaveInstanceState"); } @Override public void onActivityResumed(Activity activity) { Log.v("viclee", activity + "onActivityResumed"); } @Override public void onActivityPaused(Activity activity) { Log.v("viclee", activity + "onActivityPaused"); } @Override public void onActivityDestroyed(Activity activity) { Log.v("viclee", activity + "onActivityDestroyed"); } @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { Log.v("viclee", activity + "onActivityCreated"); } }); } }
除此之外,有沒有其他方法可以實現(xiàn)這個功能呢?
當(dāng)應(yīng)用切到后臺的時候,運(yùn)行在前臺的進(jìn)程由我們的app變成了桌面app,依據(jù)這一點,我們可以實現(xiàn)檢測應(yīng)用前后臺切換的功能。在Activity的onStop生命周期中執(zhí)行檢測代碼,如果發(fā)現(xiàn)當(dāng)前運(yùn)行在前臺的進(jìn)程不是我們自己的進(jìn)程,說明應(yīng)用切到了后臺。
想想為什么要在onStop中檢測,而不是onPause?這是由于A啟動B時,生命周期的執(zhí)行順序如下:A.onPause->B.onCreate->B.onStart->B.onResume->A.onStop,也就是說,在A的onPause方法中,B的生命周期還沒有執(zhí)行,進(jìn)程沒有進(jìn)入前臺,當(dāng)然是檢測不到的。我們把代碼移到onPause生命周期中,發(fā)現(xiàn)確實沒有效果。
具體實現(xiàn)代碼如下:
//用來控制應(yīng)用前后臺切換的邏輯 private boolean isCurrentRunningForeground = true; @Override protected void onStart() { super.onStart(); if (!isCurrentRunningForeground) { Log.d(TAG, ">>>>>>>>>>>>>>>>>>>切到前臺 activity process"); } } @Override protected void onStop() { super.onStop(); isCurrentRunningForeground = isRunningForeground(); if (!isCurrentRunningForeground) { Log.d(TAG,">>>>>>>>>>>>>>>>>>>切到后臺 activity process"); } } public boolean isRunningForeground() { ActivityManager activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> appProcessInfos = activityManager.getRunningAppProcesses(); // 枚舉進(jìn)程 for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessInfos) { if (appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { if (appProcessInfo.processName.equals(this.getApplicationInfo().processName)) { Log.d(TAG,"EntryActivity isRunningForeGround"); return true; } } } Log.d(TAG, "EntryActivity isRunningBackGround"); return false; }
相關(guān)文章
Android EditText實現(xiàn)關(guān)鍵詞批量搜索示例
本篇文章主要介紹了Android EditText實現(xiàn)關(guān)鍵詞批量搜索示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02Android 出現(xiàn)“Can''t bind to local 8602 for debugger”錯誤的解決方法
這篇文章主要介紹了Android 出現(xiàn)“Can't bind to local 8602 for debugger”錯誤的解決方法的相關(guān)資料,需要的朋友可以參考下2017-03-03Intent傳遞對象之Serializable和Parcelable的區(qū)別
Intent在不同的組件中傳遞對象數(shù)據(jù)的應(yīng)用非常普遍,大家都知道在intent傳遞對象的方法有兩種:1、實現(xiàn)Serializable接口、2、實現(xiàn)Parcelable接口,接下來通過本文給大家介紹Intent傳遞對象之Serializable和Parcelable的區(qū)別,感興趣的朋友一起學(xué)習(xí)吧2016-01-01Android編程實現(xiàn)的EditText彈出打開和關(guān)閉工具類
這篇文章主要介紹了Android編程實現(xiàn)的EditText彈出打開和關(guān)閉工具類,涉及Android輸入框EditText彈出打開和關(guān)閉功能簡單實現(xiàn)技巧,需要的朋友可以參考下2018-02-02