微信公众号:Lucidastar
如有问题或建议,请公众号留言
最近更新:2018-05-04
这篇文章主要介绍的RemoteViews的相关知识,我主要从三个方面进行学习和讲解(理解,使用及分析)
RemoteViews的理解
定义:从名字上看是远程View,它和远程Service是一样的,它表示的是一个View结构,它可以在其他进程中显示。
应用:在Android中使用的场景有两种,通知栏和桌面小部件
RemoteViews的使用
一、在通知栏上的应用
1、默认弹出
NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"chaper_5");builder.setSmallIcon(R.mipmap.ic_launcher);builder.setTicker("ticker") .setContentText("contentText") .setWhen(System.currentTimeMillis()) .setContentTitle("contentTitle") .setPriority(NotificationCompat.PRIORITY_HIGH) .setDefaults( Notification.DEFAULT_VIBRATE | Notification.DEFAULT_ALL | Notification.DEFAULT_SOUND ) .setTicker("悬浮通知");Intent intent = new Intent(this,DemoActivity_1.class);PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);builder.setContentIntent(pendingIntent);NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);manager.notify(1,builder.build());
2、自定义样式弹出
自定义一个xml layout_notification.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/light_green" android:padding="10dp" android:gravity="center" android:orientation="horizontal" > <ImageView android:id="@+id/icon" android:background="#000000" android:layout_width="40dp" android:scaleType="centerInside" android:src="@drawable/icon1" android:layout_height="40dp" /> <LinearLayout android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="right" android:orientation="vertical" > <TextView android:id="@+id/msg" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="5dp" android:textColor="@android:color/white" android:visibility="visible" android:text="TextView" /> <TextView android:id="@+id/open_activity2" android:padding="2dp" android:background="@drawable/common_bkg" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:text="open DemoActivity_2" /> </LinearLayout></LinearLayout>
代码进行设置
NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"chaper_5"); builder .setSmallIcon(R.mipmap.ic_launcher);// .setTicker("ticker")// .setContentText("contentText")// .setWhen(System.currentTimeMillis())// .setContentTitle("contentTitle")// .setPriority(NotificationCompat.PRIORITY_HIGH)// .setDefaults( Notification.DEFAULT_VIBRATE | Notification.DEFAULT_ALL | Notification.DEFAULT_SOUND )// .setTicker("悬浮通知");// Intent intent = new Intent(this,DemoActivity_2.class);// PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);// builder.setContentIntent(pendingIntent); RemoteViews remoteViews = new RemoteViews(getPackageName(),R.layout.layout_notification); remoteViews.setTextViewText(R.id.msg,"chapter_5"); remoteViews.setImageViewResource(R.id.icon,R.drawable.icon1); PendingIntent openActivity2pendingIntent = PendingIntent.getActivity(this,0,new Intent(this,DemoActivity_2.class),PendingIntent.FLAG_UPDATE_CURRENT); remoteViews.setOnClickPendingIntent(R.id.open_activity2,openActivity2pendingIntent); builder.setCustomContentView(remoteViews); builder.setContentIntent(openActivity2pendingIntent); NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); manager.notify(2,builder.build());
二、桌面小部件的使用
1、在layout下创建一个xml文件,widget.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/icon1" /></LinearLayout>
2、在res/xml/下新建appwidget_provider_info.xml文件
<?xml version="1.0" encoding="utf-8"?><appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/widget" android:minHeight="84dp" android:minWidth="84dp" android:updatePeriodMillis="8640000"// 小部件自动更新周期,毫秒为单位 ></appwidget-provider>
3、创建一个自定义类MyAPPWidgetProvider,继承APPWidgetProvider
public class MyAppWidgetProvider extends AppWidgetProvider{ private static final String TAG = "MyAppWidgetProvider"; public static final String CLICK_ACTION = "com.lucidastar.chapter_5.action.CLICK"; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); final int counter = appWidgetIds.length; for (int i = 0; i < counter; i++) { int appWidgetId = appWidgetIds[i]; onWidgetUpdate(context,appWidgetManager,appWidgetId); } } private void onWidgetUpdate(Context context, AppWidgetManager appWidgetManager, int 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.imageView1,pendingIntent); appWidgetManager.updateAppWidget(appWidgetId,remoteViews); } @Override public void onReceive(final Context context, final Intent intent) { super.onReceive(context, intent); if (intent.getAction().equals(CLICK_ACTION)){ Toast.makeText(context,"click_it",Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @Override public void run() { Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),R.drawable.icon1); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); for (int i = 0; i < 40; i++) { float degree = (i * 10) % 360; RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.widget); remoteViews.setImageViewBitmap(R.id.imageView1,rotateBitmap(context,bitmap,degree)); Intent intentClick = new Intent(); intentClick.setAction(CLICK_ACTION); PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0,intentClick,0); remoteViews.setOnClickPendingIntent(R.id.imageView1,pendingIntent); appWidgetManager.updateAppWidget(new ComponentName(context,MyAppWidgetProvider.class),remoteViews); SystemClock.sleep(30); } } }).start(); } } private Bitmap rotateBitmap(Context context, Bitmap bitmap, float degree) { Matrix matrix = new Matrix(); matrix.reset(); matrix.setRotate(degree); Bitmap bitmap1 = Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true); return bitmap1; }}
4、在AndroidManifest.xml中声明小部件
<receiver android:name=".MyAppWidgetProvider"> <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget_provider_info"></meta-data> <intent-filter> <action android:name="com.lucidastar.chapter_5.action.CLICK"/>//小部件的单击行为 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>//系统规范 </intent-filter></receiver>
了解一下APPWidgetProvider中的方法介绍
-
onEnable:当该窗口小部件第一次添加到桌面时调用该方法,可添加多次但只在第一次调用
-
onUpdate:小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的更新时机由updatePeriodMillis来指定,每个周期小部件都会自动更新一次。
-
onDeleted:每删除一次桌面小部件就调用一次
-
onDisabled:当最后一个该类型的桌面小部件被删除时调用该方法,注意是最后一个。
-
onReceive:这是广播的内置方法,用于分发具体的事件给其他方法。
在使用RemoteViews的同时,会用到PendingIntent这个类,简单的介绍一下。
PendingIntent的概述:
表示一种处于pending状态的意图,pending状态表示一种待定、等待、即将发生的意思。Intent将某个待定的确定的时刻发生。
Intent是立刻发生。因为RemoteView运行在远程进程中,因此RemoteViews不同于普通的View,所以无法直接向View那样通过setOnClickListener方法来设置单击事件。所以就必须使用PendingIntent,PendingIntent通过send和cancel方法来发送和取消特定的待定Intent。
public static PendingIntent getActivity(Context context, int requestCode, Intent intent, @Flags int flags) { return getActivity(context, requestCode, intent, flags, null);}public static PendingIntent getService(Context context, int requestCode, @NonNull Intent intent, @Flags int flags) { return buildServicePendingIntent(context, requestCode, intent, flags, ActivityManager.INTENT_SENDER_SERVICE);}public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, @Flags int flags) { return getBroadcastAsUser(context, requestCode, intent, flags, new UserHandle(UserHandle.myUserId()));}
requestCode表示PendingIntent发送方的请求码,多数设置为0就可以了。
requestCode会影响到flag的效果。
flag的类型:
-
FLAG_ONE_SHOT:
当前描述的PendingIntent只能被使用一次,然后它就会被自动cancel,如果后续还有相同的PendingIntent,那么他们的send方法就会调用失败。对于通知栏消息来说,如果采用此标记位,那么同类的通知只能使用一次,后续的通知单击后将无法打开。 -
FLAG_NO_CREATE:
当前描述的PendingIntent不会主动创建,如果当前PendingIntent之前不存在,那么getActivity、getService和getBroadcast方法会直接返回null,即获取PendingIntent失败,这个标记位很少见,无法单独使用。 -
FLAG_CANCEL_CURRENT:
当前描述的PendingIntent如果已经存在,那么他们都会被cancel,然后系统会创建一个新的PendingIntent,对于通知栏消息来说,那些被cancel的消息单击后将无法打开 -
FLAG_UPDATE_CURRENT:
当前描述的PendingIntent如果已经存在,那么它们都会被更新,即他们的Intent中的Extras会被替换成最新的。
RemoteViews的分析
首先看一张图
支持的View类型
LayoutFrameLayout 、 LinearLayout、Relativelayout、GridLayoutViewAnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewStub。不支持他们的子类及其他View的类型。也无法使用自定义的View。
我先通过提问问题来进行理解和分析
-
通过RemoteView实现的通知栏和桌面小部件是跨进程操作吗?
-
通过什么进行跨进程操作的?
-
RemoteView是如何进行更新操作的?
-
RemoteView为何只支持部分的View和View的操作
- 是跨进程操作的。
- NotificationManager和APPWidgetManager通过Binder分别和SystemServer进程中NotificationManagerService以及APPWidgetService进行通信,也就是在两个Service中被加载的,而他们运行在系统的SystemServer中,这就和我们的进程构成了跨进程通信的场景。
- 首先我们要了解它是如何加载到SystemServer进程中的,RemoteView是通过Binder传递到SystemServer进程中的,因为RemoteView实现了Parcelable接口,系统会根据RemoteView中的包名等信息得到该应用的资源,然后通过LayoutInflater去加载RemoteView中的布局文件,这就在SystemServer中形成了一个普通的View。然后通过我们的set方法对View进行更新,在RemoteView内部会记录所有的更新操作,不会立马进行更新,等到RemoteView被加载以后才执行,这样RemoteViews就可以在SystemServer进程中进行显示了,具体的操作还是在SystemServer进程中进行。
- 其实系统完全可以通过Binder去支持所有的View和View操作,这样代价太大,并且View的方法太多,大量的IPC操作会影响效率。而且系统并没有通过Binder去直接支持View的跨进程操作,而提供了一个Action概念,Action代表一个View的操作,我们每set一次操作,RemoteViews就会添加一个对应的Action对象,然后通过NotificationManager和AppWidgetManager来提交我们的操作。
然后通过代码来分析是如何进行更新操作的
remoteViews.setTextViewText(R.id.msg,"chapter_5");public void setTextViewText(int viewId, CharSequence text) { setCharSequence(viewId, "setText", text);}public void setCharSequence(int viewId, String methodName, CharSequence value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));}private void addAction(Action a) { ... if (mActions == null) { mActions = new ArrayList<Action>(); } mActions.add(a); // update the memory usage stats a.updateMemoryUsageEstimate(mMemoryUsageCounter);}
首先看一下RemoteViews的方法apply以及Action类的实现。
public View apply(Context context, ViewGroup parent, OnClickHandler handler) { RemoteViews rvToApply = getRemoteViewsToApply(context); View result = inflateView(context, rvToApply, parent);//inflateView就是通过LayoutInflat而对象获取一个View loadTransitionOverride(context, handler); rvToApply.performApply(result, parent, handler); return result;}
遍历所有的Actions列表,然后执行每个apply方法。
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); } }}
我们看一下ReflectionAction的具体实现
private final class ReflectionAction extends Action { ..... ReflectionAction(int viewId, String methodName, int type, Object value) { this.viewId = viewId; this.methodName = methodName; this.type = type; this.value = value; }@Overridepublic void apply(View root, ViewGroup rootParent, OnClickHandler handler) { final View view = root.findViewById(viewId); if (view == null) return; Class<?> param = getParameterType(); if (param == null) { throw new ActionException("bad type: " + this.type); } try { getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value)); } catch (ActionException e) { throw e; } catch (Exception ex) { throw new ActionException(ex); } }}
ReflectionAction表示的是第一个反射动作,通过它对View的操作会以反射的方式来调用。
另外:
setOnClickPendingIntent、setPendingIntentTemplate以及setOnClickFillInIntent他们之间的区别和联系
setOnClickPendingIntent用于给普通的View设置单击事件,但不能给集合(ListView和StackView)中的View设置单击事件。要给ListView和StackView中的item添加单击事件,必须将setPendingIntentTemplate和setOnClickFillInIntent组合使用才可以。
如果说两个进程间的布局更新 都是些简单的View 最好使用RemoteViews 因为它内部有Action模式比AIDL效率高 如果布局有自定义的View 那就只能使用AIDL 通信 然后更新布局了。
通过理解和分析我们需要掌握的:
- RemoteViews的使用
- 能够熟练的创建通知栏和桌面小控件
- RemoteViews内部是怎么一个过程(可以看上面的问题4的回答)
我认为掌握这三点就可以了。
网友评论