android RemoteViews解析

1.RemoteViews的作用

在其他进程中显示并更新view界面,所谓跨进程是因为view界面是运行在系统的SystemServer进程的。系统除了常见的notification和appwidget也就是通知栏和桌面小部件,notificatio是通过notificationmanager.notify方法来实现的,appwidget则是通过appwidgetProvider来实现的appwidgetProvider本质上就是个广播。

其实我们也可以通过广播来模拟发送通知栏消息。

2.RemoteViews在通知栏上的应用

2.1典型用法如下,在android8.0以后通知栏的使用有些变化:

Intent intent = new Intent(this, DemoActivity_2.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
        0, intent, PendingIntent.FLAG_ONE_SHOT);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

int channelId = 0x22222;
Notification.Builder builder;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    NotificationChannel channel = new NotificationChannel(String.valueOf(channelId), "chanel_name", NotificationManager.IMPORTANCE_HIGH);
    manager.createNotificationChannel(channel);
    builder = new Notification.Builder(this, String.valueOf(channelId));
} else {
    builder = new Notification.Builder(this);
}
builder.setContentIntent(pendingIntent);

builder.setTicker("new message")
        .setSmallIcon(R.drawable.ic_launcher_background)
        .setContentTitle("标题")
        .setContentText("内容")
        .setContentIntent(pendingIntent);
manager.notify(sId, builder.build());

3.RemoteViews在桌面小部件上的应用

定义桌面小部件也就是remoteviews的布局:

<?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>

定义桌面小部件配置信息:

<?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" >
    <!--initialLayout:初始布局-->
    <!--minHeight + minWidth:尺寸-->
    <!--updatePeriodMillis:更新周期,单位毫秒-->
</appwidget-provider>

桌面小部件实现类:

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));
                        appWidgetManager.updateAppWidget(new ComponentName(
                                context, MyAppWidgetProvider.class),remoteViews);
                        SystemClock.sleep(30);
                    }
                }
            }).start();
        }
    }

    /**
     * 最常用的onUpdate方法
     * 桌面小部件被添加或者更新时,会走到这里
     */
    @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);
        //8.0以上版本必须写
        intentClick.setComponent(new ComponentName(context,MyAppWidgetProvider.class));
        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;
    }

在manifest中声明桌面小部件

<receiver android:name="com.ryg.chapter_5.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>

4.notificationId的意义,pengdingIntent需要注意的地方?

4.1notificationId常量与变量

如果notificationId是常量多次调用notify则永远只能弹出一个通知,后面的通知会把前面的通知完全覆盖掉

如果notificationId是变量每次都不同,这时候如果pendingintent不匹配每次的通知是互不干扰的,如果pendingintent匹配(requestcode和intent都相同)这时候就跟pendingintent的标记位有关系了:

4.2pendingintent标志位

FLAG_ONE_SHOT(和第一条通知保持一致)

FALG_CANCEL_CURRENT(只有最新的这条通知可以打开,之前的都是没法打开的)

FLAG_UPDATE_CURRENT(之前的所有通知都会和最新的这一条保持一致)

5.RemoteViews的内部机制,RemoteViews支持哪些view类型,里面的view是怎么赋值的?

notificationmanager和appwidgetmanager通过binder跨进程和systemServer进程中的的notificationmanagerService和appwidgetService进行通信,也就是说remoteview其实是在notificationmanagerService和appwidgetService被加载出来的。当remoteviews调用setText

ViewText等方法的时候,remoteviews中就会添加一个Action对象,最终会形成一个actionList,当remoteviews在systemserver进程中调用apply方法时会遍历actionLsit来更新view。

6.自动模式实现通知栏消息

发送;

RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_simulated_notification);
remoteViews.setTextViewText(R.id.msg, "msg from process:" + Process.myPid());
remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
        0, new Intent(this, DemoActivity_1.class), PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(
        this, 0, new Intent(this, DemoActivity_2.class), PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.item_holder, pendingIntent);
remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2PendingIntent);
Intent intent = new Intent(MyConstants.REMOTE_ACTION);
intent.putExtra(MyConstants.EXTRA_REMOTE_VIEWS, remoteViews);
sendBroadcast(intent);

模拟接收:

private BroadcastReceiver mRemoteViewsReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        RemoteViews remoteViews = intent
                .getParcelableExtra(MyConstants.EXTRA_REMOTE_VIEWS);
        if (remoteViews != null) {
            updateUI(remoteViews);
        }
    }
};

private void updateUI(RemoteViews remoteViews) {
//        View view = remoteViews.apply(this, mRemoteViewsContent);
        int layoutId = getResources().getIdentifier("layout_simulated_notification", "layout", getPackageName());
        View view = getLayoutInflater().inflate(layoutId, mRemoteViewsContent, false);
        remoteViews.reapply(this, view);
        mRemoteViewsContent.addView(view);
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章