zoukankan      html  css  js  c++  java
  • [Android Pro] Android 4.3 NotificationListenerService使用详解

    reference to : http://blog.csdn.net/yihongyuelan/article/details/40977323

    概况

            Android在4.3的版本中(即API 18)加入了NotificationListenerService,根据SDK的描述(AndroidDeveloper)可以知道,当系统收到新的通知或者通知被删除时,会触发NotificationListenerService的回调方法。同时在Android 4.4 中新增了Notification.extras 字段,也就是说可以使用NotificationListenerService获取系统通知具体信息,这在以前是需要用反射来实现的。

    转载请务必注明出处:http://blog.csdn.net/yihongyuelan

    重要关系

            对于系统通知,三方APP使用NotificationListenerService主要目的是为了获取系统通知相关信息,主要包括:通知的新增和删 除,获取当前通知数量,通知内容相关信息等。这些信息可以通过NotificationListenerService类提供的方法以及 StatusBarNotification类对象来获取。

    NotificationListenerService主要方法(成员变量):

    cancelAllNotifications() :删除系统中所有可被清除的通知; 
    cancelNotification(String pkg, String tag, int id) :删除具体某一个通知;
    getActiveNotifications() :返回当前系统所有通知到StatusBarNotification[];
    onNotificationPosted(StatusBarNotification sbn) :当系统收到新的通知后出发回调; 
    onNotificationRemoved(StatusBarNotification sbn) :当系统通知被删掉后出发回调;

    以上是NotificationListenerService的主要方法,通过这些方法就可以在应用中操作系统通知,在 NotificationListenerService中除了对通知的操作之外,还可以获取到通知的StatusBarNotification对象, 通过该对象可以获取通知更详细的数据。

    StatusBarNotification主要方法(成员变量):

    getId():返回通知对应的id;
    getNotification():返回通知对象;
    getPackageName():返回通知对应的包名;
    getPostTime():返回通知发起的时间;
    getTag():返回通知的Tag,如果没有设置返回null;
    getUserId():返回UserId,用于多用户场景;
    isClearable():返回该通知是否可被清楚,FLAG_ONGOING_EVENT、FLAG_NO_CLEAR;
    isOngoing():检查该通知的flag是否为FLAG_ONGOING_EVENT;

    使用简介

    正确使用NotificationListenerService需要注意三点:

    (1). 新建一个类并继承自NotificationListenerService,override其中重要的两个方法;

    public class NotificationMonitor extends NotificationListenerService {
            @Override
            public void onNotificationPosted(StatusBarNotification sbn) {
                  Log.i("SevenNLS","Notification posted");
            }
     
            @Override
            public void onNotificationRemoved(StatusBarNotification sbn) {
                  Log.i("SevenNLS","Notification removed"); 
            }
    }

    (2). 在AndroidManifest.xml中注册Service并声明相关权限;

     <service android:name=".NotificationMonitor"
              android:label="@string/service_name"
              android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
         <intent-filter>
             <action android:name="android.service.notification.NotificationListenerService" />
         </intent-filter>
     </service>

    (3). 开启NotificationMonitor的监听功能;

            完成以上两步之后,将程序编译并安装到手机上,但此时该程序是无法监听到新增通知和删除通知的,还需要在"Settings > Security > Notification access"中,勾选NotificationMonitor。此时如果系统收到新的通知或者通知被删除就会打印出相应的log了。

            这里需要注意,如果手机上没有安装使用NotificationListenerService类的APP,Notification access是不会显示出来的。可以在源码/packages/apps/Settings/src/com/android/settings /SecuritySettings.java中看到,如果没有使用NotificationListenerService的APK,直接就不显示这一 项了。

    mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS);
    if (mNotificationAccess != null) {
        final int total = NotificationAccessSettings.getListenersCount(mPM);
        if (total == 0) {
            if (deviceAdminCategory != null) {
                deviceAdminCategory.removePreference(mNotificationAccess);
            }
        } else {
            final int n = getNumEnabledNotificationListeners();
            if (n == 0) {
                mNotificationAccess.setSummary(getResources().getString(
                        R.string.manage_notification_access_summary_zero));
            } else {
                mNotificationAccess.setSummary(String.format(getResources().getQuantityString(
                        R.plurals.manage_notification_access_summary_nonzero,
                        n, n)));
            }
        }
    }

    使用详解

    通过前面的讲解(实际上就是对AndroidDeveloper的解释),已经可以正常使用NotificationListenerService了,但对于实际应用中,需要考虑的事情还比较多。比如:

    1. 如何检测应用已开启Notification access监听功能?

    如果检测到应用没有激活Notification access监听功能,需要提示用户开启;

    2. 能不能主动跳转到Notification access监听页面?

    如果能够根据第1步的判断自动跳转到对应的页面,那可以省掉很多操作;

    3. 如何与NotificationListenerService交互?

    涉及到与Service的交互,但又与普通的Service不同,这里后文解释;

    4. NotificationListenerService使用过程中有哪些注意事项?

    在使用NotificationListenerService过程中自己遇到了一些坑,后文会通过分析给出相应的解决方案;

    程序运行截图

     

    图 1 程序运行截图

    示例介绍

            NotificationListenerDemo主要用于获取系统当前通知信息,并可手动创建"可清除通知",逐条删除"可清除通知",一次性删 除"可清除通知",以及显示系统当前活动的通知信息。实际上该示例回答了前面使用详解中提出的各项疑问,在实际使用过程中相信大部分人都会遇到,因此这里 逐条展开与大家分享。


    图 2 主界面

    功能分析

    1. 如何检测应用已开启Notification access监听功能?

            在程序启动时,执行Notification access的检测,查看是否访问Notification的权限。如果用户没有Enable Notification access,则弹出提示对话框,点击OK跳转到Notification access设置页面。


    图 3 首次启动 isEnable

            使用NotificationListenerService的应用如果开启了Notification access,系统会将包名等相关信息写入SettingsProver数据库中,因此可以从数据库中获取相关信息并过滤,从而判断应用是否开启了 Notification access,代码如下:

    private static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
    private boolean isEnabled() {
        String pkgName = getPackageName();
        final String flat = Settings.Secure.getString(getContentResolver(),
                ENABLED_NOTIFICATION_LISTENERS);
        if (!TextUtils.isEmpty(flat)) {
            final String[] names = flat.split(":");
            for (int i = 0; i < names.length; i++) {
                final ComponentName cn = ComponentName.unflattenFromString(names[i]);
                if (cn != null) {
                    if (TextUtils.equals(pkgName, cn.getPackageName())) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    在返回值flat中如果包含了应用的包名,即可确定应用已开启Notification access,反之则表示没有开启。

    2. 能不能主动跳转到Notification access监听页面?

            通过查看可以知道,Notification access界面接收action 为"android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"的intent启动,因此使用 startActivity可以很容易的跳转到该页面,从而避免用户在Settings中查找。代码如下:

    private static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
    private void openNotificationAccess() {
        startActivity(new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS));
    }

    3. 如何与NotificationListenerService交互?

            因为NotificationListenerService中包含了四个重要的方法,分别是:onNotificationPosted、 onNotificationRemoved、cancelNotification、cancelAllNotifications。通过这些方法我们 才能实现诸如通知信息的获取以及删除等功能,虽然这些方法是public的,那是不是意味着我们只要拿到 NotificationListenerService的对象就可以直接调用这些方法了呢?那如何拿到Service的对象呢?在之前的博文中,曾有提 到与Service的交互( 具体可参考拙作《Android中程序与Service交互的方式——交互方式》),可以看到与Service的交互有很多种方法,但如果要拿到Service的对象,归根到底还是需要Binder。

            也就是说得使用bindService的办法,将onServiceConnected回调中的IBinder对象转型成NotificationListenerService的对象。测试代码如下:

    //在MainActivity.java的onCreate方法中使用bindService帮顶NotificationMonitor服务
    bindService(new Intent(this,NotificationMonitor.class  ), new ServiceConnection() {
      @Override
      public void onServiceDisconnected(ComponentName arg0) {
      }
      
      @Override
      public void onServiceConnected(ComponentName arg0, IBinder arg1) {
        NotificationMonitor.MyBinder localBinder = (MyBinder)arg1;
        NotificationMonitor mMonitor = localBinder.getService();
      }
    }, BIND_AUTO_CREATE);
    //NotificationMonitor的onBind方法返回构造的Binder对象
    public class NotificationMonitor extends NotificationListenerService {
      private MyBinder mBinder = new MyBinder();
      public  class MyBinder extends Binder{
        public NotificationMonitor getService(){
          return NotificationMonitor.this;
        }
      }
    
      @Override
      public IBinder onBind(Intent arg0) {
        return mBinder;
      }
    
      @Override
      public void onNotificationPosted(StatusBarNotification sbn) {
        getActiveNotifications();
        cancelAllNotifications();
      }
    
      @Override
      public void onNotificationRemoved(StatusBarNotification sbn) {
      }
    }

    那这样操作之后是不是就意味着可以拿到NotificationMonitor的对象并直接调用getActiveNotifications()方法, 用于获取当前系统通知的信息了呢?很抱歉,事实证明这样是不行的。这里简单的分析下,在后面的NotificationListenerService原 理分析中再详细讲解。在NotificationListenerService的源码中可以看到:

    @Override
    public IBinder onBind(Intent intent) {
        if (mWrapper == null) {
            mWrapper = new INotificationListenerWrapper();
        }   
        return mWrapper;
    }

    这里的INotificationListenerWrapper是NotificationListenerService的一个内部类:

    private class INotificationListenerWrapper extends INotificationListener.Stub

    而NotificationMonitor继承自NotificationListenerService,默认的onBind方法却是:

    @Override
    public IBinder onBind(Intent intent) {
        return super.onBind(intent);
    }

      那应该如何使用NotificationListenerService中的方法呢?在拙作《Android中程序与Service交互的方式——交互方式》中,已经提供了很多的例子,这里仅以广播的方式为例。

            既然NotificationMonitor可以使用NotificationListenerService的方法,那通过 NotificationMonitor把通知状态的改变以及数据获取到,并使用static数据进行存储,之后再在MainActivity中直接使用 即可。在MainActivity中控制通知的单个删除和全部删除,则使用广播的方式发送给NotificationMonitor进行处理。 MainActivity与NotificationMonitor的关系类图如下:

    图 4 结构类图

    NotificationMonitor和MainActivity关键代码如下:

    public class NotificationMonitor extends NotificationListenerService {
        private static final String TAG = "SevenNLS";
        private static final String TAG_PRE = "[" + NotificationMonitor.class.getSimpleName() + "] ";
        private static final int EVENT_UPDATE_CURRENT_NOS = 0;
        public static final String ACTION_NLS_CONTROL = "com.seven.notificationlistenerdemo.NLSCONTROL";
        //用于存储当前所有的Notification的StatusBarNotification对象数组
        public static List<StatusBarNotification[]> mCurrentNotifications = new ArrayList<StatusBarNotification[]>();
        public static int mCurrentNotificationsCounts = 0;
        //收到新通知后将通知的StatusBarNotification对象赋值给mPostedNotification
        public static StatusBarNotification mPostedNotification;
        //删除一个通知后将通知的StatusBarNotification对象赋值给mRemovedNotification
        public static StatusBarNotification mRemovedNotification;
        private CancelNotificationReceiver mReceiver = new CancelNotificationReceiver();
        // String a;
        private Handler mMonitorHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case EVENT_UPDATE_CURRENT_NOS:
                        updateCurrentNotifications();
                        break;
                    default:
                        break;
                }
            }
        };
    
        class CancelNotificationReceiver extends BroadcastReceiver {
    
            @Override
            public void onReceive(Context context, Intent intent) {
                String action;
                if (intent != null && intent.getAction() != null) {
                    action = intent.getAction();
                    if (action.equals(ACTION_NLS_CONTROL)) {
                        String command = intent.getStringExtra("command");
                        if (TextUtils.equals(command, "cancel_last")) {
                            if (mCurrentNotifications != null && mCurrentNotificationsCounts >= 1) {
                                //每次删除通知最后一个
                                StatusBarNotification sbnn = getCurrentNotifications()[mCurrentNotificationsCounts - 1];
                                cancelNotification(sbnn.getPackageName(), sbnn.getTag(), sbnn.getId());
                            }
                        } else if (TextUtils.equals(command, "cancel_all")) {
                            //删除所有通知
                            cancelAllNotifications();
                        }
                    }
                }
            }
    
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            logNLS("onCreate...");
            IntentFilter filter = new IntentFilter();
            filter.addAction(ACTION_NLS_CONTROL);
            registerReceiver(mReceiver, filter);
        //在onCreate时第一次调用getActiveNotifications()
            mMonitorHandler.sendMessage(mMonitorHandler.obtainMessage(EVENT_UPDATE_CURRENT_NOS));
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            unregisterReceiver(mReceiver);
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            // a.equals("b");
            logNLS("onBind...");
            return super.onBind(intent);
        }
    
        @Override
        public void onNotificationPosted(StatusBarNotification sbn) {
            //当系统收到新的通知后,更新mCurrentNotifications列表
            updateCurrentNotifications();
            logNLS("onNotificationPosted...");
            logNLS("have " + mCurrentNotificationsCounts + " active notifications");
            mPostedNotification = sbn;
            //通过以下方式可以获取Notification的详细信息
            /*
             * Bundle extras = sbn.getNotification().extras; String
             * notificationTitle = extras.getString(Notification.EXTRA_TITLE);
             * Bitmap notificationLargeIcon = ((Bitmap)
             * extras.getParcelable(Notification.EXTRA_LARGE_ICON)); Bitmap
             * notificationSmallIcon = ((Bitmap)
             * extras.getParcelable(Notification.EXTRA_SMALL_ICON)); CharSequence
             * notificationText = extras.getCharSequence(Notification.EXTRA_TEXT);
             * CharSequence notificationSubText =
             * extras.getCharSequence(Notification.EXTRA_SUB_TEXT);
             * Log.i("SevenNLS", "notificationTitle:"+notificationTitle);
             * Log.i("SevenNLS", "notificationText:"+notificationText);
             * Log.i("SevenNLS", "notificationSubText:"+notificationSubText);
             * Log.i("SevenNLS",
             * "notificationLargeIcon is null:"+(notificationLargeIcon == null));
             * Log.i("SevenNLS",
             * "notificationSmallIcon is null:"+(notificationSmallIcon == null));
             */
        }
    
        @Override
        public void onNotificationRemoved(StatusBarNotification sbn) {
            //当有通知被删除后,更新mCurrentNotifications列表
            updateCurrentNotifications();
            logNLS("removed...");
            logNLS("have " + mCurrentNotificationsCounts + " active notifications");
            mRemovedNotification = sbn;
        }
    
        private void updateCurrentNotifications() {
            try {
                StatusBarNotification[] activeNos = getActiveNotifications();
                if (mCurrentNotifications.size() == 0) {
                    mCurrentNotifications.add(null);
                }
                mCurrentNotifications.set(0, activeNos);
                mCurrentNotificationsCounts = activeNos.length;
            } catch (Exception e) {
                logNLS("Should not be here!!");
                e.printStackTrace();
            }
        }
    
        //获取当前状态栏显示通知总数
        public static StatusBarNotification[] getCurrentNotifications() {
            if (mCurrentNotifications.size() == 0) {
                logNLS("mCurrentNotifications size is ZERO!!");
                return null;
            }
            return mCurrentNotifications.get(0);
        }
    
        private static void logNLS(Object object) {
            Log.i(TAG, TAG_PRE + object);
        }
    
    }

    而MainActivity主要负责界面显示与交互,关键代码如下:

    public class MainActivity extends Activity {
    
        private static final String TAG = "SevenNLS";
        private static final String TAG_PRE = "["+MainActivity.class.getSimpleName()+"] ";
        private static final int EVENT_SHOW_CREATE_NOS = 0;
        private static final int EVENT_LIST_CURRENT_NOS = 1;
        private static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
        private static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
        private boolean isEnabledNLS = false;
        private TextView mTextView;
    
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case EVENT_SHOW_CREATE_NOS:
                //显示创建的Notification对应的pkgName、Tag、Id
                        showCreateNotification();
                        break;
                    case EVENT_LIST_CURRENT_NOS:
                //显示当前所有的Notification数量及其包名
                        listCurrentNotification();
                        break;
    
                    default:
                        break;
                }
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mTextView = (TextView) findViewById(R.id.textView);
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            //判断是否有开启Notification access
            isEnabledNLS = isEnabled();
            logNLS("isEnabledNLS = " + isEnabledNLS);
            if (!isEnabledNLS) {
            //如果没有开启则显示确认对话框
                showConfirmDialog();
            }
        }
    
        public void buttonOnClicked(View view) {
            mTextView.setTextColor(Color.BLACK);
            switch (view.getId()) {
                case R.id.btnCreateNotify:
                    logNLS("Create notifications...");
            //创建可清除的Notification
                    createNotification(this);
            //显示当前状态栏中所有Notification数量及其包名
                    mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_SHOW_CREATE_NOS), 50);
                    break;
                case R.id.btnClearLastNotify:
                    logNLS("Clear Last notification...");
            //清除最后一个Notification
                    clearLastNotification();
            //显示当前状态栏中所有Notification数量及其包名
                    mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_LIST_CURRENT_NOS), 50);
                    break;
                case R.id.btnClearAllNotify:
                    logNLS("Clear All notifications...");
            //清除所有"可被清除"的Notification
                    clearAllNotifications();
                    mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_LIST_CURRENT_NOS), 50);
                    break;
                case R.id.btnListNotify:
                    logNLS("List notifications...");
                    listCurrentNotification();
                    break;
                case R.id.btnEnableUnEnableNotify:
                    logNLS("Enable/UnEnable notification...");
            //打开Notification access启动/取消界面
                    openNotificationAccess();
                    break;
                default:
                    break;
            }
        }
    
        //......省略
    }

    4. NotificationListenerService使用过程中有哪些注意事项?

            如果细心察看代码的童鞋,一定发现代码中有使用Handler,以及一些奇怪但又被注释掉的代码,比如"a.equals("b")"。从使用上来 说,没有必要使用handler,那干嘛要多次一举?这里就给大家分享一下在写NotificationListenerDemo时遇到的一些坑。

    ①. NotificationMonitor的onCreate方法中使用handler来调用getActiveNotifications()方法

            若直接在onCreate或者onBind方法中调用getActiveNotifications()方法是无法获取当前系统通知。主要是因为 NotificationMonitor还未完成初始化,而根本原因则是INotificationListenerWrapper对象mWrapper 还未初始化,此时使用getActiveNotifications()方法又会调用到mWrapper,因此无法返回正常数据。在 NotificationListenerService中可以看到getActiveNotifications()的源码:

    public StatusBarNotification[] getActiveNotifications() {
        try {
            return getNotificationInterface().getActiveNotificationsFromListener(mWrapper);
        } catch (android.os.RemoteException ex) {
            Log.v(TAG, "Unable to contact notification manager", ex);
        }   
        return null;
    }

    也就是说只要在onBind方法完成之后,再调用getActiveNotifications()方法就可以正常获取数据了,因此这里使用了handler多线程的方式。当然,为了保险可以使用sendEmptyMessgeDelay加上延时。

    ②. 如果NotificationMonitor在onCreate或onBind方法中crash,则该service已经失效,需重启手机才能进行后续开发验证

            如果在onCreate或者onBind方法中,出现异常导致NotificationMonitor发生crash,就算找到问题并将其改正,之后 的验证还是无法继续进行的,也就是无法收到通知的新增和删除消息,onNotificationPosted和 onNotificationRemoved方法不会被调用。

            这也是我在onBind方法中故意注释导致空指针异常的代码,有兴趣的童鞋可以把注释去掉后尝试,去掉注释会导致 NotificationListenerDemo异常停止,此时你再加上注释再次运行NotificationListenerDemo,虽然程序可以 正常启动,但无法正常执行NotificationMonitor中的onNotificationPosted和 onNotificationRemoved方法。这个涉及NotificationListenerService的原理,后面会另行分析。

    ③. MainActivity中onClick方法里使用handler操作

            当点击删除通知时,系统通知相关状态还未更新,此时还没有回调到NotificationMonitor中,所以获取的数据就还是上一次的数据。为了 能够获取到正确的Notification数据,可以使用handler并加上延时,这样再去获取Notification信息时,系统已经触发了 NotificationMonitor回调,数据也有正常了。另外,50ms的延时几乎是感知不到的。

    ④. 为什么要使用ArrayList来保存StatusBarNotification数组对象

            当新增或者删除通知时,会触发onNotificationPosted或onNotificationRemoved回调,在该方法中调用 getActiveNotifications()方法用以获取当前系统通知信息。而getActiveNotifications()返回的是 StatusBarNotification[]数组,因为这个数组是可变长的,也就是长度会随时变化,因此无法直接存储。使用ArrayList可以很 好的解决这个问题,在ArrayList对象中添加一个StatusBarNotification[]对象,之后使用 ArrayList.set(0,statusbar[])方法对数据进行更新即可。

    总结

            NotificationListenerService是Android 4.3 之后新增的接口服务,用于获取系统Notification信息,这在之前的Android版本是无法直接办到的。在Android 4.4中,增加了Notification.extra变量,使得获取Notification相关信息更加丰富,这些接口的开放更加利于三方应用的使 用,但同时也会带来一些隐私问题。

            本文针对NotificationListenerService的使用进行了详细分析,当然其中不乏有失偏颇的地方,本着互联网知识共享精神也将自己的一些记录发布出来,一来可做笔记,二来希望能够给苦苦寻觅的童鞋一些帮助。

            后续会对NotificationListenerService的原理进行分析,敬请期待。

            NotificationMonitor代码免积分下载:下载Demo

            为了后续能够更新,已经代码传到github上,有兴趣的童鞋可以在github上查看,连接戳这里

  • 相关阅读:
    滚轮事件
    键盘事件
    运动(学习)
    事件(没有尽头的待完善)
    js 盒子模型(没写完)
    Number 数字相关的方法, 强制 、隐式类型转换 、进制之间转换
    操作DOM 和 节点
    DOM
    Object 的一些静态方法 、 for-in 循环、Object.keys() 、Object.values()、Object.entries()
    删除字段
  • 原文地址:https://www.cnblogs.com/0616--ataozhijia/p/4914645.html
Copyright © 2011-2022 走看看