Android中AppWidget的分析與應用:AppWidgetProvider

尊重作者原創版權,原文出處:點擊打開鏈接


注:在幾個重要的操作後面我用其他顏色進行標記


本文從開發AppWidgetProvider角度出發,看一個AppWidgetPrvodier在整個AppWidget體系中所扮演的角色。分析了AppWidgetProvider如何被AppWidget系統所識別;AppWidgetProvider何時/如何通過RemoteViews提供並更新數據;如何響應通過RemoteViews提供的PendingIntent的按鈕點擊操作。

 

因爲一般應用開發者並不關注AppWidget其他部分(比如,AppWidgetHost,或AppWidget內部組件)的開發,所以一般就直接把AppWidgetProvider開發稱爲“AppWidget開發”。


一、實現一個AppWidgetProvider

 

要實現一個AppWidgetProvider,需要:

  1. 實現AppWidgetProvider的子類,並至少override onUpdate()方法[非必須,但是如果不這樣做,該AppWidgetProvider就沒有提供任何內容,也就不是AppWidgetProvider了];
  2. 在AndroidManifest.xml中,聲明上述的AppWidgetProvider的子類是一個Receiver,並且:
    • 該Receiver的intent-filter的Action必須包含“android.appwidget.action.APPWIDGET_UPDATE”;
    • 該Receiver的meta-data爲“android.appwidget.provider”,並用一個xml文件來描述佈局屬性。
  3. 在2.2的xml文件中描述佈局屬性的節點名稱必須爲“appwidget-provider”。

以上幾點皆是AppWidget系統判斷是否是AppWidgetProvider的標誌。後面本文的3.2中詳述是如何被檢索並加入到系統中的。

 

二、AppWidgetProvider類分析

 

AppWidgetProvider是一個BroadcastReceiver,必須在AndroidManifest.xml中聲明該Receiver,並接收“android.appwidget.action.APPWIDGET_UPDATE”。

此外,在AndroidManifest.xml中還有第一節第二點中的兩個注意點(添加註解)

 

類AppWidgetProvider的實現是一個模板模式:

AppWidgetProvider

圖一、AppWidgetProvider

 

在AppWidgetProvider的onReceiver()實現中已經對接收到的ActionAppWidgetManager.ACTION_APPWIDGET_UPDATE / AppWidgetManager.ACTION_APPWIDGET_DELETED/ AppWidgetManager.ACTION_APPWIDGET_ENABLED以及AppWidgetManager.ACTION_APPWIDGET_DISABLED做了處理,分別執行onUpdate()/ onDeleted() / onEnabled() / onDisabled()。

 

所以,AppWidgetProvider的實現類,要overrideonReceive(),以及onXXX()[注:至少要實現onUpdate(),在這裏AppWidgetProvider通過RemoteViews提供內容給AppWidgetHost,否則,所謂的AppWidgetProvider什麼也沒提供]。

而在onReceive()的開始處就要執行super.onReceive()讓AppWidgetProvider來分發AppWidgetProvider所要處理的上述廣播消息。

 

AppWidgetProvider處理AppWidget中的廣播:

  • onUpdate() 處理AppWidgetManager.ACTION_APPWIDGET_UPDATE廣播。該廣播在需要AppWidgetProvider提供RemoteViews數據時,由AppWidgetService.sendUpdateIntentLocked()發出。
  • onDeleted() 處理AppWidgetManager.ACTION_APPWIDGET_DELETED廣播。該廣播在有該AppWidgetProvider的實例被刪除時,由AppWidgetService.deleteAppWidgetLocked()發出。
  • onEnabled() 處理AppWidgetManager.ACTION_APPWIDGET_ENABLED廣播。該廣播在該AppWidgetProvider被實例化時,由AppWidgetService.sendEnableIntentLocked()發出。
  • onDisabled() 處理AppWidgetManager.ACTION_APPWIDGET_DISABLED廣播。該廣播在該AppWidgetProvider的所有實例中的最後一個實例被刪除時,由AppWidgetService.deleteAppWidgetLocked()發出。

一般地,AppWidgetProvider必須處理onUpdate();onEnabled()和onDisabled()最好也要處理;onDeleted()可以不處理。

 

三、AppWidgetProvider的如何被系統識別

 

3.1 AppWidgetProvider中配置AndroidManifest.xml

 

Android中“電量控制”這個AppWidget是由Settings中的SettingsAppWidgetProvider實現的,先來看它的AndroidManifest.xml。

 

下面是Settings中關於SettingsAppWidgetProvider這個AppWidgetProvider的描述信息:

[html] view plaincopy
  1. <receiver android:name=".widget.SettingsAppWidgetProvider"  
  2.         android:label="@string/gadget_title"android:exported="true">  
  3.     <intent-filter>  
  4.         <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />  
  5.         <action android:name="android.net.wifi.WIFI_STATE_CHANGED"/>  
  6.         <action android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" />  
  7.         <action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />  
  8.         <action android:name="android.location.PROVIDERS_CHANGED"/>  
  9.         <action android:name="com.android.sync.SYNC_CONN_STATUS_CHANGED" />  
  10.     </intent-filter>  
  11.     <meta-data android:name="android.appwidget.provider"android:resource="@xml/appwidget_info" />  
  12. </receiver>  

這其中滿足一中的1&2的要求,另外,這個AppWidget要處理設置Wifi、Bluetooth、GPS、數據同步和亮度,所以要處理這些相應的設置項變化時的廣播通知。

對3這點,還要要看res/xml/appwidget_info.xml文件

[html] view plaincopy
  1. <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"  
  2.    android:minWidth="294dip"  
  3.    android:minHeight="72dip"  
  4.    android:updatePeriodMillis="0"  
  5.    android:initialLayout="@layout/widget"  
  6.     >  
  7. </appwidget-provider>  

這裏定義了最小寬度minWidth、最小高度minHeight和初始layoutinitialLayout,用來在AppWidgetProvider還未通過RemoteViews提供數據之前,AppWidgetHost就能夠獲知需要爲該AppWidget預留大概的位置;

updatePeriodMillis指示是否需要週期性的更新AppWidget,0是不需要週期更新。

 

3.2 AppWidgetProvider的信息被系統所識別

這部分是由AppWidgetService實現。

當包含AppWidgetProvider的apk被安裝到系統中的時候,AppWidgetService會監聽廣播,並處理相應的AppWidgetProvider:

  • 監聽到有包被加入(Intent.ACTION_PACKAGE_ADDED或Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE)時,執行addProvidersForPackageLocked()/ updateProvidersForPackageLocked() 添加或更新其中的AppWidgetProvider;
  • 監聽到有包被移除(Intent.ACTION_PACKAGE_REMOVED或Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)時,執行removeProvidersForPackageLocked()移除其中的AppWidgetProvider。

 

下面重點關注如何加入AppWidgetProvider,看addProvidersForPackageLocked()的實現:

[java] view plaincopy
  1. void addProvidersForPackageLocked(String pkgName) {  
  2.    Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);  
  3.    intent.setPackage(pkgName);  
  4.    List<ResolveInfo> broadcastReceivers =mPackageManager.queryBroadcastReceivers(intent,  
  5.            PackageManager.GET_META_DATA);  
  6.   
  7.    final int N = broadcastReceivers == null ? 0 :broadcastReceivers.size();  
  8.    for (int i=0; i<N; i++) {  
  9.        ResolveInfo ri = broadcastReceivers.get(i);  
  10.        ActivityInfo ai = ri.activityInfo;  
  11.        if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE)!= 0) {  
  12.            continue;  
  13.        }  
  14.        if (pkgName.equals(ai.packageName)) {  
  15.            addProviderLocked(ri);  
  16.        }  
  17.     }  
  18. }  

  1. 檢索所加入包中的所有AppWidgetManager.ACTION_APPWIDGET_UPDATE的Receiver,並放入broadcastReceivers:List<ResolveInfo>;[Line#2~ 5]
  2. 對每一個這樣的Broadcast,滿足下列要求的加入已安裝AppWidgetProvider列表:

    • 不是安裝在外存儲器上;
    • Receiver的包名與安裝的包名相同;
    • Meta-data的節點名必須是"appwidget-provider"[解析meta-data中android:resource指向的xml內容]

 

解析meta-data中android:resource指向的xml內容時,所要解析哪些內容是由frameworks/base/core/res/res/values/attrs.xml中的AppWidgetProviderInfo配置:

[html] view plaincopy
  1. <declare-styleable name="AppWidgetProviderInfo">  
  2.     <!-- Minimum width of the AppWidget. -->  
  3.     <attr name="minWidth"/>  
  4.     <!-- Minimum height of the AppWidget. -->  
  5.     <attr name="minHeight"/>  
  6.     <!-- Update period in milliseconds, or 0 if the AppWidget will updateitself. -->  
  7.     <attr name="updatePeriodMillis" format="integer"/>  
  8.     <!-- A resource id of a layout. -->  
  9.     <attr name="initialLayout" format="reference"/>  
  10.     <!-- A class name in the AppWidget's package to be launched toconfigure.  
  11.          If not supplied, then no activity will be launched. -->  
  12.     <attr name="configure" format="string" />  
  13. </declare-styleable>  

這些值被解析出來之後,連同label、icon以及由ComponentName(packageName,className)構造的provider被賦值到AppWidgetProviderInfo中,並被記錄在AppWidgetService的mInstalledProviders:ArrayList<Provider>中。

AppWidgetProvider AppWidgetProviderInfo

圖二、AppWidgetProviderInfo

 

四、AppWidgetProvider的Enable與Disable

 

因爲AppWidgetProvider只是提供顯示內容,具體顯示是顯示在AppWidgetHost中的。因爲Android機制的關係,後臺的AppWidgetProvider很容易被系統殺掉。所以AppWidgetProvider在收到AppWidgetManager.ACTION_APPWIDGET_ENABLED和AppWidgetManager.ACTION_APPWIDGET_DISABLED而執行的onEnbaled()和onDisabled()中是恰當的設置實現AppWidgetProvider的包能不能被移除設置的恰當點。

 

在onEnbaled()中,該AppWidgetProvider正在被使用,不讓被殺掉:

[java] view plaincopy
  1. PackageManager pm = context.getPackageManager();  
  2. pm.setComponentEnabledSetting(  
  3.         new ComponentName("com.android.settings",".widget.SettingsAppWidgetProvider"),  
  4.         PackageManager.COMPONENT_ENABLED_STATE_ENABLED,  
  5.         PackageManager.DONT_KILL_APP);  

在onDisabled()中,該AppWidgetProvider不再被使用,可以被殺掉了:

[java] view plaincopy
  1. Class clazz = com.android.settings.widget.SettingsAppWidgetProvider.class;  
  2. PackageManager pm = context.getPackageManager();  
  3. pm.setComponentEnabledSetting(  
  4.        new ComponentName("com.android.settings",".widget.SettingsAppWidgetProvider"),  
  5.        PackageManager.COMPONENT_ENABLED_STATE_DISABLED,  
  6.        PackageManager.DONT_KILL_APP);  

 

五、AppWidgetProvider通過RemoteViews提供內容

 

在需要AppWIdgetProvider提供RemoteViews時,AppWidget系統會發出AppWidgetManager.ACTION_APPWIDGET_UPDATE廣播,進而onUpdate()會被執行。

AppWidgetProvider onUpdate remoteViews

圖三、AppWidgetProvider提供RemoteViews

 

  •  在onUpdate()中,創建RemoteViews的實例,傳入AppWidgteProvider所在的包名和該AppWidget所要用的Layout;[Seq#5]
  •  如果要響應layoutId中某個viewId被點擊操作,要創建本地的PendingIntent,並通過setOnClickPendingIntetn設置到RemoteViews中;[Seq#6~ #9]
  •  爲layoutId中要顯示的控件加上顯示元素,比如某個ImageView的ImageResource。[Seq#10]
  •  用AppWidgetManager.updateAppWidget()更新RemoteViews到系統中,AppWidget系統會更新與之綁定的AppWidgetHost。[Seq#11]
以上標紅的是實際開發中需要用到的。(此爲後添備註)

 

RemoteViews設置與顯示詳細實現可參考《Android中RemoteViews的實現》;AppWidgetHost如何更新RemoteView可參看《Android中Launcher對於AppWidget處理的分析:AppWidgetHost角色》。

 

六、通過PendingIntent設置按鈕響應

 

上面講到,可以通過給RemoteViews設置PendingIntent獲知感興趣的View被點擊時的響應:

[java] view plaincopy
  1. Intent launchIntent = new Intent();  
  2. launchIntent.setClass(context, SettingsAppWidgetProvider.class);  
  3. launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE);  
  4. launchIntent.setData(Uri.parse("custom:" + buttonId));  
  5. PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* norequestCode */,  
  6.         launchIntent, 0 /* no flags */);  

buttonId是layout中的各個Button對應的自定義的Id,該Id只要在本程序中用來能夠區分出是哪個Button就可以,被指進”custom:”參數。

 

AppWidgetProvider本身就是個BroadcastReceiver,在其onReceive()中,就可以判斷出是哪個Button被點擊了:

[java] view plaincopy
  1. if(intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {  
  2.    Uri data = intent.getData();  
  3.    int buttonId = Integer.parseInt(data.getSchemeSpecificPart());  
  4.    if (buttonId == BUTTON_WIFI) {  
  5.        // 切換Wifi狀態  
  6.    } else if (buttonId == BUTTON_BRIGHTNESS) {  
  7.        // 切換亮度  
  8.    } else if (buttonId == BUTTON_SYNC) {  
  9.        // 切換數據同步設置  
  10.    } else if (buttonId == BUTTON_GPS) {  
  11.        // 切換GPS打開開關  
  12.    } else if (buttonId == BUTTON_BLUETOOTH) {  
  13.        // 切換藍牙打開狀態  
  14.    }  
  15. }  

 

總結

本文講述了:

  •  實現一個AppWidgetProvider所需要的配置和實現;
  •  AppWidgetProvider如何被AppWidget識別和加入到已安裝列表;
  •  AppWidgetProvider如何生成RemoteViews對象,並更新到AppWidgetHost;
  •  AppWidgetProvider響應按鈕操作。
發佈了20 篇原創文章 · 獲贊 25 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章