天天看點

Android實作程式前背景切換效果

太久沒動這一塊了。借助了下面的文章得到了類似QQ的效果 ,是比較複雜的TABHOST的處理~有需要的Q我。

轉了下面的文章:

================

本文示範如何在Android中實作程式前背景切換效果。 

  在介紹程式實作之前,我們先看下Android中Activities和Task的基礎知識。

  我們都知道,一個Activity 可以啟動另一個Activity,即使這個Activity是定義在别一個應用程式裡的,比如說,想要給使用者展示一個地圖的資訊,現在已經有一個Activity可以做這件事情,那麼現在你的Activity需要做的就是将請求資訊放進一個Intent對象裡,并且将這個Intent對象傳遞給startActivity(),那麼地圖就可顯示出來了,但使用者按下Back鍵之後,你的Activity又重新出現在螢幕上。

  對使用者來講,顯示地圖的Activity和你的Activity好像在一個應用程式中的,雖然是他們是定義在其他的應用程式中并且運作在那個應有程序中。Android将你的Activity和借用的那個Activity被放進一個Task中以維持使用者的體驗。那麼Task是以棧的形式組織起來一組互相關聯的Activity,棧中底部的Activity就是開辟這個Task的,通常是使用者在應用程式啟動器中選擇的Activity。棧的頂部的Activity是目前正在運作的Activity--使用者正在互動操作的Activity。

  當一個Activity啟動另一個Activity時,新啟動的Activity被壓進棧中,成為正在運作的Activity。舊的Activity仍然在棧中。當使用者按下BACK鍵之後,正在運作的Activity彈出棧,舊的Activity恢複成為運作的Activity。棧中包含對象,是以如果一個任務中開啟了同一個Activity子類的的多個對象——例如,多個地圖浏覽器——則棧對每一個執行個體都有一個單獨的入口。棧中的Activity不會被重新排序,隻會被、彈出。Task是一組Activity執行個體組成的棧,不是在manifest檔案裡的某個類或是元素,是以無法設定一個Task的屬性而不管它的Activity,一個Task的所有屬性值是在底部的Activity裡設定的,這就需要用于Affinity。關于Affinity這裡不再詳述,大家可以查詢文檔。

  一個Task裡的所有Activity作為一個整體運轉。整個Task(整個Activity堆棧)可以被推到前台或被推到背景。假設一個正在運作的Task中有四個Activity——正在運作的Activity下面有三個Activity,這時使用者按下HOME鍵,回到應有程式啟動器然後運作新的應用程式(實際上是運作了一個新的Task),那麼目前的Task就退到了背景,新開啟的應用程式的root Activity此時就顯示出來了,一段時間後,使用者又回到應用程式器,又重新選擇了之前的那個應用程式(先前的那個Task),那麼先前的那個Task此時又回到了前台了,當使用者按下BACK鍵時,螢幕不是顯示剛剛離開的那個新開啟的那個應用程式的Activity,而是被除回到前台的那個Task的棧頂Activity,将這個Task的下一個Activity顯示出來。

上述便是Activity和Task一般的行為,但是這個行為的幾乎所有方面都是可以修改的。Activity和Task的關系,以及Task中Activity的行為,是受啟動該Activity的Intent對象的辨別和在manifest檔案中的Activity的<Activity>元素的屬性共同影響的。

  以上是關于Activity和Task的描述。

  在開發Android項目時,使用者難免會進行程式切換,在切換過程中,程式将進入背景運作,需要用時再通過任務管理器或是重新點選程式或是通過點選資訊通知欄中的圖示傳回原來的界面。這種效果類似于騰訊QQ的效果,打開QQ後顯示主界面,在使用其他的程式時,QQ将以圖示的形式顯示在資訊通知欄裡,如果再用到QQ時再點選資訊通知欄中的圖示顯示QQ主界面。

  先看下本示例實作效果圖:

Android實作程式前背景切換效果
Android實作程式前背景切換效果

  在上圖第二個圖中,我們點選時将會傳回到的原來的Activity中。 

  當我們的程式進入背景運作時,在我們的模拟器頂部将以圖示形式出現,如下圖:

  

Android實作程式前背景切換效果

  對于這種效果一般的做法是在Activity中的onStop()方法中編寫相應代碼,因為當Activity進入背景時将會調用onStop()方法,我們可以在onStop()方法以Notification形式顯示程式圖示及資訊,其中代碼如下所示:

Android實作程式前背景切換效果
Android實作程式前背景切換效果

  以上的showNotification()方法就是Notification。

  然後點選資訊通知欄的Notification後再傳回到原來的Activity。

  當然,我們也可以捕捉HOME鍵,在使用者按下HOME鍵時顯示Notification, 以下是代碼示例:

Android實作程式前背景切換效果

    // 點選HOME鍵時程式進入背景運作

    @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);

    }

Android實作程式前背景切換效果

   這裡的NotificationExtend是對顯示Notification的一個封裝,類中的代碼如下:

Android實作程式前背景切換效果

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擴充類

 * @Description: Notification擴充類

 * @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() {

        // 建立一個NotificationManager的引用

        NotificationManager notificationManager = (

                NotificationManager)context.getSystemService(

                        android.content.Context.NOTIFICATION_SERVICE);

        // 定義Notification的各種屬性

        Notification notification = new Notification(

                R.drawable.icon,"閱讀器", 

                System.currentTimeMillis());

        // 将此通知放到通知欄的"Ongoing"即"正在運作"組中

        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;

        // 設定通知的事件消息

        CharSequence contentTitle = "閱讀器顯示資訊"; // 通知欄标題

        CharSequence contentText = "推送資訊顯示,請檢視……"; // 通知欄内容

        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) context.getSystemService(

        notificationManager.cancel(0);

}

Android實作程式前背景切換效果

  這裡需要在配置檔案中設定每個Activity以單任務運作,否則,每次傳回原Activity時會新增加一個Activity,而不會傳回到原Activity。

  在使用FLAG_ACTIVITY_NEW_TASK控制辨別時也會出現不會傳回到原Activity的現象。如果該辨別使一個Activity開始了一個新的Task,然後當使用者按了HOME鍵離開這個Activity,在使用者按下BACK鍵時将無法再傳回到原Activity。一些應用(例如Notification)總是在一個新的Task裡打開Activity,而從來不在自己的Task中打開,是以它們總是将包含FLAG_ACTIVITY_NEW_TASK的Intent傳遞給startActivity()。是以如果有一個可以被其他的東西以這個控制标志調用的Activity,請注意讓應用程式有獨立的回到原Activity的方法。 代碼如下:

   <activity android:name="ShowMessageActivity"

             android:launchMode="singleTask"></activity> 

   這裡需要注意的是:

  <activity>下的launchMode屬性可以設定四種啟動方式: 

standard (預設模式) 

singleTop 

singleTask 

singleInstance 

  這四個模式有以下的幾個不同點:

  1. 響應Intent時Activity将被裝入哪個task。

  對于standard和singleTop模式,由産生該Intent(調用startActivity())的task持有該Activity——除非Intent對象裡含有FLAG_ACTIVITY_NEW_TASK标志,那麼就會尋找一個新的task。 

  相反的,singTask和singleInstance模式,總是标志Activity為task的root Activity,開啟這樣的活動會建立一個task,而不是裝入某個正在運作的任務。 

  2. 一個Activity是否可以有多個執行個體。

  一個standard或者singleTop屬性的Activity可以執行個體化多次,他們可以屬于多個不同的task,而且一個task也可以含有相同Activity的多個執行個體。 

  相反的,singleTask或者singleInstance屬性的Activity隻能有一個執行個體(單例),因為這些Activity是位于task的底部,這種限制意味着同一裝置的同一時刻該task隻能有一個執行個體。

  3. 執行個體是否能允許在它的task裡有其他的Activity。 

  一個singleInstance屬性的Activity是它所在的task裡僅有的一個Activity,如果他啟動了另一個Activity,那個Activity會被加載進一個不同的task而無視它的啟動模式——就如Intent裡有FLAG_ACTIVITY_NEW_TASK辨別一樣。在其他的方面,singleInstance和singleTask一樣的。 

  其他三個模式允許有多個Activity在一個task裡,一個singleTask屬性的Activity總是一個task裡的root Activity,但是他可以啟動另外的Activity并且将這個新的Activity裝進同一個task裡,standard和singleTop屬性的Activity可以出現在task的任何位置。 

  4. 是否建立一個新的Activity執行個體來處理一個新的Intent。

  對于預設的standard方式,将會生成新的執行個體來處理每一個新的Intent。每個執行個體處理一個新的Intent。

  對singleTop模式,如果一個已經存在的執行個體在目标task的棧頂,那麼就重用這個執行個體來處理這個新的Intent,如果這個執行個體存在但是不在棧頂,那就不重用他,而是重新建立一個執行個體來處理這個新的Intent并且将這個執行個體壓入棧。 

  例如現在有一個task堆棧ABCD,A是root Activity,D是棧頂Activity,現在有一個啟動D的Intent來了,如果D是預設的standard方法,那麼就會建立一個新的執行個體來處理這個Intent,是以這個堆棧就變為ABCDD,然而如果D是singleTop方式,這個已經存在的棧頂的D就會來處理這個Intent,是以堆棧還是ABCD。 

  如果另外一種情況,到來的Intent是給B的,不管B是standard還是singleTop(因為現在B不在棧頂),都會建立一個新的執行個體,是以堆棧變為ABCDB。 

  如上所述,一個"singleTask"或"singleInstance"模式的activity隻會有一個執行個體,這樣它們的執行個體就會處理所有的新intent。一個"singleInstance" activity總是在棧裡的最上面

(因為它是task裡的唯一的activity), 這樣它總是可以處理一個intent。而一個"singleTask" activity在棧裡可以有或沒有其他activity在它上面。如果有的話,它就不能對新到的intent進行處理,intent将被丢棄。(即使intent被丢棄,它的到來将使task來到前台,并維持在那裡。) 

  當一個已有的Activity被請求去處理一個新的Intent時,Intent對象會通過onNewIntent()的調用傳遞給這個活動。(傳遞進來的原始的Intent對象可以通過調用getIntent()擷取)。  

  注意,當建立一個新的Activity的執行個體來處理一個新收到的Intent時,使用者可以按BACK鍵回到上一個狀态(上一個Activity)。但是使用一個已有的Activity執行個體操作新收到的Intent時,使用者不能通過按下BACK鍵回到這個執行個體在接受到新Intent之前的狀态。 

  呵呵,不好意思,扯得有點多了,我們繼續看我們的程式。

  在這裡,如果是對一個Activity實作時可以這樣實作,如果有多個Activity,我們就需要在每個Activity裡重寫onKeyDown事件并捕捉使用者是否按下HOME鍵。

  為了實作友善,我們可以使用一個Service專門用于監聽程式是否進入背景或前台工作,如果程式進入背景運作就顯示Notification,這樣不管程式中有多少個Activity就可以很友善的實作程式前後如切換。 

  為此,我在程式中新添加了一個AppStatusService 類,目的是監聽程式是否在前背景運作,如果在背景運作則顯示資訊提示。

  代碼如下:

Android實作程式前背景切換效果

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.ActivityManager.RunningAppProcessInfo;

import android.app.Service;

import android.content.Context;

import android.os.IBinder;

import android.util.Log;

 * 監聽程式是否在前背景運作Service

 * @Description: 監聽程式是否在前背景運作Service

 * @FileName: AppStatusService.java 

 * @Package com.test.service

 * @Date 2012-4-13 下午04:13:47 

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) {

        return null;

    public int onStartCommand(Intent intent, int flags, int startId) {

        activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE); 

        packageName = this.getPackageName(); 

        System.out.println("啟動服務");

        new Thread() { 

            public void run() { 

                try { 

                    while (!isStop) { 

                        Thread.sleep(1000); 

                        if (isAppOnForeground()) { 

                            Log.v(TAG, "前台運作");

                        } else { 

                            Log.v(TAG, "背景運作");

                            showNotification();

                        } 

                    } 

                } catch (Exception e) { 

                    e.printStackTrace(); 

                } 

            } 

        }.start(); 

        return super.onStartCommand(intent, flags, startId);

    /**

     * 程式是否在前台運作

     * @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; 

    } 

    public void onDestroy() {

        super.onDestroy();

        System.out.println("終止服務");

        isStop = true;

                NotificationManager)getSystemService(

        // 點選後自動清除Notification

        notification.flags |= Notification.FLAG_AUTO_CANCEL;

        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);

Android實作程式前背景切換效果

  在這裡為了在資訊提示欄裡點選後能夠傳回到原來的Activity,需要在AppManager裡記下我們目前的Activity。

  完畢。^_^ 

繼續閱讀