首页 > 系统 > Android > 正文

Android关于Notifiacation的封装

2019-11-09 18:08:31
字体:
来源:转载
供稿:网友

之前在框架封装中提到了有关Notification的处理,觉得很有必要详细整理一下封装的思路。

Notification是一个Android中经常使用的组件了,但是原生API提供的功能并不能够很好的满足商业应用开发时的需求,初始化也比较繁琐,个人认为在开发时有必要在现有API的基础上做一下封装,目的有二,其一是集合应用中针对Notification所需的定制功能,其二是提升项目的代码整洁性和模块化程度。

本文只贴出我认为重要的代码:完整源码以及可演示的demo请查看 QuickDevFramework。

0. Notification构建的封装

目前Android中Notification构建是通过NotificationCompat.Builder来完成的,在Builder类中配置在通知栏上显示所需的各项参数,其中有三个参数是必须的:smallIcon, contentText, contentTitle,这三个元素在我的原生Android 7.0中显示效果如下这三个参数如果任意一条不配置,则Notification不会显示,但是也不会有任何编译错误或运行时异常,因此在构建Notification封装的过程中将这三个参数作为创建一条Notification的必须参数是一个很好的避免犯浑出错的方法;其中,smallIcon对于一个应用来说,应该是一样的,因此可以将其写为常量或者以常量作为默认值重载一个无需SmallIcon的构造方法,我个人选择了后者作为封装方案
// source code from NotificationWrapperPRivate static int defaultIconRes = R.drawable.ic_notify_default;public static void setDefaultIconRes(@DrawableRes int resId) {    defaultIconRes = resId;}public static int getDefaultIconRes() {    return defaultIconRes;}//source code from SimpleNotificationBuilderprivate Context mContext;private NotificationCompat.Builder mBuilder;/** * 构造方法 * <p>此处将Notification的icon设定为只用NotificationWrapper中所配置的默认icon</p> * */public SimpleNotificationBuilder(Context context, @NonNull String contentTitle, @NonNull String contentText) {    this(context, NotificationWrapper.getDefaultIconRes(), contentTitle, contentText);}/** * 构造方法 * <p>icon,contentTitle,contentText为构建一个Notification的必须参数, * 如果不传递这三个参数,代码不会报错,但通知不会显示</p> * */public SimpleNotificationBuilder(Context context, @DrawableRes int icon, @NonNull String contentTitle, @NonNull String contentText) {    this.mContext = context;    mBuilder = new NotificationCompat.Builder(context);    mBuilder.setSmallIcon(icon)    .setContentTitle(contentTitle)    .setContentText(contentText);}

1. Notification常见样式的封装

出去上面只包含最基本元素的Notification外,Notification有多种样式,常见的有如下三种,BigText、BigPicture、Inbox,分别是大文本,大图,多行这三种样式分别对应NotificationCompat下属的BigTextStyle, BigPictureStyle和InboxStyle,在配置好Style之后通过NotificationCompat.Builder.setStyle()方法设置样式生效。可以看到上述三种样式其实也是比较简单的,完全可以在封装中简化配置过程,将样式配置归一化处理
//source code from simpleNotificationBuilderpublic SimpleNotificationBuilder setBigText(String title, String contentText, String summaryText) {    NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();    bigTextStyle.setBigContentTitle(title);    bigTextStyle.bigText(contentText);    if (!TextUtils.isEmpty(summaryText)) {        bigTextStyle.setSummaryText(summaryText);    }    setStyle(bigTextStyle);    return this;}public SimpleNotificationBuilder setBigPicture(String title, Bitmap picture, String summaryText) {    NotificationCompat.BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();    bigPictureStyle.setBigContentTitle(title);    bigPictureStyle.bigPicture(picture);    if (!TextUtils.isEmpty(summaryText)) {        bigPictureStyle.setSummaryText(summaryText);    }    setStyle(bigPictureStyle);    return this;}public SimpleNotificationBuilder setInboxMessages(String title, String summaryText, List<String> lines) {    NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();    inboxStyle.setBigContentTitle(title);    if (!TextUtils.isEmpty(summaryText)) {        inboxStyle.setSummaryText(summaryText);    }    for (String line : lines) {        inboxStyle.addLine(line);    }    setStyle(inboxStyle);    return this;}

2. Notification点击操作的封装

Notification点击产生的操作时通过PendingIntent来设定的,PendingIntent可以看做是Intent的一个包装,使用它的目的就是为了在App未启动的状态下,也可以通过PendingIntent里面的Context执行Intent,从而达到点击通知时无论应用是否启动都能做出操作的目的。直接使用PendingIntent来打开App中的页面是可以的,但是所能做到的事情就是直接通过Intent唤起一个组件而已,如果想退回上一级页面目前网上很多博客都在说TaskStackBuilder、ParentStack,但这个方法只是形式上的指定“这个页面的上一级页面是什么”,两个页面可以完全没有关系,在实际操作中也不是先启动上一级页面再进入目标页面,而是在当前页面退出后如果存在Parent就打开Parent,与我们在进行商业应用开发中所需的逻辑相去甚远。仔细把玩一下目前的主流应用可以看到Notification的处理有如下逻辑:收到一条通知后,如果应用处于启动状态(无论是处于前台还是后台),点击通知时直接打开目标页面,不破坏当前的Activity栈;如果应用处于未启动状态,则先启动应用,然后打开目标页面。如果想要实现这样的功能单靠PendingIntent就不行了,这个功能大概有两个重点:1. 如何判断应用是否启动,2. 在什么时机和地方去判断应用是否启动并执行相应操作。这两个问题需要结合在一起看,因为判断的时机不同判断的方法也可能不同。使用PendingIntent可以做到唤起Activity之类的组件,那么在什么地方和时机去执行判断操作这个问题就找到了突破口:让PendingIntent启动一个Activity或者BroadcastReceiver,在其中进行判断,第一个原因的考虑,我选择了使用BroadcastReceiver,下面叙述。对于应用是否启动的判断,思路有如下:判断进程是否存在获取应用当前Activity的数量判断是否存在启动的前台页面。根据这个思路我找到了判断进程是否存在的方法
/** * 判断应用是否已经启动 * * @param context     一个context * @param packageName 要判断应用的包名 * @return boolean */public static boolean isProcessRunning(Context context, String packageName) {    ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);    List<ActivityManager.RunningAppProcessInfo> processInfo = activityManager.getRunningAppProcesses();    for (int i = 0; i < processInfo.size(); i++) {        if (processInfo.get(i).processName.equals(packageName)) {            return true;        }    }    return false;}使用这个方法可以检查一个进程是否正在运行,而且不需要多余的权限,是比较好的手段,但存在一个问题就是如果检查函数本身就处于被检查的进程中,那么自己检查自己肯定是运行状态。这时候,要么检查activity数量,要么将检查组件置于不同进程中。出于从简处理的目的我自己的实现方案没有选择使用不同进程,而是采取通过ActivityLifeCycle来维护一个Activity计数。以此来判断应用是否正在运行,此时可以明显看到使用BroadcastReceiver比Activity要合适。这个检测方法顺便也解决了判断应用前后台的问题 :)
public atract class Baseapplication extends Application {    /**     * 用于判断一个app是否处于前台     */    private static int mForegroundCount = 0;    /**     * 应用内Activity的数量,如果数量为0,则可以判断当前应用未启动     * <p>如果有什么一像素Activity等东西存在请另改值</p>     * */    private static int mActivityCount = 0;     @Override    public void onCreate() {        super.onCreate();        this.registerActivityLifecycleCallbacks(new FrameworkActivityLifeCycleCallback());    }    ……    /**     * 检查应用主进程是否正在运行     * */    public static boolean isAppRunning() {        Context mContext = getAppContext();        String packageName = mContext.getPackageName();        ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);        List<ActivityManager.RunningAppProcessInfo> processInfo = activityManager.getRunningAppProcesses();        for(int i = 0; i < processInfo.size(); i++){            if(processInfo.get(i).processName.equals(packageName)){                //如果有没被销毁的Activity,则App至少处于后台正在运行,否则App应处于未运行状态                return mActivityCount > 0;            }        }        return false;    }    /**     * 检查App是否处于前台     * */    public static boolean isAppForeground() {        return isAppRunning() && mForegroundCount > 0;    }    private class FrameworkActivityLifeCycleCallback implements ActivityLifecycleCallbacks {        @Override        public void onActivityCreated(Activity activity, Bundle bundle) {            mActivityCount++;        }        @Override        public void onActivityStarted(Activity activity) {            mForegroundCount++;        }        @Override        public void onActivityResumed(Activity activity) {}        @Override        public void onActivityPaused(Activity activity) {}        @Override        public void onActivityStopped(Activity activity) {            mForegroundCount--;        }        @Override        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {}        @Override        public void onActivityDestroyed(Activity activity) {            mActivityCount--;        }    }}负责跳转的Intent封装
Intent broadcastIntent = new Intent(mContext, NotificationReceiver.class);Bundle bundle = new Bundle();if (targetActivityIntent.getExtras() != null) {    bundle.putAll(targetActivityIntent.getExtras());}bundle.putString(NotificationWrapper.KEY_TARGET_ACTIVITY_NAME, targetActivityIntent.getComponent().getClassName());broadcastIntent.putExtra(NotificationWrapper.KEY_NOTIFICATION_EXTRA, bundle);PendingIntent mPendingIntent = PendingIntent.getBroadcast(mContext, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);负责判断逻辑处理的BroadcastReceiver
public abspublic class NotificationReceiver extends BroadcastReceiver {    private static final String TAG = NotificationReceiver.class.getSimpleName();    @Override    public void onReceive(Context context, Intent intent) {        Bundle notificationExtra = intent.getBundleExtra(NotificationWrapper.KEY_NOTIFICATION_EXTRA);        if (notificationExtra == null) {            return;        }        if (BaseApplication.isAppRunning()) {            Intent targetIntent = new Intent();            String targetKey = notificationExtra.getString(NotificationWrapper.KEY_TARGET_ACTIVITY_NAME);            if (TextUtils.isEmpty(targetKey)) {                targetKey = NotificationResumeActivity.class.getName();            }            try {                Class<?> targetActivityClass = Class.forName(targetKey);                targetIntent.setClass(context, targetActivityClass);                targetIntent.putExtras(notificationExtra);            } catch (ClassNotFoundException e) {                targetIntent.setClass(context, NotificationResumeActivity.class);            }            targetIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);            context.startActivity(targetIntent);        }        else {            Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(                    BaseApplication.getAppContext().getPackageName());            launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);            launchIntent.putExtra(NotificationWrapper.KEY_NOTIFICATION_EXTRA, notificationExtra);            context.startActivity(launchIntent);        }    }}在应用启动/初始化Activity中的处理
@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    Intent intent = getIntent();    notificationExtra = intent.getBundleExtra(NotificationWrapper.KEY_NOTIFICATION_EXTRA);    isHandleNotification = notificationExtra != null;}@Overrideprotected void onDestroy() {    super.onDestroy();    handleNotification();}protected void handleNotification() {    if (!isHandleNotification) {        return;    }    String targetKey = notificationExtra.getString(NotificationWrapper.KEY_TARGET_ACTIVITY_NAME);    if (TextUtils.isEmpty(targetKey)) {        Logger.e(TAG, "handleNotification: target key is null !");        return;    }    try {        Class<?> destActivityClass = Class.forName(targetKey);        Intent destIntent = new Intent(this, destActivityClass);        destIntent.putExtras(notificationExtra);        startActivity(destIntent);    } catch (ClassNotFoundException e) {        e.printStackTrace();        Logger.e(TAG, "handleNotification: reflect to get activity class failed !");    }}这样就在同一进程的方案下实现了Notification所需的跳转功能。

3. 扩展预备

上述内容只是对最为常见的一些Notification构建、样式、操作的基础封装,在未来的使用中必然还会有更多的需求,因此自定义的Builder以及一些独特的功能应该有预留的扩展方法,至少不至于在当前Notification构建满足不了需求的时候就只能copy代码...我在项目中编写的SimpleNotificationBuilder代理了常用的NotificationCompat.Builder所含方法,但并没有全部代理,主要是考虑到潜在的SDK更新后源码变更的可能性,因此配置了一个接口用于对类中的NotificationCompat.Builder进行编辑。另一方面可能某些自己构建的Notification也会用到根据应用启动状态判断打开方式的功能,因此将打开BroadcastReceiver的Intent包装功能单独做了一个方法以供使用:
/** * 将Notification的Intent转换成用广播传递的Intent * <p> * 主要用于自定义Notification时处理点击打开Activity的事件,使用此方法 * 将会在应用启动时直接打开目标Activity,应用未启动时先启动应用再打开Activity * </p> * */public static Intent getBroadcastIntent(Context context, Intent targetActivityIntent) {    Intent broadcastIntent = new Intent(context, NotificationReceiver.class);    Bundle bundle = new Bundle();    bundle.putAll(targetActivityIntent.getExtras());    bundle.putString(NotificationWrapper.KEY_TARGET_ACTIVITY_NAME, targetActivityIntent.getComponent().getClassName());    broadcastIntent.putExtra(NotificationWrapper.KEY_NOTIFICATION_EXTRA, bundle);    return broadcastIntent;}
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表