1. 程式人生 > >Android Widget工作原理詳解(一) 最全介紹

Android Widget工作原理詳解(一) 最全介紹

      Widget是安卓的一應用程式元件,學名視窗小部件,它是微型應用程式檢視, 可以嵌入到其他應用程式(如主螢幕)和接收資料定期更新。,可以使其他應用程式的外掛被稱為應用程式部件。使用者可以通過新增視窗小部件來新增自己喜歡的APPwidget ,widget主要用於展現程式快捷入口,下面的螢幕快照展示了音樂應用程式的Widget。

                                    

 本文描述瞭如何使用應用程式部件釋出應用程式提供者。建立您自己的喜歡的AppWidgetHost主機應用程式的小部件。

一 建立AppWidget元件

   1 AppWidgetProviderInfo 

      描述了應用程式的元資料部件,如應用程式部件的佈局,更新頻率,AppWidgetProvider類。這需要在XML中定義。

 2 AppWidgetProvider 

     定義允許的基本方法與應用程式程式設計介面部件,基於廣播事件。通過它我們將會收到廣播 ,用來更新應用程式的widget,用來進行啟用,關閉,刪除的操作。繼承父類是一個BroadcastReceiver,擁有廣播的一切特性,我們可以這麼理解:AppWidgetProvider 是帶有介面的廣播。

3 檢視佈局 View layout

在建立時間。

怎麼建立widget。

 A :在清單中申明widget部件

  首先,宣告應用程式的AndroidManifest AppWidgetProvider類。xml檔案。例如:

<receiver android:name="ExampleAppWidgetProvider" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/example_appwidget_info" />
</receiver>

<receiver>需要android:name屬性,它指定了AppWidgetProvider的具體類。

< intent-filter >元素必須包含一個<action>元素與android:name屬性。這個屬性指定AppWidgetProvider接受系統的ACTION_APPWIDGET_UPDATE廣播。這是唯一的廣播,申明,您必須顯式地宣告。代表此類就是一個widget。AppWidgetManager 自動傳送所有其他應用程式部件廣播此註冊的廣播才能收到,也就是說我們必須要指定識別為widget的action,當然你需要這個AppWidgetProvider接收接她action,ni

 <Mata_data>元素 指定AppWidgetProviderInfo 資源和需要以下屬性:

 android:name——指定Mata_data名稱。使用android.appwidgetb必須確定AppWidgetProviderInfo描述符的資料。

 android:resource——指定AppWidgetProviderInfo資源XML。

二 新增 AppWidgetProviderInfo


      AppWidgetProviderInfo定義了應用程式的小部件的基本屬性,如最小尺寸佈局,其最初的佈局資源,多久更新應用程式的小部件,和(可選)配置活動啟動建立時間。定義AppWidgetProviderInfo在XML資源使用一個< appwidget-provider >標籤,並將其儲存在專案的res / XML /資料夾下。
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:updatePeriodMillis="86400000"
    android:previewImage="@drawable/preview"
    android:initialLayout="@layout/example_appwidget"
    android:configure="com.example.android.ExampleAppWidgetConfigure" 
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen|keyguard"
    android:initialKeyguardLayout="@layout/example_keyguard">
</appwidget-provider>

   < appwidget-provider >屬性:介紹
       minWidth和minHeight屬性指定了widget所佔據的寬和高。主螢幕位置預設的應用程式的widget基於網格,其每個item有一個定義的高度和寬度。如果應用程式部件的最小寬度或高度值不匹配的螢幕的單元寬高,,然後widget的尺寸就會自動縮放到最適合的網格大小。

     注意:如果想讓你的widget跨裝置、移植widget的最小大小不應超過4 x 4單元。


    minResizeWidth和minResizeHeight屬性指定widget的絕對最小大小。Android 3.1中引入此屬性,用來指定應用程式的小部件的大小低於多少將字跡模糊的或無法使用。此屬性允許使用者自定義調整大小,但此widget可能小於預設小部件 由minWidth和minHeight屬性定義的大小。。


    updatePeriodMillis:用來  設定widget更新時間,也就是會所從AppWidgetProvider通過呼叫onUpdate()回撥方法更新資料的時間週期。實際的更新不能保證準時就能更新,為了節省電量和保護電池,規格建議一般一小時更新一次。開發者還可以讓使用者調整頻率configuration-some。
   注意:如果裝置時熄屏狀態下 ,需要更新(如由updatePeriodMillis定義)的時候 ,裝置就會自動喚醒,用來執行更新。如果每小時更新不超過一次,這可能不會導致電池壽命的重大問題。然而,如果您需要更新很頻繁頻繁 ,或者裝置在滅屏下不需要了更新,我們可以給更新操作時設定一個警報提醒,,此時在滅屏狀態下也不會執行更新操作,。為此,設定一個報警意圖 讓AppWidgetProvider接收, 使用alarmmanager。設定報警action 為ELAPSED_REALTIME或RTC,    這樣到了一定的更新時間,此事件就會被停止。然後設定updatePeriodMillis為零(“0”)。


initialLayout 屬性:   定義了Widget的XML佈局。
   配置屬性定義了活動推出當用戶新增應用程式部件,為了他(她)來配置應用程式視窗小部件的屬性。這是無需必須新增的。


previewImage  在Android 3.0中引入的。指定了使用者第一次看見預覽介面的widget是什麼樣子。如果不設定,預設為APP的啟動圖示。這個欄位對應於android:previewImage屬性在AndroidManifest <recevicer>元素。


autoAdvanceViewId 屬性指定應用程式的檢視ID應該auto-advanced部件子檢視部件的主機。這是在Android 3.0中引入的。


resizeMode  在Android 3.1中引入的。指定規則的一個小部件可以調整大小。使用這個屬性使widget 的resizeable-horizontally,垂直,或在兩個軸。使用者touch-hold小部件,以顯示其調整處理,然後拖動水平和/或垂直佈局網格處理改變大小。值resizeMode屬性包括“水平”、“垂直”,“沒有”。設定一個部件resizeable水平方向和垂直方向,
minResizeHeight 屬性指定的最低高度(dps)小部件可以調整大小。大於minHeight 或者如果沒有啟用垂直調整(見resizeMode)。在Android 4.0中引入的。
minResizeWidth屬性指定的最小寬度(dps)小部件可以調整大小。 大於minWidth或 如果沒有啟用水平調整(見resizeMode)。在Android 4.0中引入的。
widgetCategory屬性 決定widget是否可以顯示在主螢幕上, 鎖屏也包括其中。這個屬性的值包括“home_screen”和“power”鍵。顯示一個小部件都需要確保它遵循視窗小部件類的設計指導方針。有關更多資訊,請參見啟用應用程式部件千篇一律。預設值是“home_screen”。在Android 4.2中引入的。
initialKeyguardLayout   在Android 4.2中引入, 用來定義鎖屏應用小部件的佈局。同樣android:initialLayout,可指定一個佈局資源 ,可以立即出現,直到應用程式部件初始化,能夠更新佈局。

     你必須為應用程式在專案的res /佈局/目錄下定義個xml檔案 ,因為widget的佈局需要的RemoteViews的支援。不能隨便定義自定義view,支援的控制元件有:

支援的佈局:

支援的控制元件:

三   AppWidgetProvider 

     此類是widget的控制核心,主要控制新增,刪除,更新等。

    onUpdate()   widget更新時觸發

   onDeleted(Context, int[] )  widget被刪除是觸發

  
    onEnabled(Context),widget可用時觸發

   

    onDisabled(Context)  widget不可用時觸發

   onReceive(Context, Intent)  收到指定的廣播時觸發

 指定某個widget建立以及更新可以重寫onUpdate() ,通過遍歷註冊的appwidget的ID,建立一個RemoteViews來載入佈局,最後呼叫updateAppWidget

來載入介面。  

public class ExampleAppWidgetProvider extends AppWidgetProvider {

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int N = appWidgetIds.length;

        // Perform this loop procedure for each App Widget that belongs to this provider
        for (int i=0; i<N; i++) {
            int appWidgetId = appWidgetIds[i];

            // Create an Intent to launch ExampleActivity
            Intent intent = new Intent(context, ExampleActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

            // Get the layout for the App Widget and attach an on-click listener
            // to the button
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
            views.setOnClickPendingIntent(R.id.button, pendingIntent);

            // Tell the AppWidgetManager to perform an update on the current app widget
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
}

  用於接收指定意圖,處理相關需求,可以重寫onRecrive(),列如我們收到一個toast的動作時,顯示一條Toast
@Override
    public void onReceive(Context context, Intent intent) {
        AppWidgetManager mgr = AppWidgetManager.getInstance(context);
        if (intent.getAction().equals(TOAST_ACTION)) {
            int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
            int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
            Toast.makeText(context, "Touched view " + viewIndex, Toast.LENGTH_SHORT).show();
        }
        super.onReceive(context, intent);
    }

     AppWidgetProvider只是一個方便的類。如果你想接收應用程式部件直接廣播,您可以實現自己的BroadcastReceiver或複寫onReceive()的回撥方法。意圖需要關心如下:

ACTION_APPWIDGET_UPDATE //處理更新

ACTION_APPWIDGET_DELETED // 處理刪除

ACTION_APPWIDGET_ENABLED //可用

ACTION_APPWIDGET_DISABLED//不可用

ACTION_APPWIDGET_OPTIONS_CHANGED // 配置改變

四 RemoteViewsService


1. RemoteViews


    顧名思義,它是一個遠端檢視。App Widget中的檢視,都是通過RemoteViews轉換表現的。 
    在RemoteViews的建構函式中,通過傳入layout檔案的id來獲取 “layout檔案對應的檢視(RemoteViews)”;呼叫RemoteViews中的方法能對layout中的元件進行設定  widgetViews.setOnClickPendingIntent(R.id.widget_btn, calendarIntent);  來設ID對應的Button的點選響應事件)。


ps: 如果使自己的自定義的view顯示在widget上,我們必須在這個類中加上我們自定義的全路徑,前提是我們有許可權修改rom.

    可以將RemoteViews看做是widget資源檢視的所有集合工具管理者。


2 RemoteViewsService

RemoteViewsService子類提供了RemoteViewsFactory用於填充遠端集合檢視。

具體地說,需要執行以下步驟:

子類RemoteViewsService。

 RemoteViewsService是一個遠端的服務介面卡可以請求RemoteViews,管理RemoteViews的服務。 

   在你RemoteViewsService子類,包括一個實現RemoteViewsFactory介面的類。RemoteViewsFactory之間是一個介面卡的介面遠端集合檢視(如列表檢視,顯示資料表格,等等)和底層資料檢視。實現執行RemoteViews物件中每一項的資料集。這個介面是一個介面卡。

    一般,當App Widget 中包含“GridView、ListView、StackView等”集合檢視時,才需要使用RemoteViewsService來進行更新、管理。(集合檢視是指GridView、ListView、StackView等包含子元素的檢視) 
    RemoteViewsService更新“集合檢視”的一般步驟是: 
(01) 通過setRemoteAdapter來設定 “RemoteViews對應RemoteViewsService”。 
(02) 之後在RemoteViewsService中,實現RemoteViewsFactory介面。然後,在RemoteViewsFactory介面中對“集合檢視”的各個子項進行設定(“集合檢視”的各個子項:例如,GridView的每一個格子都是一個子項;ListView中的每一列也是一個子項)。
public class StackWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
    }
}




3  RemoteViewsFactory

     RemoteViewsFactory介面提供了應用程式的小部件的資料項的集合。要做到這一點,它結合了你的應用程式部件條目XML佈局檔案的源資料。這個源的資料可以從一個數據庫到一個簡單的陣列。在StackView小部件示例中,資料來源是一個WidgetItems陣列。RemoteViewsFactory作為介面卡的將資料到設定到RemoteViews上

    最重要的兩個方法你需要實現你的RemoteViewsFactory子類onCreate()和getViewAt()。

   系統呼叫onCreate()首次在建立Factory。在這裡可以設初始化一些資料。例如,StackView小部件示例使用onCreate()來初始化一個WidgetItem物件陣列。

    通過RemoteViewsService中的介紹,我們可以瞭解“RemoteViewsService是通過RemoteViewsFactory來具體管理layout中集合檢視的”,即“RemoteViewsFactory管理集合檢視的實施者”。 
    RemoteViewsFactory是RemoteViewsService中的一個介面。RemoteViewsFactory提供了一系列的方法管理“集合檢視”中的每一項。例如: 
(01)RemoteViews getViewAt(int position) 
      通過getViewAt()來獲取“集合檢視”中的第position項的檢視,檢視是以RemoteViews的物件返回的。
public RemoteViews getViewAt(int position) {
   
    // Construct a remote views item based on the app widget item XML file, 
    // and set the text based on the position.
    RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
    rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text);

    ...
    // Return the remote views object.
    return rv;
}


(02)int getCount() 
      通過getCount()來獲取“集合檢視”中所有子項的總數。

demo:

class StackRemoteViewsFactory implements
RemoteViewsService.RemoteViewsFactory {
    private static final int mCount = 10;
    private List<WidgetItem> mWidgetItems = new ArrayList<WidgetItem>();
    private Context mContext;
    private int mAppWidgetId;

    public StackRemoteViewsFactory(Context context, Intent intent) {
        mContext = context;
        mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
    }

    public void onCreate() {
        // In onCreate() you setup any connections / cursors to your data source. Heavy lifting,
        // for example downloading or creating content etc, should be deferred to onDataSetChanged()
        // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
        for (int i = 0; i < mCount; i++) {
            mWidgetItems.add(new WidgetItem(i + "!"));
        }
        ...
    }
...

五 widget工作原理

   當widget指定其具體的AppWidgetProvider,AppWidgetProvider通過建立RemoteViews來載入檢視,其RemoteViews將會呼叫setRemoteViewsAdapter來設定內部介面卡,此介面卡也將會繼續獲取widget管理器呼叫updateAppWidget()方法,此方法有會用遠端檢視工廠(RemoteViewsFactroy)來初始化資料並呼叫其onDataSetChanged()來通知介面卡更新資料,具體更新那個widget的介面,是通過其GetViewAt將介面更新後並返回,其詳細流程圖如下:


        Widget使用集合的特徵是能夠為使用者圖提供更好的更新資料的檢視內容的方法。例如,考慮Android 3.0 Gmail應用部件,它為使用者提供了一個快照的收件箱。為做到這一點,你需要能夠觸發RemoteViewsFactory和RemoteViews獲取並顯示新的資料。使用AppWidgetManager的notifyAppWidgetViewDataChanged()即可通知更新資料,這個呼叫的結果會繼續回撥到RemoteViewsFactory的 onDataSetChanged()方法,這裡你可以去拿初始化和拿任何的資料。請注意,呼叫onDataSetChanged()方法可以更新資料。在呼叫它之前將必須保證先完成從RemoteViewsFactory獲取元資料或檢視的資料。此外,呼叫在getViewAt()方法。如果這個呼叫需要很長時間去載入檢視(規定RemoteViewsFactory getLoadingView()方法)將會顯示在相應的位置集合檢視,直到它返回真正結果為止。