1.辅助性服务实战介绍

上一篇文章介绍了什么是Accessibility以及简单的使用,这一篇文章就来讲讲如何使用Accessibility服务来创建一个简单的Android通知中心。Android中通知中心是一个系统层面的服务,负责显示应用和系统发来的通知(Notification,比如USB插入、选择输入法、未接来电、截图、天气信息、新闻推送等等)。在android4.3之前,一般的第三方应用是无法获取Notification list的(在Android4.3之后,有了一个新的接口,NotificationListenerService.getActiveNotifications(),可以获取当前的Notification)。但是利用Accessibility服务可以监听到各种事件的特性,可以开发一个第三方的通知中心,实现与系统通知栏类似的功能。

下面就来介绍如何开发自己的通知中心。

2.开发第三方通知中心

2.1继承AccessibilitySerivce

按照上一篇辅助性服务的介绍,一个辅助性服务可以被捆绑到一个标准的应用程序上,或者以一个单独的安卓工程被创建,我们这里建立一个服务,继承AccessibilitySerivce

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对象:

Notification localNotification = (Notification)event.getParcelableData();

得到Notification对象之后,就可以进行自己的操作,我这里是通过广播的形式,将收到的Notification发送给Activity进行处理。

这里也会碰到一个小问题:当一个Notification对象太大时(比如截图、未接来电等,Notification.contentView就很大,通过广播传播会出现data过大无法传输的问题),这时可以把Notification.contentView对象暂时保存在Application中,然后再置为null,Activity中接收到数据后,再进行赋值。

2.2在Manifest中注册service

<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,内容如下:

<?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中就可以接受这个数据,然后怎么处理就看自己了,这里只是简单地显示出来。

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的实战就讲到这里,这一篇博文也是拖了一段时间才写完的,也算是为前一段时间的项目做个了结。

项目中目前还存在的问题:

  1. 无法获取安装这个应用之前的系统的Notification
  2. 得到的Notification对象没法保存在本地,所以这个服务被杀掉之后,所有的数据都会丢失。(试过用db4o这种对象数据库来进行存储,发现行不通)
  3. 对Android系统的Notification对象的行为模仿不够(有些系统的事件监听不到,比如usb的插拔、usb调试的开关等)

上面的问题,如果你有好的想法,我们私下交流。

作者:Gracker
出处:androidperformance.com
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
打赏一下: 微博打赏