5.1 RemoteViews的应用

1. RemoteViews简介

远程View?远程服务更好理解。远程服务是跨进程的服务,那么远程View当然是跨进程的View。
RemoteViews可以跨进程显示,并且可以跨进程更新页面。
其使用场景有两种通知栏和桌面小部件。

2. RemoteViews应用概述

通知栏和桌面小部件的开发过程都会用到RemoteViews,他们在更新界面时无法像在Activity里面那样去直接更新View,这是因为二者的界面都运行在其他进程中,确切来说是系统的SystemServer进程。为了跨进程更新界面,RemoteViews提供了一系列set方法,并且这些方法只是View全部方法的子集,另外RemoteViews中所支持的View类型也是有限的。

3. RemoteViews在通知栏上的应用(默认样式)

notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

NotificationCompat.Builder builder = new NotificationCompat.Builder(RemoteActivity.this);
Notification notification = builder
        .setContentTitle("contentTitle") // title
        .setContentText("contentText")   // content
        .setWhen(System.currentTimeMillis()) // time
        .setSmallIcon(R.mipmap.ic_launcher)  // 5.0开始只有alpha图层
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) // 
        .build();
notificationManager.notify(1, notification);

其他功能

// 点击跳到RemoteActivity页面
Intent intent = new Intent(RemoteActivity.this, RemoteActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(RemoteActivity.this, 0, intent, 0);
builder.setContentIntent(pendingIntent);
//通知被点击后,自动消失
notification.flags |= Notification.FLAG_AUTO_CANCEL;
//点击'Clear'时,不清楚该通知(QQ的通知无法清除,就是用的这个)
notification.flags |= Notification.FLAG_NO_CLEAR;
//通知的默认参数 DEFAULT_SOUND, DEFAULT_VIBRATE, DEFAULT_LIGHTS.
//如果要全部采用默认值, 用 DEFAULT_ALL.
//此处采用默认声音
notification.defaults |= Notification.DEFAULT_SOUND;
notification.defaults |= Notification.DEFAULT_VIBRATE;
notification.defaults |= Notification.DEFAULT_LIGHTS;

这里使用的是Notification的v7兼容包,因为Android各个版本的Notification有差别。

4. RemoteViews在通知栏上的应用(自定义样式)

NotificationCompat.Builder builder = new NotificationCompat.Builder(RemoteActivity.this);
// smallIcon是必须的,没有会报错,其他可以没有
builder.setSmallIcon(R.mipmap.ic_launcher);

RemoteViews remoteViews = new RemoteViews("qingfengmy.developmentofart", R.layout.remote_views);
remoteViews.setImageViewResource(R.id.icon, R.mipmap.ic_launcher);
remoteViews.setTextViewText(R.id.title, "remote views title");

// icon单独处理点击事件(setOnClickPendingIntent)
Intent intent = new Intent(this, MainActivity.class);
PendingIntent iconPendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
remoteViews.setOnClickPendingIntent(R.id.icon, iconPendingIntent);
builder.setContent(remoteViews);

// 点击跳到RemoteActivity页面
Intent intent2 = new Intent(RemoteActivity.this, RemoteActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(RemoteActivity.this, 0, intent2, 0);
builder.setContentIntent(pendingIntent);

Notification notification = builder.build();
notificationManager.notify(2, 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:id="@+id/icon"
        android:layout_width="120dp"
        android:layout_height="120dp" />

    <TextView
        android:id="@+id/title"
        android:textColor="#456789"
        android:layout_gravity="center_vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

5. 扩展样式通知(InboxStyle)

自定义通知布局的可用高度取决于通知视图。普通视图布局限制为 64 dp,扩展视图布局限制为 256 dp。

NotificationCompat.Builder builder = new NotificationCompat.Builder(this);

builder.setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle("My notification")
        .setContentText("Hello World!");

NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
inboxStyle.setBigContentTitle("邮件标题:");
for (int i=0; i < 5; i++) {
    inboxStyle.addLine("邮件内容" + i);
}
builder.setStyle(inboxStyle);

Notification notification = builder.build();
notificationManager.notify(3, notification);

6. 扩展样式通知(BigPictureStyle)

NotificationCompat.Builder builder = new NotificationCompat.Builder(this);

builder.setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle("My notification")
        .setContentText("Hello World!");

//get the bitmap to show in notification bar
Bitmap bitmap_image = BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher);
NotificationCompat.BigPictureStyle s = new NotificationCompat.BigPictureStyle().bigPicture(bitmap_image);
s.setSummaryText("Summary text appears on expanding the notification");
builder.setStyle(s);

Notification notification = builder.build();
notificationManager.notify(3, notification);

7. 默认样式的进度条处理

final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
mBuilder.setContentTitle("Picture Download")
        .setContentText("Download in progress")
        .setSmallIcon(R.mipmap.ic_launcher);
new Thread(
        new Runnable() {
            @Override
            public void run() {
                int incr;
                for (incr = 0; incr <= 100; incr += 5) {
                    mBuilder.setProgress(100, incr, false);
                    notificationManager.notify(0, mBuilder.build());
                    try {
                        Thread.sleep(5 * 1000);
                    } catch (InterruptedException e) {
                    }
                }
                // When the loop is finished, updates the notification
                mBuilder.setContentText("Download complete")
                        .setProgress(0, 0, false);
                notificationManager.notify(4, mBuilder.build());
            }
        }
).start();

默认Notification的完全体:


Paste_Image.png

参考文章
android官网Notification介绍
android notification 的总结分析

8. RemoteViews在桌面小部件应用简介

AppWidgetProvider是Android中提供的用于实现桌面小部件的类,其本质是一个广播即BroadcastingReceiver。

9. 桌面小部件开发步骤

    1. 定义小部件界面
<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/launcher"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher" />
</LinearLayout>
    1. 定义小部件配置信息
      配置信息放在 xml/appwidget_provider_info.xml
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget"
    android:minHeight="84dp"
    android:minWidth="84dp"
    android:updatePeriodMillis="100000">

</appwidget-provider>

updatePeriodMillis:定义了 Widget 的刷新频率,也就是 App Widget Framework 多久请求一次 AppWidgetProvider 的 onUpdate() 回调函数。该时间间隔并不保证精确,出于节约用户电量的考虑,Android 系统默认最小更新周期是 30 分钟,也就是说:如果您的程序需要实时更新数据,设置这个更新周期是 2 秒,那么您的程序是不会每隔 2 秒就收到更新通知的,而是要等到 30 分钟以上才可以,要想实时的更新 Widget,一般可以采用 Service 和 AlarmManager 对 Widget 进行更新。

    1. 定义小部件的实现类
public class MyAppWidgetProvider extends AppWidgetProvider {
    public static final String click_action = "QINGFENGMY.DEVELOPMENTOFART.APPWIDGET.ACTION";

    // 接受广播
    @Override
    public void onReceive(final Context context, Intent intent) {
        super.onReceive(context, intent);
       // 自己的小部件被单击了,执行相应逻辑
    }

    // 每次桌面小部件更新时都调用一次该方法,第一次放到桌面也执行
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        // 通过RemoteView添加点击事件,发送广播
    }

}

  • onUpdate实现
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    super.onUpdate(context, appWidgetManager, appWidgetIds);
    Log.e("aaa", "onUpdate");
    int counter = appWidgetIds.length;
    // 可以在桌面添加多个这样的小部件
    for (int i = 0; i < counter; i++) {
        int appWidgetId = appWidgetIds[i];
        Log.e("aaa", "-----------" + appWidgetId);
        onWidgetUpdate(context, appWidgetManager, appWidgetId);
    }
}

private void onWidgetUpdate(Context context, AppWidgetManager appWidgetManager, int 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.launcher, pendingIntent);
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}
  • onReceiver实现
@Override
public void onReceive(final Context context, Intent intent) {
    super.onReceive(context, intent);
    Log.e("aaa", "onReceive action=" + intent.getAction());
    if (click_action.equals(intent.getAction())) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher);
                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.launcher, rotateBitmap(context, bitmap, degree));

                    Intent intentClick = new Intent(click_action);
                    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentClick, 0);
                    remoteViews.setOnClickPendingIntent(R.id.launcher, pendingIntent);
                    appWidgetManager.updateAppWidget(new ComponentName(context, MyAppWidgetProvider.class),remoteViews);
                    SystemClock.sleep(30);
                }
            }
        }).start();
    }
}
private Bitmap rotateBitmap(Context context, Bitmap srcBitmap, float degree) {
    Matrix matrix = new Matrix();
    matrix.reset();
    matrix.setRotate(degree);
    Bitmap bitmap = Bitmap.createBitmap(srcBitmap, 0, 0, srcBitmap.getWidth(), srcBitmap.getHeight(), matrix, true);
    return bitmap;
}
    1. manifest中定义receiver
<receiver android:name="._5remoteviews.MyAppWidgetProvider">
    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/appwidget_provider_info">
    </meta-data>
    
    <intent-filter>
        <action android:name="QINGFENGMY.DEVELOPMENTOFART.APPWIDGET.ACTION"/>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
    </intent-filter>
</receiver>

上面的代码中有两个Action,其中第一个Action用于识别小部件的单击行为,而第二个Action则作为小部件的标识而必须存在,这是系统规范,如果不加,那么这个receiver就不是一个桌面小部件,也无法出现在手机的小部件列表里。

10. AppWidgetProvider的其他几个方法

AppWidgetProvider除了最常用的onUpdate方法,还有其他几个方法:onEnabled、onDisabled、onDelete以及onReceive。这些方法会自动地被onReceiver方法在合适的事件调用。确切的说,当广播到来以后,AppWidgetProvider会自动根据广播的Action通过onReceiver方法来自动分发广播。

  • onEnable:当该窗口小部件第一次添加到桌面时调用该方法,可添加多次但只在第一次调用。
  • onUpdate:小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的更新时机有updatePeriodMillis来指定。
  • onDelete:每删除一次桌面小部件就调用一次。
  • onDisabled:当最后一个该类型的桌面小部件被删除时调用该方法,注意是最后一个。
  • onReceiver:这是广播的内置方法,用于分发具体的事件给其他方法。

AppWidgetProvider的onReceiver方法源码:

public void onReceive(Context context, Intent intent) {
    
    String action = intent.getAction();
    if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
        Bundle extras = intent.getExtras();
        this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
    } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
        Bundle extras = intent.getExtras();
        this.onDeleted(context, new int[] { appWidgetId });
    } else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
        Bundle extras = intent.getExtras();
        this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
                    appWidgetId, widgetExtras);
    } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
        this.onEnabled(context);
    } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
        this.onDisabled(context);
    } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
        Bundle extras = intent.getExtras();
        this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
    }
}

11. PendingIntent概述

PendingIntent表示一种处于pending状态的意图,而pending状态表示的是一种待定,等待,即将发生的意思,就是说接下来有一个Intent将在某个带点的时刻发生。
PendingIntent典型的使用场景是给RemoteViews添加点击事件,因为RemoteViews运行在远程进程中,因此RemoteViews不同于普通的View,无法直接像View那样通过setOnClickListener方法来设置单击事件。要想给RemoteViews设置单击事件,就必须使用PendingIntent,PendingIntent通过send和cancle方法来发送和取消特定的Intent。
PendingIntent支持三种待定意图:启动Activity,启动Service和发送广播。

public static PendingIntent getActivity(Context context, int requestCode,
    Intent intent, int flags);

启动Service和发送广播也是这四个参数。第一个参数和第三个参数好理解,这里主要说下第二个参数requestCode和第四个参数flags.
requestCode表示PendingIntent发送方的请求码,多数情况设为0即可,另外requestCode也会影响flags的效果。flags常见的类型有:FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT,FLAG_UPDATE_CURRENT。
PendingIntent的匹配规则: 如果两个PendingIntent他们内部的Intent相同并且requestCode也相同,那么这两个PendingIntent就是相同的。
Intent的匹配规则是: 如果两个Intent的ComponentName和intent-filter都相同,那么这两个Intent就是相同的,注意Extras不参与匹配规则。

12. 举例说明flags

notificationManager.notify(1, notification);
  • 如果notify的第一个参数id是常量,那么多次调用notify只能弹出一个通知,后续的通知会把前面的通知完全替代掉,其中的PendingIntent当然也会被直接替换。
  • 如果notify每次id都不同,那么当pendingIntent不匹配时,这种情况不管采用什么标记位(flags),他们之间不会相互影响。
  • 如果notify每次id不同,pendingIntent匹配,且标记位是FLAG_ONE_SHOT。那么后续通知中的PendingIntent会和第一条通知保持完全一致,包括其中的Extras,单击任何一条通知后,剩余的通知均无法再打开。
  • 如果notify每次id不同,pendingIntent匹配,且标记位是FLAG_CANCEL_CURRENT。那么只有最新的通知可以打开,之前的通知均无法打开。
  • 如果notify每次id不同,pendingIntent匹配,且标记位是FLAG_UPDATE_CURRENT。那么之前的通知中的PendingIntent会被更新,最终他们和最新的一条保持完全一致,包括其中的Extras,并且这些通知都是可以打开的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容