Android Widget的使用和需要注意的問題

一.簡單上手

1. 配置并顯示widget

1.1 繼承AppWidgetProvider

自定義MyWidgetProvider繼承AppWidgetProvider,重寫相關方法。

class MyWidgetProvider : AppWidgetProvider() {
    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
        //當更新widget的時候會觸發,添加的時候也會觸發
    }
    override fun onDeleted(context: Context, appWidgetIds: IntArray) {
        super.onDeleted(context, appWidgetIds)
        //刪除widget的時候會觸發
    }
    override fun onDisabled(context: Context?) {
        super.onDisabled(context)
        //最后一個widget被刪除的時候觸發
    }
    override fun onEnabled(context: Context?) {
        super.onEnabled(context)
        //第一個widget被添加的時候觸發
    }
    override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle?) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
        //當widget被第一次添加或者widget大小改變的時候觸發
    }
    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)
        //處理方式和普通廣播一樣
    }
}

AppWidgetProvider實質上就是一個廣播,其中處理了相關的action并給出了回調方法。

1.2 配置appwidget-provider

找到res目錄下的xml目錄,若沒有xml目錄就新建一個,然后新建一個文件widget_info.xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialKeyguardLayout="@layout/widget_layout"
    android:initialLayout="@layout/widget_layout"
    android:minWidth="@dimen/dp_320"
    android:minHeight="@dimen/dp_110"
    android:previewImage="@mipmap/img_widget_preview"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="5"
    android:widgetCategory="home_screen">
</appwidget-provider>

這里介紹下常用屬性

  • android:initialLayout添加到桌面的widget布局
  • android:initialKeyguardLayout添加到鎖屏頁面的widget布局
  • android:minWidth最小寬度,通用計算方式: (N * 70)-30=寬度
  • android:minHeight最小高度,通用計算方式: (N * 70)-30=高度,寬度和高度的格數按照google標準是這樣設置的,但是有很多廠家對Launcher重新定義,所以比如你設置的是5 * 1,但是某些手機上就會變成4 * 1。
  • android:previewImage預覽圖
  • android:resizeMode允許橫向縱向拉伸
  • android:updatePeriodMillis刷新間隔,最小刷新間隔是半小時,設置小于半小時也會按半小時算,且這里還有一點要注意,并不是每過半小時就一定會準時刷新,受設備影響這個時間可能略有提前或延遲。還有當手機息屏后可能會進入休眠狀態,在休眠狀態時不會自動更新,當設備解鎖從休眠狀態恢復時會立即刷新widget。

1.3 配置AndroidManifest.xml

        <receiver
            android:name=".MyWidgetProvider"
            android:label="@string/app_widget_string">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>

在AndroidManifest.xml中需要配置一個廣播接受者,其中固定的兩個配置參數

  • <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>指定這個才能接收到widget更新。
  • android:name="android.appwidget.provider"告訴系統這個廣播接受者是一個widget。
    還可以在intent-filter里面配置自定義的action,用法就和普通廣播一樣。

現在已經可以添加widget顯示啦,顯示的內容為widget_layout.xml里的布局,沒錯就是這么簡單。

2. 更新widget

上面已經顯示了widget,接下來就要給widget更新UI。
更新widget的UI是通過AppWidgetManager的updateAppWidget方法實例來更新的,我們可以通過AppWidgetManager.getInstance(context)來獲取實例。updateAppWidget有三個重載方法。

  • updateAppWidget(ComponentName provider, RemoteViews views)
    指定要刷新widget的ComponentName和RemoteViews,通過AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteView)來刷新。舉個例子,我在桌面第一頁和第三頁都添加了同一個widget,現在若點擊其中一個的刷新按鈕兩個widget要同時都更新界面,這時就可以用這個方法。這個方法也是最常用來更新widget的方式,可以刷新添加到桌面的所有widget。一般來說,更新widget并不要求在AppWidgetProvider中進行,因為AppWidgetProvider本質上就是一個廣播,只要通過指定remoteView和ComponentName,可在任何包含上下文的環境下更新widget。
  • updateAppWidget(int[] appWidgetIds, RemoteViews views)
    刷新部分指定的widget
  • updateAppWidget(int appWidgetId, RemoteViews views)
    刷新一個指定的widget
class MyWidgetProvider : AppWidgetProvider() {
    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
        for (appWidgetId in appWidgetIds) {
            appWidgetManager.updateAppWidget(appWidgetId, remoteView)
        }
        //uploadWidget(context)
    }
    private fun uploadWidget(context: Context) { 
        val remoteView = RemoteViews(context.packageName, R.layout.widget_layout)
        val componentName = ComponentName(context, javaClass) 
        AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteView)
    }
}

上面代碼兩種方式都能刷新全部widget

3. widget的點擊

package com.example.kotlintest.widget

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import com.example.kotlintest.R

class MyWidgetProvider : AppWidgetProvider() {
    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
        val intent = Intent(REFRESH_CLICK).apply {
            component = ComponentName(context, MyWidgetProvider::class.java)
        }
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            R.id.tv_refresh,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT
        )
        val remoteView = RemoteViews(context.packageName, R.layout.widget_layout)
        remoteView.setOnClickPendingIntent(R.id.tv_refresh, pendingIntent)
        uploadWidget(context,remoteView)
    }
    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)
        when (intent.action) {
            REFRESH_CLICK -> {
                //點擊事件
            }
        }
    }
    private fun uploadWidget(context: Context,remoteView: RemoteViews) {
        val componentName = ComponentName(context, javaClass)
        AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteView)
    }
    companion object {
        const val REFRESH_CLICK = "com.example.kotlintest.action.CLICK_REFRESH"
    }
}

二. 開發widget中需要注意處理的點

1. 初始化問題

當widget刷新時,如果應用沒有處于開啟狀態下,這時會創建APP進程并初始化Application,之后回調widget的onUpdate方法。然而這里會有一個問題,由于部分app為了性能優化,將部分初始化操作移動到了引導頁或Main頁面里了,這樣當widget想使用某些功能時,由于只創建了Application,在引導頁或main頁面里進行初始化的那部分功能沒有進行初始化,便會拋出各種異常。所以這里開發的時候需要重點檢查一遍。

2. UI設置

  • 當添加widget出現小組件添加錯誤、顯示失敗等,優先檢查xml布局是否正確,尤其是不能包含自定義View等。
  • 通過RemoteViews更新widget,可能每次更新都創建了一個RemoteViews對象,但是RemoteViews只是一個action集合,只代表你對systemServer端widget的操作,一旦通過RemoteViews更新過widget,有些步驟就可以不用重復設置(列如點擊事件)
  • widget不支持動畫,如果一定要實現動畫,可以開子線程循環刷新bitmap。

3. 網絡請求

盡量不要直接在AppWidgetProvider中進行網絡請求,和耗時操作。

  • 在AppWidgetProvider中進行網絡請求,當未開啟APP情況下,會請求失敗拋出SocketTimeoutException異常。這一點很重要,很多系統都會限制在后臺程序里靜態廣播的網絡請求。如果有需要,請開啟Service,在Service中進行網絡請求。
  • 由于AppWidgetProvider優先級很低,代表當前進程容易被系統回收,所以盡量不要再AppWidgetProvider中進行耗時操作,否則可能會出現AppWidgetProvider中的任務未執行完進程就已經被系統回收。建議耗時操作開啟Service執行。

4. 定時任務

很大一部分app都有定時刷新widget的需求,而系統的刷新間隔要求大于等于30分鐘,這顯然是滿足不了需求。這里有兩種方案。

  1. 單獨進程的前臺service
  2. 通過JobScheduler
    如果對實時性要求不是太高,可以考慮使用JobScheduler

5. 關于Service通知問題

我們知道在Android8.0后開啟Service需要指定為前臺通知,這樣就會有一個通知欄效果。如果在widget中想開啟Service進行網絡請求,而又不想出通知,可以使用bindService方式。
bindService是Context的方法,網上大部分文章都拿Activity做例子,導致很多人不知道bindService其實在Application等Context的子類中都能使用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容