RemoteViews使用,内部原理史上最全 (一)

 

RemoteViews是什么?

翻译过来,顾名思义就是远程的View,先看下官方文档的说明


/**
 * A class that describes a view hierarchy that can be displayed in
 * another process. The hierarchy is inflated from a layout resource
 * file, and this class provides some basic operations for modifying
 * the content of the inflated hierarchy.
   public class RemoteViews implements Parcelable, Filter {

意思是这个类是一个描述视图结构的类、以及提供了一些基本的修改操作,注意这个RemoteViews并没有继承于View,不是一个View,只是提供方法,并且继承了Parcelable,为实现跨进程提供了条件。

RemoteViews常用的就是自定义通知栏界面、桌面小控件、当然也可以更新两个不同应用的界面、或者用于闹钟功能(闹钟功能这里不讲)这些接下来会讲到。

 

一、RemoteViews和Notification

public final String CHANNEL_ID_SERVICE_HANDLE = "com.xsx.Channel_id";
 
 private void setNotification() {
        //判断版本
        setChannel();
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_SERVICE_HANDLE);
        builder.setSmallIcon(R.mipmap.ic_launcher_round);
        builder.setContentTitle("我去也");
        builder.setContentText("我來也");
        builder.setSound(null);
        //点击消失
        //builder.setAutoCancel(true);
        //默认高度256 超出则显示不全
        //builder.setCustomBigContentView(remoteViews);
        Notification notification = builder.build();
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        Intent intent = new Intent();
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        notification.contentIntent = pendingIntent;
        // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            // 高度256 超出则显示不全 可以更大的显示通知栏高度
            // 通知栏默认高度是64
            // notification.bigContentView = remoteViews;
        // }
        manager.notify(1, notification);
    }
    
   private void setChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = "CHANNEL_NAME";
            String description = "CHANNEL_DESCRIPTION";
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID_SERVICE_HANDLE, name, importance);
            channel.setDescription(description);
            channel.setSound(null, null);
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);

        }
    }

效果图如下:

RemoteViews使用,内部原理史上最全 (一)_第1张图片

 

1.1、自定义通知栏界面

上面是简单的实现系统通知栏的效果,但是无法自定义通知栏界面,并且无法实现通知栏里特定控件的点击事件,这就可以用到RemoteViews,先初始化RemoteViews,通过setOnClickPendingIntent(注意不是setOnClickListener)来设置点击事件,再写一个广播来监听点击事件,

如下:

private void setNotification() {
        //判断版本
        setChannel();
        //初始化RemoteViews
        RemoteViews remoteViews = new RemoteViews(this.getPackageName(), R.layout.remote_view);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_SERVICE_HANDLE);
        builder.setSmallIcon(R.mipmap.ic_launcher_round);
        builder.setContentTitle("我去也");
        builder.setContentText("我來也");
        builder.setSound(null);
   
        Notification notification = builder.build();
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        //设置头部图片
        remoteViews.setImageViewResource(R.id.head_imageVie, R.drawable.zip1);
        //头部图片点击
        Intent intent = new Intent("com.heard.picture");
        // intent.setPackage("com.example.appwdightdemo");
        // 最好这样写 包名和类名路径
        intent.setComponent(new ComponentName("com.example.appwdightdemo","com.example.appwdightdemo.receiver.MyReceiver"));
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.head_imageVie, pendingIntent);
        //设置contentView
        notification.contentView = remoteViews;
        manager.notify(1, notification);
    }

 

1.2、最后记着在xml中注册,并添加action,下面是广播类


public class MyReceiver extends BroadcastReceiver {
    private final String heardAction = "com.heard.picture";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (heardAction.equals(intent.getAction())) {
            Toast.makeText(context, "点击了头部图片", Toast.LENGTH_SHORT).show();
        }
    }
}

运行效果图如下:

RemoteViews使用,内部原理史上最全 (一)_第2张图片

 

1.3、这里顺便说下关于PendingIntent:

PendingIntent意为:有待、听候的Intent,它不是立即执行,而是等待某个时机,某个点击,然后执行某个操作。它有下面四个方法:

  • getActivity(Context contex,int requestCode,Intent intent,int flags)

效果相当于 Context.startActivity(intent)

  • getService(Context contex,int requestCode,Intent intent,int flags)

效果相当于 Context.startService(intent)                                                                                                                                   

  • getBroadcast(Context contex,int requestCode,Intent intent,int flags)

效果相当于 Context.startBroadcast(intent)

  • getForegroundService(Context contex,int requestCode,Intent intent,int flags)

效果相当于 Context.startForegroundService(intent),这是Api 26 以后出来的

这四个方法都有四个参数,第一个参数context和第三个参数intent都很好理解,第二个参数多数情况下可以默认写成0,第四个参数是flags标识

 

1.4、在说flags标识前,先说下pendingIntent的匹配规则:

什么叫做PendingIntent的匹配规则?就是什么情况下两个PendingIntent是一样的, 如果PendingIntent内部的requestCode和Intent都完全相同的,那么这两个PendingIntent就是相同;什么情况下Intent是相同的?如果Intent的ComponentName和Intent-filter相同,则两个Intent就是相同的,注意的是Intent的Extras不参与匹配,如果两个Intent的ComponentName和Intent-filter相同,而Extras不同,那这两个Intent也是相同的。

 

1.5、PendingIntent的flags含义:

  • FLAG_ONE_SHOT

表示此PendingIntent只能使用一次的标志,如果设置,则在调用之后,它将自动为您取消

  • FLAG_NO_CREATE

表示当前的PendingIntent不会主动去创建,如果当前PendingIntent不存在,则getActivity、getService、getBroadcast方法会返回null,这个在开发中很少用到,没有多大的意义,这里无需介绍

  • FLAG_CANCEL_CURRENT

描述的PendingIntent已存在的标志,如果当前的PendingIntent已经存在,那么会被cancel,系统就会创建一个新的

  • FLAG_UPDATE_CURRENT

如果当前的PendingIntent已经存在,再次创建新的PendingIntent,那些之前的都会被更新,即Intent中的Extras会替换成最新的,通常情况下我们都是用这个

  • FLAG_IMMUTABLE

这个是Api 23 以后出的,PendingIntent应该是不可变的,设置 Intent 在 send 的时候不能更改,send 是触发 PendingIntent 包含的行为,我们通常的开发用不到他,除非我们做桌面程序开发或者 Android Framework 开发。大体说下,可以传给它一个 Intent 来对它原来的 Intent 做修改,但是设置了 FLAG_IMMUTABLE 则参数修改忽略。可以设置 callback 当发送完成获得回调,并且可以通过设置handler决定回调发生的线程

 

1.6、上面的标识可能不太好理解,结合通知栏效果在描述一遍

发送通知栏,通过manager.notify(1,notification),如果notify第一个参数Id是一样的,那么多次调用notify,只会调出一个通知栏,后面的通知栏会将前面的给覆盖掉;如果id不一样,多次调用notify会调出多个通知栏

 

当notify的Id一样的时候:

不管PendingIntent的是否匹配,后面的会顶掉前面的,这很好理解

当notify的Id不一样的时候:

这个时候分两种情况,如果PendingIntent不匹配的时候,不管采用何种标记,这些通知之间都相互不影响

如果PendingIntent匹配的话,采用FLAG_ONE_SHOT标记时,后面的通知会和之前的通知保持一致,包括里面的Extras,点击其中的一个通知后,其他的通知将无法打开;

采用FLAG_CANCEL_CURRENT标记时,只有最后一个通知可以打开,也就是说只有最新的通知可以打开,其他的通知都无法打开;采用FLAG_UPDATE_CURRENT标记时,之前的通知都会更新,包括里面的Extras,所有的通知都可以打开

 

1.7、setOnClickPendingIntent、支持的View类型

上面代码实现了自定义通知栏布局,但是和常规的不一样的是设置图片是通过setImageViewResource方法设置图片资源;通过setOnClickPendingIntent方法设置点击事件,也可以通过setTextViewText来设置文字,当点击图片的时候会跳出一个Toast,当然根据需求可以添加其他UI设计和点击事件,特别说下如果想要更新通知栏UI,可以发送相同id的manager.notify()进行实现

 

但是RemoteViews也有局限性,它只支持部分View类型

  • Layout

LinearLayout、RelativeLayout、FrameLayout、GridLayout

  • View

AdapterVIewFlipper、ViewStub、StackView、ViewFlipper、ListView、GridView、AnalogClock 、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView

如果用到其他的View,则会报错,所以如果想搞个花里胡哨的通知栏界面,还是趁早放弃吧

 

二、RemoteViews和桌面小控件

先看下效果图,运行后长按屏幕,选择添加两个桌面小部件,点击其中一个改变部件图片

RemoteViews使用,内部原理史上最全 (一)_第3张图片

 

2.1、首先在res/layout/文件下写一个布局文件,这里我写的是wdight

 

RemoteViews使用,内部原理史上最全 (一)_第4张图片


    
    

 

2.2、其次在res下新建一个xml文件,建一个myappwidght文件

RemoteViews使用,内部原理史上最全 (一)_第5张图片

 

initialLayout:布局

minWidth:最小宽度

minHeight:最小高度

updatePeriodMillis:更新时间,单位为毫秒,8.1以上的系统更新时间最快也要半小时,也就是说如果设置的时间小于半小时,也是按半小时执行。



 

2.3、还要用到AppWidgetProvider,AppWidgetProvider简单的可以理解为一个广播类,源码里继承了BroadcastReceiver


public class AppWidgetProvider extends BroadcastReceiver {
    /**
     * Constructor to initialize AppWidgetProvider.
     */
    public AppWidgetProvider() {
    }

 

2.4、AppWidgetProvider

写一个MyWidgetProvider类继承AppWidgetProvider,长按屏幕就可以将部件放置到桌面上。这里实现的功能比较简单,点击桌面上指定部件的图标的时候,可以改变部件的图标,实际开发没这么简写,根据具体的业务来实现


public class MyWidgetProvider extends AppWidgetProvider {
    private final String TAG = "MyWidgetProvider";
    private final String mActionHead = "com.heard.picture";
    AppWidgetManager appWidgetManager;

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        appWidgetManager = AppWidgetManager.getInstance(context);
        String mAction = intent.getAction();
        Log.e(TAG, "mAction = " + mAction);
        switch (mAction) {
            case mActionHead:
                int appWidgetId = intent.getIntExtra("appWidgetId", 0);
                Bitmap srcBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.zip1);
                RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.wdight);
                remoteViews.setImageViewBitmap(R.id.iv_imageView, srcBitmap);
                //根据appWidgetId 来更新特定的部件
                appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
                break;

            default:
                break;
        }
    }

    /**
     * 当小部件第一次被添加的时候 注意是第一次
     */
    @Override
    public void onEnabled(Context context) {
        super.onEnabled(context);
        Log.d(TAG, "onEnabled");
    }

    /**
     * 当小部件更新的时候,或者在桌面添加小部件的时候
     */
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        Log.d(TAG, "onUpdate");
        int size = appWidgetIds.length;
        for (int i = 0; i < size; i++) {
            int appWidgetId = appWidgetIds[i];
            onUpdateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    /**
     * 每删除一个小部件的时候
     */
    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        super.onDeleted(context, appWidgetIds);
        Log.d(TAG, "onDeleted");
    }

    /**
     * 当小部件最后一个被删除的时候 注意是最后一个
     */
    @Override
    public void onDisabled(Context context) {
        super.onDisabled(context);
        Log.d(TAG, "onDisabled");
    }

    /**
     * 当小部件从备份恢复时调用该方法
     */
    @Override
    public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
        super.onRestored(context, oldWidgetIds, newWidgetIds);
        Log.d(TAG, "onRestored");
    }

    /**
     * 当小部件大小发生改变的时候
     */
    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
        Log.d(TAG, "onAppWidgetOptionsChanged");
    }

    private void onUpdateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {

        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.wdight);
        Intent intent = new Intent(mActionHead);
        intent.putExtra("appWidgetId", appWidgetId);
        //包名和类名路径
        intent.setComponent(new ComponentName("com.example.appwdightdemo",
                "com.example.appwdightdemo.provider.MyWidgetProvider"));
        // requestCode 使用appWidgetId 保证每个PendingIntent不相同
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.iv_imageView, pendingIntent);
        appWidgetManager.updateAppWidget(appWidgetId, remoteViews);

    }

}

其中:

  • OnEnable

当小部件第一次被添加的时候,注意是第一次

  • onUpdate

当小部件更新的时候,或者在桌面添加小部件的时候

  • onDeleted

每删除一个小部件的时候

  • onDisabled

当小部件最后一个被删除的时候,注意是最后一个

  • onRestored

当小部件从备份恢复时调用该方法

  • onAppWidgetOptionsChanged

当小部件大小发生改变的时候

 

2.5、在xml中注册

注意:

里面的配置是一定要加的,android.appwidget.provider是固定写法,

android:resource = "@xml/myappwidght"是刚才xml下的文件

这也是固定写法,不能变,编译器无法提示,需要自己写


            
            
                
                
            
        

 

三、两个应用用RemoteViews更新UI

3.1、这里写个简单的例子,实际开发中根据具体业务进行实现,两个应用A和B,A利用广播去通知B,B注册动态广播接收A传过来的RemoteViews,实现UI界面更新

A应用代码

 private final String mActionStr = "com.xsx.remoteViews";
    private final String mActionViews = "com.xsx.send.remoteViews";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RemoteViews remoteViews = new RemoteViews(this.getPackageName(), R.layout.remote_view);
        remoteViews.setImageViewResource(R.id.head_imageVie, R.drawable.zip1);

        Intent intent = new Intent(mActionStr);
        intent.setPackage("com.example.myapplication");
        intent.putExtra(mActionViews, remoteViews);
        sendBroadcast(intent);

    }

B应用的代码

public class MainActivity extends AppCompatActivity {

    private LinearLayout linearLayout;
    private final String mActionStr = "com.xsx.remoteViews";
    private final String mActionViews = "com.xsx.send.remoteViews";
    private RemoteViewsReceiver receiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        linearLayout = findViewById(R.id.ll_content);
        receiver = new RemoteViewsReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(mActionStr);
        registerReceiver(receiver, intentFilter);

    }

    private class RemoteViewsReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            RemoteViews remoteViews = intent.getParcelableExtra(mActionViews);
            View view = remoteViews.apply(context, linearLayout);
            linearLayout.addView(view);

        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);
    }
}

B应用的activity_main.xml




 

先运行B应用再运行A应用,发现B应用的UI更新了,这里就不贴出运行结果了,读者可以直接复制代码,自己运行。

RemoteViews的使用到这里就结束了,下一篇RemoteView的内部原理

 

 

 

你可能感兴趣的:(Android,RemoteViews,Binder)