💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 5.3 RemoteViews的意义 在5.2节中我们分析了RemoteViews的内部机制,了解RemoteViews的内部机制可以让我们更加清楚通知栏和桌面小工具的底层实现原理,但是本章对RemoteViews的探索并没有停止,在本节中,我们将打造一个模拟的通知栏效果并实现跨进程的UI更新。 首先有2个Activity分别运行在不同的进程中,一个名字叫A,另一个叫B,其中A扮演着模拟通知栏的角色,而B则可以不停地发送通知栏消息,当然这是模拟的消息。为了模拟通知栏的效果,我们修改A的process属性使其运行在单独的进程中,这样A和B就构成了多进程通信的情形。我们在B中创建RemoteViews对象,然后通知A显示这个RemoteViews对象。如何通知A显示B中的RemoteViews呢?我们可以像系统一样采用Binder来实现,但是这里为了简单起见就采用了广播。B每发送一次模拟通知,就会发送一个特定的广播,然后A接收到广播后就开始显示B中定义的RemoteViews对象,这个过程和系统的通知栏消息的显示过程几乎一致,或者说这里就是复制了通知栏的显示过程而已。 首先看B的实现,B只要构造RemoteViews对象并将其传输给A即可,这一过程通知栏是采用Binder实现的,但是本例中采用广播来实现,RemoteViews对象通过Intent传输到A中,代码如下所示。 RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout. layout_simulated_notification); remoteViews.setTextViewText(R.id.msg, "msg from process:" + Process. myPid()); remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, DemoActivity_1.class), PendingIntent.FLAG_ UPDATE_CURRENT); PendingIntent openActivity2PendingIntent = PendingIntent.getActivity( this, 0, new Intent(this, DemoActivity_2.class), PendingIntent. FLAG_UPDATE_CURRENT); remoteViews.setOnClickPendingIntent(R.id.item_holder, pendingIntent); remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2- PendingIntent); Intent intent = new Intent(MyConstants.REMOTE_ACTION); intent.putExtra(MyConstants.EXTRA_REMOTE_VIEWS, remoteViews); sendBroadcast(intent); A的代码也很简单,只需要接收B中的广播并显示RemoteViews即可,如下所示。 public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private LinearLayout mRemoteViewsContent; private BroadcastReceiver mRemoteViewsReceiver=new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { RemoteViews remoteViews = intent.getParcelableExtra(MyConstants. EXTRA_REMOTE_VIEWS); if (remoteViews ! = null) { updateUI(remoteViews); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView() { mRemoteViewsContent = (LinearLayout) findViewById(R.id.remote_ views_content); IntentFilter filter = new IntentFilter(MyConstants.REMOTE_ACTION); registerReceiver(mRemoteViewsReceiver, filter); } private void updateUI(RemoteViews remoteViews) { View view = remoteViews.apply(this, mRemoteViewsContent); mRemoteViewsContent.addView(view); } @Override protected void onDestroy() { unregisterReceiver(mRemoteViewsReceiver); super.onDestroy(); } } 上述代码很简单,除了注册和解除广播以外,最主要的逻辑其实就是updateUI方法。当A收到广播后,会从Intent中取出RemoteViews对象,然后通过它的apply方法加载布局文件并执行更新操作,最后将得到的View添加到A的布局中即可。可以发现,这个过程很简单,但是通知栏的底层就是这么实现的。 本节这个例子是可以在实际中使用的,比如现在有两个应用,一个应用需要能够更新另一个应用中的某个界面,这个时候我们当然可以选择AIDL去实现,但是如果对界面的更新比较频繁,这个时候就会有效率问题,同时AIDL接口就有可能会变得很复杂。这个时候如果采用RemoteViews来实现就没有这个问题了,当然RemoteViews也有缺点,那就是它仅支持一些常见的View,对于自定义View它是不支持的。面对这种问题,到底是采用AIDL还是采用RemoteViews,这个要看具体情况,如果界面中的View都是一些简单的且被RemoteViews支持的View,那么可以考虑采用RemoteViews,否则就不适合用RemoteViews了。 如果打算采用RemoteViews来实现两个应用之间的界面更新,那么这里还有一个问题,那就是布局文件的加载问题。在上面的代码中,我们直接通过RemoteViews的apply方法来加载并更新界面,如下所示。 View view = remoteViews.apply(this, mRemoteViewsContent); mRemoteViewsContent.addView(view); 这种写法在同一个应用的多进程情形下是适用的,但是如果A和B属于不同应用,那么B中的布局文件的资源id传输到A中以后很有可能是无效的,因为A中的这个布局文件的资源id不可能刚好和B中的资源id一样,面对这种情况,我们就要适当修改RemoteViews的显示过程的代码了。这里给出一种方法,既然资源id不相同,那我们就通过资源名称来加载布局文件。首先两个应用要提前约定好RemoteViews中的布局文件的资源名称,比如“layout_simulated_notification”,然后在A中根据名称查找到对应的布局文件并加载,接着再调用RemoteViews的reapply方法即可将B中对View所做的一系列更新操作全部作用到A中加载的View上面。关于apply和reapply方法的差别在前面已经提到过,这里就不多说了,这样整个跨应用更新界面的流程就走通了,具体效果如图5-4所示。可以发现B中的布局文件已经成功地在A中显示了出来。修改后的代码如下: :-: ![](https://img.kancloud.cn/a3/03/a303d8e9a67c04f9bf528ac412feb6b6_875x608.png) 图5-4 模拟通知栏的效果 int layoutId = getResources().getIdentifier("layout_simulated_ notification", "layout", getPackageName()); View view = getLayoutInflater().inflate(layoutId, mRemoteViewsContent, false); remoteViews.reapply(this, view); mRemoteViewsContent.addView(view);