5.1 RemoteViews的應(yīng)用

1. RemoteViews簡介

遠(yuǎn)程View?遠(yuǎn)程服務(wù)更好理解。遠(yuǎn)程服務(wù)是跨進程的服務(wù),那么遠(yuǎn)程View當(dāng)然是跨進程的View。
RemoteViews可以跨進程顯示,并且可以跨進程更新頁面。
其使用場景有兩種通知欄和桌面小部件。

2. RemoteViews應(yīng)用概述

通知欄和桌面小部件的開發(fā)過程都會用到RemoteViews,他們在更新界面時無法像在Activity里面那樣去直接更新View,這是因為二者的界面都運行在其他進程中,確切來說是系統(tǒng)的SystemServer進程。為了跨進程更新界面,RemoteViews提供了一系列set方法,并且這些方法只是View全部方法的子集,另外RemoteViews中所支持的View類型也是有限的。

3. RemoteViews在通知欄上的應(yīng)用(默認(rèn)樣式)

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;
//通知的默認(rèn)參數(shù) DEFAULT_SOUND, DEFAULT_VIBRATE, DEFAULT_LIGHTS.
//如果要全部采用默認(rèn)值, 用 DEFAULT_ALL.
//此處采用默認(rèn)聲音
notification.defaults |= Notification.DEFAULT_SOUND;
notification.defaults |= Notification.DEFAULT_VIBRATE;
notification.defaults |= Notification.DEFAULT_LIGHTS;

這里使用的是Notification的v7兼容包,因為Android各個版本的Notification有差別。

4. RemoteViews在通知欄上的應(yīng)用(自定義樣式)

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("郵件標(biāo)題:");
for (int i=0; i < 5; i++) {
    inboxStyle.addLine("郵件內(nèi)容" + 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. 默認(rèn)樣式的進度條處理

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();

默認(rèn)Notification的完全體:


Paste_Image.png

參考文章
android官網(wǎng)Notification介紹
android notification 的總結(jié)分析

8. RemoteViews在桌面小部件應(yīng)用簡介

AppWidgetProvider是Android中提供的用于實現(xiàn)桌面小部件的類,其本質(zhì)是一個廣播即BroadcastingReceiver。

9. 桌面小部件開發(fā)步驟

    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() 回調(diào)函數(shù)。該時間間隔并不保證精確,出于節(jié)約用戶電量的考慮,Android 系統(tǒng)默認(rèn)最小更新周期是 30 分鐘,也就是說:如果您的程序需要實時更新數(shù)據(jù),設(shè)置這個更新周期是 2 秒,那么您的程序是不會每隔 2 秒就收到更新通知的,而是要等到 30 分鐘以上才可以,要想實時的更新 Widget,一般可以采用 Service 和 AlarmManager 對 Widget 進行更新。

    1. 定義小部件的實現(xiàn)類
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);
       // 自己的小部件被單擊了,執(zhí)行相應(yīng)邏輯
    }

    // 每次桌面小部件更新時都調(diào)用一次該方法,第一次放到桌面也執(zhí)行
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        // 通過RemoteView添加點擊事件,發(fā)送廣播
    }

}

  • onUpdate實現(xiàn)
@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);

    // 桌面小部件 單擊事件發(fā)送的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實現(xiàn)
@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則作為小部件的標(biāo)識而必須存在,這是系統(tǒng)規(guī)范,如果不加,那么這個receiver就不是一個桌面小部件,也無法出現(xiàn)在手機的小部件列表里。

10. AppWidgetProvider的其他幾個方法

AppWidgetProvider除了最常用的onUpdate方法,還有其他幾個方法:onEnabled、onDisabled、onDelete以及onReceive。這些方法會自動地被onReceiver方法在合適的事件調(diào)用。確切的說,當(dāng)廣播到來以后,AppWidgetProvider會自動根據(jù)廣播的Action通過onReceiver方法來自動分發(fā)廣播。

  • onEnable:當(dāng)該窗口小部件第一次添加到桌面時調(diào)用該方法,可添加多次但只在第一次調(diào)用。
  • onUpdate:小部件被添加時或者每次小部件更新時都會調(diào)用一次該方法,小部件的更新時機有updatePeriodMillis來指定。
  • onDelete:每刪除一次桌面小部件就調(diào)用一次。
  • onDisabled:當(dāng)最后一個該類型的桌面小部件被刪除時調(diào)用該方法,注意是最后一個。
  • onReceiver:這是廣播的內(nèi)置方法,用于分發(fā)具體的事件給其他方法。

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狀態(tài)的意圖,而pending狀態(tài)表示的是一種待定,等待,即將發(fā)生的意思,就是說接下來有一個Intent將在某個帶點的時刻發(fā)生。
PendingIntent典型的使用場景是給RemoteViews添加點擊事件,因為RemoteViews運行在遠(yuǎn)程進程中,因此RemoteViews不同于普通的View,無法直接像View那樣通過setOnClickListener方法來設(shè)置單擊事件。要想給RemoteViews設(shè)置單擊事件,就必須使用PendingIntent,PendingIntent通過send和cancle方法來發(fā)送和取消特定的Intent。
PendingIntent支持三種待定意圖:啟動Activity,啟動Service和發(fā)送廣播。

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

啟動Service和發(fā)送廣播也是這四個參數(shù)。第一個參數(shù)和第三個參數(shù)好理解,這里主要說下第二個參數(shù)requestCode和第四個參數(shù)flags.
requestCode表示PendingIntent發(fā)送方的請求碼,多數(shù)情況設(shè)為0即可,另外requestCode也會影響flags的效果。flags常見的類型有:FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT,FLAG_UPDATE_CURRENT。
PendingIntent的匹配規(guī)則: 如果兩個PendingIntent他們內(nèi)部的Intent相同并且requestCode也相同,那么這兩個PendingIntent就是相同的。
Intent的匹配規(guī)則是: 如果兩個Intent的ComponentName和intent-filter都相同,那么這兩個Intent就是相同的,注意Extras不參與匹配規(guī)則。

12. 舉例說明flags

notificationManager.notify(1, notification);
  • 如果notify的第一個參數(shù)id是常量,那么多次調(diào)用notify只能彈出一個通知,后續(xù)的通知會把前面的通知完全替代掉,其中的PendingIntent當(dāng)然也會被直接替換。
  • 如果notify每次id都不同,那么當(dāng)pendingIntent不匹配時,這種情況不管采用什么標(biāo)記位(flags),他們之間不會相互影響。
  • 如果notify每次id不同,pendingIntent匹配,且標(biāo)記位是FLAG_ONE_SHOT。那么后續(xù)通知中的PendingIntent會和第一條通知保持完全一致,包括其中的Extras,單擊任何一條通知后,剩余的通知均無法再打開。
  • 如果notify每次id不同,pendingIntent匹配,且標(biāo)記位是FLAG_CANCEL_CURRENT。那么只有最新的通知可以打開,之前的通知均無法打開。
  • 如果notify每次id不同,pendingIntent匹配,且標(biāo)記位是FLAG_UPDATE_CURRENT。那么之前的通知中的PendingIntent會被更新,最終他們和最新的一條保持完全一致,包括其中的Extras,并且這些通知都是可以打開的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內(nèi)容