android的通知欄消息點擊事件如果是打開一個activity時,我們要考慮兩種情況:
- 應用正在前臺運行。
- 應用已退出。
如果是第一種情況那麼就好處理了,直接爲Intent設置flag爲FLAG_ACTIVITY_NEW_TASK,然後調用context.startActivity方法就行了。flag不是必須的,什麼情況下需要設置flag?當在廣播接收器中跳轉到activity時,當在service中轉到activity時。
對於第二種情況,我參照了很多app的做法,現總結爲以下兩種:
- 點擊通知欄消息打開activity按下返回鍵後判斷app是否啓動,如果沒有啓動就要啓動app;
- 點擊通知欄消息時判斷app是否正在前臺運行,否則先啓動app再打開activity,參數通過Intent一層一層往下傳遞。
需要用到幾個方法:獲取應用的運行狀態,判斷應用進程是否在運行,判斷某個activity是否存在任務棧裏面。
判斷某個服務是否正在運行,這個不重要,可能其它地方用到就先貼出來了。 ==
/**
* 判斷某個service是否正在運行
*
* @param context
* @param runService
* 要驗證的service組件的類名
* @return
*/
public static boolean isServiceRunning(Context context,
Class<? extends Service> runService) {
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
ArrayList<RunningServiceInfo> runningService = (ArrayList<RunningServiceInfo>) am
.getRunningServices(1024);
for (int i = 0; i < runningService.size(); ++i) {
if (runService.getName().equals(
runningService.get(i).service.getClassName().toString())) {
return true;
}
}
return false;
}
獲取app的運行狀態,返回1代表當前應用在前臺運行,返回2代表當前應用在後臺運行,返回0代表應用未啓動(沒有一個存活的activity)。
/**
* 返回app運行狀態
*
* @param context
* 一個context
* @param packageName
* 要判斷應用的包名
* @return int 1:前臺 2:後臺 0:不存在
*/
public static int isAppAlive(Context context, String packageName) {
ActivityManager activityManager = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> listInfos = activityManager
.getRunningTasks(20);
// 判斷程序是否在棧頂
if (listInfos.get(0).topActivity.getPackageName().equals(packageName)) {
return 1;
} else {
// 判斷程序是否在棧裏
for (ActivityManager.RunningTaskInfo info : listInfos) {
if (info.topActivity.getPackageName().equals(packageName)) {
return 2;
}
}
return 0;// 棧裏找不到,返回3
}
}
判斷某個進程是否運行
/**
* 判斷進程是否運行
*
* @param context
* @param proessName 應用程序的主進程名一般爲包名
* @return
*/
public static boolean isProessRunning(Context context, String proessName) {
boolean isRunning = false;
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> lists = am.getRunningAppProcesses();
for (RunningAppProcessInfo info : lists) {
if (info.processName.equals(proessName)) {
isRunning = true;
}
}
return isRunning;
}
判斷某個activity是否在任務棧裏面,app啓動後會有一個首頁,該首頁只有當app退出時纔會被銷燬,因此可用判斷MainActivity是否在任務棧裏面來判斷應用是否已經啓動。
/**
* 判斷某一個類是否存在任務棧裏面
*
* @return
*/
public static boolean isExsitMianActivity(Context context, Class<?> cls) {
Intent intent = new Intent(context, cls);
ComponentName cmpName = intent.resolveActivity(context
.getPackageManager());
boolean flag = false;
if (cmpName != null) { // 說明系統中存在這個activity
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> taskInfoList = am.getRunningTasks(10);
for (RunningTaskInfo taskInfo : taskInfoList) {
if (taskInfo.baseActivity.equals(cmpName)) { // 說明它已經啓動了
flag = true;
break; // 跳出循環,優化效率
}
}
}
return flag;
}
接下來是第一種方法的實現:
在需要跳轉的activity中或BaseActivity中的onCreate方法中獲取intent傳遞過來的數據,判斷是否是從點擊通知欄消息跳轉過來,並用一個字段保存這個狀態,再處理相應的邏輯業務。
private int isNoticeOpen = 0;// 是否是點擊消息通知跳轉進來的
```
```java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bun = getIntent().getExtras();
if (bun != null) {
// 判斷是否是消息通知點擊跳轉進行的
try{
isNoticeOpen = Integer.valueOf(bun.getString("NOTICE"));
}catch(NumberFormatException e){
isNoticeOpen = 0;
e.printStackTrace();
}
}...............獲取其它通知傳遞過來的參數...........
}
在onDestroy方法中判斷該應用是否正在前臺運行,但是這裏只能用MainActivity是否存在任務棧裏面判斷,因爲當你點擊通知消息跳轉到某個activity的時候,任務棧裏該activity就處於棧頂了,而棧頂的activity的包名就是該應用的包名。
@Override
public void onDestroy() {
super.onDestroy();
//如果是點擊消息跳轉進來的,且(該運行的進程裏沒有該應用進程 或 應用首頁的Activity不存在任務棧裏面)
if (isNoticeOpen==1&&
(!ServiceHelper.isProessRunning(getApplicationContext(),this.getPackageName())
||!ServiceHelper.isExsitMianActivity(this,MainActivity_.class))) {
//啓動app
Intent intent = getBaseContext().getPackageManager()
.getLaunchIntentForPackage(getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
}
最後是第二種實現方式,在點擊通知消息時就判斷應用是否在前臺運行,沒有就啓動應用。這裏用到了一個ServiceHelper類,這個類是我寫的,爲了簡寫跳轉過程而封裝一些步驟的類。
1.處理通知消息點擊事件跳轉到某個頁面,這裏的Intent並沒有設置要跳轉到的activity,而是將要跳轉到的activity當作Intent的參數。
/**
* 處理通知消息點擊事件跳轉到指定界面
*
* @param msg
*/
private void handerMsgCallToActivity(Context context, UMessage msg) {
......................................................
Intent intent = new Intent();
/**
* 兼容友盟後臺直接推送的
*/
// 獲取動作參數
String actionKey = msg.extra.get("action");
if (null == actionKey)
return;
if ("message".equals(actionKey)) {
intent.putExtra(UmenPushManager.CALL_TO_ACTIVITY,
MessageActivity_.class);
intent.putExtra("NOTICE", true);
intent.putExtra("msgCount", 999);// 大於0即可
} else if ("news".equals(actionKey)) {
String newtype = msg.extra.get("newstype");
String newsId = msg.extra.get("nid");
String newsTitle = msg.extra.get("ntitle");
String newsUrl = msg.extra.get("nurl");
..............................
intent.putExtra(UmenPushManager.CALL_TO_ACTIVITY,DetailsActivity_.class);
intent.putExtra("NOTICE", true);
intent.putExtra("news_id", newsId);
intent.putExtra("url", newsUrl);
} else if ("outlink".equals(actionKey)) {
String title = msg.extra.get("title");
String url = msg.extra.get("url");
intent.putExtra(UmenPushManager.CALL_TO_ACTIVITY,
BrowserActivity_.class);
intent.putExtra("title", title);
intent.putExtra("url", url);
}
ServiceHelper.startActivityWithAppIsRuning(context, intent);
}
2.上一步中只是獲取並設置頁面跳轉中要傳遞的數據並指定了要跳轉到哪個頁面,而真正的跳轉任務交給了ServiceHelper類的startActivityWithAppIsRuning方法實現。在startActivityWithAppIsRuning方法中進行判斷應用是否在運行,沒有則創建一個Intent,設置跳轉目標Activity,該Activity由上一步傳過來的Intent獲取到。否則就啓動應用,intent中傳遞一個鍵爲FORM_NOTICE_OPEN,值爲true的參數標識是從點擊消息通知跳轉過來的,再將上一步傳遞過來的intent當做參數傳給當前的intent。
/**
* 自動判斷appUI進程是否已在運行,設置跳轉信息
*
* @param context
* @param intent
*/
public static void startActivityWithAppIsRuning(Context context,
Intent intent) {
int isAppRuning = isAppAlive(context, UmenPushManager.APP_PACKAGE);
if (isAppRuning != 0) {
Intent newIntent = new Intent(context, (Class<?>) intent
.getExtras().getSerializable(
UmenPushManager.CALL_TO_ACTIVITY));
newIntent.putExtras(intent.getExtras());
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(newIntent);
return;
}
// 如果app進程已經被殺死,先重新啓動app,將DetailActivity的啓動參數傳入Intent中,參數經過
// SplashActivity傳入MainActivity,此時app的初始化已經完成,在MainActivity中就可以根據傳入
// 參數跳轉到DetailActivity中去了
Intent launchIntent = context.getPackageManager()
.getLaunchIntentForPackage(UmenPushManager.APP_PACKAGE);
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
launchIntent.putExtra(UmenPushManager.FORM_NOTICE_OPEN, true);
launchIntent.putExtra(UmenPushManager.FORM_NOTICE_OPEN_DATA, intent);
context.startActivity(launchIntent);
}
3.在應用的啓動頁中不做處理,直接傳遞給MainActivity,首先是在啓動頁WelcomeActivity中調用ServiceHelper類的startAppMainActivitySetNoticeIntent方法判斷是否從點擊通知消息跳轉過來,如果是則爲跳轉到MainActivity的Intent寫入傳遞過來的數據。
//如果是點擊通知打開的則設置通知參數
ServiceHelper.startAppMainActivitySetNoticeIntent(this, intent);
/**
* 啓動App時,爲跳轉到主頁MainActivity的Intent寫入打開通知的Intent,如果有通知的情況下
*
* @param appStartActivity
* app啓動的第一個activity,在配置文件中設置的mainactivity
* @param startMainActivityIntent
*/
public static void startAppMainActivitySetNoticeIntent(
Activity appStartActivity, Intent startMainActivityIntent) {
/**
* 如果啓動app的Intent中帶有額外的參數,表明app是從點擊通知欄的動作中啓動的 將參數取出,傳遞到MainActivity中
*/
try {
if (appStartActivity.getIntent().getExtras() != null) {
if (appStartActivity.getIntent().getExtras()
.getBoolean(UmenPushManager.FORM_NOTICE_OPEN) == true) {
startMainActivityIntent
.putExtra(
UmenPushManager.FORM_NOTICE_OPEN_DATA,
appStartActivity
.getIntent()
.getExtras()
.getParcelable(
UmenPushManager.FORM_NOTICE_OPEN_DATA));
}
}
} catch (Exception e) {
}
}
4.在MainActivity的onCreate中調用
/**
* 如果是從點擊通知欄的通知跳轉過來的
*/
ServiceHelper.isAppWithNoticeOpen(this);
再看ServiceHelper的isAppWithNoticeOpen方法。
/**
* 判斷是否是點擊消息通知欄跳轉過來的
*
* @param mainActivity
* 主頁
*/
public static void isAppWithNoticeOpen(Activity mainActivity) {
try {
if (mainActivity.getIntent().getExtras() != null) {
Intent intent = mainActivity.getIntent().getExtras()
.getParcelable(UmenPushManager.FORM_NOTICE_OPEN_DATA);
Intent newIntent = new Intent(mainActivity, (Class<?>) intent
.getExtras().getSerializable(
UmenPushManager.CALL_TO_ACTIVITY));
newIntent.putExtras(intent.getExtras());
mainActivity.startActivity(newIntent);
}
} catch (Exception e) {
}
}
最關鍵的一點時,到這一步才處理點擊通知消息真正要跳轉到的頁面。(Class<?>) intent .getExtras().getSerializable( UmenPushManager.CALL_TO_ACTIVITY)獲取到的是需要跳轉到的頁面,其它數據原封不動往下傳遞過去就行了。
最後附上完整的ServiceHelper類:
/**
* 後臺service組件助手
*
* @author wujiuye
*
*/
public final class ServiceHelper {
/**
* 判斷某個service是否正在運行
*
* @param context
* @param runService
* 要驗證的service組件的類名
* @return
*/
public static boolean isServiceRunning(Context context,
Class<? extends Service> runService) {
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
ArrayList<RunningServiceInfo> runningService = (ArrayList<RunningServiceInfo>) am
.getRunningServices(1024);
for (int i = 0; i < runningService.size(); ++i) {
if (runService.getName().equals(
runningService.get(i).service.getClassName().toString())) {
return true;
}
}
return false;
}
/**
* 返回app運行狀態
*
* @param context
* 一個context
* @param packageName
* 要判斷應用的包名
* @return int 1:前臺 2:後臺 0:不存在
*/
public static int isAppAlive(Context context, String packageName) {
ActivityManager activityManager = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> listInfos = activityManager
.getRunningTasks(20);
// 判斷程序是否在棧頂
if (listInfos.get(0).topActivity.getPackageName().equals(packageName)) {
return 1;
} else {
// 判斷程序是否在棧裏
for (ActivityManager.RunningTaskInfo info : listInfos) {
if (info.topActivity.getPackageName().equals(packageName)) {
return 2;
}
}
return 0;// 棧裏找不到,返回3
}
}
/**
* 自動判斷appUI進程是否已在運行,設置跳轉信息
*
* @param context
* @param intent
*/
public static void startActivityWithAppIsRuning(Context context,
Intent intent) {
int isAppRuning = isAppAlive(context, UmenPushManager.APP_PACKAGE);
if (isAppRuning != 0) {
Intent newIntent = new Intent(context, (Class<?>) intent
.getExtras().getSerializable(
UmenPushManager.CALL_TO_ACTIVITY));
newIntent.putExtras(intent.getExtras());
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(newIntent);
return;
}
// 如果app進程已經被殺死,先重新啓動app,將DetailActivity的啓動參數傳入Intent中,參數經過
// SplashActivity傳入MainActivity,此時app的初始化已經完成,在MainActivity中就可以根據傳入
// 參數跳轉到DetailActivity中去了
Intent launchIntent = context.getPackageManager()
.getLaunchIntentForPackage(UmenPushManager.APP_PACKAGE);
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
launchIntent.putExtra(UmenPushManager.FORM_NOTICE_OPEN, true);
launchIntent.putExtra(UmenPushManager.FORM_NOTICE_OPEN_DATA, intent);
context.startActivity(launchIntent);
}
/**
* 啓動App時,爲跳轉到主頁MainActivity的Intent寫入打開通知的Intent,如果有通知的情況下
*
* @param appStartActivity
* app啓動的第一個activity,在配置文件中設置的mainactivity
* @param startMainActivityIntent
*/
public static void startAppMainActivitySetNoticeIntent(
Activity appStartActivity, Intent startMainActivityIntent) {
/**
* 如果啓動app的Intent中帶有額外的參數,表明app是從點擊通知欄的動作中啓動的 將參數取出,傳遞到MainActivity中
*/
try {
if (appStartActivity.getIntent().getExtras() != null) {
if (appStartActivity.getIntent().getExtras()
.getBoolean(UmenPushManager.FORM_NOTICE_OPEN) == true) {
startMainActivityIntent
.putExtra(
UmenPushManager.FORM_NOTICE_OPEN_DATA,
appStartActivity
.getIntent()
.getExtras()
.getParcelable(
UmenPushManager.FORM_NOTICE_OPEN_DATA));
}
}
} catch (Exception e) {
}
}
/**
* 判斷是否是點擊消息通知欄跳轉過來的
*
* @param mainActivity
* 主頁
*/
public static void isAppWithNoticeOpen(Activity mainActivity) {
try {
if (mainActivity.getIntent().getExtras() != null) {
Intent intent = mainActivity.getIntent().getExtras()
.getParcelable(UmenPushManager.FORM_NOTICE_OPEN_DATA);
Intent newIntent = new Intent(mainActivity, (Class<?>) intent
.getExtras().getSerializable(
UmenPushManager.CALL_TO_ACTIVITY));
newIntent.putExtras(intent.getExtras());
mainActivity.startActivity(newIntent);
}
} catch (Exception e) {
}
}
/**
* 判斷進程是否運行
*
* @param context
* @param proessName
* @return
*/
public static boolean isProessRunning(Context context, String proessName) {
boolean isRunning = false;
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> lists = am.getRunningAppProcesses();
for (RunningAppProcessInfo info : lists) {
if (info.processName.equals(proessName)) {
isRunning = true;
}
}
return isRunning;
}
/**
* 判斷某一個類是否存在任務棧裏面
*
* @return
*/
public static boolean isExsitMianActivity(Context context, Class<?> cls) {
Intent intent = new Intent(context, cls);
ComponentName cmpName = intent.resolveActivity(context
.getPackageManager());
boolean flag = false;
if (cmpName != null) { // 說明系統中存在這個activity
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> taskInfoList = am.getRunningTasks(10);
for (RunningTaskInfo taskInfo : taskInfoList) {
if (taskInfo.baseActivity.equals(cmpName)) { // 說明它已經啓動了
flag = true;
break; // 跳出循環,優化效率
}
}
}
return flag;
}
}
** 最後想在結尾簡單的提一下Activity的四種加載模式:**
1.standard:Activity的默認加載方法,即使某個Activity在Task棧中已經存在,另一個activity通過Intent跳轉到該activity,同樣會新創建一個實例壓入棧中。例如:現在棧的情況爲:A B C D,在D這個Activity中通過Intent跳轉到D,那麼現在的棧情況爲: A B C D D 。此時如果棧頂的D通過Intent跳轉到B,則棧情況爲:A B C D D B。此時如果依次按返回鍵,D D C B A將會依次彈出棧而顯示在界面上。
2.singleTop:如果某個Activity的Launch mode設置成singleTop,那麼當該Activity位於棧頂的時候,再通過Intent跳轉到本身這個Activity,則將不會創建一個新的實例壓入棧中。例如:現在棧的情況爲:A B C D。D的Launch mode設置成了singleTop,那麼在D中啓動Intent跳轉到D,那麼將不會新創建一個D的實例壓入棧中,此時棧的情況依然爲:A B C D。但是如果此時B的模式也是singleTop,D跳轉到B,那麼則會新建一個B的實例壓入棧中,因爲此時B不是位於棧頂,此時棧的情況就變成了:A B C D B。
3.singleTask:如果某個Activity是singleTask模式,那麼Task棧中將會只有一個該Activity的實例。例如:現在棧的情況爲:A B C D。B的Launch mode爲singleTask,此時D通過Intent跳轉到B,則棧的情況變成了:A B。而C和D被彈出銷燬了,也就是說位於B之上的實例都被銷燬了。
4.singleInstance:將Activity壓入一個新建的任務棧中。例如:Task棧1的情況爲:A B C。C通過Intent跳轉到D,而D的Launch mode爲singleInstance,則將會新建一個Task棧2。此時Task棧1的情況還是爲:A B C。Task棧2的情況爲:D。此時屏幕界面顯示D的內容,如果這時D又通過Intent跳轉到D,則Task棧2中也不會新建一個D的實例,所以兩個棧的情況也不會變化。而如果D跳轉到C,則棧1的情況變成了:A B C C,因爲C的Launch mode爲standard,此時如果再按返回鍵,則棧1變成:A B C。也就是說現在界面還顯示C的內容,不是D。