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的知识,有以下几个方案:
-
TempActivity把要添加的两个Button的布局的ID值通过BroadcastRecriver发送,在MainActivity中注册该广播,同时获取其中的布局ID值,通过LayoutInflater来绘制那两个Button,最后添加到MainActivity的布局中去。
-
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的实现过程如下:
- 通过RemoteViews的getLayoutId方法获取要显示的资源ID值
- 利用LayoutInflater加载要加载的xml文件,生成View。
- 调用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内部机制,至于上面两个问题我想也不需要解释太多了。