开发艺术探索之RemoteView学习

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.remoteViewBT1,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);

        // update the memory usage stats
        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);

        //加载remoteViews的布局文件
        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) {
        // RemoteViews may be built by an application installed in another
        // user. So build a context that loads resources from that user but
        // still returns the current users userId so settings like data / time formats
        // are loaded without requiring cross user persmissions.
        final Context contextForResources = getContextForResources(context);
        Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources);

        LayoutInflater inflater = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        // Clone inflater so we load resources from correct context and
        // we don't add a filter to the static version returned by getSystemService.
        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);
            }
        }
  • 可以看到,最后使用了反射来调用相关方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值