RemoteView
简单使用
RemoteView在通知栏的使用
通知栏除了默认的布局格式之外,还支持自定义布局 先看一下通知栏默认布局格式的使用
Intent intent = new Intent(this,RemoteView_goActivity.class )
PendingIntent pendingIntent = PendingIntent.getActivity (this,0 ,
intent,PendingIntent.FLAG _UPDATE_CURRENT)
Notification notification = new Notification.Builder (this)
.setContentTitle ("主题" )
.setContentText ("内容" )
.setSmallIcon (R.drawable .remoteviewicon )
.setContentIntent (pendingIntent)
.build ()
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION _SERVICE)
manager.notify (1 ,notification)
大概就是设置他的主题文字,内容文字,以及图标等,这里不再具体深究使用方法 下来看一下为通知自定义布局 写一个自己的布局
<LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android"
android:layout_width ="match_parent"
android:layout_height ="match_parent"
android:orientation ="horizontal" >
<ImageView
android:background ="@color/colorPrimary"
android:id ="@+id/remoteViewIV"
android:layout_width ="wrap_content"
android:layout_height ="match_parent" />
<TextView
android:id ="@+id/remoteViewText"
android:layout_width ="wrap_content"
android:layout_height ="wrap_content" />
<Button
android:id ="@+id/remoteViewBT1"
android:text ="1"
android:layout_width ="wrap_content"
android:layout_height ="match_parent" />
<Button
android:text ="2"
android:layout_width ="wrap_content"
android:layout_height ="match_parent" />
</LinearLayout >
使用remoteViews包装这个布局文件,然后应用到通知
Intent intent = new Intent(this,RemoteView_goActivity.class )
PendingIntent pendingIntent = PendingIntent.getActivity (this,0 ,
intent,PendingIntent.FLAG _UPDATE_CURRENT)
RemoteViews remoteViews = new RemoteViews(getPackageName(),R.layout .remoteviewlayout )
remoteViews.setTextViewText (R.id .remoteViewText ,"这是文字" )
remoteViews.setOnClickPendingIntent (R.id .remoteViewBT 1,pendingIntent)
remoteViews.setImageViewResource (R.id .remoteViewIV ,R.drawable .remoteviewicon )
Notification notification = new Notification.Builder (this)
.setSmallIcon (R.drawable .remoteviewicon )
.build ()
notification.contentView = remoteViews
notification.contentIntent = pendingIntent
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION _SERVICE)
manager.notify (2 ,notification)
桌面小部件的应用
<LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android"
android:layout_width ="match_parent"
android:layout_height ="match_parent" >
<ImageView
android:id ="@+id/widgetImageView1"
android:layout_width ="wrap_content"
android:layout_height ="wrap_content" />
</LinearLayout >
定义小部件的配置信息 在res->xml(如果没有就新建XML属性的文件夹)文件夹下新建配置文件,名字随意
<appwidget-provider
xmlns:android ="http://schemas.android.com/apk/res/android"
android:initialLayout ="@layout/widget_layout"
android:minHeight ="84dp"
android:minWidth ="84dp"
android:updatePeriodMillis ="86400000"
>
</appwidget-provider >
public class MyAppWidthProvider extends AppWidgetProvider {
public static final String TAG = "MyAppWidthProvider" ;
public static final String CLICK_ACTION = "com.example.learnretrofit.MyAppWidthProvider" ;
public MyAppWidthProvider (){
super ();
}
@Override
public void onReceive (final Context context, final Intent intent) {
super .onReceive(context, intent);
Log.i(TAG, "onReceive: action = " +intent.getAction());
if (intent.getAction().equals(CLICK_ACTION)){
Toast.makeText(context,"clicked it" ,Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run () {
Bitmap b = BitmapFactory.decodeResource(context.getResources(), R.drawable.remoteviewicon);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
for (int i = 0 ;i < 37 ;i++){
float drgree = (i * 10 ) % 360 ;
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.widget_layout);
remoteViews.setImageViewBitmap(R.id.widgetImageView1,rotateBitmap(context,b,drgree));
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0 ,intentClick,0 );
remoteViews.setOnClickPendingIntent(R.id.widgetImageView1,pendingIntent);
appWidgetManager.updateAppWidget(new ComponentName(context,MyAppWidthProvider.class),remoteViews);
SystemClock.sleep(30 );
}
}
}).start();
}
}
@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, "onUpdate: 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, "onWidgetUpdate: appwidgetId = " + appWidgetId);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.widget_layout);
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0 ,intentClick,0 );
remoteViews.setOnClickPendingIntent(R.id.widgetImageView1,pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId,remoteViews);
}
private Bitmap rotateBitmap (Context context, Bitmap bitmap, float degree){
Matrix matrix = new Matrix();
matrix.reset();
matrix.setRotate(degree);
Bitmap b = Bitmap.createBitmap(bitmap,0 ,0 ,
bitmap.getWidth(),bitmap.getHeight(),matrix,true );
return b;
}
}
可以看到,在这个小部件实现类中,我们具体实现了小部件更新逻辑以及其他一些必要的方法 最后在androidManifest.xml文件中注册这个小部件
<receiver android:name =".LearnRemoteView.MyAppWidthProvider" >
<meta-data android:name ="android.appwidget.provider"
android:resource ="@xml/xml" />
<intent-filter >
<action android:name ="com.example.learnretrofit.MyAppWidthProvider" />
<action android:name ="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter >
</receiver >
到此为止,小部件的实现过程就到这里 可以看得到,小部件的通信是依赖于广播的(AppWidgetProvider这个类是继承自BroadcastReceiver),也就是说小部件的跨进程通信是依赖于广播,而跨进程数据显示是通过remoteVIew具体的小部件的东西在这里不再阐述 接下来理解remoteView
RemoteView
remoteView是一种在另外进程中运行的View,可以跨进程去更新他的View界面以及数据 目前的remoteView并不能支持所有类型的View,它所支持的布局类型和小部件类型如下: 布局:AdapterViewFlipper,FrameLayout,GridLayout,GridView,LinearLayout,ListView,RelativeLayout,StackView,ViewFlipper 小部件:AnalogClock Button Chronometer ImageButton ImageView ProgressBar TextClock TextView 并且不支持以上类的子类及后代
remoteView更新VIew的方法
remoteView不提供直接findViewByID的这些方法来更新UI,它为我们提供了一系列的set方法让我们用以更新UI 比如,void setBitmap(int viewId, String methodName, Bitmap value) 这种方法表示调用参数为BItmap格式的方法名为methodName的方法,将其返回值设置给viewId为这个的控件 第二种,void setImageViewBitmap(int viewId, Bitmap bitmap) 这种方法用来将bitmap这个参数设置给Id为viewId的控件 而他们的本质大多数是通过反射来实现的
remoteView内部机制
remoteView的一般应用是notification或者APPWidget的,而他们俩分别使用NotificationManager和APPWidgetManager通过Binder分别和SystemSever进程中的NotificationManagerService和APPWidgetService进行通信 可见remoteView是运行在systemServer进程中的,这就和我们的进程构成了跨进程通信的场景 首先RemoteView通过BInder传递到systemServer进程,这是因为RemoteView实现了Parcelable接口,所以他能跨进程运输,系统会根据RemoteViews中的包名等信息去得到该应用的资源,然后通过LayoutInflate去加载RemoteViews的布局文件 当我们通过各个set方法去设置remoteView的属性之后,它是等到remoteView被SystemServer进程中加载出来之后才去设置的 当然可以想象,当我们在程序中如果大量这样使用跨进程更新View的话,对于系统来说是吃不消的,相较于此,系统为我们实现了一个Action的概念 Action Action代表一个View操作,action同样实现了parcelable接口,在我们调用一个set方法之后,系统首先将这个Set操作封装到Action对象(本地进程),在调用Manager提交后,系统会将这些Action运输到远程进程并进行操作 这样的好处是,不必为每个View实现特定的跨进程接口,减少跨进程次数,提高效率。
remoteView 分析源码
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 (hasLandscapeAndPortraitLayouts()) {
throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
" layouts cannot be modified. Instead, fully configure the landscape and" +
" portrait layouts individually before constructing the combined layout." );
}
if (mActions == null ) {
mActions = new ArrayList<Action>();
}
mActions.add(a);
a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}
代码很简单,也很直接,可以看到,内部使用反射操作,最终将操作包装成action添加到一个集合当中,等待manager提交 来看一下remoteView的apply方法 那么还记得上面说过什么,将set操作包装好之后,就等着manager提交了 ,他们的提交正是调用的是apply和reappy方法
public View a
pply(Context context, ViewGroup parent , OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result = inflateView(context, rvToApply, parent );
loadTransitionOverride(context, handler);
rvToApply.performApply(result, parent , handler);
return result;
}
private View inflateView (Context context, RemoteViews rv, ViewGroup parent) {
final Context contextForResources = getContextForResources(context);
Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources);
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater = inflater.cloneInContext(inflationContext);
inflater.setFilter(this );
View v = inflater.inflate(rv.getLayoutId(), parent, false );
v.setTagInternal(R.id.widget_frame, rv.getLayoutId());
return v;
}
而 rvToApply.performApply(result, parent, handler);这个方法是遍历actions集合去执行每个action的apply方法的 我们转过头去看action的具体实现类吧,比如说ReflectionAction的apply
public 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);
}
}