《Android开发艺术探索》第五章重点笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lou_liang/article/details/88778985

                                                                         第五章   理解RemoteViews

##1、RemoteViews介绍
       它和远程service一样,RemoteViews可以在其他进程中显示。我们可以跨进程更新它的界面。在Android中,主要有两种场景:通知栏和桌面小部件。
     通知栏主要是通过NotificationManager的notify方法实现。桌面小部件是通过APPWidgetProvider来实现。APPWidgetProvider本质是一个广播。RemoteViews运行在系统的SystemServer进程。

##2、RemoteViews在通知栏的应用
        通知栏除了默认的效果外,还支持自定义布局,我们用到自定义通知,首先要提供一个布局文件,然后通过RemoteViews来加载,可以自定义通知的样式。更新view时,通过RemoteViews提供的一系列方法。如果给一个控件加点击事件,要使用PendingIntent。

##3、RemoteViews在桌面小部件的应用
          AppWidgetProvider是实现桌面小部件的类,本质是一个BroadcastReceiver。开发步骤如下:

          1:定义小部件界面------在res/layout下新建一个xml文件,命名为widget.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:orientation="vertical" >
                                <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="86400000" >
                    </appwidget-provider>
                    
       3:定义小部件实现类,继承AppWidgetProvider

                    public class MyAppWidgetProvider extends AppWidgetProvider {
                        public static final String TAG = "MyAppWidgetProvider";
                        public static final String CLICK_ACTION = "com.ryg.chapter_5.action.CLICK";
                    
                        public MyAppWidgetProvider() {
                            super();
                        }
                    
                        @Override
                        public void onReceive(final Context context, Intent intent) {
                            super.onReceive(context, intent);
                            Log.i(TAG, "onReceive : action = " + intent.getAction());
                    
                            // 这里判断是自己的action,做自己的事情,比如小工具被点击了要干啥,这里是做一个动画效果
                            if (intent.getAction().equals(CLICK_ACTION)) {
                                Toast.makeText(context, "clicked it", Toast.LENGTH_SHORT).show();
                    
                                new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        Bitmap srcbBitmap = BitmapFactory.decodeResource(
                                                context.getResources(), R.drawable.icon1);
                                        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.widget);
                                            remoteViews.setImageViewBitmap(R.id.imageView1,
                                                    rotateBitmap(context, srcbBitmap, 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();
                            }
                        }
                    
                        /**
                         * 每次窗口小部件被点击更新都调用一次该方法
                         */
                        @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, "counter = " + counter);
                            for (int i = 0; i < counter; i++) {
                                int appWidgetId = appWidgetIds[i];
                                onWidgetUpdate(context, appWidgetManager, appWidgetId);
                            }
                    
                        }
                    
                        /**
                         * 窗口小部件更新
                         * 
                         * @param context
                         * @param appWidgeManger
                         * @param appWidgetId
                         */
                        private void onWidgetUpdate(Context context,
                                AppWidgetManager appWidgeManger, int appWidgetId) {
                    
                            Log.i(TAG, "appWidgetId = " + appWidgetId);
                            RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
                                    R.layout.widget);
                    
                            // "窗口小部件"点击事件发送的Intent广播
                            Intent intentClick = new Intent();
                            intentClick.setAction(CLICK_ACTION);
                            PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
                                    intentClick, 0);
                            remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent);
                            appWidgeManger.updateAppWidget(appWidgetId, remoteViews);
                        }
                    
                        private Bitmap rotateBitmap(Context context, 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;
                        }
                    }
                    
        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.ryg.chapter_5.action.CLICK" />
                         <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                     </intent-filter>
                 </receiver>

                
第一个action用于识别小部件的单击,第二个action作为小部件的标识必须存在。

AppWidgetProvider除了onUpdate方法,还有一系列方法。这些方法会自动被onReceive方法调用。当广播到来以后,AppWidgetProvider会自动根据广播的action通过onReceive方法分发广播。

(1)onEnable:该小部件第一次添加到桌面时调用,添加多次只在第一次调用
(2)onUpdate:小部件被添加或者每次小部件更新调用,更新时由updatePeriodMillis指定,每个周期小部件会自动更新一次。
(3)onDeleted:每删除一次桌面小部件都会调用一次
(4)onDisabled:最后一个该类型的桌面小部件被删除时调用
(5)onReceive:内置方法,用于分发具体事件给以上方法


##4、RemoteViews的内部机制

        构造方法

public RemoteViews(String packageName,int layoutId)

第一个参数是当前应用的包名,第二个参数是待加载的布局文件。
RemoteViews并不支持所有的view类型,支持类型如下:

(1)Layout:FrameLayout、LinearLayout、RelativeLayout、GridLayout
(2)View:AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper,ListView,GridView、StackView、AdapterViewFlipper、ViewStub。
(3)RemoteViews不支持以上view的子类

访问RemoteViews的view元素,必须通过一系列set方法完成:

方法名                                                                                作用
setTextViewText(int viewId,CharSequence text)           设置TextView的内容 第一个参数是TextView的id 第二个参数是设置的内容
setTextViewTextSize(int viewId,int units,float size)       设置TextView的字体大小 第二个参数是字体的单位
setTextColor(int viewId,int color)                                   设置TextView字体颜色
setImageViewResource(int viewId,int srcId)                 设置ImageView的图片
setInt(int viewId,String methodName,int value)            反射调用View对象的类型为Int的方法 setLong setBoolean 类似于setInt
setOnClickPendingIntent(int viewId,PendingIntent pendingIntent)         添加点击事件的方法

RemoteViews内部机制

通知栏和小组件分别由NotificationManager(NM)和AppWidgetManager(AWM)管理,而NM和AWM通过Binder分别和SystemService进程中的NotificationManagerService以及AppWidgetService中加载的,而它们运行在系统的SystemService中,这就和我们进程构成了跨进程通讯。

首先RemoteViews会通过Binder传递到SystemService进程,因为RemoteViews实现了Parcelable接口,因此它可以跨进程传输,系统会根据RemoteViews的包名等信息拿到该应用的资源;然后通过LayoutInflater去加载RemoteViews中的布局文件。接着系统会对View进行一系列界面更新任务,这些任务就是之前我们通过set来提交的。set方法对View的更新并不会立即执行,会记录下来,等到RemoteViews被加载以后才会执行。

为了提高效率,系统没有直接通过Binder去支持所有的View和View操作。而是提供一个Action概念,Action同样实现Parcelable接口。系统首先将View操作封装到Action对象并将这些对象跨进程传输到SystemService进程,接着SystemService进程执行Action对象的具体操作。远程进程通过RemoteViews的apply方法来进行View的更新操作,RemoteViews的apply方法会去遍历所有的Action对象并调用他们的apply方法。这样避免了定义大量的Binder接口,也避免了大量IPC操作。


apply和reApply的区别在于:apply会加载布局并更新界面,而reApply则只会更新界面。RemoteViews在初始化界面时会调用apply方法,后续更新界面调用reApply方法。

关于单击事件,RemoteViews中只支持发起PendingIntent,不支持onClickListener那种模式。setOnClickPendingIntent用于给普通的View设置单击事件,不能给集合(ListView/StackView)中的View设置单击事件(开销大,系统禁止了这种方式)。如果要给ListView/StackView中的item设置单击事件,必须将setPendingIntentTemplate和setOnClickFillInIntent组合使用才可以。

猜你喜欢

转载自blog.csdn.net/lou_liang/article/details/88778985