1.辅助性服务实战介绍 上一篇文章介绍了什么是Accessibility以及简单的使用,这一篇文章就来讲讲如何使用Accessibility服务来创建一个简单的Android通知中心。Android中通知中心是一个系统层面的服务,负责显示应用和系统发来的通知(Notification,比如USB插入、选择输入法、未接来电、截图、天气信息、新闻推送等等)。在android4.3之前,一般的第三方应用是无法获取Notification list的(在Android4.3之后,有了一个新的接口,NotificationListenerService.getActiveNotifications(),可以获取当前的Notification)。但是利用Accessibility服务可以监听到各种事件的特性,可以开发一个第三方的通知中心,实现与系统通知栏类似的功能。
下面就来介绍如何开发自己的通知中心。
2.开发第三方通知中心 2.1继承AccessibilitySerivce 按照上一篇辅助性服务的介绍,一个辅助性服务可以被捆绑到一个标准的应用程序上,或者以一个单独的安卓工程被创建,我们这里建立一个服务,继承AccessibilitySerivce
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import android.accessibilityservice.AccessibilityService;import android.accessibilityservice.AccessibilityServiceInfo;import android.app.Notification;import android.app.PendingIntent;import android.content.Intent;import android.os.Parcelable;import android.util.Log;import android.view.accessibility.AccessibilityEvent;import android.view.accessibility.AccessibilityNodeInfo;import android.view.accessibility.AccessibilityRecord;import android.widget.Toast;public class NotificationFetcherService extends AccessibilityService { private static final String TAG = \"NotificationFetcherService: \"; public void onAccessibilityEvent(AccessibilityEvent event) { if (!(event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) ){ return; } Notification localNotification = (Notification)event.getParcelableData(); if (localNotification != null) { Intent intent=new Intent(); intent.putExtra(\"NotifyData\", localNotification); intent.setAction(\".NotificationFetcherService\"); sendBroadcast(intent); } } @Override protected void onServiceConnected() { // Define it in both xml file and here, for compatibility with pre-ICS devices AccessibilityServiceInfo info = new AccessibilityServiceInfo(); info.eventTypes = AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; setServiceInfo(info); } @Override public void onInterrupt() { System.out.println(\"onInterrupt\"); } }
继承AccessibilitySerivce必须要重写几个重要的方法:
onServiceConnected方法负责在服务和Activity绑定的时候,进行初始化数据,这里新建了一个AccessibilityServiceInfo对象,并将TYPE_NOTIFICATION_STATE_CHANGED、TYPE_WINDOW_STATE_CHANGED、TYPE_WINDOW_CONTENT_CHANGED纳入监听范围,TYPE_NOTIFICATION_STATE_CHANGED表示这个服务可以监听Notification的变化,我们正是使用这个特性来实现第三方的通知中心功能。
onInterrupt是服务断开时调用的函数
onAccessibilityEvent是最重要的,它负责监听所注册的eventTypes(在onServiceConnected中注册的)的事件。从上面的代码中我们可以得到一个Notification对象:
1 Notification localNotification = (Notification)event.getParcelableData();
得到Notification对象之后,就可以进行自己的操作,我这里是通过广播的形式,将收到的Notification发送给Activity进行处理。
这里也会碰到一个小问题:当一个Notification对象太大时(比如截图、未接来电等,Notification.contentView就很大,通过广播传播会出现data过大无法传输的问题),这时可以把Notification.contentView对象暂时保存在Application中,然后再置为null,Activity中接收到数据后,再进行赋值。
2.2在Manifest中注册service 1 2 3 4 5 6 7 8 9 10 11 <service android:name =\ ".NotificationFetcherService \" android:permission =\ "android.permission.BIND_ACCESSIBILITY_SERVICE \" > <intent-filter > <action android:name =\ "android.accessibilityservice.AccessibilityService \" /> </intent-filter > <meta-data android:name =\ "android.accessibilityservice \" android:resource =\ "@xml /accessibilityserviceconfig /> </service >
这里就是普通的service注册,注意 <mate-data>标签中的xml文件:从Android 4.0版本开始,有另外一种方法:使用XML文件来配置这类服务。如果你以XML的形式来定义你的服务,某些像canRetrieveWindowContent可配置的选项就可用了。和service一样的配置,使用XML来定义。如果你要使用XML路径,要在你的mainfest文件中指定它,在你的服务声明中添加<meta-data>标签,并指向这个XML资源文件。比如上面的代码,我们在res/xml/中建立accessibilityaseviceconfig.xml,内容如下:
1 2 3 4 5 <?xml version=\"1.0\" encoding=\"utf-8\"?> <accessibility-service xmlns:android=\"http://schemas.android.com/apk/res/android\" android:accessibilityEventTypes=\"typeWindowStateChanged|typeNotificationStateChanged|typeWindowContentChanged\" android:accessibilityFeedbackType=\"feedbackGeneric\" />
服务这里就配置好了。
2.3 接受并处理Notification 下面的Activity中就可以接受这个数据,然后怎么处理就看自己了,这里只是简单地显示出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 import android.app.Activity;import android.app.Application;import android.app.Notification;import android.app.PendingIntent;import android.app.PendingIntent.CanceledException;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.os.Parcelable;import android.os.Process;import android.text.method.ScrollingMovementMethod;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.Button;import android.widget.LinearLayout;import android.widget.RemoteViews;import android.widget.TextView;public class NotificaitonActivity extends Activity { private static final int NOTIFY_DATA_FLAG = 1 ; private static final String NOTIFY_DATA_ID_STR= \"NotifyData\"; private NotifyDataReceiver receiver; private TextView textView; private LinearLayout rootLayout; private Button button; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.notify_test_textview); textView.setMovementMethod(ScrollingMovementMethod.getInstance()); rootLayout = (LinearLayout) findViewById(R.id.root_layout); registerBroadcast(); button = (Button) findViewById(R.id.test_button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Button b = new Button(NotificaitonActivity.this); b.setText(" Tthis"); rootLayout.addView(b); } }); } private void registerBroadcast() { receiver = new NotifyDataReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(" .NotificationFetcherService"); this.registerReceiver(receiver, filter); Log.e(" Dx:", " Broadcast registered........."); } private void addToUi(RemoteViews remoteView) { rootLayout.addView(remoteView); } private void showNotify(String notiString) { textView.setText(textView.getText() + \"n\" + notiString); } private class NotifyDataReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { Log.e(\"Dx:\", \"Receiver got msg in onReceive()...\"); Parcelable notifyParcelable = intent.getParcelableExtra(\"NotifyData\"); if (notifyParcelable != null) { Notification notification = (Notification) notifyParcelable; showNotify(\"tickerText: \" + notification.tickerText); showNotify(\"toString: \" + (String)(notification.toString())); RemoteViews remoteV = notification.contentView; if (remoteV==null) { showNotify(\"remoteView is: null\" ); } else { showNotify(\"remoteView is: not null\" ); addToUi(remoteV); } PendingIntent pendIntent = notification.contentIntent; if (pendIntent==null) { showNotify(\"pendIntent is: null\" ); } else { showNotify(\"pendIntent is: not null\" ); } showNotify(\"**************************\" ); showNotify(\" \" ); } } } }
注:这里有很重要的一点,由于AccessibilityService的特殊性,用户必须手动到设置-辅助功能中,打开对应的服务,我们才可以通过AccessibilityService获得对应的数据,这一点非常重要。
上面的Activity只是简单地显示Notification,关于更多Notification的操作,可以参考Notification这个类,其中重要的属性有:contentView,flags。要模拟真正的通知中心,还是要费一番功夫的。这里由于公司项目的保密,暂不提供对应的实现代码(其实得到Notification就已经成功了一半了),有兴趣的同学可以私下和我交流。
3.总结和问题 AccessibilityService的实战就讲到这里,这一篇博文也是拖了一段时间才写完的,也算是为前一段时间的项目做个了结。
项目中目前还存在的问题:
无法获取安装这个应用之前的系统的Notification
得到的Notification对象没法保存在本地,所以这个服务被杀掉之后,所有的数据都会丢失。(试过用db4o这种对象数据库来进行存储,发现行不通)
对Android系统的Notification对象的行为模仿不够(有些系统的事件监听不到,比如usb的插拔、usb调试的开关等)
关于我 && 博客
关于我 , 非常希望和大家一起交流 , 共同进步 .
博客内容导航
优秀博客文章记录 - Android 性能优化必知必会
一个人可以走的更快 , 一群人可以走的更远