Android開發(fā)中實(shí)現(xiàn)應(yīng)用的前后臺(tái)切換效果
在介紹程序?qū)崿F(xiàn)之前,我們先看下Android中Activities和Task的基礎(chǔ)知識(shí)。
我們都知道,一個(gè)Activity 可以啟動(dòng)另一個(gè)Activity,即使這個(gè)Activity是定義在別一個(gè)應(yīng)用程序里的,比如說,想要給用戶展示一個(gè)地圖的信息,現(xiàn)在已經(jīng)有一個(gè)Activity可以做這件事情,那么現(xiàn)在你的Activity需要做的就是將請(qǐng)求信息放進(jìn)一個(gè)Intent對(duì)象里,并且將這個(gè)Intent對(duì)象傳遞給startActivity(),那么地圖就可顯示出來了,但用戶按下Back鍵之后,你的Activity又重新出現(xiàn)在屏幕上。
對(duì)用戶來講,顯示地圖的Activity和你的Activity好像在一個(gè)應(yīng)用程序中的,雖然是他們是定義在其他的應(yīng)用程序中并且運(yùn)行在那個(gè)應(yīng)有進(jìn)程中。Android將你的Activity和借用的那個(gè)Activity被放進(jìn)一個(gè)Task中以維持用戶的體驗(yàn)。那么Task是以棧的形式組織起來一組相互關(guān)聯(lián)的Activity,棧中底部的Activity就是開辟這個(gè)Task的,通常是用戶在應(yīng)用程序啟動(dòng)器中選擇的Activity。棧的頂部的Activity是當(dāng)前正在運(yùn)行的Activity--用戶正在交互操作的Activity。
當(dāng)一個(gè)Activity啟動(dòng)另一個(gè)Activity時(shí),新啟動(dòng)的Activity被壓進(jìn)棧中,成為正在運(yùn)行的Activity。舊的Activity仍然在棧中。當(dāng)用戶按下BACK鍵之后,正在運(yùn)行的Activity彈出棧,舊的Activity恢復(fù)成為運(yùn)行的Activity。棧中包含對(duì)象,因此如果一個(gè)任務(wù)中開啟了同一個(gè)Activity子類的的多個(gè)對(duì)象——例如,多個(gè)地圖瀏覽器——?jiǎng)t棧對(duì)每一個(gè)實(shí)例都有一個(gè)單獨(dú)的入口。棧中的Activity不會(huì)被重新排序,只會(huì)被、彈出。Task是一組Activity實(shí)例組成的棧,不是在manifest文件里的某個(gè)類或是元素,所以無法設(shè)定一個(gè)Task的屬性而不管它的Activity,一個(gè)Task的所有屬性值是在底部的Activity里設(shè)置的,這就需要用于Affinity。關(guān)于Affinity這里不再詳述,大家可以查詢文檔。
一個(gè)Task里的所有Activity作為一個(gè)整體運(yùn)轉(zhuǎn)。整個(gè)Task(整個(gè)Activity堆棧)可以被推到前臺(tái)或被推到后臺(tái)。假設(shè)一個(gè)正在運(yùn)行的Task中有四個(gè)Activity——正在運(yùn)行的Activity下面有三個(gè)Activity,這時(shí)用戶按下HOME鍵,回到應(yīng)有程序啟動(dòng)器然后運(yùn)行新的應(yīng)用程序(實(shí)際上是運(yùn)行了一個(gè)新的Task),那么當(dāng)前的Task就退到了后臺(tái),新開啟的應(yīng)用程序的root Activity此時(shí)就顯示出來了,一段時(shí)間后,用戶又回到應(yīng)用程序器,又重新選擇了之前的那個(gè)應(yīng)用程序(先前的那個(gè)Task),那么先前的那個(gè)Task此時(shí)又回到了前臺(tái)了,當(dāng)用戶按下BACK鍵時(shí),屏幕不是顯示剛剛離開的那個(gè)新開啟的那個(gè)應(yīng)用程序的Activity,而是被除回到前臺(tái)的那個(gè)Task的棧頂Activity,將這個(gè)Task的下一個(gè)Activity顯示出來。 上述便是Activity和Task一般的行為,但是這個(gè)行為的幾乎所有方面都是可以修改的。Activity和Task的關(guān)系,以及Task中Activity的行為,是受啟動(dòng)該Activity的Intent對(duì)象的標(biāo)識(shí)和在manifest文件中的Activity的<Activity>元素的屬性共同影響的。
以上是關(guān)于Activity和Task的描述。
在開發(fā)Android項(xiàng)目時(shí),用戶難免會(huì)進(jìn)行程序切換,在切換過程中,程序?qū)⑦M(jìn)入后臺(tái)運(yùn)行,需要用時(shí)再通過任務(wù)管理器或是重新點(diǎn)擊程序或是通過點(diǎn)擊信息通知欄中的圖標(biāo)返回原來的界面。這種效果類似于騰訊QQ的效果,打開QQ后顯示主界面,在使用其他的程序時(shí),QQ將以圖標(biāo)的形式顯示在信息通知欄里,如果再用到QQ時(shí)再點(diǎn)擊信息通知欄中的圖標(biāo)顯示QQ主界面。
先看下本示例實(shí)現(xiàn)效果圖:


在上圖第二個(gè)圖中,我們點(diǎn)擊時(shí)將會(huì)返回到的原來的Activity中。
當(dāng)我們的程序進(jìn)入后臺(tái)運(yùn)作時(shí),在我們的模擬器頂部將以圖標(biāo)形式出現(xiàn),如下圖:

對(duì)于這種效果一般的做法是在Activity中的onStop()方法中編寫相應(yīng)代碼,因?yàn)楫?dāng)Activity進(jìn)入后臺(tái)時(shí)將會(huì)調(diào)用onStop()方法,我們可以在onStop()方法以Notification形式顯示程序圖標(biāo)及信息,其中代碼如下所示:
@Override
protected void onStop() {
// TODO Auto-generated method stub
super.onStop();
Log.v("BACKGROUND", "程序進(jìn)入后臺(tái)");
showNotification();
}
以上的showNotification()方法就是Notification。
然后點(diǎn)擊信息通知欄的Notification后再返回到原來的Activity。
當(dāng)然,我們也可以捕捉HOME鍵,在用戶按下HOME鍵時(shí)顯示Notification, 以下是代碼示例:
// 點(diǎn)擊HOME鍵時(shí)程序進(jìn)入后臺(tái)運(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是對(duì)顯示Notification的一個(gè)封裝,類中的代碼如下:
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
* @Author Hanyonglu
* @Date 2012-4-13 下午02:00:44
* @Version V1.0
*/
public class NotificationExtend {
private Activity context;
public NotificationExtend(Activity context) {
// TODO Auto-generated constructor stub
this.context = context;
}
// 顯示Notification
public void showNotification() {
// 創(chuàng)建一個(gè)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;
// 表明在點(diǎn)擊了通知欄中的"清除通知"后,此通知自動(dòng)清除。
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 = "推送信息顯示,請(qǐng)查看……"; // 通知欄內(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è)置每個(gè)Activity以單任務(wù)運(yùn)行,否則,每次返回原Activity時(shí)會(huì)新增加一個(gè)Activity,而不會(huì)返回到原Activity。
在使用FLAG_ACTIVITY_NEW_TASK控制標(biāo)識(shí)時(shí)也會(huì)出現(xiàn)不會(huì)返回到原Activity的現(xiàn)象。如果該標(biāo)識(shí)使一個(gè)Activity開始了一個(gè)新的Task,然后當(dāng)用戶按了HOME鍵離開這個(gè)Activity,在用戶按下BACK鍵時(shí)將無法再返回到原Activity。一些應(yīng)用(例如Notification)總是在一個(gè)新的Task里打開Activity,而從來不在自己的Task中打開,所以它們總是將包含F(xiàn)LAG_ACTIVITY_NEW_TASK的Intent傳遞給startActivity()。所以如果有一個(gè)可以被其他的東西以這個(gè)控制標(biāo)志調(diào)用的Activity,請(qǐng)注意讓應(yīng)用程序有獨(dú)立的回到原Activity的方法。 代碼如下:
<activity android:name="ShowMessageActivity"
android:launchMode="singleTask"></activity>
這里需要注意的是:
<activity>下的launchMode屬性可以設(shè)置四種啟動(dòng)方式:
- standard (默認(rèn)模式)
- singleTop
- singleTask
- singleInstance
這四個(gè)模式有以下的幾個(gè)不同點(diǎn):
1. 響應(yīng)Intent時(shí)Activity將被裝入哪個(gè)task。
對(duì)于standard和singleTop模式,由產(chǎn)生該Intent(調(diào)用startActivity())的task持有該Activity——除非Intent對(duì)象里含有FLAG_ACTIVITY_NEW_TASK標(biāo)志,那么就會(huì)尋找一個(gè)新的task。
相反的,singTask和singleInstance模式,總是標(biāo)志Activity為task的root Activity,開啟這樣的活動(dòng)會(huì)新建一個(gè)task,而不是裝入某個(gè)正在運(yùn)行的任務(wù)。
2. 一個(gè)Activity是否可以有多個(gè)實(shí)例。
一個(gè)standard或者singleTop屬性的Activity可以實(shí)例化多次,他們可以屬于多個(gè)不同的task,而且一個(gè)task也可以含有相同Activity的多個(gè)實(shí)例。
相反的,singleTask或者singleInstance屬性的Activity只能有一個(gè)實(shí)例(單例),因?yàn)檫@些Activity是位于task的底部,這種限制意味著同一設(shè)備的同一時(shí)刻該task只能有一個(gè)實(shí)例。
3. 實(shí)例是否能允許在它的task里有其他的Activity。
一個(gè)singleInstance屬性的Activity是它所在的task里僅有的一個(gè)Activity,如果他啟動(dòng)了另一個(gè)Activity,那個(gè)Activity會(huì)被加載進(jìn)一個(gè)不同的task而無視它的啟動(dòng)模式——就如Intent里有FLAG_ACTIVITY_NEW_TASK標(biāo)識(shí)一樣。在其他的方面,singleInstance和singleTask一樣的。
其他三個(gè)模式允許有多個(gè)Activity在一個(gè)task里,一個(gè)singleTask屬性的Activity總是一個(gè)task里的root Activity,但是他可以啟動(dòng)另外的Activity并且將這個(gè)新的Activity裝進(jìn)同一個(gè)task里,standard和singleTop屬性的Activity可以出現(xiàn)在task的任何位置。
4. 是否創(chuàng)建一個(gè)新的Activity實(shí)例來處理一個(gè)新的Intent。
對(duì)于默認(rèn)的standard方式,將會(huì)生成新的實(shí)例來處理每一個(gè)新的Intent。每個(gè)實(shí)例處理一個(gè)新的Intent。
對(duì)singleTop模式,如果一個(gè)已經(jīng)存在的實(shí)例在目標(biāo)task的棧頂,那么就重用這個(gè)實(shí)例來處理這個(gè)新的Intent,如果這個(gè)實(shí)例存在但是不在棧頂,那就不重用他,而是重新創(chuàng)建一個(gè)實(shí)例來處理這個(gè)新的Intent并且將這個(gè)實(shí)例壓入棧。
例如現(xiàn)在有一個(gè)task堆棧ABCD,A是root Activity,D是棧頂Activity,現(xiàn)在有一個(gè)啟動(dòng)D的Intent來了,如果D是默認(rèn)的standard方法,那么就會(huì)創(chuàng)建一個(gè)新的實(shí)例來處理這個(gè)Intent,所以這個(gè)堆棧就變?yōu)锳BCDD,然而如果D是singleTop方式,這個(gè)已經(jīng)存在的棧頂?shù)腄就會(huì)來處理這個(gè)Intent,所以堆棧還是ABCD。
如果另外一種情況,到來的Intent是給B的,不管B是standard還是singleTop(因?yàn)楝F(xiàn)在B不在棧頂),都會(huì)創(chuàng)建一個(gè)新的實(shí)例,所以堆棧變?yōu)锳BCDB。
如上所述,一個(gè)"singleTask"或"singleInstance"模式的activity只會(huì)有一個(gè)實(shí)例,這樣它們的實(shí)例就會(huì)處理所有的新intent。一個(gè)"singleInstance" activity總是在棧里的最上面
(因?yàn)樗莟ask里的唯一的activity), 這樣它總是可以處理一個(gè)intent。而一個(gè)"singleTask" activity在棧里可以有或沒有其他activity在它上面。如果有的話,它就不能對(duì)新到的intent進(jìn)行處理,intent將被丟棄。(即使intent被丟棄,它的到來將使task來到前臺(tái),并維持在那里。)
當(dāng)一個(gè)已有的Activity被請(qǐng)求去處理一個(gè)新的Intent時(shí),Intent對(duì)象會(huì)通過onNewIntent()的調(diào)用傳遞給這個(gè)活動(dòng)。(傳遞進(jìn)來的原始的Intent對(duì)象可以通過調(diào)用getIntent()獲取)。
注意,當(dāng)創(chuàng)建一個(gè)新的Activity的實(shí)例來處理一個(gè)新收到的Intent時(shí),用戶可以按BACK鍵回到上一個(gè)狀態(tài)(上一個(gè)Activity)。但是使用一個(gè)已有的Activity實(shí)例操作新收到的Intent時(shí),用戶不能通過按下BACK鍵回到這個(gè)實(shí)例在接受到新Intent之前的狀態(tài)。
呵呵,不好意思,扯得有點(diǎn)多了,我們繼續(xù)看我們的程序。
在這里,如果是對(duì)一個(gè)Activity實(shí)現(xiàn)時(shí)可以這樣實(shí)現(xiàn),如果有多個(gè)Activity,我們就需要在每個(gè)Activity里重寫onKeyDown事件并捕捉用戶是否按下HOME鍵。
為了實(shí)現(xiàn)方便,我們可以使用一個(gè)Service專門用于監(jiān)聽程序是否進(jìn)入后臺(tái)或前臺(tái)工作,如果程序進(jìn)入后臺(tái)運(yùn)行就顯示Notification,這樣不管程序中有多少個(gè)Activity就可以很方便的實(shí)現(xiàn)程序前后如切換。
為此,我在程序中新添加了一個(gè)AppStatusService 類,目的是監(jiān)聽程序是否在前后臺(tái)運(yùn)行,如果在后臺(tái)運(yùn)行則顯示信息提示。
代碼如下:
package com.test.service;
import java.util.List;
import com.test.background.MainActivity;
import com.test.background.NotificationExtend;
import com.test.background.R;
import com.test.util.AppManager;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.IBinder;
import android.util.Log;
/**
* 監(jiān)聽程序是否在前后臺(tái)運(yùn)行Service
* @Description: 監(jiān)聽程序是否在前后臺(tái)運(yùn)行Service
* @FileName: AppStatusService.java
* @Package com.test.service
* @Author Hanyonglu
* @Date 2012-4-13 下午04:13:47
* @Version V1.0
*/
public class AppStatusService extends Service{
private static final String TAG = "AppStatusService";
private ActivityManager activityManager;
private String packageName;
private boolean isStop = false;
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
packageName = this.getPackageName();
System.out.println("啟動(dòng)服務(wù)");
new Thread() {
public void run() {
try {
while (!isStop) {
Thread.sleep(1000);
if (isAppOnForeground()) {
Log.v(TAG, "前臺(tái)運(yùn)行");
} else {
Log.v(TAG, "后臺(tái)運(yùn)行");
showNotification();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
return super.onStartCommand(intent, flags, startId);
}
/**
* 程序是否在前臺(tái)運(yùn)行
* @return
*/
public boolean isAppOnForeground() {
// Returns a list of application processes that are running on the device
List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if (appProcesses == null) return false;
for (RunningAppProcessInfo appProcess : appProcesses) {
// The name of the process that this object is associated with.
if (appProcess.processName.equals(packageName)
&& appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return true;
}
}
return false;
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
System.out.println("終止服務(wù)");
isStop = true;
}
// 顯示Notification
public void showNotification() {
// 創(chuàng)建一個(gè)NotificationManager的引用
NotificationManager notificationManager = (
NotificationManager)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;
// 點(diǎn)擊后自動(dòng)清除Notification
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 = "推送信息顯示,請(qǐng)查看……"; // 通知欄內(nèi)容
Intent notificationIntent = new Intent(AppManager.context,AppManager.context.getClass());
notificationIntent.setAction(Intent.ACTION_MAIN);
notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent contentIntent = PendingIntent.getActivity(
AppManager.context, 0, notificationIntent,PendingIntent.FLAG_UPDATE_CURRENT);
notification.setLatestEventInfo(
AppManager.context, contentTitle, contentText, contentIntent);
// 把Notification傳遞給NotificationManager
notificationManager.notify(0, notification);
}
}
在這里為了在信息提示欄里點(diǎn)擊后能夠返回到原來的Activity,需要在AppManager里記下我們當(dāng)前的Activity。
PS:監(jiān)聽程序是否進(jìn)入后臺(tái)
方法一:
/**
*判斷當(dāng)前應(yīng)用程序處于前臺(tái)還是后臺(tái)
*
* @param context
* @return
*/
public static boolean isApplicationBroughtToBackground(final Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> tasks = am.getRunningTasks(1);
if (!tasks.isEmpty()) {
ComponentName topActivity = tasks.get(0).topActivity;
if (!topActivity.getPackageName().equals(context.getPackageName())) {
return true;
}
}
return false;
}
這段代碼是需要一個(gè)權(quán)限的:
<uses-permission android:name="android.permission.GET_TASKS" />
方法二:
/**
*
* @param context
* @return
*/
public static boolean isBackground(Context context) {
ActivityManager activityManager = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> appProcesses = activityManager
.getRunningAppProcesses();
for (RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.processName.equals(context.getPackageName())) {
if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
Log.i(String.format("Background App:", appProcess.processName));
return true;
}else{
Log.i(String.format("Foreground App:", appProcess.processName));
return false;
}
}
}
return false;
}
方法三:
使用api提供的
@Override
protected void onUserLeaveHint() { //當(dāng)用戶按Home鍵等操作使程序進(jìn)入后臺(tái)時(shí)即開始計(jì)時(shí)
super.onUserLeaveHint();
if(!isLeave){
isLeave=true;
saveStartTime();
}
}
這個(gè)需要需要加一個(gè)intent flag
public static final int FLAG_ACTIVITY_NO_USER_ACTION
不然從一個(gè)activity依次調(diào)用startActivity,finish關(guān)閉自己,啟動(dòng)一個(gè)新的activity時(shí),onUserLeaveHint也會(huì)被調(diào)用
相關(guān)文章
Android編程設(shè)置TextView顏色setTextColor用法實(shí)例
這篇文章主要介紹了Android編程設(shè)置TextView顏色setTextColor用法,結(jié)合實(shí)例形式分析了Android設(shè)置TextView顏色setTextColor、ColorStateList等方法的使用技巧與布局文件的設(shè)置方法,需要的朋友可以參考下2016-01-01
Android 10 啟動(dòng)分析之init語法詳解
這篇文章主要為大家介紹了Android 10 啟動(dòng)分析之init語法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Android實(shí)現(xiàn)計(jì)步傳感器功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)計(jì)步傳感器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
android開發(fā)教程之使用listview顯示qq聯(lián)系人列表
這篇文章主要介紹了android使用listview顯示qq聯(lián)系人列表的示例,需要的朋友可以參考下2014-02-02
獲取控件大小和設(shè)置調(diào)整控件的位置XY示例
我需要的設(shè)置控件相對(duì)屏幕左上角的X 、Y位置,而不是自己本身位置的偏移,下面與大家介紹下怎么獲取設(shè)置控件的信息2013-06-06
Android Studio 導(dǎo)入新工程項(xiàng)目圖解
這篇文章主要介紹了Android Studio 導(dǎo)入新工程項(xiàng)目圖解,需要的朋友可以參考下2017-12-12

