Android 点击通知栏消息打开activity,并判断app是否运行

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。

你可能感兴趣的:(Android,Android笔记)