Android Remote Views

听名字就可以看出,remote views是一种远程view,感觉有点像远程service,其实remote views是view的一个结构,他可以在其他的进程中显示,由于它可以在其他的进程中显示,那么他就可以跨进程的更新其他进程的view,这听起来有点不可思议,感觉有点像aidl,但是我要告诉你这确实不是,那它的原理是什么呢?且听后面慢慢道来。

remote views在Android中有两个常见的应用场景:通知栏和桌面小部件。

Remote Views的应用

桌面部件与通知栏分别由AppWidgetManager 与 NotificationManager来管理.。分别与systemService进程中的AppWidgetServer的NotificationManagerServer进行通讯 ,所以,才需要RemoteView来更新界面.RemoteView实现了Paracelable,通过Bindler传递到 systemService进程中。

RemoteViews的内部结构

我们首先来看看RomoteViews的类图:

RemoteViews:中保存Remote端的mPackage和mLayoutId;并用mActions:ArrayList<RemoteViews.Action>保存各种Action。

mPackagemLayoutId是在构造RemoteViews时传进去的[上文图中的seq#1];

mActions是设置各种Remote端的响应Intent以及图形元素的时候,保存到相应的Action中,然后把Action加入到这里保存的;

mLayoutId里的各种控件通过setTextViewText()/ setImageViewResource() / setProgressBar(),等函数在remote端设置的。这些方法再调用setType()[Type可为Boolean
/ Byte / Short / Int/ Long / Char / String / Uri / Bitmap/ Bundle, etc]保存到ReflectionAction中。

SetOnClickPendingIntent是用来在local端用户点击viewId时,发出pendingIntent通知的。在SetOnClickPendingIntent的构造方法中保存viewId和pendingIntent。

ReflectionAction用来在local端显示时,通过Reflect机制执行获得Remote端资源的。在ReflectionAction的构造方法中保存viewId,methodName,type以及value。

ViewGroupActionSetDrawableParameters也是RemoteViews.Action的子类,在这个场景中并未用到,基本原理相似,读者可自行分析。

AppWidgetHostView(显示RemoteViews内容)

RemoteViews提供了内容之后,AppWidgetHost会通过IAppWidgetHost.updateAppWidget()被通知到Remote端有更新,本地端把RemoteViews提供的内容显示在AppWidgetHostView上。

我们来看下类图:

我们分析下流程:

1.      获取RemoteViews里Remote端(AppWidgetProvider)的packageName和layoutId,通过packageName创建远端的context——remoteContext。

2.      通过RemoteViews的apply()方法,真正开始执行侦听Click操作的动作;通过远端Layout获得本地使用的View。

2.1.  克隆一个本地的LayoutInflater;[Seq#8]

2.2.  用克隆出的LayoutInflater对remote端的layoutId执行Inflate,获得Layout所描述的View的Hierarchy,亦即后面用到的rootView;[Seq#9~ 10]

2.3.  对2.2中获得的view执行performApply。[Seq#11~ 19]

performApply()对所有mActions中的Action都执行apply()操作。这样,

2.3.1 对于setOnClickPendingIntent来说,[Seq#12~ 15]

  • 通过rootView(2.2获得的Remote端的Layout的总的View)的findViewById(viewId),找到要侦听的View;[Seq#13]
  • 对找到的要侦听的View设置Click的Listener。[Seq#14]

2.3.2对于ReflectionAction来说,[Seq#16~ 19]

  • 通过rootView(2.2获得的Remote端的Layout的总的View)的findViewById(viewId),找到要设置内容的对象View;[Seq#17]
  • 然后通过Reflect机制,执行View实现类里的方法(比如这里是setImageResource()),把相应的资源的Id设置给它. [Seq#18]

3.      把获得的View加入到本地的View系统中。[Seq#21]

看一段关键代码,通过Reflect机制设置内容的代码片段:

@Override
public void apply(View root) {
    final View view = root.findViewById(viewId);
    Class param = getParameterType();  // 通过this.type得到class:int.class
    Class klass = view.getClass();     // 这个类在Remote的Layout中定义,这里为ImageView
    Method method = klass.getMethod(this.methodName, param); // methodName是实现View类里的方法名:setImageResource(int)  

    try {
        // 执行ImageView.setImageResource(value),value为resId
         method.invoke(view, this.value);
    } catch (Exception ex) {
        throw new ActionException(ex);
    }
 }  

应用实例:

本文开始说过Android RemoteView主要两个应用,其中一个就是桌面小控件。那么接下来说说AppWidget。

AppWidget

AppWidget也就是“窗口小部件”,当我们点击桌面的小部件的时候,其实是触发Remote端的AppWidgetProvider实现;具体显示是Local的AppWidgetHost通过AppWidgetHostView实现。AppWidgetHost、AppWidgetProvider与AppWidgetService和AppWidgetManager按照特有的机制组合在一起,才能完整的实现AppWidget机制。接下来我们具体分析下他的实现流程。

AppWidget系统框架

AppWidget实现Remote端提供UI元素,Local端具体显示。AppWidgetHost在AppWidget系统中是Local端;AppWidgetProvider端是Remote端。AppWidgetHost和AppWidgetProvider直接或通过IAppWidgetService或间接的通过AppWidgetManager,与AppWidgetService实现交互。AppWidgetService是所有元素的总管,负责协调其他各个部分。

AppWidget简要分析

AppWidgetHost

AppWidgetHost通过IAppWidgetService利用Binder机制实现与系统进程中的AppWidgetService通信;

AppWidgetHost有IAppWidgetHost(通过Callbacks)的实现,并在AppWidgetHost.startListening()中注册到AppWidgetService中,实现当Remote端的数据有更新时,通过IAppWidgetHost.updateAppWidget()通知AppWidgetHost更新本地的显示;或者当Remote端的Provider改变时通知AppWidgetHost。

AppWidgetHost创建本地AppWidgetHostView时,会以AppWidgetId和AppWidgetHostView加入mViews: HashMap<Interger,AppWidgetHostView>

AppWidgetProvider

AppWidgetProvider是AppWidget的Remote端内容提供方,并能注册响应其所提供内容的某个View被点击时,响应的Intent。

AppWidgetProvider是一个抽象类,实现类需要实现抽象方法onUpdate() / onDeleted()/ onEnabled()和onDisabled()。这是AppWidgetProvider的一个模板模式实现,要求AppWidgetProvider的实现者:

  • 在AndroidManefest.xml中声明这个AppWidgetProvider是"android.appwidget.action.APPWIDGET_UPDATE"的Receiver,这样AppWidgetProvider作为一个BroardcastReceiver才能接收到AppWidgetService发出的消息。而AppWidgetService查询系统中已经安装了哪些AppWidgetProvider也是通过查询这个接收者的Intent来的实现。所以如果没有这个Receiver,安装的Provider里就没有这个Provider,亦即,未加入到AppWidget系统中。
  • 另外,这个Receiver的meta-data的name指定为“android.appwidget.provider”;resource中用xml定义appwidget-provider内的各种属性。这些属性按包被安装时,检索出来赋给AppWidgetProviderInfo。

通常,对于应用开发来说不太注重AppWidget其他的部分,只是写AppWidgetProvider,但一般也都称AppWidgetProvider为AppWidget开发。

AppWidgetService

AppWidgetService通过IAppWidgetService提供方法给AppWidgetHost、AppWidgetProvider使用。

在mInstalledProviders:AppWidgetService.Provider中保存AppWidgetProvider的信息;在mHost:AppWidgetService.Host中保存AppWidgetHost的信息;并用mAppWidgetIds:AppWidgetService.AppWidgetId保存AppWidgetHost与AppWidgetProvider的绑定关系。

注意:

AppWidgetService运行于三个各自不同的进程空间:

  • AppWidgetService运行于system_process进程;
  • AppWidgetHost运行于自己的进程空间,典型的桌面上的AppWidgetHost运行于Launcher中;
  • AppWidgetProvider也是运行于自己的进程空间,典型的如“电量控制”这个AppWidgetProvider运行于Settings中。

AppWidgetHost和AppWidgetProvider要用到AppWidgetService的服务时,用Binder机制通过IAppWidgetService实现。AppWidgetService通过IAppWidgetHost通知AppWidgetHost;AppWidgetService通过发Broadcast通知AppWidgetProvider。

讲完概念,我们来两个实用的例子:

通知栏:

 Notification notification=new Notification();
        notification.icon=R.drawable.ic_launcher;
        notification.tickerText="hello world";
        notification.when=System.currentTimeMillis();
        notification.flags=Notification.FLAG_AUTO_CANCEL;
        Intent intent=new Intent(this,NotificationActivity.class);
        //定义延迟的意图
        PendingIntent pendingIntent=PendingIntent.getActivity(this,
                 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        notification.setLatestEventInfo(this, "我的标题","我的文本", pendingIntent);

        RemoteViews remoteViews=new RemoteViews(getPackageName(),
                R.layout.remoteview);
        remoteViews.setTextViewText(R.id.title, "我的标题");
        remoteViews.setTextViewText(R.id.content, "我的内容");
        notification.contentView=remoteViews;
        notification.contentIntent=pendingIntent;

        NotificationManager manager=(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        manager.notify(1, notification);

桌面小控件:

要完成桌面小控件需要如下几个步骤:

1 自定义布局

2 定义配置文件

3 自定义类继承AppWidgetProvider

4 功能清单注册

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="84dip"
    android:minHeight="84dip"
    android:initialLayout="@layout/widget"
    android:updatePeriodMillis="300000"
    >
</appwidget-provider>

功能清单的配置:

 <receiver android:name="com.example.remoteviewfinal.MyAppWidgetProvider">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/provider_info"
                ></meta-data>
        </receiver>

自定义类:

public class MyAppWidgetProvider extends AppWidgetProvider {

    @Override
    public void onReceive(final Context context, Intent intent) {
        // TODO 自动生成的方法存根
        super.onReceive(context, intent);

        /**
         * 将图片旋转一圈
         */
        new Thread(new Runnable() {

            @Override
            public void run() {
                Bitmap bitmap=BitmapFactory.decodeResource(context.getResources(),
                        R.drawable.ic_launcher);
                AppWidgetManager manager=AppWidgetManager.getInstance(context);
                for(int i=0;i<37;i++){
                    float degree=(i*10)%360;
                    RemoteViews remoteViews=new RemoteViews(context.getPackageName(),
                            R.layout.widget);
                    remoteViews.setImageViewBitmap(R.id.imageview, rotateBitmap(context,bitmap,degree));
                    ComponentName cn=new ComponentName(context, MyAppWidgetProvider.class);
                    //更新界面
                    manager.updateAppWidget(cn, remoteViews);
                }
            }
        }).start();
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {
        // TODO 自动生成的方法存根
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }

    /**
     * 当第一个实例被添加的时候调用
     */
    @Override
    public void onEnabled(Context context) {
        // TODO 自动生成的方法存根
        super.onEnabled(context);
    }

    /**
     * 当实例被删除的时候调用
     */
    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        // TODO 自动生成的方法存根
        super.onDeleted(context, appWidgetIds);
    }

    /**
     * 当最后一个实例被删除的时候调用
     */
    @Override
    public void onDisabled(Context context) {
        // TODO 自动生成的方法存根
        super.onDisabled(context);
    }

    private Bitmap rotateBitmap(Context context,Bitmap bitmap,float degree){
        Matrix matrix=new Matrix();
        matrix.reset();
        matrix.setRotate(degree);
        Bitmap result=Bitmap.createBitmap(bitmap, 0, 0,bitmap.getWidth(),
                bitmap.getHeight(), matrix,true);
        return result;
    }
}

更新不同进程的界面:

定义界面A
来更新不同进程的界面B. 我们可以将界面A中的remoteView 传递到界面B,界面B 获取对象,调用控件的apply方法更新界面 修改A界面的process属性,使其在不同的进程中运行。

界面A:

public void send(){

        RemoteViews remoteViews=new RemoteViews(getPackageName(), R.layout.widget);
        remoteViews.setTextViewText(R.id.msg, "我的信息");
        PendingIntent pendingIntent=PendingIntent.getActivity(this,0,
                new Intent(this,NotificationActivity.class),
                PendingIntent.FLAG_UPDATE_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.btn, pendingIntent);

        Intent intent=new Intent(Constant.ACTION);
        intent.putExtra(Constant.VIEW, remoteViews);
        sendBroadcast(intent);
    }

界面B:

 private BroadcastReceiver mRemoteBroadcastReceiver=new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            RemoteViews remoteViews=intent.getParcelableExtra(Constant.VIEW);
            if(remoteViews!=null){
                View view=remoteViews.apply(NotificationActivity.this, container);
                container.addView(view);
            }
        }
    };
时间: 2024-10-18 03:06:14

Android Remote Views的相关文章

Android界面绘制流程--------How Android Draws Views

在学习自定义组件的时候,偶然发现官网的这篇文章,觉得不错,于是试着翻译出来.一是为了和大家分享,二是为了加深自己的印象.水平有限,翻译过程中有不正确的地方,欢迎指正. 原文地址为: How Android Draws Views 当一个Activity呈现在用户面前时,其布局将被绘制出来.android系统将处理绘制的过程,但是,前提是Activity需要提供其布局的根节点. 绘制过程从布局的根节点开始,然后对整个布局树型结构(layout tree)进行测量并绘制,绘制过程沿着布局树型结构(l

Android Remote Service 外部访问权限控制

<Service> 可以通过以下参数限制外部访问 android:exported="false"  //不允许其他进程访问 android:process=":remote"  //声明service独立运行进程名称 如果需要支持外部访问,但限定只有某些进程可以访问 <permission android:name="com.example.REQUEST_FINGERPRINT" android:protectionLeve

android remote submix 录制系统内置的声音

Android 4.4中AudioRecord用例 - 录制系统内置声音 http://blog.csdn.net/jinzhuojun/article/details/33748031?utm_source=tuicool&utm_medium=referral 通过API 19新加的MediaRecorder.AudioSource.REMOTE_SUBMIX参数可以让系统App录制系统内置的声音,也就是扬声器的声音.下面是一个巨简单的例子来示例如何通过AudioRecord配合REMOTE

Android Chart Views

2021/1/21 1. 饼状图 实现内容: <1> 动态增加数据 <2> 对于描述位置的冲突,牺牲空间的前提下进行避免 View 地址: https://github.com/meetsl/SAndroidChart/blob/master/app/src/main/java/com/meetsl/sandroidchart/widgets/PieChartView.kt                         开发记录: <1> Android 中绘制角度的

android remote

https://github.com/omerjerk/RemoteDroid https://github.com/DesignativeDave/androrat https://github.com/justin-taylor https://github.com/fatsoon/FloatWifiADB https://github.com/pedrovgs/AndroidWiFiADB

Android AppWidget(转)

AppWidget不知道大家使用这个多不多,这个在手机上也叫做挂件,挂件也就是放在桌面方便用户进行使用的,从android1.6开始挂件支持一些简单的lauout和view,到了android4.0之后谷歌在挂件上也是加上了更为丰富的view支持,下面我们就从头开始来介绍一下这些挂件吧. 如何添加一个简单的AppWidget挂件 添加一个挂件很简单,分为四部,只要按照这四部来弄就很容易添加上一个挂件: (1)添加AppWidgetProviderInfo信息,这个信息是一个以xml文件形式出现的

【起航计划 026】2015 起航计划 Android APIDemo的魔鬼步伐 25 App-&gt;Notification-&gt;Status Bar 状态栏显示自定义的通知布局,省却声音、震动

这个例子的Icons Only 和 Icons and marquee 没有什么特别好说明的. 而Use Remote views in balloon 介绍了可以自定义在Extended Status bar显示Notification的Layout,Extended Status Bar缺省显示Notification 是一个图标后接文字,对应大多数情况是够用了.但如果有需要也可以使用自定义的Layout在Extented Status bar显示Notification,方法是通过Remo

Android中View的绘制过程 onMeasure方法简述

Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布局的根节点开始,从根节点开始测量和绘制整个layout tree. 每一个ViewGroup 负责要求它的每一个孩子被绘制,每一个View负责绘制自己. 因为整个树是按顺序遍历的,所以父节点会先被绘制,而兄弟节点会按照它们在树中出现的顺序被绘制. 绘制是一个两遍(two pass)的过程:一个mea

Android远程桌面助手(Build 0737)

Android Remote Displayer and Controller Build 0737, Aug 02, 2017 新增功能: 录制MP4文件,突破了Android原生180S的限制 截屏并保存成png 拖动左右边框调整窗口大小 adb连接设备时,增加了详细的状态指示 修复:一堆bugs 下载:http://files.cnblogs.com/files/we-hjb/ARDC%28Build0737%29.7z