项目需求讨论-Android推送及Activity启动方式

以前做过一个项目是这样的。手机端监控服务器的状态,首页显示的是监控的服务器的总数及报错数量,然后点击进入后,是列表界面,可以看到报错的服务器列表及好的服务器列表,然后点击列表中的某个可以具体看这个服务器的详情内容,比如监控哪几个进程,哪个进程报错了等信息。所以一共是三个界面:1、首页,2、列表界面,3、详情界面

需求:
  1. 当服务器有报错信息,或者是服务器由错误变成正确的时候,都会有信息推送到手机端。消息推送到达率尽可能要高,因为是监控服务器,所以不像一般的软件通知可有可无。

  2. 当用户是把软件关掉的状态下接受到信息是没关系,反正点击推送信息,都会再打开应用;但是当用户处于上述三个界面的某个界面的时候,都需要立即刷新当前所属界面的信息。比如当前客户是在首页的话,那信息推送过来后,首页就要被刷新;如果是在列表界面,有信息的话,列表界面就要被刷新;如果在详情界面,那详情界面就要被刷新。**


需求一处理:

前提说明,关于推送,我这边因为也做了个iOS小项目。也写了iPhone的推送,所以就粗略说下二者的推送(希望大家能指正,让我学习下):

  • 苹果推送是利用它自带的APNS推送,相当于后台其实推送的信息是推送到了苹果的APNS,再发送到设备从而收到推送信息。举例来说,就是我们在iPhone上把QQ关了。这时候QQ是真的都关了,但是我们还会收到QQ的信息通知,但这时候并不是说QQ在后台活着,收到了信息,而是QQ服务器先把信息发给了APNS,然后APNS发送给iPhone设备,因为iPhone内部有系统进程会来接受APNS发送来的信息,然后层显出来通知。同理什么其他应用的通知也是都这个流程,所以这时候iPhone手机里面等于只要存活着一个能接受到APNS发送过来信息的系统进程,就可以实现呈现不同软件的通知信息了。
IPhone信息推送
  • Android上本来也有类似的苹果的推送,就是谷歌的推送GCM,大家都知道国内不能用这个,因为国内的手机厂商都是对原生安卓系统进行了Google内容的剔除。那国内的推送是怎么实现呢,就只能是每个应用自己有个接受信息的进程。
Android信息推送

所以在Android设备上想要确保推送率,就是说如何让你接受监控服务器信息进程能尽可能的存活在手机中,从而能够接收到服务器发来的信息。
大家可以看下下面这边链接:
Android 进程保活招式大全

如果自己觉得写接受信息的进程太麻烦,就可以直接使用市面上现场的第三方推送,我大概分了下是这几种:

  • 厂商推送:小米推送,华为推送。
    因为是小米手机和华为手机收合入了自家的推送进程,不会被杀死,所以比如在小米手机上正确的合入了小米推送,消息就会准确被推送到手机上。

  • 软件商推送:腾讯信鸽推送,百度云推送,阿里云推送等
    腾讯,百度这些软件厂商也做起了推送,当然别想着合入了腾讯信鸽推送就可以像微信,QQ那样就能一直收到信息。

  • 其他第三方推送:极光推送,友盟推送,LeanCloud等

我这边用的是LeanCloud的混合推送,因为混合推送是内部帮忙集成了小米推送和华为推送,及自带的LeanCloud推送,尽量确保了推送的到达率。阿里云推送也有个混合推送,也是同样的道理。
LeanCloud混合推送


设置完推送,记得对不同手机开启不同权限开关

Paste_Image.png
需求二处理:

(ps:可能大家有更好的需求二的处理方法,希望大家下面回复我。可能我那时候这么处理并不是特别好。)

推送的讨论完了。我们来看下我们的第二个需求,因为推送信息的接受及处理,是在自定义BroadcastReceiver的onReceive(Context context, Intent intent) 方法中获取到信息。所以我想到了context.startActivity方法来启动相应的界面。而且这个界面还不能被重复的开了多个,不然按个返回按钮,就又出现那个老的界面了。在这个应用中,这个界面startActivity多次也只有这一个Activity实例。那这个界面不就等于被刷新了么。

我们分步来解决第二个需求:

1.如何知道当前是处于那个界面,是首页、列表界面,还是详情界面?

String className = ((ActivityManager.RunningTaskInfo) ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningTasks(1).get(0)).topActivity.getClassName();
if ("com.monitorapp.module.home.ui.Act_Main".equals(className)) {
        Intent intent_main = new Intent(context, Act_Main.class);
        intent_main.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent_main);
} else if ("com.monitorapp.module.servers.ui.Act_ServerList".equals(className)) {
        Intent intent_list = new Intent(context, Act_ServerList.class);
        intent_list.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent_list);
} else if ("com.monitorapp.module.servers.ui.Act_MonitorDetail".equals(className)) {
        Intent intent_detail = new Intent(context, Act_MonitorDetail.class);
        intent_detail.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent_detail);
}

没错,我们可以获取到顶部的Activity的类名。然后我们把这个类名与这三个界面的类名进行比较就可以了。然后和哪个界面的Activity类名相同,就去启动相应的Activity就可以。

2.如何startActivity的时候不会开多个该Activity的实例。这就与Activity的启动方式有关。

Activity启动方式:

  • standard 标准模式,默认启动模式:同一个Activity每次都会创建一个新的实例
  • singleTop: 同一个Activity可以实例化多次,但是栈顶只能出现一个,当栈顶不存在要启动的Activity实例时,系统会创建一个新的Activity实例,并且放入栈顶,当栈顶存在要启动的Activity实例时,系统会调用onNewIntent()方法,把Intent对象传递给已经存在的Activity实例,重用栈顶的Activity
  • singleTask: 同一个Activity实例在栈中只能有一个,当栈中不存在要启动的Activity实例时,系统会创建一个新的Activity实例,并放入栈顶,当栈中已存在Activity的实例时,系统会调用已存在的Activity的onNewIntent(),把Intent对象传递给已经存在的Activity实例(并不会创建新的实例),并且不允许栈的上方存在其他的Activity实例,他上方的Activity实例将会被移出栈并销毁
  • singleInstance: 当使用这种模式启动Activity时,系统会分配一个单独的任务,并将Activity的实例放入栈的底端,它不允许其他Activity实例和它共享一个栈

大家也可以看下相关介绍:
Activity任务栈以及启动模式

好了。我们也了解了Activity的启动方式,当时我用的是singleInstance.但是对于这个需求的实现,singleTop和singleTask都可以使用。然后因为这时候这么设置了Activity的启动方式后。再去startActivity,这时候就会去调用改Activity的onNewIntent方法,这时候我们只要在各自的Activity的onNewIntent方法中写上相关接口调用及界面的数据刷新功能就可以了。

<activity
    android:name=".module.home.ui.Act_Main"
    android:launchMode="singleInstance"
    >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    </activity>

<activity
    android:name=".module.monitor.ui.Act_ServerList"
    android:launchMode="singleInstance"
    >
</activity>

<activity
    android:name=".module.monitor.ui.Act_MonitorDetail"
    android:launchMode="singleInstance"
    >
</activity>
@Override
protected void onNewIntent(Intent intent) {
     super.onNewIntent(intent);
     //调用相关接口,然后进行界面刷新
}

推荐阅读更多精彩内容