編輯:關於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主界面。
先看下本示例實現效果圖:
在上圖第二個圖中,我們點擊時將會返回到的原來的Activity中。
當我們的程序進入後台運作時,在我們的模擬器頂部將以圖標形式出現,如下圖:
對於這種效果一般的做法是在Activity中的onStop()方法中編寫相應代碼,因為當Activity進入後台時將會調用onStop()方法,我們可以在onStop()方法以Notification形式顯示程序圖標及信息,其中代碼如下所示:
@Override protected void onStop() { // TODO Auto-generated method stub super.onStop(); Log.v("BACKGROUND", "程序進入後台"); showNotification(); }
以上的showNotification()方法就是Notification。
然後點擊信息通知欄的Notification後再返回到原來的Activity。
當然,我們也可以捕捉HOME鍵,在用戶按下HOME鍵時顯示Notification, 以下是代碼示例:
// 點擊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); }
這裡的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擴展類 * @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 notificationManager = ( NotificationManager) context.getSystemService( android.content.Context.NOTIFICATION_SERVICE); notificationManager.cancel(0); } }
這裡需要在配置文件中設置每個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屬性可以設置四種啟動方式:
這四個模式有以下的幾個不同點:
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 類,目的是監聽程序是否在前後台運行,如果在後台運行則顯示信息提示。
代碼如下:
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; /** * 監聽程序是否在前後台運行Service * @Description: 監聽程序是否在前後台運行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("啟動服務"); 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; } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); System.out.println("終止服務"); isStop = true; } // 顯示Notification public void showNotification() { // 創建一個NotificationManager的引用 NotificationManager notificationManager = ( NotificationManager)getSystemService( android.content.Context.NOTIFICATION_SERVICE); // 定義Notification的各種屬性 Notification notification = new Notification( R.drawable.icon,"閱讀器", System.currentTimeMillis()); // 將此通知放到通知欄的"Ongoing"即"正在運行"組中 notification.flags |= Notification.FLAG_ONGOING_EVENT; // 點擊後自動清除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; // 設置通知的事件消息 CharSequence contentTitle = "閱讀器顯示信息"; // 通知欄標題 CharSequence contentText = "推送信息顯示,請查看……"; // 通知欄內容 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); } }
在這裡為了在信息提示欄裡點擊後能夠返回到原來的Activity,需要在AppManager裡記下我們當前的Activity。
PS:監聽程序是否進入後台
方法一:
/** *判斷當前應用程序處於前台還是後台 * * @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; }
這段代碼是需要一個權限的:
<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() { //當用戶按Home鍵等操作使程序進入後台時即開始計時 super.onUserLeaveHint(); if(!isLeave){ isLeave=true; saveStartTime(); } }
這個需要需要加一個intent flag
public static final int FLAG_ACTIVITY_NO_USER_ACTION
不然從一個activity依次調用startActivity,finish關閉自己,啟動一個新的activity時,onUserLeaveHint也會被調用
第1章 用戶界面和布局應用程序的用戶界面就是用戶能看到並可以與它交互的任何東西。Android提供多種預置的UI組件,如結構化布局對象和允許你為應用程序創建圖形用戶界面的
首先來看效果: 一、實現原理在實現過程中,主要考慮整個界面由若干個字母組成的子母線條組成,這樣的話把固定數量的字母封裝成一個字母線條,而每個字母又封裝成一個對象,這樣的話
Service 是一個可以在後台執行長時間運行操作而不使用用戶界面的應用組件。 例如,服務可以處理網絡事務、播放音樂,執行文件 I/O 或與內容提供程序交互,而所有這一切
概要 若讀者之前沒接觸過service,對下面內容有個大致了解即可。待使用過service之後再來閱讀本章內容,會理解更深刻。