天天看點

AppWidget實作機制分析--launcher添加和删除appwidget深入分析

    通過前面的《什麼是桌面插件》的講解,估計你對桌面插件應用有了一定的了解,接着那這篇文章,我們繼續講解在一個桌面上如何建立一個桌面插件執行個體,以及它是如何顯示在我們的桌面上的,如何被删除的,這些都是這篇文章要解答的問題。

    用過Android原生Launcher的都知道,長按桌面空白處會彈出一個對話框,這個對話框就對應CreateShortcut類,手上有原生launcher代碼的同學可以找到對應的代碼看一下,但這不是我重點要關注的,我重點要關注的從你點選對話框中的桌面插件項開始了,你點選這個選項後它就會彈出一個Activity,裡面列出了所有的系統安裝的桌面插件應用,等等,在跳出這個桌面插件應用清單Activity前有一件重要的事情值得我們關注,那就是,配置設定桌面插件執行個體id,為什麼還沒有選擇桌面插件應用就要配置設定桌面插件執行個體id了呢?因為一個桌面插件執行個體它不僅與桌面插件應用有關,而且與一個桌面有關,具體點說與一個AppWidgetHost有關系,AppWidgetHost我稱之為桌面插件宿主,每一個桌面程式,或者一個app,它上面能容納桌面插件執行個體首先得有一個桌面插件宿主對象,這個AppWidgetHost對象怎麼來的我在後續文章中再繼續作介紹。回到配置設定桌面插件執行個體id配置設定,桌面插件宿主通過函數allocateAppWidgetId來配置設定一個與自身有綁定關系的桌面插件執行個體Id,實際配置設定是通過AppWidgetService這個服務來實作的:

public int allocateAppWidgetId(String packageName, int hostId);

packageName是桌面插件宿主應用包名,hostId是代表桌面插件宿主對象的一個id,一旦成功配置設定一個桌面插件執行個體id後,這個桌面插件執行個體id就與hostId代表的桌面插件宿主對象綁定了,但這時這個桌面插件執行個體id隻是一個數字,需要綁定一個具體桌面插件應用執行個體它才有意義,而彈出的一個桌面插件應用清單Activity就是讓你選擇這個執行個體的桌面插件應用。

    桌面插件應用清單Activity名字是AppWidgetPickActivity,它在源生應用settings中,AppWidgetPickActivity中的桌面插件應用清單通過AppWidgetService擷取,實際内容就是前面一篇文章中提到的mInstalledProviders。當我們選中一個桌面插件應用它會執行什麼動作呢?看代碼:

//綁定一個桌面元件
                mAppWidgetManager.bindAppWidgetId(mAppWidgetId, intent.getComponent());
                result = RESULT_OK;
           

對這段代碼進行說明,mAppWidgetManager實質就是一個AppWidgetService的代理,mAppWidgetId是前面桌面插件宿主配置設定的桌面插件執行個體id,intent.getComponent()是一個桌面插件應用,它正真進行的桌面插件執行個體id與桌面插件應用綁定并生成一個桌面插件執行個體的程式在AppWidgetService中進行:

/**
     * 綁定一個appWidgetId到一個provider
     * @param appWidgetId 桌面插件執行個體id
     * @param provider    桌面插件應用<包名,provider類名>
     */
    public void bindAppWidgetId(int appWidgetId, ComponentName provider) {
        mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET,
                "bindGagetId appWidgetId=" + appWidgetId + " provider=" + provider);
        synchronized (mAppWidgetIds) {
        	//根據桌面插件執行個體id找到桌面插件AppWidgetId對象,AppWidgetId對象在配置設定桌面插件執行個體id時生成的
            AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
            
            if (id == null) {
                throw new IllegalArgumentException("bad appWidgetId");
            }
            //id.provider未設定目的是保證該桌面插件執行個體id是未綁定到桌面插件應用的
            if (id.provider != null) {
                throw new IllegalArgumentException("appWidgetId " + appWidgetId + " already bound to "
                        + id.provider.info.provider);
            }
            //根據桌面插件應用包名,provider類名,找到桌面插件應用provider執行個體,一個Provider對象代表一個桌面插件應用
            Provider p = lookupProviderLocked(provider);
            if (p == null) {
                throw new IllegalArgumentException("not a appwidget provider: " + provider);
            }
            if (p.zombie) {
                throw new IllegalArgumentException("can't bind to a 3rd party provider in"
                        + " safe mode: " + provider);
            }
            //桌面插件執行個體與桌面插件應用互相關聯
            id.provider = p;
            p.instances.add(id);
            int instancesSize = p.instances.size();
            if (instancesSize == 1) {
            	//第一次添加系統插件執行個體,發送ACTION_APPWIDGET_ENABLED消息,讓桌面插件應用做好準備
                sendEnableIntentLocked(p);
            }

            // send an update now -- We need this update now, and just for this appWidgetId.
            // It's less critical when the next one happens, so when we schdule the next one,
            // we add updatePeriodMillis to its start time.  That time will have some slop,
            // but that's okay.
            //發送更新系統插件消息ACTION_APPWIDGET_UPDATE
            sendUpdateIntentLocked(p, new int[] { appWidgetId });
            //注冊定時更新消息ACTION_APPWIDGET_UPDATE廣播,如果沒有注冊的話.
            registerForBroadcastsLocked(p, getAppWidgetIds(p));
            //儲存資料到持久化檔案
            saveStateLocked();
        }
    }
           

這個函數我全拷過來了,因為它确實很重要,讀懂它,估計你以前很多的關于桌面插件的疑問都會得以解答,同學如果你有時間的話建議你對函數中調用到的函數比如lookupAppWidgetIdLocked,registerForBroadcastsLocked,saveStateLocked等都深入去閱讀,我這裡不深入進去,隻是作一個解讀幫助你了解桌面插件的機制:lookupAppWidgetIdLocked,lookupProviderLocked分别是根據桌面插件執行個體id找到桌面插件AppWidgetId對象,根據桌面插件應用包名,provider類名,找到桌面插件應用provider執行個體,然後作一些驗證,保證桌面插件執行個體id是已經配置設定好的,并且沒有被綁定到一個桌面插件應用上的,保證桌面插件應用是存在的。AppWidgetId對象就代表這一個桌面插件執行個體,provider對象就代表一個桌面插件應用,id.provider = p; p.instances.add(id);把兩者進行關聯,就建立了兩者的綁定關系,其實到這裡,桌面插件執行個體id與桌面插件應用綁定就已經完成了,那接下來的代碼是需要做什麼呢? sendEnableIntentLocked實際就是發送一個ACTION_APPWIDGET_ENABLED廣播消息,當該桌面插件執行個體是桌面插件應用的第一個桌面插件執行個體時,就會發送該消息,對應會執行AppWidgetProvider onEnabled方法,提示桌面插件應用你已經啟動了,做好相關準備。接下來執行sendUpdateIntentLocked方法,就是發送ACTION_APPWIDGET_UPDATE廣播消息到桌面插件應用,對應AppWidgetProvider onUpdate方法就會執行,是以寫過桌面插件應用的同學你可能知道,每次添加一個桌面插件執行個體到桌面,onUpdate方法就會被調用,而registerForBroadcastsLocked方法就與桌面插件應用配置項android:updatePeriodMillis:指定桌面元件的更新周期有關了,它指定系統周期發送ACTION_APPWIDGET_UPDATE廣播消息到桌面插件應用,注意是系統自動的,它的目的也不是更新桌面插件執行個體,我更傾向于是系統讓桌面插件應用保持活動狀态而不至于讓系統登出這個桌面插件應用,因為這個桌面插件應用一旦被登出,那那些綁定該桌面插件應用的桌面插件執行個體就再也不會更新了。最後一個saveStateLocked其實就是把AppWidgetService的資料結構儲存到檔案中,防止手機重新開機這些資料沒有了,那樣的話你一重新開機手機,你手機上的那些桌面插件執行個體就都不見了,又得重新添加一遍,這樣的話估計就沒人用這玩意了。

    似乎已經說了不少,不過現在還隻是停留在資料結構層面的添加桌面插件執行個體,按MVC模型我們現在還是在M階段,要到V還得回到桌面來,因為你要了解,所有的桌面插件執行個體都是再桌面程式中給畫出來的。再回到添加桌面插件執行個體操作場景中來,選擇一個桌面插件後AppWidgetPickActivity會傳回到Launcher,傳回碼是REQUEST_PICK_APPWIDGET

case REQUEST_PICK_APPWIDGET:
                    addAppWidget(data);
                    break;
                case REQUEST_CREATE_APPWIDGET:
                    completeAddAppWidget(data, mAddItemCellInfo);
                    break;
           

這個是Launcher.onActivityResult函數中的一段代碼,我們可以猜想得到在AppWidgetPickActivity完成插件執行個體id與插件應用綁定後接下來這段代碼要做的就是要建立真正的插件執行個體的視圖了,不過,按我們正常的了解,AppWidgetPickActivity傳回REQUEST_PICK_APPWIDGET Laucher進行響應,建立插件執行個體的視圖就ok了,那還有一個activity傳回事件REQUEST_CREATE_APPWIDGET它的目的又是什麼呢?不知道你有沒有用過這種時鐘插件,當你選中一個時鐘插件到你的桌面時,它并不會馬上在你的桌面上建立一個時鐘,而是先跳出一個配置界面,要你選擇你目前所在的時區,以便時間能夠正确的顯示出來,而addAppWidget(data)其目的就是處理這個配置頁面的,代碼比較簡單,可以貼出來看一下:

void addAppWidget(Intent data) {
        // 擷取插件的配置資訊AppWidgetProviderInfo
        int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
        AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);

        //若桌面元件配置屬性不為空,啟動桌面元件配置activity
        if (appWidget.configure != null) {
            // Launch over to configure widget, if needed
            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
            intent.setComponent(appWidget.configure);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);

            startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
        } else {
            // 否則直接添加該桌面插件執行個體
            onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data);
        }
    }
           

appWidget.configure實際就對應我前一篇文章講到的一個桌面插件應用android:configure,這個函數所做的就是當有配置android:configure時打開配置Acitivity,否則就直接添加該桌面插件執行個體,也就是回到onActivityResult的REQUEST_CREATE_APPWIDGET,代碼中可以明顯的看出這一點,不管有沒有配置界面,它最終都會調用completeAddAppWidget,它才是最終添加桌面插件視圖的實作代碼,為了盡量能講清楚,我還是把代碼給貼出來:

/**
     * Add a widget to the workspace.
     * 添加一個桌面元件執行個體到桌面上
     *
     * @param data The intent describing the appWidgetId.
     * 			桌面插件執行個體相關資訊
     * @param cellInfo The position on screen where to create the widget.
     * 			添加桌面插件執行個體的位置資訊
     */
    private void completeAddAppWidget(Intent data, CellLayout.CellInfo cellInfo) {
        Bundle extras = data.getExtras();
        //擷取桌面插件執行個體id和配置資訊
        int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
        if (LOGD) Log.d(TAG, "dumping extras content=" + extras.toString());
        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);

        //計算該桌面插件執行個體添加的位置并驗證該位置是否有效,若位置已經被占用,删除桌面元件執行個體
        CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen);
        int[] spans = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight);
        final int[] xy = mCellCoordinates;
        if (!findSlot(cellInfo, xy, spans[0], spans[1])) {
            if (appWidgetId != -1) mAppWidgetHost.deleteAppWidgetId(appWidgetId);
            return;
        }

        //建立一個LauncherAppWidgetInfo對象并儲存到資料庫中
        LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
        launcherInfo.spanX = spans[0];
        launcherInfo.spanY = spans[1];
        LauncherModel.addItemToDatabase(this, launcherInfo,
                LauncherSettings.Favorites.CONTAINER_DESKTOP,
                mWorkspace.getCurrentScreen(), xy[0], xy[1], false);

        if (!mRestoring) {
            //添加一個桌面插件項
            mDesktopItems.add(launcherInfo);

            // Perform actual inflation because we're live
            //建立桌面插件執行個體view
            launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);

            launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
            launcherInfo.hostView.setTag(launcherInfo);
            //添加該桌面插件執行個體view到桌面
            mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1],
                    launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
        }
    }
           

整個函數的函數我在注釋中已經大緻給講清楚了,主要做的事情就是根據插件執行個體資訊以及要添加到桌面的位置資訊建立LauncherAppWidgetInfo對象并儲存到資料庫中,然後調用桌面插件宿主mAppWidgetHost建立桌面插件執行個體view(該桌面插件執行個體在launcher中的view對象,我稱之為桌面插件執行個體view),最後把該桌面插件執行個體view添加到整個桌面的視圖中,這就完成了桌面插件執行個體view的添加。不過在這裡有兩處我要展開講一下,一個是建立桌面插件執行個體view的過程,另一個是添加桌面插件執行個體view到桌面的過程。

    桌面插件執行個體view的建立是通過調用桌面插件宿主mAppWidgetHost的createView成員函數來實作的,它有三個參數,我們關注後面兩個,一個是appWidgetId,它表示一個桌面插件執行個體id,一個是appWidgetInfo它裡面包含桌面插件應用的配置資訊,我們看函數實作:

public final AppWidgetHostView createView(Context context, int appWidgetId,
            AppWidgetProviderInfo appWidget) {
    	//建立一個AppWidgetHostView
        AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
        //設定桌面元件執行個體id和配置資訊
        view.setAppWidget(appWidgetId, appWidget);
        //緩存appWidgetId 與桌面插件執行個體映射表
        synchronized (mViews) {
            mViews.put(appWidgetId, view);
        }
        //通過桌面插件服務代理從桌面插件服務中擷取該appWidgetId的RemoteViews對象
        RemoteViews views;
        try {
            views = sService.getAppWidgetViews(appWidgetId);
        } catch (RemoteException e) {
            throw new RuntimeException("system server dead?", e);
        }
        //把RemoteViews對象的操作應用到該系統插件執行個體view上
        view.updateAppWidget(views);
        return view;
    }
           

第一步是通過接口方法onCreateView來建立AppWidgeHostView對象,LauncherAppWidgetHost類中實作了該方法,如若需要,你也可繼承該方法,做一些個性化的定制;

第二步把appWidgetId 桌面插件應用配置資訊對象設定到該AppWidgeHostView對象中;

第三步添加appWidgetId與桌面插件執行個體view的映射;

第四步根據appWidgetId擷取一個RemoteViews對象,它是從系統桌面插件服務中擷取的,這裡需要作一些說明,這個RemoteViews對象由何而來。回想一下前面講到的bindAppWidgetId過程,每綁定一個新的桌面插件執行個體就會發送ACTION_APPWIDGET_UPDATE廣播消息到桌面插件應用 ,對應AppWidgetProvider onUpdate方法就會執行,寫過桌面插件應用的同學應該知道,這個時候就需要建立并發送RemoteViews對象

//建構RemoteViews對象來對桌面插件進行更新
		RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider);
		//更新文本内容,指定布局的元件
		views.setTextViewText(R.id.appwidget_text, titlePrefix+mUpdateCount);
		//将RemoteViews的更新傳入AppWidget進行更新
		appWidgetManager.updateAppWidget(appWidgetId, views);
           

appWidgetMananger為桌面插件服務的代理,調用updateAppWidget方法并吧RemoteViews對象傳到了桌面插件服務,這個時候桌面插件執行個體view還沒有建立,那這個 RemoteViews對象是儲存在哪呢?它實際儲存在桌面插件服務中mAppWidgetIds清單中appWidgetId對應的桌面插件執行個體下面有一個views成員,我們可以看一下AppWidgetId類的結構:

/**一個系統插件執行個體*/
    static class AppWidgetId {
        int appWidgetId;
        Provider provider;
        RemoteViews views;
        Host host;
    }
           

該步驟就是通過 appWidgetId從桌面插件服務中找到 mAppWidgetIds清單中對應的AppWidgetId對象,擷取對象中的views成員傳回。

第五步就是通過RemoteViews對象來建構AppWidgeHostView對象。前面步驟建立的AppWidgeHostView對象隻是一個沒有任何内容的view,如何根據桌面插件應用中的布局檔案建立與之一緻的view對象就是在這一步實作的,通過調用AppWidgeHostView的updateAppWidget方法實作的,那下面我們就深入該函數一探究竟:

public void updateAppWidget(RemoteViews remoteViews) {
        ...        
        if (remoteViews == null) {
            ...
        } else {
            
            //擷取桌面元件應用上下文(克隆對象)
            mRemoteContext = getRemoteContext(remoteViews);
            //擷取桌面插件布局id
            int layoutId = remoteViews.getLayoutId();

            //當系統插件已經建立了,進入下面邏輯進行處理
            if (content == null && layoutId == mLayoutId) {               
                    //更新桌面元件
                    remoteViews.reapply(mContext, mView);
            
            }
            //首次update content為空,通過remoteViews建構content
            if (content == null) {
                //建立系統插件view
                content = remoteViews.apply(mContext, this);
            }
            //設定布局id
            mLayoutId = layoutId;
            mViewMode = VIEW_MODE_CONTENT;
        }
        
	//建立view失敗,設定一個error view
        if (content == null) {
            if (mViewMode == VIEW_MODE_ERROR) {
                // We've already done this -- nothing to do.
                return ;
            }
            Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception);
            content = getErrorView();
            mViewMode = VIEW_MODE_ERROR;
        }
        
        if (!recycled) {
            //調整系統插件view的布局參數
            prepareView(content);
            //添加系統插件view到hostview
            addView(content);
        }
        //設定mview為新的系統插件view
        if (mView != content) {
            //删除舊的系統插件view
            removeView(mView);
            //設定mView為新的系統插件view
            mView = content;
        }       
    }
           

這個過程首先是通過remoteviews擷取桌面插件應用的上下文,然後擷取桌面插件的布局檔案id,這樣就可以擷取該桌面插件執行個體建立view所需要的布局以及圖檔,檔案資源了;然後調用remoteViews.apply方法來建構一個view,若建構失敗擷取一個預設的錯誤view作為桌面插件執行個體的view,主要流程就是這樣的,實作view的構造主要還是在apply方法中:

public View apply(Context context, ViewGroup parent) {
        View result;

        //建立一個系統插件應用的上下文
        Context c = prepareContext(context);

        //擷取系統插件的LayoutInflater
        LayoutInflater inflater = (LayoutInflater)
                c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        //複制inflater
        inflater = inflater.cloneInContext(c);
        inflater.setFilter(this);
        
        //建立一個系統插件的view
        result = inflater.inflate(mLayoutId, parent, false);

        //應用remoteview中所有的更新action
        performApply(result);

        return result;
    }
           

函數比較簡短,我也作了詳細的注釋,這裡再梳理一下,context函數參數是AppWidgetHostView所在桌面的上下文,通過prepareContext函數建立了一個系統插件應用對應的上下檔案c,這樣c就可以擷取系統插件應用的資源檔案和布局檔案,然後擷取系統布局器服務,通過布局器LayoutInflater以及桌面插件應用上下文對象c來執行個體化出來一個view,該view的父view正是即将要添加到桌面的AppWidgetHostView執行個體,AppWidgetHostView執行個體是真正系統插件view的容器,最後,有些通過代碼設定的action通過調用performApply方法加以應用,例如設定系統插件textview的不同文字,performApply方法實作的原理就是反射調用action中定義的類方法,達到對系統插件view内容進行控制的目的。

    完成建立AppWidgetHostView對象後再回到方法completeAddAppWidget,通過調用mWorkspace.addInCurrentScreen方法把桌面插件view執行個體添加到桌面的,xy[0], xy[1],

                    launcherInfo.spanX, launcherInfo.spanY分别指定桌面插件在桌面左上角的起始位置以及橫向和縱向占據的位置範圍,這些位置是桌面劃分的一個一個空格,一般一個app占據一個1*1的空格,當然,不同桌面可以自己具體實作。

     到這裡,桌面launcher添加一個桌面插件執行個體算是完成了,接着再看删除桌面插件執行個體,這個過程在手機上直覺的展現就是拖動一個桌面插件執行個體到桌面垃圾箱放手,然後桌面插件執行個體就被删除了。launcher中有一個DeleteZone類,該類的對象就是我們所看到了桌面上的垃圾箱,它可以接收拖放對象,是以它實作了DropTarget接口,然後在onDrop方法中實作對拖放到垃圾箱的對象的處理,這其中就包括桌面插件執行個體的處理:

if (item instanceof UserFolderInfo) {
            final UserFolderInfo userFolderInfo = (UserFolderInfo)item;
            LauncherModel.deleteUserFolderContentsFromDatabase(mLauncher, userFolderInfo);
            mLauncher.removeFolder(userFolderInfo);
        } else if (item instanceof LauncherAppWidgetInfo) {
            final LauncherAppWidgetInfo launcherAppWidgetInfo = (LauncherAppWidgetInfo) item;
            final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost();
            if (appWidgetHost != null) {
                final int appWidgetId = launcherAppWidgetInfo.appWidgetId;
                // Deleting an app widget ID is a void call but writes to disk before returning
                // to the caller...
                new Thread("deleteAppWidgetId") {
                    public void run() {
                        appWidgetHost.deleteAppWidgetId(launcherAppWidgetInfo.appWidgetId);
                    }
                }.start();
            }
        }
        LauncherModel.deleteItemFromDatabase(mLauncher, item);
           

    這裡 把關鍵代碼标記成了紅色,判斷拖放對象類型為LauncherAppWidgetInfo後,就把它當作桌面插件處理,處理流程大緻是擷取桌面插件宿主appWidgetHost對象,然後根據桌面插件執行個體的appWidgetId來删除桌面插件執行個體,最後對桌面資料庫中的資料進行删除處理,這其中删除桌面插件執行個體主要實作是在appWidgetHost.deleteAppWidgetId函數中,appWidgetHost則是通過AppWidgetService服務來遠端實作該過程的,看一下AppWidgetService中該函數的實作:

/**删除系統插件執行個體appWidgetId,清理該執行個體的所有資料結構,并把狀态資料儲存到檔案*/
    public void deleteAppWidgetId(int appWidgetId) {
        synchronized (mAppWidgetIds) {
            AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
            if (id != null) {
                deleteAppWidgetLocked(id);//清理所有該系統插件執行個體的資料
                saveStateLocked(); //把狀态資料持久化到/data/system/appwidgets.xml
            }
        }
    }
           
/**删除一個系統插件執行個體*/
    void deleteAppWidgetLocked(AppWidgetId id) {
        Host host = id.host;
        host.instances.remove(id);
        pruneHostLocked(host);

        mAppWidgetIds.remove(id);

        Provider p = id.provider;
        if (p != null) {
            p.instances.remove(id);
            if (!p.zombie) {
                // send the broacast saying that this appWidgetId has been deleted
                Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
                intent.setComponent(p.info.provider);
                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
                mContext.sendBroadcast(intent);//發送删除系統插件廣播
                if (p.instances.size() == 0) {//若系統插件個數為0 發送DISABLED廣播
                    // cancel the future updates
                    cancelBroadcasts(p);//登出定時重新整理系統插件廣播

                    // send the broacast saying that the provider is not in use any more
                    intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
                    intent.setComponent(p.info.provider);
                    mContext.sendBroadcast(intent);
                }
            }
        }
    }
           

整個删除過程可以分為兩個部分,一是清理桌面插件服務中的相關記憶體資料結構,另一個是把修改後的資料持久化到appWidgets.xml檔案中。對于記憶體資料結構的修改,主要包括所有桌面插件執行個體清單mAppWidgetIds中資料的清理,以及該桌面插件應用下桌面插件應用執行個體對象清單p.instances的資料清理工作,同時發送ACTION_APPWIDGET_DELETED廣播通知桌面插件應用進行處理,AppWidgetProvider中onDelete方法将對該廣播進行響應。最後,若該桌面插件應用的所有桌面插件執行個體都删除時,需要發送ACTION_APPWIDGET_DISABLED廣播來通知桌面插件應用進行相應處理,AppWidgetProvider中onDisabled方法将對該廣播進行響應,同時要登出原來在系統中注冊的定時更新廣播,避免定時更新廣播激活桌面插件應用。

     至此,桌面添加和删除appwidget過程就算是分析完成了,希望對你有所幫助。