RemoteViews表示的是一个view结构,它提供了一组基础的操作用于跨进程更新它的界面,常见的应用场景是通知栏和桌面小部件。在学习它之前我们先了解PendingIntent。
PendingIntent表示一种处于pending状态的意图,是在将来的某个不确定的时刻发生,而Intent是立刻发生,PendingIntent是通过send和cancle方法来发送和取消待定的Intent.
FLAG_NOE_SHOT
PendingIntent只能被调用一次,然后它会自动cancel,如果后续还有相同的PendingIntent,那么send方法会调用失败。
FLAG_NO_CREATE
PendingIntent不会主动创建如果当前PendingIntent之前不存在,那么getActivity、getService、getBroadcast方法会直接返回null,即获取PendingIntent失败。
FLAG_CANCEL_CURRENT
当前描述的PendingIntent已经存在,那么它们都会被cancel,然后系统会创建一个新的PendingIntent,对于那些被取消的通知单击后无法打开
FLAG_UPDATE_CURRENT
当前描述的PendingIntent如果已经存在,那么他们都会被更新,即他们的Intent的Extras会被替换成最新的。
如果两个PendingIntent它们内部的Intent相同,并且requestCode也相同,那么这两个PendingIntent就是相同的。如果两个Intent的ComponentName和intent-filter都相同那么这两个Intent就是相同。
RemoteViews只支持几个set方法,没有提供findViewbyid,所以无法获取到RemoteViews的子view,同时它所支持的view也很少,有如下几个,除了一下View,其他的一概不支持:
Layout:
FrameLayout、LinearLayout、RelativeLayout、GridLayout
View:
AnalogClock,Button,Chronometer,ImageButton,ImageView,ProgressBar,TextView,ViewFlipper,ListView,GridView,StackView,AdapterViewFlipper,ViewSub
桌面小部件和通知都是由binder和SystemServer进程中的各自的服务进行通信的,这就构成了跨进程通信, RemoteViews实现了Parcelable,因此可以跨进程传输,当需要更新的时候就采用RemoteViews的一系列的set方法提交到SystemServer进程中的各自的服务去加载更新,大量的IPC操作会影响效率,因此一个view操作代表一个action,然后action实现了Parcelable,每调一次set方法, RemoteViews 就添加一个对应的action,当调用各自的manager提交的时候,这些action就会被传输到远程并在远程中依次执行,远程进程通过 RemoteViews的apply方法来进行view的更新,apply方法会遍历所有的action对象并调用它们的apply方法,避免了大量的IPC操作,提高了远程的性能。
RemoteViews的单击事件只支持发起PendingIntent,setOnClickPendingIntent用于给普通的view设置单击事件,但不能给Listview以及StackView这些控件的item设置点击事件,因为开销大系统禁用了这种方式,如果要给这些item设置则必须将setPendingIntentTemplate和setOnClickFillInIntent组合使用才可以。
一个应用去更新另一个应用的中的某个界面,当更新频繁时这个时候有效率问题,可以采用RemoteViews和aidl实现,但是RemoteViews只支持常见的view.A中发广播并且把RemoteViews放在intent中传递到B中去,B中接受广播获取RemoteViews调用apply方法的到view并加以利用。但是RemoteViews实现两个应用之间的界面更新,如果将B中的布局文件的资源id传输到A中有可能是无效的。这时候需要两个应用提前约定好布局文件的名称,然后B中根据名称获取layoutId,然后根据layoutID找到view,然后调用reapply方法加载到B中,伪代码如下:
int layoutid=getResource().getIdentifier(“layoutid”,”layout”,getPackageName());
View view=getLayoutInfater().inflate(layoutid,mView,false);
remoteViews.reapply(this,view);
mView.addView(view);
使用系统默认样式的通知
Notification.Builder builder=new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setTicker("测试");
builder.setContentTitle("xxxx");
builder.setContentText("yyyy");
builder.setWhen(System.currentTimeMillis());
Intent intent=new Intent(this,MainActivity.class);
PendingIntent pendingIntent=PendingIntent.getActivity(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
Notification notification= builder.build();
notification.flags=Notification.FLAG_AUTO_CANCEL;
NotificationManager manager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(1,notification);
使用自定义的样式通知
Notification.Builder builder=new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setTicker("测试");
builder.setContentTitle("xxxx");
builder.setContentText("yyyy");
builder.setWhen(System.currentTimeMillis());
Intent intent=new Intent(this,MainActivity.class);
PendingIntent pendingIntent=PendingIntent.getActivity(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
RemoteViews remoteViews=new RemoteViews(getPackageName(),R.layout.noti);
remoteViews.setTextViewText(R.id.textView,"11111");
remoteViews.setImageViewResource(R.id.imageView,R.mipmap.aa);
//remoteViews.setOnClickPendingIntent(R.id.zy,pendingIntent);
builder.setContent(remoteViews);
Notification notification= builder.build();
notification.flags=Notification.FLAG_AUTO_CANCEL;
NotificationManager manager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(1,notification);
自定义桌面小控件
首先在layout下创建widght.xml,此为小控件的布局desk.x "1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/zy"
android:layout_height="match_parent"
>
"wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/aa"
android:layout_alignTop="@+id/textView"
android:layout_toRightOf="@+id/textView"
android:layout_toEndOf="@+id/textView"
android:layout_marginLeft="52dp"
android:layout_marginStart="52dp"
android:id="@+id/imageView" />
在res/xml下新建appwidget_provider_info.xml,添加如下"@layout/desk"
android:minHeight="84dp"
android:minWidth="84dp"
android:updatePeriodMillis="864000"
xmlns:android="http://schemas.android.com/apk/res/android">
建一个类继承AppWidgetProvider类,实现onReceive,AppWidgetProvider的父类是一个广播类,因此需要在manifest注册。
AppWidgetProvider除了常用的onUpdate还有其他的方法onEnabled、onDisable、onDeleted以及onReceive,这些方法会被onReceive
合适的时间给分发掉。
onEnabled:当该小部件第一次添加到桌面时调用该方法,可添加多次但只在第一次调用
onUpdate:小部件被添加时或者每次更新时都会调用一次该方法,小部件的更新周期由updatePeriodMillis来指定,每个周期小部件会
自动更新一次。
onDeleted:每删除一次桌面小部件就调用一次。
onDisabled:当最后一个该类型的桌面小部件被删除时调用该方法,注意是最后一个。
onReceive:广播内置的方法,用于分发具体的事件给其他的方public class MyAppWidgetProvider extends AppWidgetProvider {
private static final String TAG="MyAppWidgetProvider";
private static final String Click_ACTION="site.zhangyun.appliction.Click";
@Override
public void onReceive(final Context context, Intent intent) {
super.onReceive(context, intent);
switch (intent.getAction()){
case Click_ACTION:
Toast.makeText(context,"2222", Toast.LENGTH_LONG).show();
new Thread(new Runnable() {
@Override
public void run() {
Bitmap bitmap= BitmapFactory.decodeResource(context.getResources(), R.mipmap.aa);
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.desk);
remoteViews.setImageViewBitmap(R.id.imageView, rotateBitmap( bitmap,degree));
Intent clickIntent=new Intent();
clickIntent.setAction(Click_ACTION);
PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,clickIntent,0);
remoteViews.setOnClickPendingIntent(R.id.imageView,pendingIntent);
appWidgetManager.updateAppWidget(new ComponentName(context,MyAppWidgetProvider.class),remoteViews);
SystemClock.sleep(30);
}
}
}).start();
break;
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
final int length=appWidgetIds.length;
for (int i = 0; i < length; i++) {
onWidgetUpdate( context, appWidgetManager,appWidgetIds[i]);
}
}
private void onWidgetUpdate(Context context,AppWidgetManager appWidgetManager,int appWidgetId){
RemoteViews remoteViews=new RemoteViews(context.getPackageName(),R.layout.desk);
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);
}
private Bitmap rotateBitmap(Bitmap srcbBitmap,float degree){
Matrix matrix=new Matrix();
matrix.reset();
matrix.setRotate(degree);
Bitmap tmpBitmap=Bitmap.createBitmap(srcbBitmap,0,0,srcbBitmap.getWidth(),srcbBitmap.getHeight(),matrix,true);
return tmpBitmap;
}
}
Manifest中的配置如下:
"com.robot.zhangyun.deme.md.MyAppWidgetProvider" >
"android.appwidget.action.APPWIDGET_UPDATE" />//小部件的标示,必须存在
"site.zhangyun.appliction.Click"/>//识别小部件的单击事件
"android.appwidget.provider"
android:resource="@xml/appwidget_provider_info" />