app消息推送、显示通知栏,点击跳转页面是很通常的功能了,下面以个推为例演示push集成,消息处理模块及app内部路由模块的简单设计。java
集成sdk步骤根据文档一步步作就好了,通常包括lib引入,AndroidManifest.xml
配置,gradle配置,拷贝资源和java文件等。
须要注意的,本身应该作一层封装,由于像图片,统计,推送等第三方api,若是有替换升级等需求,那么封装一层来确保本身代码的更少变更也是蛮必要的。android
服务端推送消息的操做是非UI操做,个推接入后在一个IntentService中收到透传消息(透明传输消息):web
@Override public void onReceiveMessageData(Context context, GTTransmitMessage msg) { try { String payload = new String(msg.getPayload()); PushManager.handlePushMsg(context, payload); } catch (Exception ex) { // log it. } }
payload就是收到的push消息,通常是约定好的json文本。
下面是一个基本示例:编程
{ title:"通知标题", content:"通知内容", payload: { type:"page", extra:{ path:"/article/articleDetail", params:{ "id":"10086" } } } }
通常的推送都须要当即显示通知的,因此会有通知的信息。固然也能够是不带通知的推送。
这里payload里面携带了点击推送后的操做数据,type="page"表示此推送须要执行一个跳转。
path是跳转到的(如下路由表示相同含义)页面的路径——相似url那样的格式,抽象了具体界面。params包括了跳转相关参数,好比这里须要打开文章详情页,那么传递了文章id。json
web中的url跳起色制很是值得借鉴。api
程序设计中,有一种模式:命令模式,将操做和具体的执行分开。安卓系统中的输入事件的处理,Handler+Message机制等,都是相似的。
Msg用来抽象一个消息,而对应的有Handler来处理它。
这样的好处是命令被对象化,以后对它的处理能够利用多态等特性,命令的处理或许要经历多个阶段(Stage),这样能够动态组合不一样的Handler完成对同一个Msg的处理。服务器
若是仅仅是简单的switch+static method去实现的话,随着业务增长,是没法胜任变化的。若是有实现涉及到“消息处理”相似功能的话,不一样消息不一样处理的大前提,多重处理的须要,会让switch泛滥成灾的,而msg+handler仅须要一次switch选择合适的Handler,以后的处理是链式的,不会有再次switch的须要的。app
能够思考下“消息+处理”这类功能的设计方案。
下面分PushMessage和PushHandler两个抽象,分别是推送消息和对应处理。
这里的思路借鉴了安卓中Handler的机制——Handler+Message这样的设计。框架
此外,源码ViewRootImpl、InputStage对输入事件的处理也能够借鉴。异步
类PushMessage其实就是个bean,它对后台推送的消息进行表示。
class PushMessage implements Serializable { private String title; private String content; private Payload payload; ... }
每个PushHandler处理一个PushMessage。这里是一个基类:
/** * PushMsgHandler基类 * ,PushMsgHandler用来处理“某一个”PushMessage */ public abstract class BasePushMsgHandler { protected PushMessage mMessage; public BasePushMsgHandler(PushMessage message) { mMessage = message; } public abstract void handlePushMsg(); }
handlePushMsg()用来供子类完成具体的消息处理。
这里假设业务功能上,须要一类推送是弹通知,并处理通知点击后的路由操做——界面跳转。
这里引入另外一个模块——路由模块,路由模块完成界面跳转相关操做。
像Arouter这样的开源库就是作这类事情的——不论web仍是移动app,都会碰到接收并响应界面跳转指令的功能。
接下来继续本身尝试实现路由功能。
由于路由模块和推送不是相关的——路由命令(或者称为消息)的发出不必定是推送,也能够是其它界面中的按钮等,知道路由模块和推送模块须要分别设计很重要。
有一部分推送是须要执行路由的,对这类推送的处理就是获得其对应的路由命令,以后交给路由模块去处理。
public abstract class BaseRoutePushHandler extends BasePushMsgHandler { public BaseRoutePushHandler(PushMessage message) { super(message); } @Override public void handlePushMsg(Context context) { BaseRouteMsg msg = getRouteMsg(); if (msg != null) { RouterManager.navigate(context, msg); } } public abstract BaseRouteMsg getRouteMsg(); }
BaseRoutePushHandler重写handlePushMsg()完成routeMsg——路由命令的push消息的处理。getRouteMsg()供子类获取到路由命令的消息对象,以后交给RouterManager去处理。
路由模块实现app内不一样界面之间的跳转导航。设计上,RouteMsg表示一个具体的路由命令,以后会有一个(或多个——若是对命令的处理是链式的话?)RouteHandler来处理此路由消息。
鉴于URL对不一样web界面的定位导航优点,为系统中不一样的跳转定义路由path是很不错的想法。
甚至能够定位到界面中的tab子界面,若是直接去关联Activity等,那么耦合很是严重。
RouteMsg设计上只用来表达路由命令,它包含路由path和额外参数。为了面向对象化,参数是有含义的强类型,而不是queryParams那样的基本类型key-value集合,要知道key的命名自己就是一种依赖,那么还不如定义key对应的java属性更直接些。
RouteMsg也是一个bean,固然能够跨进程,这里实现Parcelable固然更好,简单点就实现Serializable标记接口便可。
基类BaseRouteMsg定义以下:
public abstract class BaseRouteMsg implements Serializable { private static int mIdSeed; static { // 设置mIdSeed初始值: // 容许0.1s一个的间隔,不会有超过8*100天的msg还没被处理 long stamp = System.currentTimeMillis() / 100; mIdSeed = (int) (stamp % (8 * 24 * 3600 * 1000)); } private final int mMsgId = mIdSeed++; /** * 获取路由对应的path * @return route path * @see RouteMap */ public abstract String getPath(); /** * 消息编号,递增惟一(方便跨进程)。 */ public final int getMsgId() { return mMsgId; } }
其中getPath()方法要求每一个具体的路由消息声明其对应的跳转路径。子类能够定义其它任意属性——能够被序列化便可。
做为示例,下面是文章详情界面的跳转路由消息:
public class ArticleDetailMsg extends BaseRouteMsg { private int mArticleId; @Override public String getPath() { return RouteMap.PATH_ARTICLE_DETAIL; } public int getArticleId() { return mArticleId; } public void setArticleId(int articleId) { this.mArticleId = articleId; } }
对应每一个RouteMsg对象须要有RouteHandler来处理它,这里引入路由表的概念——RouteMap,它定义了全部的path常量以及获取不一样path对应RouteHandler的方法(工厂方法)。
public final class RouteMap { public static final String PATH_ARTICLE_DETAIL = "articleDetail"; public static BaseRouter getRouter(String path) { switch (path) { case RouteMap.PATH_ARTICLE_DETAIL: return new ArticleDetailRouter(); default: return null; } } }
getRouter(path)根据path返回处理它的RouteHandler,而且RouteMap定义了全部可能的路由path。BaseRouter就是处理某个path对应路由消息的Handler。
基类BaseRouter是抽象的路由消息处理器。将路由模块做为框架设计,须要尽量使用抽象的东西,容许变动及扩展。
public abstract class BaseRouter<T> { protected T mRouteMsg; /** * 路由操做的前置判断 * @return 是否继续前往目标界面 */ public boolean canRoute(Context context) { return true; } /** * 执行导航到目标界面 * * @return 导航成功? */ public abstract boolean navigate(Context context); public void setRouteMsg(T msg) { mRouteMsg = msg; } }
对于mRouteMsg可能更应该是构造函数参数,并且藐似不该该被setter篡改。这里为了可能的方便性(目前不知道是什么),决定仍是做为普通的属性对待。
注意Context是android中的上帝对象,能够确定导航操做须要它,但为了弱化它和RouteHandler的依赖关系(或许是生命周期)仅做为参数提供,而非字段。
方法canRoute(context)用来作导航操做的前置判断,由于路由可能涉及登陆判断等环境问题,这个逻辑须要子类去重写,若是没特殊要求,这里默认返回true。
方法navigate(context)是具体的导航操做,如打开某个Activity。
上面分别介绍了推送和路由模块的大致设计,那么收到一个推送消息,弹出通知,用户点击通知后的跳转,这一系列操做是如何贯彻的呢?接下来就看看。
在sdk提供的IntentService.onReceiveMessageData()中收到透传消息,这里的代码是依赖服务器返回的数据格式的,即json和PushMessage对应,第一步将push消息转为java对象,接着交给PushManager去处理:
// 在PushIntentService.java中,这是sdk提供的接收推送消息的地方 public void onReceiveMessageData(Context context, GTTransmitMessage msg) { try { String payload = new String(msg.getPayload()); PushMessage message = PushMessage.build(payload); PushManager.handlePushMsg(context, message); } catch (Exception ex) { } } // PushManager.handlePushMsg() public static void handlePushMsg(Context context, PushMessage message) throws Exception { BasePushMsgHandler pushHandler = PushHandlerManager.getPushHandler(message); if (pushHandler != null) { BaseRouteMsg routeMsg = pushHandler.getRouteMsg(); if (routeMsg != null) { NotifiyManager.notifyRouteMsg(context, message.getTitle() , message.getContent(), routeMsg); } } }
这里使用一个Manaher类来完成对PushMessage的通常处理逻辑。由于需求假定push都须要谈通知,而且通知点击后执行路由,那么先获得一个routeMsg,以后调用NotifiyManager.notifyRouteMsg()来发送通知。
通知以相似Intent的方式携带了以后的路由消息数据。
安卓中发送通知到通知栏是很简单的操做,须要注意的是:
String tag
来区分不一样的通知。使用tag来发送通知的notify()方法以下:
/** * Post a notification to be shown in the status bar. If a notification with * the same tag and id has already been posted by your application and has not yet been * canceled, it will be replaced by the updated information. * * @param tag A string identifier for this notification. May be {@code null}. * @param id An identifier for this notification. The pair (tag, id) must be unique * within your application. * @param notification A {@link Notification} object describing what to * show the user. Must not be null. */ public void notify(String tag, int id, Notification notification)
由于id是一个int整数,很难作到对不一样业务通知进行惟一区分。
使用tag,由于是一个能够组合的字符串,那么格式就比较灵活了,例如可使用uri这种格式,或者其它任意你可以轻松用来区分不一样业务模块不一样通知的格式来产生tag做为通知的标识。
有关Notification的完整用法这里不去展开,为了能在点击通知以后作一些控制——好比判断用户是否登陆等,可让通知的点击行为是打开一个Service,而不是跳转到某个Activity。
这样的好处是不至于去修改Activity的代码来插入通知跳转的各类逻辑,固然必要的处理有时是必须的——好比Activity打开后清除对应通知。但这类工做能够作的更通常化,让Activity提供最少的逻辑,好比提供管理的跳转path,这样清除通知(或须要撤销的其它路由命令)的动做就能够框架去作了。这部分的功能目前不打算提供,但的确是一个须要考虑的必要功能。
下面的代码展现了点击通知启动Service的操做:
private static void sendRouteNotification(Context context, String title, String content, String notificationTag, BaseRouteMsg msg) { Intent startIntent = RouteIntentService.getIntentOfActionDoRoute(context, msg); PendingIntent pendingIntent = PendingIntent.getService(context, DEFAULT_SERVICE_REQUEST_CODE, startIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(title) .setContentText(content) .setAutoCancel(true) .setContentIntent(pendingIntent); NotificationManager notifyMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notifyMgr.notify(notificationTag, NOTIFICATION_ID, builder.build()); }
类RouteIntentService是继承IntentService的业务类,它响应全部来源(包括此处的通知)的路由命令。下面看它是如何工做的。
在RouteIntentService.java中:
// RouteIntentService.onHandleIntent() @Override protected void onHandleIntent(Intent intent) { if (intent != null) { final String action = intent.getAction(); if (ACTION_DO_ROUTE.equals(action)) { BaseRouteMsg routeMsg = (BaseRouteMsg) intent.getSerializableExtra(EXTRA_ROUTE_MSG); handleActionDoRoute(routeMsg); } } } // RouteIntentService.onHandleIntent() /** * 处理路由跳转命令 * @param routeMsg */ private void handleActionDoRoute(BaseRouteMsg routeMsg) { boolean jumpDone = false; try { if (routeMsg != null) { jumpDone = RouterManager.navigate(this, routeMsg); } } catch (Exception e) { } if (!jumpDone) { RouterManager.openApp(this); } }
从intent中获取到发送通知时设置的routeMsg,交给RouterManager去处理。
// 在RouterManager.navigate() /** * 导航到目标界面 * * @param msg 路由信息 * @return 是否完成跳转 */ public static boolean navigate(Context context, BaseRouteMsg msg) { BaseRouter router = RouteMap.getRouter(msg.getPath()); if (router == null || !router.canJump(context)) { return false; } router.setRouteMsg(msg); return router.navigate(context); }
调用RouteMap.getRouter()获取到对应routeMsg的处理器——router。
router.canJump()用来对当前导航作前置判断,默认返回true。
router.navigate(context)执行具体的跳转逻辑。
做为示例,文章详情界面的路由器以下:
public class ArticleDetailRouter extends BaseRouter<ArticleDetailMsg> { @Override public boolean navigate(Context context) { if (mRouteMsg == null) { return false; } ArticleDetailActivity.launchActivity(context, mRouteMsg.getArticleId()); return true; } }
本文整理了实现“推送、通知、页面跳转”功能的一个简单设计。
Message+Handler模式是一个典型的编程模型。相似Task+Schedulers(异步任务+线程池)那样,体现一种数据和处理的分离思想。
若是后续有更多的关于推送、路由的要求,优先选择改进框架去知足通常需求。
面向抽象编程,不要直接对具体业务编程。
TODO:demo代码后续补上。
(本文使用Atom编写)