Android神奇“控件”-----RemoteViews

Android神奇“控件”-----RemoteViews

2016年12月30日 19:31:30 王月半子 阅读数:4750 标签: androidRemoteView跨进程更新UI 更多

个人分类: android

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 
转载请注明 http://blog.csdn.net/wrg_20100512/article/details/53940485 
  好久没有写博客了,主要是忙着找工作和做毕业设计,没有时间来看一些值得分享的东西。今天写这篇博客是因为我被安排了解下RemoteViews。说实话,这个东西真心没有看过,仅仅看《Android艺术开发探索》的时候知道有这么个“控件”,还记得任主席在群里说过这是他这本书中最自豪的一章,有没有之一我就不清楚啦!话不多说,那就开始Android神奇“控件”之旅吧!

RemoteViews是什么?

先从表层意思理解RemoteViews感觉它是一个view的集合,而且和远程有关系。那事实上它是什么呢?请看官方对它的说明:

 

这里写图片描述

 

从说明可以看出,RemoteViews是用来描述一个视图的,它描述的这个视图将显示在另外一个进程中,这也就符合了RemoteViews中Remote这层含义。同时说明里也说了RemoteViews提供了一些基本的操作方法来修改它描述的那个视图的内容。听起来它还真像是个“控件”,那它真的是吗?

看一下RemoteViews的类继承关系: 

这里写图片描述

 

 

这里写图片描述

 

从图中发现,RemoteViews与View没有半毛钱的关系,它仅仅就是Object的一个子类,实现了Parcelable接口(这就为RemoteViews能够实现跨进程提供了条件)。所以从严格意义上来说,RemoteViews并不是一个控件,它仅仅是为生成控件和修改控件属性提供一系列的方法。

总结:RemoteViews就是为跨进程生成控件和修改控件属性提供一系列方法的一个类。

说了RemoteViews是什么之后,咱们来看看为什么要用RemoteViews!

为什么要用RemoteViews?

既然RemoteViews是用于跨进程更新UI的,那咱们就来创造这么一个场景:

同一个应用中有两个Activity,这两个Activity分别处在不同的进程中

 

这里写图片描述      这里写图片描述


            MainActivity                       TempActivity

 

 

这里写图片描述


其中MainActivity所属的进程为com.example.bjwangruigang.remoteviewstudy,TempActivity所属的进程为com.example.bjwangruigang.remoteviewstudy:remote。现在需要通过TempActivity来改变MainActivity中的视图,也就是实现跨进程更新UI这么一个功能。具体来说就是在MainActivity中添加两个Button。

 

传统方式实现跨进程更新UI

拿到这个场景需求,结合跨进程和更新UI的知识,有以下几个方案:

  1. TempActivity把要添加的两个Button的布局的ID值通过BroadcastRecriver发送,在MainActivity中注册该广播,同时获取其中的布局ID值,通过LayoutInflater来绘制那两个Button,最后添加到MainActivity的布局中去。

  2. TempActivity通过AIDL这种方式将要添加的两个Button的布局的ID值发送到AIDLService中,通过Handler来发送消息、处理消息。处理过程同样是通过LayoutInflater来绘制那两个Button,最后添加到MainActivity的布局中去。

其实这两种方案大同小异,无非采用的进程间通信方式不同,后续的添加视图是一模一样的。方案一采用广播的形式来进行IPC通信,而方案二则采用AIDL这种相对原生的IPC方式。为了重温AIDL,这里我采用AIDL 的方式来实现上述效果。

首先建立IViewManager.aidl。

interface IViewManager {
    void setTextViewText(in int id,in String text);//设置TextView的内容
    void addView(in int layoutId);                 //添加View视图
}
  • 1
  • 2
  • 3
  • 4

rebuild project让IDE工具自己生成AIDL借口对应的java文件。

建立ViewAIDLService文件。

public class ViewAIDLService extends Service {
    private static final String TAG = "ViewAIDLService";
    private Binder viewManager = new IViewManager.Stub(){
        @Override
        public void setTextViewText(int id, String text) throws RemoteException {
            Message message = new Message();
            message.what = 2;
            Bundle bundle = new Bundle();
            bundle.putInt("id",id);
            bundle.putString("text",text);
            message.setData(bundle);
            new MainActivity.MyHandler(ViewAIDLService.this,getMainLooper()).sendMessage(message);
        }

        @Override
        public void addView(int layoutId) throws RemoteException {
            Message message = new Message();
            message.what = 3;
            Bundle bundle = new Bundle();
            bundle.putInt("layoutId",layoutId);
            message.setData(bundle);
            Log.i(TAG,"thread name = "+Thread.currentThread().getName());
            new MainActivity.MyHandler(ViewAIDLService.this,getMainLooper()).sendMessage(message);
        }

    };
    public ViewAIDLService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return viewManager;
    }
}
  • 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

在TempActivity中绑定服务,并在绑定成功后,针对实现的功能调用不同的远程方法。

  private ServiceConnection viewServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG,"onServiceConnected");
            IViewManager viewsManager = IViewManager.Stub.asInterface(service);
            try {
                viewsManager.setTextViewText(R.id.text,"通过AIDL跨进程修改TextView内容");
                viewsManager.addView(R.layout.layout);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    Intent viewServiceIntent = new Intent(this,ViewAIDLService.class);
    bindService(viewServiceIntent,viewServiceConnection,Context.BIND_AUTO_CREATE);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

最终在MainActivity中处理消息,实现功能。(传统的实现方式对应着case 2和case 3)

public static class MyHandler extends Handler {
        WeakReference<Context> weakReference;
        public MyHandler(Context context, Looper looper) {
            super(looper);
            weakReference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.i(TAG, "handleMessage");
            switch (msg.what) {
                case 1: //RemoteViews的AIDL实现
                    RemoteViews remoteViews = msg.getData().getParcelable("remoteViews");
                    if (remoteViews != null) {
                        Log.i(TAG, "updateUI");

                        View view = remoteViews.apply(weakReference.get(), mLinearLayout);
                        mLinearLayout.addView(view);
                    }
                    break;
                case 2: //修改MainActivity中TextView的内容
                    Bundle bundle = msg.getData();
                    TextView textView = (TextView) mLinearLayout.findViewById(bundle.getInt("id"));
                    textView.setText(bundle.getString("text"));
                    break;
                case 3: //在MainActivity中添加View视图
                    LayoutInflater inflater = LayoutInflater.from(weakReference.get());
                    View view = inflater.inflate(msg.getData().getInt("layoutId"),null);
                    mLinearLayout.addView(view);
                default:
                    break;
            }
        }

    };
  • 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

大功告成,看一下效果吧! 

这里写图片描述

 

这里我在绑定服务成功之后 ,相继调用了两次远程服务来实现两种远程UI更新(修改MainActivity中TextView中的内容和为MainActivity中添加两个Button)。那问题来了,如果这时候我们有其他的需求,比如我要为Button中修改内容,这时候我们还需要在IViewManager添加新的接口,在ViewAIDLService实现接口,当然MainActivity中对应的代码同样要修改,牵一发而动全身。除此以外,多次IPC带来的开销问题不容小觑。

终上所述,传统方式实现跨进程更新UI是可行的,但不得不提有以下弊端:

  • View中的方法数比较多,在IPC中需要增加对应的方法比较繁琐。

  • View的每一个方法都会涉及到IPC操作,多次IPC带来的开销问题不容小觑。

  • View中方法的某些参数可能不支持IPC传输。例如:OnClickListener,它仅仅是个接口没有序列化。

接下来我们来看看RemoteViews在实现上述功能有什么优势

RemoteViews实现跨进程更新UI

RemoteViews实现跨进程更新UI同样既可以通过AIDL也可以使用BroadcastReceiver,这里为了和传统方式做下对比,只贴出AIDL方式的代码。

首先建立IremoteViewsManager.aidl。

interface IremoteViewsManager {
    void addRemoteView(in RemoteViews remoteViews);
}
  • 1
  • 2
  • 3

rebuild project让IDE工具自己生成AIDL借口对应的java文件。

建立RemoteViewsAIDLService文件。

public class RemoteViewsAIDLService extends Service {
    private static final String TAG = "RemoteViewsAIDLService";

    private Binder remoteViewsBinder = new IremoteViewsManager.Stub(){
        @Override
        public void addRemoteView(RemoteViews remoteViews) throws RemoteException {
            Message message = new Message();
            message.what = 1;
            Bundle bundle = new Bundle();
            bundle.putParcelable("remoteViews",remoteViews);
            message.setData(bundle);
            new MainActivity.MyHandler(RemoteViewsAIDLService.this,getMainLooper()).sendMessage(message);
        }
    };

    public RemoteViewsAIDLService() {

    }

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

}
  • 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

在TempActivity中绑定服务

  private ServiceConnection remoteViewServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG,"onServiceConnected");
            IremoteViewsManager remoteViewsManager = IremoteViewsManager.Stub.asInterface(service);
            RemoteViews remoteViews = new RemoteViews(TempActivity.this.getPackageName(),R.layout.layout);

            Intent intentClick = new Intent(TempActivity.this,MainActivity.class);
            PendingIntent openMainActivity = PendingIntent.getActivity(TempActivity.this,0,intentClick,0);
            remoteViews.setOnClickPendingIntent(R.id.firstButton,openMainActivity);

            Intent secondClick = new Intent(TempActivity.this,LoginActivity.class);
            PendingIntent openLoginActivity = PendingIntent.getActivity(TempActivity.this,0,secondClick,0);
            remoteViews.setOnClickPendingIntent(R.id.secondButton,openLoginActivity);
            try {
                remoteViewsManager.addRemoteView(remoteViews);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
     Intent remoteViewServiceIntent = new Intent(this,RemoteViewsAIDLService.class);
bindService(remoteViewServiceIntent,remoteViewServiceConnection,Context.BIND_AUTO_CREATE);
  • 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

最终在MainActivity中处理消息,实现功能。(代码在上面已经贴出 对应着case 1) 
实现效果如下: 

这里写图片描述

 

细心的同学可能发现,TempActivity在绑定服务中的代码中似乎为两个button做了监听。

 Intent intentClick = new Intent(TempActivity.this,MainActivity.class);
            PendingIntent openMainActivity = PendingIntent.getActivity(TempActivity.this,0,intentClick,0);
            remoteViews.setOnClickPendingIntent(R.id.firstButton,openMainActivity);

            Intent secondClick = new Intent(TempActivity.this,LoginActivity.class);
            PendingIntent openLoginActivity = PendingIntent.getActivity(TempActivity.this,0,secondClick,0);
            remoteViews.setOnClickPendingIntent(R.id.secondButton,openLoginActivity);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

是的,这里是对button做了监听,妈妈再也不用担心OnClickListener不能在IPC中传递了。当然RemoteViews的强大之处还不止体现在这,如果想修改button中的内容,这时候你也不需要修改IremoteViewsManager.aidl、RemoteViewsAIDLService文件啦!你只需在传递RemoteViews之前添加一行代码:

         remoteViews.setCharSequence(R.id.firstButton,"setText","想改就改");
  • 1

这里就不贴效果图啦,anyway这都不重要。

最重要的是:整个过程只有一次IPC,只有一次哦,一次哦。

整体来说,RemoteViews就是为跨进程更新UI而生的,内部封装了多种方法用来跨进程更新UI。但这也不代表RemoteViews是宇宙强无敌,因为它也有软肋,它目前支持的布局和View有限

layout: 
FrameLayout LinearLayout RelativeLayout GridLayout 
View: 
AnalogClock button Chronometer ImageButton ImageView ProgressBar TextView ViewFlipper ListView GridView StackView AdapterViewFlipper ViewStub

不支持自定义View 所以传统的方式依旧是有用武之地的。

深入理解RemoteViews

按着是什么、为什么的规矩,接下来就是怎么用啦。其实上面在介绍为什么用RemoteViews的时候已经介绍了如何使用,但是并不是开发中常用的方式,仅仅是为了说明它相对于传统的跨进程更新UI的优势在哪。RemoteViews最常用的两个场景是Notification和AppWidget小部件,因为这两者的界面都运行在其他进程进程,确切来说它们所属systemServer进程,所以RemoteViews是它两的不二之选。

那这部分就结合着AppWidget使用RemoteViews,深入学习RemoteViews是怎么保证它强大的跨进程更新UI的优势的。

这里需要注意两个问题: 
1、RemoteViews为什么可以通过一次IPC实现对多个View的操作。 
2、其他进程怎么获取布局文件。

首先准备AppWidget的所有文件:MyAppWidgetProvider、要显示的xml以及向AndroidManifest.xml中注册MyAppWidgetProvider 等等。

AndroidManifest.xml

 <receiver android:name=".MyAppWidgetProvider">
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/appwidget_provider_info" />
            <intent-filter>
            <!--自定义的action用于响应点击AppWidget-->
                <action android:name="com.wrg.study" />
            <!--必须要显示声明的action!因为所有的widget的广播都是通过它来发送的;要接收widget的添加、删除等广播,就必须包含它-->
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
 </receiver>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

MyAppWidgetProvider

public class MyAppWidgetProvider extends AppWidgetProvider {
    private static final String TAG = "MyAppWidgetProvider";
    private static final String CLICK_ACTION = "com.wrg.study";

    public MyAppWidgetProvider(){
        super();
    }

    @Override
    public void onReceive(final Context context, Intent intent) {
        super.onReceive(context,intent);
        Log.i(TAG,"onReceive : action = "+intent.getAction());
        if(intent.getAction().equals(CLICK_ACTION)){
            Toast.makeText(context,"click it",Toast.LENGTH_SHORT).show();
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    Bitmap srcBitmap = BitmapFactory.decodeResource(context.getResources(),R.drawable.time);
                    AppWidgetManager appWidgetManager = 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(srcBitmap,degree));
                        appWidgetManager.updateAppWidget(new ComponentName(context,MyAppWidgetProvider.class),remoteViews);
                        SystemClock.sleep(30);
                    }
                }
            }.start();
        }
    }
    private Bitmap rotateBitmap(Bitmap srcBitmap, float degree) {
        Matrix matrix = new Matrix();
        matrix.reset();
        matrix.setRotate(degree);
        Bitmap tempBitmap = Bitmap.createBitmap(srcBitmap,0,0,srcBitmap.getWidth(),srcBitmap.getHeight(),matrix,true);
        return tempBitmap;
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        Log.i(TAG,"onUpdate");
        final int counter = appWidgetIds.length;
        Log.i(TAG,"counter = " + counter);
        for(int i=0;i<counter;i++){
            int appWidgetId = appWidgetIds[i];
            onWidgetUpdate(context,appWidgetManager,appWidgetId);
        }
    }

    private void onWidgetUpdate(Context context,AppWidgetManager appWidgetManager,int appWidgetId){
        Log.i(TAG,"appWidgetId = "+appWidgetId);
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.widget);
        Intent intentClick = new Intent();
        intentClick.setAction(CLICK_ACTION);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0,intentClick,0);
        remoteViews.setOnClickPendingIntent(R.id.imageView,pendingIntent);
        appWidgetManager.updateAppWidget(appWidgetId,remoteViews);
    }
}
  • 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

接下来结合代码来分析RemoteViews是怎么发挥它的优势的:

1、当用户将AppWidget拖到桌面上时,MyAppWidgetProvider继承AppWidgetProvider原有的onReceive方法,回调其onUpdate方法

 

这里写图片描述

 

2、在onWidgetUpdate方法中建立RemoteViews,之后调用appWidgetManager的updateAppWidget发起IPC。

 

这里写图片描述

 

这里实例化了RemoteViews,先看RemoteViews的构造函数 

这里写图片描述

 

 

这里写图片描述

 

这里我们关注RemoteViews的mLayoutId成员变量。

之后RemoteViews调用了setOnClickPendingIntent方法。

remoteViews.setOnClickPendingIntent(R.id.imageView,pendingIntent);
  • 1

 

这里写图片描述

 

 

这里写图片描述

 

setOnClickPendingIntent方法在内部利用viewId, pendingIntent生成SetOnClickPendingIntent对象,并将此对象作为参数传入addAction中,这里不难看出SetOnClickPendingIntent和Action存在继承或者实现的关系。先看addAction的具体逻辑,发现addAction中将传入的参数添加至RemoteViews的成员变量mActions中。

看一下Action类 

这里写图片描述

 

Action类为一个抽象类,同时实现了Parcelable接口,支持IPC。唯一的一个抽象方法apply。 
再看涉及到的Action的子类SetOnClickPendingIntent

    private class SetOnClickPendingIntent extends Action {
        public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
            this.viewId = id;
            this.pendingIntent = pendingIntent;
        }
        @Override
        public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
            final View target = root.findViewById(viewId);//1
            ...........
            OnClickListener listener = null;
            if (pendingIntent != null) {
                listener = new OnClickListener() {
                    public void onClick(View v) {
                        final Rect rect = getSourceBounds(v);
                        final Intent intent = new Intent();
                        intent.setSourceBounds(rect);
                        handler.onClickHandler(v, pendingIntent, intent);
                    }
                };
            }
            target.setOnClickListener(listener);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

RemoteViews的setOnClickPendingIntent方法可以这么理解:将添加监听的一个View动作,封装成一个Action类,保存在RemoteViews的mActions中。其实查看RemoteViews的每一个set方法,不难发现都是把对View操作的动作封装成Action类,最终保存在RemoteViews的mActions中。这个过程可以理解为: 

这里写图片描述

 

到目前为止发现RemoteViews更多承担的是信息的一个载体,这些信息包括:要显示View的资源ID值、mActions等等。

接下来来看看appWidgetManager.updateAppWidget内部发生了什么

    public void updateAppWidget(int appWidgetId, RemoteViews views) {
        if (mService == null) {
            return;
        }
        updateAppWidget(new int[] { appWidgetId }, views);
    }
        public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
        if (mService == null) {
            return;
        }
        try {
            mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
        }
        catch (RemoteException e) {
            throw new RuntimeException("system server dead?", e);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

看到了RemoteException猜测这里就开始了远程服务的调用,而这个远程服务对象mService的类型是 IAppWidgetService。之后由AppWidgetService发送消息,AppWidgetHost监听来自AppWidgetService的事件(这其中的细节涉及太多知识点,毕竟要扒的是RemoteViews。这是详细分析AppWidget生成流程的一系列文章),AppWidgetHost收到AppWidgetService发送的消息,创建AppWidgetHostView,然后通过AppWidgetService查询appWidgetId对应的RemoteViews,最后把RemoteViews传递给AppWidgetHostView去updateAppWidget。

 public void updateAppWidget(RemoteViews remoteViews) {
        .......
        if (remoteViews == null) {
           .......
        } else {
            mRemoteContext = getRemoteContext();
            int layoutId = remoteViews.getLayoutId();
            //如果remoteViews中的layoutId值和mLayoutId相等说明已经加载过了,只需要更新界面
            //不需要重新加载
            if (content == null && layoutId == mLayoutId) {
                try {
                    remoteViews.reapply(mContext, mView, mOnClickHandler);
                    content = mView;
                    recycled = true;
                    if (LOGD) Log.d(TAG, "was able to recycled existing layout");
                } catch (RuntimeException e) {
                    exception = e;
                }
            }

            // 需要重新加载remoteViews的布局
            if (content == null) {
                try {
                    content = remoteViews.apply(mContext, this, mOnClickHandler);
                } catch (RuntimeException e) {
                    exception = e;
                }
            }
        }
    }
  • 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

updateAppWidget的实现逻辑很好理解(当然这里只是保留了主要的逻辑代码),如果没有加载过remoteViews的布局则调用remoteViews.apply方法,若加载过了则调用remoteViews.reapply方法。 
其实这个时候所有的操作已经处于systemServer进程中了,所要理解的也就是remoteViews的apply和reapply方法了。由于apply比reapply方法中多了一道加载布局文件的程序,这里选择分析apply的实现过程。

 public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
        RemoteViews rvToApply = getRemoteViewsToApply(context);

        View result;
        final Context contextForResources = getContextForResources(context);
        LayoutInflater inflater = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater = inflater.cloneInContext(inflationContext);
        inflater.setFilter(this);
        result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
        rvToApply.performApply(result, parent, handler);
        return result;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

apply的实现过程如下:

  1. 通过RemoteViews的getLayoutId方法获取要显示的资源ID值
  2. 利用LayoutInflater加载要加载的xml文件,生成View。
  3. 调用RemoteViews的performApply方法。
 private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
        if (mActions != null) {
            handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
            final int count = mActions.size();
            for (int i = 0; i < count; i++) {
                Action a = mActions.get(i);
                a.apply(v, parent, handler);
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

performApply的流程相对简单,就是将前面存入mActions中的Action遍历取出来,并调用action的apply方法。接下来再看具体的Action的apply的方法,就拿上面的SetOnClickPendingIntent类来分析这个过程吧!

 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
            final View target = root.findViewById(viewId);//1
            ...........
            OnClickListener listener = null;
            if (pendingIntent != null) {
                listener = new OnClickListener() {
                    public void onClick(View v) {
                        final Rect rect = getSourceBounds(v);
                        final Intent intent = new Intent();
                        intent.setSourceBounds(rect);
                        handler.onClickHandler(v, pendingIntent, intent);
                    }
                };
            }
            target.setOnClickListener(listener);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

实现的过程如下: 
1.通过View的id值获取对应的view(target)。 
2.SetOnClickPendingIntent类中的成员变量pendingIntent生成相应的OnClickListener。 
3.为target设置监听。 
当然这里就分析了一个相对简单的Action,其他的Action逻辑也是相同的,有的会使用反射技术来修改View的某些属性。

到这里关于RemoteViews的学习也就结束了,最后盗用别人的图来进一步解释下RemoteView内部机制,至于上面两个问题我想也不需要解释太多了。

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值