现有如下需求:
后台做一个推送,APP收到后以notification的形式展示,用户点击,跳转到指定界面。
需求简单,但是有些细节,确是要在写的时候,通过大量测试才能知道。
1、如果用户双击退出了,这个时候,APP并没有被系统杀死,点击手机上的菜单键,可以看到它还在系统中存活着,这个时候,如果点击notification去跳转指定界面(如:Activity1),会直接打开,但是用户点击返回键的时候,会立刻结束这个Activity,回到手机桌面。
2、如果用户在APP被清理、杀死的情况下收到推送,展示了一个notification(用三方的可以做到这样),这个时候用户点击,会唤醒APP,但是最后停到APP首页,并没有去指定界面。
经过测试今日头条和美团的notification消息(有了推送不立刻点,调整到我想要的状态再去点击),模拟的写了个处理方法,最后效果差不多
要跳转的界面,肯定不止一个,暂定3个Activity,1-3,创建一个bean,创建跳转工具类等进行操作。
特别注意!
特别注意!
特别注意!
本来这个说明,计划写在最后,不过担心看到下面就没耐心了,就提前到前面!
说明:
注意清单文件中,唤醒中转界面:AwakeTempActivity的启动模式,是android:launchMode=”singleTask”。不要用默认的。
经大量测试发现,以下情况,点通知栏不会跳转,需要这样设置AwakeTempActivity的启动模式才能解决
1、(此方法复现率100%)启动APP,双击退出(不要杀死),后台推送2条(及以上),手机上点击其中一条,APP被唤醒并去到指定界面,然后点击其他通知,不会跳转;
2、(此方法复现率很高)启动APP,双击退出(不要杀死),后台推送,手机收到后,启动APP,APP稳定到首页后,点击收到的推送,此时不会跳转到指定界面
其他:
目前我知道的,有5种方法可以唤醒指定APP:h5唤醒,需要清单文件中做一定的配置,这个我在前面博客中写过,不多说了;广播(如果用notification,因为有PendingIntent,发广播貌似效果不好(我没测试错误的话))。剩下的3种,在AwakeTempActivity中都提到了。需要注意的是,第二种方法,需要清单文件中做一些设置,配合使用(详见注释以及下面代码)。其他的不用。
说明:
Activity1-3,是要去的目标页面,其中,Activity_2特殊,在那个界面,就算收到消息,也不做提示和展示,用于模拟特殊界面
代码实现:
Activity1-3,是测试的,不做多余操作。他们3个的代码一样
package com.chen.demo2;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
public class Activity_1 extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
MyApplication
package com.chen.demo2;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
public class MyApplication extends Application {
private Activity app_activity = null;
private static MyApplication mContext = null;
@Override
public void onCreate() {
super.onCreate();
mContext=this;
initGlobeActivity();
}
/**
* 获取栈顶Activity
*/
private void initGlobeActivity() {
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
app_activity = activity;
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
// 对外暴露上下文
public static MyApplication getApplication() {
return mContext;
}
/**
* 公开方法,外部可通过 BaseApplication.getCurrentActivity() 获取到当前最上层的activity
*/
public Activity getCurrentActivity() {
return app_activity;
}
}
工具:
package com.chen.demo2;
import android.app.Activity;
import android.content.Context;
import android.text.TextUtils;
public class Util {
/**
* 是否被双击退出
*/
public static boolean isDoubleClickExit = false;
/**
* 用于标记APP是不是存活。
* 在MainActivity中,置为true,表示APP已经启动。除此之外不做赋值操作。如果变成了false,表示APP被清掉了。
*/
public static boolean isAPPAlive = false;
public static Context getContext() {
return MyApplication.getApplication();
}
/**
* 检查字符串是否是空
*
* @param str
* @return true:字符串为空
*/
public static boolean checkStringIsEmpty(String str) {
if (TextUtils.isEmpty(str) || "null".equals(str) || "(null)".equals(str) || "(Null)".equals(str) || "Null".equals(str) || "NULL".equals(str)) {
return true;
} else {
return false;
}
}
/**
* 是否允许处理展示消息(展示消息类型为运营的弹框消息和notification消息)
*
* @return 返回true,表示在不想接受展示消息的界面
*/
public static boolean isAllowMessageShow() {
Activity act = MyApplication.getApplication().getCurrentActivity();
String s = "";
if (act != null) {
// if (act == null) 获取不到顶部的Activity,说明没有Activity启动,即。APP被杀死
s = act.getLocalClassName();
}
return !checkStringIsEmpty(s) && s.contains("Activity_2");
}
}
AwakePageInfoBean
package com.chen.demo2;
import java.io.Serializable;
/**
* 唤醒页面,所需内容封装bean
* chenjianqiang
*/
public class AwakePageInfoBean implements Serializable {
//跳转类型
/**
* 1:去Activity_1
* 2:去Activity_2
* 3:去Activity_3
*/
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
SharedPreferencesUtil
package com.chen.demo2;
import android.content.Context;
import android.content.SharedPreferences;
public class SharedPreferencesUtil {
private SharedPreferences sp;
private Context context;
public SharedPreferencesUtil(Context context) {
this.context = context;
}
/**
* 保存唤醒APP的对象
*/
public void saveAwakeAPPBean(AwakePageInfoBean awakePageInfoBean) {
sp = context.getSharedPreferences("AwakeAPPBeanINFO", Context.MODE_PRIVATE);
sp.edit().putString("awakeType", awakePageInfoBean.getType()).apply();
}
/**
* 获取唤醒APP的对象
*/
public AwakePageInfoBean getAwakeAPPBean() {
sp = context.getSharedPreferences("AwakeAPPBeanINFO", Context.MODE_PRIVATE);
AwakePageInfoBean awakePageInfoBean = new AwakePageInfoBean();
awakePageInfoBean.setType(sp.getString("awakeType", ""));
return awakePageInfoBean;
}
/*
* 清空
*/
public void deleteBrowserOpenAppBean(String name) {
sp = context.getSharedPreferences(name, Context.MODE_PRIVATE);
sp.edit().clear().apply();
}
}
AwakePageUtil
package com.chen.demo2;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
/**
* 唤醒页面的工具
*/
public class AwakePageUtil {
public static void awakePage(Context context, AwakePageInfoBean bean) {
try {
if (bean != null && !Util.checkStringIsEmpty(bean.getType()) && context != null) {
//类型不为空。
String type = bean.getType();
Intent intent = null;
if (TextUtils.equals("1", type)) {
//去展示webView的界面
intent = new Intent(context, Activity_1.class);
} else if (TextUtils.equals("2", type)) {
//去展示webView的界面
intent = new Intent(context, Activity_2.class);
} else if (TextUtils.equals("3", type)) {
//去展示webView的界面
intent = new Intent(context, Activity_3.class);
}
if (intent != null) {
context.startActivity(intent);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
AwakeTempActivity
package com.chen.demo2;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
/**
* 唤醒APP的临时界面。当用户把APP杀死或者双击退出后,点击notification,先到这个界面,唤醒APP,然后再去目标界面。
* 这样做的原因:
* 1、假如用户双击退出APP,推送是Activity_1。这个时候,手机系统并没有杀死APP,用户点击通知栏,页面被唤醒,进行后续操作,当用户在Activity_1点击返回键,界面被销毁,直接退出程序。
* 2、假如用户手动或者某些情况下手机系统自己杀死了APP,这个时候做推送,用户点击后,有时候仅仅是打开APP首页,不会去到目标界面。有时候不会有任何反应
* 为了避免上述2个情况出现,在这里先判断手机状态,双击或被杀死的话,就先唤醒APP,然后再去目标界面,这样,用户点击返回键,也不会直接退出。增加用户在APP中的停留时间。
* 2017/7/26
* chenjianqiang
*/
public class AwakeTempActivity extends Activity {
private AwakePageInfoBean bean;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bean = (AwakePageInfoBean) getIntent().getSerializableExtra("AwakePageInfoBean");
if (bean == null) {
//bean是空,不知道目标界面,就默认唤醒APP
PackageManager pm = getPackageManager();
//包名
Intent intent = pm.getLaunchIntentForPackage("com.chen.demo2");
startActivity(intent);
//这种唤醒方式,需要清单文件中做对应的配置,详见清单文件中,MainActivity下的节点
// Intent intent = new Intent(AwakeTempActivity.this, MainActivity.class);
// intent.setAction("android.intent.chen.CALL");
// startActivity(intent);
// Intent intent = new Intent();
// ComponentName name = new ComponentName("com.chen.demo2"
// ,"com.chen.demo2.MainActivity");
// intent.setComponent(name);
// startActivity(intent);
} else {
if (Util.isDoubleClickExit || !Util.isAPPAlive) {
//处于双击退出或者APP被手机杀死情况
SharedPreferencesUtil spu = new SharedPreferencesUtil(Util.getContext());
spu.saveAwakeAPPBean(bean);
PackageManager pm = getPackageManager();
//包名
Intent intent = pm.getLaunchIntentForPackage("com.chen.demo2");
startActivity(intent);
//这种唤醒方式,需要清单文件中做对应的配置,详见清单文件中,MainActivity下的节点
// Intent intent = new Intent(AwakeTempActivity.this, MainActivity.class);
// intent.setAction("android.intent.chen.CALL");
// startActivity(intent);
// Intent intent = new Intent();
// ComponentName name = new ComponentName("com.chen.demo2"
// ,"com.chen.demo2.MainActivity");
// intent.setComponent(name);
// startActivity(intent);
} else {
AwakePageUtil.awakePage(AwakeTempActivity.this, bean);
}
}
finish();
}
}
MainActivity
package com.chen.demo2;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
public class MainActivity extends Activity {
private SharedPreferencesUtil mSpu;
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSpu = new SharedPreferencesUtil(Util.getContext());
Util.isDoubleClickExit = false;
Util.isAPPAlive = true;
}
@Override
protected void onStart() {
super.onStart();
AwakePageInfoBean bean = mSpu.getAwakeAPPBean();
if (bean != null && !Util.checkStringIsEmpty(bean.getType())) {
AwakePageUtil.awakePage(MainActivity.this, bean);
}
mSpu.deleteBrowserOpenAppBean("AwakeAPPBeanINFO");
}
}
MessageReceive
package com.chen.demo2;
import android.content.Context;
import android.content.Intent;
import org.json.JSONObject;
//模拟的消息push消息接收器。假的
public class MessageReceive {
private void getPushMessaage(Context context, String message) {
try {
JSONObject jsonObj = new JSONObject(message);
if (!Util.checkStringIsEmpty(jsonObj.optString("app")) && !Util.isAllowMessageShow()) {
//所需要的数据不为空,并且,是运行接收的Activity
JSONObject value_jo = new JSONObject(jsonObj.optString("app"));
//类型
String type = value_jo.optString("type");
AwakePageInfoBean bean = new AwakePageInfoBean();
bean.setType(type);
Intent intent = new Intent(context, AwakeTempActivity.class);
intent.putExtra("AwakePageInfoBean", bean);
//把intent放到PendingIntent中,然后和Notification进行后续处理。。。。。。
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
清单文件
<manifest package="com.chen.demo2"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
intent-filter>
activity>
<activity
android:name=".Activity_1"
android:screenOrientation="portrait"/>
<activity
android:name=".Activity_2"
android:screenOrientation="portrait"/>
<activity
android:name=".Activity_3"
android:screenOrientation="portrait"/>
<activity
android:name=".AwakeTempActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait"/>
application>
manifest>