情况简述

在开发Android app的过程中,遇到这样一个需求:app中启动一个Service,该Service在独立进程中运行,与服务器保持长连接,将服务器推送过来的消息在通知栏中显示,并设置点击动作,点击后跳转到app中对应的Activity。目前遇到的问题是Service以独立进程运行,在收到消息并弹出通知后,app本身的进程有两种情况:

  1. app正在运行
  2. app已退出

对于第一种情况,处理就非常简单了,直接将参数传入Intent并打开对应的Activity即可。

但第二种情况比较复杂,因为app已经退出,而要打开的Activity中的某些操作是需要依赖app的初始化的,这些初始化操作是在app启动过程中进行的。举个例子,一个购物应用推送了某个新商品的消息,用户点击通知后进入商品详情的Activity,而该Activity中有个订购Button,点击该Button后就会从本地中获取用户的Id等信息并发一条消息给服务器,告诉服务器某用户订购了该商品。这些用户信息是在app启动时与服务器进行一系列交互后取得的。如果app退出后直接进入详情Activity并点击购买,就会因为获取不到用户信息而出错。

所以目前要解决的问题时,在Notification中设置点击动作,如果app本身正在运行,直接跳转到目标Activity;如果app已经退出,先启动app完成初始化,再跳转到目标Activity。

方案和思路

我们假设目前有三个Activity:

  1. SplashActivity 用于显示app大图,同时进行用户登录等操作,服务器返回数据后跳转到MainActivity。
  2. MainActivity app的主Activity。
  3. DetailActivity MainActivity中点击Button进入的Activity,用于显示某件商品详情。

而弹出通知的Service在另外一个进程中。

我们要达到的目的是:

  1. 点击通知栏通知,假如app正在运行,则直接跳转到DetailActivity显示具体内容,在DetailActivity中按Back键返回MainActivity
  2. 点击通知栏通知,假如app已经退出,先从SplashActivity进入,显示app启动界面,初始化操作完成后进入MainActivity再跳转到DetailActivity显示具体内容,在DetailActivity中按Back键返回MainActivity。

初步的思路是先判断app进程是否存在,如果存在的话,就利用startActivities启动MainActivity和DetailActivity。为什么还要启动MainActivity而不直接只启动DetailActivity?因为有如下情况,进程中的所有Activity都已经退出了,但进程还没有被系统回收,这时判断进程是否存在返回true,然后只启动DetailActivity的话,按Back键任务栈就直接到底,返回桌面了。而我们要的效果是按Back键返回上一级Activity,也就是MainActivity。

如果app进程已经退出,不存在了,此时就用一个Intent启动应用,该Intent中包含一个Bundle, Bundle中存有启动DetailActivity所需的参数,这个Intent传入SplashActivity后,再由SplashActivity传给MainActivity,在MainActivity中加入判断,如果有该参数,则表示应用是从通知栏启动的,要进行跳转到DetailActivity的操作,否则就是常规启动。

代码实现

有了大概的实现思路后,大家来个demo实际操作一下。
首先,我们的demo有简单的组件:

  1. PushService,在新进程中启动的Service,负责监听服务器,收到服务器的信息后将消息广播出去,在本demo中,为了简化,只是简单的广播一个消息
  2. ShowNotificationReceiver,在新进程中注册的BroadcastReceiver,收到PushService发的消息后,会在通知栏弹出通知
  3. NotificationReceiver, 在新进程中注册的BroadcastReceiver,用来设置点击通知栏通知的动作,打开app中的某个Activity
  4. SplashActivity, app启动页面,先是启动图片,3s后进入MainActivity
  5. MainActivity,app的主Activity
  6. DetailActivity,app中显示详情的Activity

PushService.java

首先是PushService,要在新进程中启动,要在AndroidManifest.xml中加入以下注册Service的代码

<service android:name=".PushService"
android:process=":push"/>

PushService的工作很简单,启动后发一个广播在通知栏显示通知,然后常驻在后台

public class PushService extends Service{
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
} @Override
public void onCreate() {
super.onCreate();
Log.i("PushService", "PushService onCreate");
//用AlarmManager定时发送广播
AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(this, ShowNotificationReceiver.class); PendingIntent pendingIntent =
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); am.set(AlarmManager.ELAPSED_REALTIME, SystemClock.currentThreadTimeMillis(), pendingIntent); } }

ShowNotificationReceiver.java

这个广播类用来在通知栏弹出通知

public class ShowNotificationReceiver extends BroadcastReceiver{
private static final String TAG = "RepeatReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "ShowNotificationReceiver onReceive");
//设置点击通知栏的动作为启动另外一个广播
Intent broadcastIntent = new Intent(context, NotificationReceiver.class);
PendingIntent pendingIntent = PendingIntent.
getBroadcast(context, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setContentTitle("这就是通知的头")
.setTicker("这是通知的ticker")
.setContentIntent(pendingIntent)
.setSmallIcon(android.R.drawable.ic_lock_idle_charging); Log.i("repeat", "showNotification");
NotificationManager manager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(2, builder.build());
}
}

NotificationReceiver.java

点击通知栏后,会发送一个广播,NotificationReceiver收到该广播后,就会判断,app进程是否仍然存活,根据app进程的不同状态,定义不同的app启动方式

public class NotificationReceiver extends BroadcastReceiver{

    @Override
public void onReceive(Context context, Intent intent) {
//判断app进程是否存活
if(SystemUtils.isAppAlive(context, "com.liangzili.notificationlaunch")){
//如果存活的话,就直接启动DetailActivity,但要考虑一种情况,就是app的进程虽然仍然在
//但Task栈已经空了,比如用户点击Back键退出应用,但进程还没有被系统回收,如果直接启动
//DetailActivity,再按Back键就不会返回MainActivity了。所以在启动
//DetailActivity前,要先启动MainActivity。
Log.i("NotificationReceiver", "the app process is alive");
Intent mainIntent = new Intent(context, MainActivity.class);
//将MainAtivity的launchMode设置成SingleTask, 或者在下面flag中加上Intent.FLAG_CLEAR_TOP,
//如果Task栈中有MainActivity的实例,就会把它移到栈顶,把在它之上的Activity都清理出栈,
//如果Task栈不存在MainActivity实例,则在栈顶创建
mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Intent detailIntent = new Intent(context, DetailActivity.class);
detailIntent.putExtra("name", "电饭锅");
detailIntent.putExtra("price", "58元");
detailIntent.putExtra("detail", "这是一个好锅, 这是app进程存在,直接启动Activity的"); Intent[] intents = {mainIntent, detailIntent}; context.startActivities(intents);
}else {
//如果app进程已经被杀死,先重新启动app,将DetailActivity的启动参数传入Intent中,参数经过
//SplashActivity传入MainActivity,此时app的初始化已经完成,在MainActivity中就可以根据传入 //参数跳转到DetailActivity中去了
Log.i("NotificationReceiver", "the app process is dead");
Intent launchIntent = context.getPackageManager().
getLaunchIntentForPackage("com.liangzili.notificationlaunch");
launchIntent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Bundle args = new Bundle();
args.putString("name", "电饭锅");
args.putString("price", "58元");
args.putString("detail", "这是一个好锅, 这是app进程不存在,先启动应用再启动Activity的");
launchIntent.putExtra(Constants.EXTRA_BUNDLE, args);
context.startActivity(launchIntent);
}
}
}

SplashActivity.java

SplashActivity.java先是app启动的图片,3s后进入MainActivity, 如果启动SplashActivity的Intent中带有参数,就将参数取出,放入启动MainActivity的Intent中

public class SplashActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
//隐藏ActionBar
getSupportActionBar().hide();
//使用handler倒数3秒后进入MainActivity
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
//如果启动app的Intent中带有额外的参数,表明app是从点击通知栏的动作中启动的
//将参数取出,传递到MainActivity中
if(getIntent().getBundleExtra(Constants.EXTRA_BUNDLE) != null){
intent.putExtra(Constants.EXTRA_BUNDLE,
getIntent().getBundleExtra(Constants.EXTRA_BUNDLE));
}
startActivity(intent);
finish();
}
}, 3000);
}
}

MainActivity.java

MainActivity中,如果有参数传入,就在初始化结束后,根据参数启动DetailActivity,如果没有参数传入,就此结束自己的任务

public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity"; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, PushService.class);
startService(intent);
setTitle("MainActivity");
Bundle bundle = getIntent().getBundleExtra(Constants.EXTRA_BUNDLE);
if(bundle != null){
//如果bundle存在,取出其中的参数,启动DetailActivity
String name = bundle.getString("name");
String price = bundle.getString("price");
String detail = bundle.getString("detail");
SystemUtils.startDetailActivity(this, name, price, detail);
Log.i(TAG, "launchParam exists, redirect to DetailActivity");
}
} @Override
protected void onResume() {
super.onResume();
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
} @Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId(); //noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
} return super.onOptionsItemSelected(item);
}
}

DetailActivity.java

比较简单,显示传入的参数即可:-D

public class DetailActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
getSupportActionBar().setTitle("DetailActivity");
String name = getIntent().getStringExtra("name");
String price = getIntent().getStringExtra("price");
String detail = getIntent().getStringExtra("detail"); ((TextView)findViewById(R.id.name)).setText(name);
((TextView)findViewById(R.id.price)).setText(price);
((TextView)findViewById(R.id.detail)).setText(detail);
}
}

效果展示

http://v.youku.com/v_show/id_XMTMwMjgyNTUwMA==.html?from=y1.7-1.2

demo下载

https://github.com/slimhippo/androidcode

Android实现点击通知栏后,先启动应用再打开目标Activity的更多相关文章

  1. Android实现点击通知栏后,先启动应用再打开目标Activity ,极光推送等推送的也可以参考一下(转)

    我因为项目中集成了极光推送,推送的通知栏点开需要确定进入哪个界面就参考了这边文章,感谢作者的无私. 标签: 情况简述 在开发Android app的过程中,遇到这样一个需求:app中启动一个Servi ...

  2. 8. Android加载流程(打包与启动)

    移动安全的学习离不开对Android加载流程的分析,包括Android虚拟机,Android打包,启动流程等... 这篇文章  就对Android的一些基本加载进行学习. Android虚拟机 And ...

  3. android开发之Intent.setFlags()_让Android点击通知栏信息后返回正在运行的程序

    android开发之Intent.setFlags()_让Android点击通知栏信息后返回正在运行的程序     在应用里使用了后台服务,并且在通知栏推送了消息,希望点击这个消息回到activity ...

  4. Android 主页面顶部栏的通知Notification ,可以自定义通知消息栏的风格,并且点击通知栏进人本程序。

    常用的程序通知,显示到主页面的顶部栏. package com.lixu.tongzhi; import android.app.Activity; import android.app.Notifi ...

  5. Android编程 高德地图 中如何重写 定位按键 的触发事件 (com.amap.api.maps2d.LocationSource)点击定位后不仅定位在地图中心点上而且可以设置地图的缩放大小和提示

    在利用高德地图来编写自己的APP的时候,发现了一种对定位按键的重写方法,那就是利用   com.amap.api.maps2d.LocationSource  接口来重写. 什么是定位按键呢,下图中右 ...

  6. Android监听消息通知栏点击事件

    Android监听消息通知栏点击事件 使用BroadCastReceiver 1 新建一个NotificationClickReceiver 类,并且在清单文件中注册!! public class N ...

  7. Android ADT中增大AVD内存后无法启动:emulator failed to allocate memory 8 (转)

    Android ADT中增大AVD内存后无法启动:emulator failed to allocate memory 8http://www.crifan.com/android_emulator_ ...

  8. 【已解决】Android ADT中增大AVD内存后无法启动:emulator failed to allocate memory 8

    [问题] 折腾: [已解决]Android ADT中增大AVD内存后无法启动:emulator failed to allocate memory 8 过程中,增大对应AVD的内存为2G后,结果无法启 ...

  9. Android Studio 安装后首次启动的 Config path ...... is invalid 问题(转)

    原文链接:http://m.blog.csdn.net/blog/hnust_xiehonghao/46127775 1. 问题描述: 安装好Android Studio后,启动时弹出如下信息: Co ...

随机推荐

  1. SQLite: sqlite_master(转)

    转自:http://blog.sina.com.cn/s/blog_6afeac500100yn9k.html SQLite数据库中一个特殊的名叫 SQLITE_MASTER 上执行一个SELECT查 ...

  2. Nginx 代理以及HTTPS (二)

    一.HTTPS解析 https 加密 私钥 公钥 http 的握手 是确认网络是连通的. https 的握手 是一个加密的过程 加密图 二. 使用Nginx 部署HTTPS 服务 1.证书生成命令(h ...

  3. Linux PuTTY 更改字体

    Linux PuTTY默认的字体比较小看着比较不舒服,Linux PuTTY的字体更改与Windows下的设置有所不同 1.Linux PuTTY默认的字体 ,Font used for ordina ...

  4. 【 正确使用vim编辑器的姿势】

    vi:可视化接口(Visual Interface) vim:是vi的增强版(vi iMprove) vi编辑器是所有Unix及Linux系统下标准的编辑器,他就相当于windows系统中的记事本一样 ...

  5. 【Django】视图系统

    目录 FBV 与 CBV 给视图加装饰器 使用装饰器装饰CBV 关于dispatch()方法 Request对象 与 Response对象 request对象 response对象 @ *** 一个视 ...

  6. python创建多层目录的方式

    将 os.mkdir 改成 os.makedirs(opDir) 哈.

  7. C++虚表的原理,很好

    下面这篇文章讲的很好. http://www.cnblogs.com/lihaosky/articles/1606502.html 假设我们有这样的一个类: class Base { public: ...

  8. python调用Java代码,完毕JBPM工作流application

    1.缘由 有一庞大Python django webproject,要引入工作流引擎,像OA一样.方便的流程控制与管理.Python或django关于工作流的开源插件,稀少,并且弱爆了,终于选用jav ...

  9. JavaScript中获取Map集合中的key和value值(前提是:既不知道key为什么值,也不知道value有哪些值)

    for(var i in maps){//通过定义一个局部变量i遍历获取map里面的所有key值 alert(maps[i]); //通过获取key对应的value值 }

  10. C#运算符小例子

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...