Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> AppWidget源碼分析(2)---updateAppWidget過程分析.md

AppWidget源碼分析(2)---updateAppWidget過程分析.md

編輯:關於Android編程

前面一篇文章,分析了AppWidgetProvider和RemoteView的源碼,從中我們可以知道它們的實現原理,AppWidgetProvider是一個BroadcastReceiver,所以它是通過廣播接收通知的,收到更新通知後,AppWidgetProvider需要去提供View供遠程進程顯示,而提供的View則是使用RemoteView來代替,通過RemoteView(是一個Parcelable,可跨進程傳輸數據類型)來作為媒介去傳遞給遠程進程。由遠程進程解析RemoteView,然後顯示RemoteView表示的真正的View。在這篇文章將分析這個傳輸過程。

流程

還是先看一下上一篇給出的一個過程圖示:

這裡寫圖片描述

實際上,AppWidgetProvider是通過AppWidgetManager來更新View的,而AppWidgetManager裡面是有一個IAppWidgetService,一看就知道這是一個idle生成的,是一個Binder通信。而服務端的AppWidgetService是AppWidgetServiceImpl,AppWidgetServiceImpl又會通過一個IAppWidgetHost來跨進程通知AppWidgetHZ喎?/kf/ware/vc/" target="_blank" class="keylink">vc3SjrEFwcFdpZGdldEhvc3TE2rK/tcRJQXBwV2lkZ2V0SG9zdMrHQXBwV2lkZ2V0SG9zdC5DYWxsYmFja6Gj1eLR+b7Ntb3By8/Uyr7O0sPHtcRBcHBXaWRnZXS1xL34s8yjqLTzsr+31srHTGF1bmNoZXLTptPDo6mhozwvcD4NCjxwPrfWx+Wz/sO/uPayv7fWysfU2sqyw7S9+LPM1MvQ0LXEttTT2sDtveLV+7j2wfezzMrHt8ezo9PQsO/W+rXEo6xBcHBXaWRnZXRQcm92aWRlcsrH1NrO0sPH19S8urXE06bTw7PM0PK9+LPMtbHW0KOstvhBcHBXaWRnZXRTZXJ2aWNlysfUy9DQ1NpTeXN0ZW1TZXJ2ZXK9+LPMo6xBcHBXaWRnZXRIb3N01PLKx9TL0NDU2s/Uyr5BcHBXaWRnZXS1xL34s8zW0KOsscjI50xhdW5jaGVy06bTw6Oo18DD5qOpoaPV+7j2wfezzM/gtbHT2srHv+fUvcHLyP249r34s8yhozwvcD4NCjxoMiBpZD0="updateappwidgetids過程分析">updateAppWidgetIds過程分析

這是AppWidgetManager的一個接口函數,根據id來更新AppWidget。先把整個更新過程的時序圖拿出來看一下:

這裡寫圖片描述

從我們普通的調用AppWidgetManager的updateAppWidget看起吧:

    appWidgetManager.updateAppWidget(appwidgetids,remoteViews);
這裡進入updateAppWidget方法:

public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
   if (mService == null) {
       return;
   }
   try {
       mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
   }
   catch (RemoteException e) {
       throw new RuntimeException("system server dead?", e);
   }
}

mService是一個IAppWidgetService類型,初始化是在SystemServiceRegistry(6.0才出現的,之前在ContextImpl裡面)裡面:


registerService(Context.APPWIDGET_SERVICE, AppWidgetManager.class,
       new CachedServiceFetcher() {
   @Override
   public AppWidgetManager createService(ContextImpl ctx) {
       IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
       return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b));
   }});

注冊服務的時候獲取APPWIDGET_SERVICE。所以mService.updateAppWidgetIds最後會調用到遠程進程,而IAppWidgetService的實現者是AppWidgetServiceImpl,所以最終會調用AppWidgetServiceImpl的updateAppWidgetIds。

AppWidgetServiceImpl中的updateAppWidgetIds:

@Override
public void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
       RemoteViews views) {
   if (DEBUG) {
       Slog.i(TAG, "updateAppWidgetIds() " + UserHandle.getCallingUserId());
   }

   updateAppWidgetIds(callingPackage, appWidgetIds, views, false);
}

這就是AppWidgetServiceImpl的updateAppWidgetIds方法。程序已經開始進入到AppWidgetServiceImpl所在的進程了,實際上是SystemServer進程。怎麼看出AppWidgetServiceImpl是運行在SystemServer進程呢?AppWidgetService類new了一個AppWidgetServiceImpl,並且注冊到ServiceManager中:


public class AppWidgetService extends SystemService {
   private final AppWidgetServiceImpl mImpl;

   public AppWidgetService(Context context) {
       super(context);
       mImpl = new AppWidgetServiceImpl(context);
   }

   @Override
   public void onStart() {
       publishBinderService(Context.APPWIDGET_SERVICE, mImpl); //注冊mImpl到ServiceManager當中
       AppWidgetBackupBridge.register(mImpl);
   }

   @Override
   public void onBootPhase(int phase) {
       if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
           mImpl.setSafeMode(isSafeMode());
       }
   }
}

而AppWidgetService在SystemServer.java中使用:


private static final String APPWIDGET_SERVICE_CLASS =
       "com.android.server.appwidget.AppWidgetService";

SystemServer是通過反射的方式new一個AppWidgetService對象,然後調用它的start函數。很多service類都是這樣啟動的。

回到updateAppWidgetIds方法,最後它會調用四個參數的updateAppWidgetIds方法:


private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
       RemoteViews views, boolean partially) {
   final int userId = UserHandle.getCallingUserId();

   if (appWidgetIds == null || appWidgetIds.length == 0) {
       return;
   }

   // Make sure the package runs under the caller uid.
   mSecurityPolicy.enforceCallFromPackage(callingPackage);

   final int bitmapMemoryUsage = (views != null) ? views.estimateMemoryUsage() : 0;
   if (bitmapMemoryUsage > mMaxWidgetBitmapMemory) {
       throw new IllegalArgumentException("RemoteViews for widget update exceeds"
               + " maximum bitmap memory usage (used: " + bitmapMemoryUsage
               + ", max: " + mMaxWidgetBitmapMemory + ")");
   }

   synchronized (mLock) {
       ensureGroupStateLoadedLocked(userId);

       final int N = appWidgetIds.length;
       for (int i = 0; i < N; i++) {
           final int appWidgetId = appWidgetIds[i];

           // NOTE: The lookup is enforcing security across users by making
           // sure the caller can only access widgets it hosts or provides.
           Widget widget = lookupWidgetLocked(appWidgetId,
                   Binder.getCallingUid(), callingPackage);

           if (widget != null) {
               updateAppWidgetInstanceLocked(widget, views, partially);
           }
       }
   }
}

該方法首先會做些安全性檢查,以及圖片大小限制檢查。最後會針對每一個appWidgetId,通過lookupWidgetLocked找到其對應的Widget。

Widget是一個帶有很多信息的類,我們看看lookupWidgetLocked方法:

private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) {
   final int N = mWidgets.size();
   for (int i = 0; i < N; i++) {
       Widget widget = mWidgets.get(i);
       if (widget.appWidgetId == appWidgetId
               && mSecurityPolicy.canAccessAppWidget(widget, uid, packageName)) {
           return widget;
       }
   }
   return null;
}

實際上它是從mWidgets找到對應的Widget,先看看Widget類,它是AppWidgetServiceImpl的非靜態內部類:


private static final class Widget {
   int appWidgetId;
   int restoredId;  // tracking & remapping any restored state
   Provider provider; // 對應AppWidgetProvider,裡面有AppWidgetProvider信息。
   RemoteViews views; //表示View的RemoteView
   Bundle options;
   Host host; //顯示的地方

   @Override
   public String toString() {
       return "AppWidgetId{" + appWidgetId + ':' + host + ':' + provider + '}';
   }
}

而是從什麼時候把Widget添加到mWidgets的呢?主要有三個地方,一個是綁定AppWidgetProvider跟id時,初始化時加載AppWidget與對應的host;一個是第一次添加AppWidget到桌面時,給AppWidget分配id的時候;一個是restore AppWidget的時候。我們看看分配id時,添加Widget的代碼:


@Override

public int allocateAppWidgetId(String callingPackage, int hostId) {
   final int userId = UserHandle.getCallingUserId();

   // Make sure the package runs under the caller uid.
   mSecurityPolicy.enforceCallFromPackage(callingPackage);

   synchronized (mLock) {
       ensureGroupStateLoadedLocked(userId);

       if (mNextAppWidgetIds.indexOfKey(userId) < 0) {
           mNextAppWidgetIds.put(userId, AppWidgetManager.INVALID_APPWIDGET_ID + 1);
       }

       final int appWidgetId = incrementAndGetAppWidgetIdLocked(userId); //增量分配一個id,保證不沖突

       // NOTE: The lookup is enforcing security across users by making
       // sure the caller can only access hosts it owns.
       HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage); //得到hostid
       Host host = lookupOrAddHostLocked(id); //根據id獲取host

       Widget widget = new Widget();
       widget.appWidgetId = appWidgetId;
       widget.host = host;

       host.widgets.add(widget);   //把widget添加到host的widgets列表中
       addWidgetLocked(widget); //添加

       saveGroupStateAsync(userId);

       return appWidgetId;
   }
}

實際上這裡還沒有添加對應的provider,所以在Launcher開發的時候,我們需要先調用allocateAppWidgetId方法,然後調用bindAppWidgetId方法綁定id與AppWidgetProvider。而當View有變化的時候,Host需要接收AppWidgetServiceImpl的通知,如何實現的呢?Host(Launcher應用)會跨進程調用AppWidgetServiceImpl的startListening方法,將AppWidgetHost端的Callback服務傳遞給AppWidgetServiceImpl:


// 在AppWidgetHost類當中,AppWidgetHost是Host端的代碼

public void startListening() {
   int[] updatedIds;
   ArrayList updatedViews = new ArrayList();
   try {
       updatedIds = sService.startListening(mCallbacks, mContextOpPackageName, mHostId,
               updatedViews); //把AppWidgetHost端的mCallbacks傳遞給AppWidgetService,mCallbacks是Binder對象。
   }
   catch (RemoteException e) {
       throw new RuntimeException("system server dead?", e);
   }

   final int N = updatedIds.length;
   for (int i = 0; i < N; i++) {
       updateAppWidgetView(updatedIds[i], updatedViews.get(i));
   }
}

在AppWidgetServiceImpl裡面會將mCallbacks保存在對應的Host當中。

從mWidgets裡面找到Widget後,會調用updateAppWidgetInstanceLocked方法來更新Widget。

private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
       boolean isPartialUpdate) {
   if (widget != null && widget.provider != null
           && !widget.provider.zombie && !widget.host.zombie) { // 保證widget有效,並且host也有效

       if (isPartialUpdate && widget.views != null) {
           // For a partial update, we merge the new RemoteViews with the old. 這裡是對於partial update的。
           widget.views.mergeRemoteViews(views);
       } else {
           // For a full update we replace the RemoteViews completely.
           widget.views = views;
       }

       scheduleNotifyUpdateAppWidgetLocked(widget, views);
   }
}
而scheduleNotifyUpdateAppWidgetLocked 則是使用handler發送一個消息給主線程處理:

private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
   if (widget == null || widget.provider == null || widget.provider.zombie
           || widget.host.callbacks == null || widget.host.zombie) {
       return;
   }

   SomeArgs args = SomeArgs.obtain();
   args.arg1 = widget.host;
   args.arg2 = widget.host.callbacks; //callbacks 是host的跨進程調用接口,來自於startListening

   args.arg3 = updateViews;
   args.argi1 = widget.appWidgetId;

   mCallbackHandler.obtainMessage(
           CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,
           args).sendToTarget();
}
使用mCallbackHandler發送一條MSG_NOTIFY_UPDATE_APP_WIDGET的消息,mCallbackHandler是AppWidgetServiceImpl.CallbackHandler的實例。如果處理,具體就到CallbackHandler的handleMessage方法中,就是一個Handler機制,讓代碼運行在主線程:

@Override
public void handleMessage(Message message) {
   switch (message.what) {
       case MSG_NOTIFY_UPDATE_APP_WIDGET: {
           SomeArgs args = (SomeArgs) message.obj;
           Host host = (Host) args.arg1;
           IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
           RemoteViews views = (RemoteViews) args.arg3;
           final int appWidgetId = args.argi1;
           args.recycle();

           handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views);
       } break;

       ...

   }

}
handleNotifyUpdateAppWidget方法:

private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
       int appWidgetId, RemoteViews views) {
   try {
       callbacks.updateAppWidget(appWidgetId, views); //通知AppWidgtHost
   } catch (RemoteException re) {
       synchronized (mLock) {
           Slog.e(TAG, "Widget host dead: " + host.id, re);
           host.callbacks = null;
       }
   }
}

這裡實際上就是調用IAppWidgetHost類型的updateAppWidget,進行跨進程調用。而callbacks就是在AppWidgetServiceImpl的startListening設置的:


@Override
public int[] startListening(IAppWidgetHost callbacks, String callingPackage,
       int hostId, List updatedViews) {
   final int userId = UserHandle.getCallingUserId();

   if (DEBUG) {
       Slog.i(TAG, "startListening() " + userId);
   }

   // Make sure the package runs under the caller uid.
   mSecurityPolicy.enforceCallFromPackage(callingPackage);

   synchronized (mLock) {
       ensureGroupStateLoadedLocked(userId);

       // NOTE: The lookup is enforcing security across users by making
       // sure the caller can only access hosts it owns.
       HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
       Host host = lookupOrAddHostLocked(id);

       host.callbacks = callbacks; //設置callbacks

       updatedViews.clear();

       ArrayList instances = host.widgets;
       int N = instances.size();
       int[] updatedIds = new int[N];
       for (int i = 0; i < N; i++) {
           Widget widget = instances.get(i);
           updatedIds[i] = widget.appWidgetId;
           updatedViews.add(cloneIfLocalBinder(widget.views));
       }

       return updatedIds;
   }
}
最終updateAppWidget的實現代碼是:

static class Callbacks extends IAppWidgetHost.Stub {
   private final WeakReference mWeakHandler;

   public Callbacks(Handler handler) {
       mWeakHandler = new WeakReference<>(handler);
   }

   public void updateAppWidget(int appWidgetId, RemoteViews views) {
       if (isLocalBinder() && views != null) {
           views = views.clone();
       }
       Handler handler = mWeakHandler.get();
       if (handler == null) {
           return;
       }
       Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
       msg.sendToTarget();
   }
}
最後使用Handler發送消息給主線程,然後在handleMessage中有具體的處理程序:

class UpdateHandler extends Handler {
   public UpdateHandler(Looper looper) {
       super(looper);
   }

   public void handleMessage(Message msg) {
       switch (msg.what) {
           case HANDLE_UPDATE: {
               updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
               break;
           }
       }
   }
}
然後調用AppWidgetHost的updateAppWidgetView方法:

void updateAppWidgetView(int appWidgetId, RemoteViews views) {
   AppWidgetHostView v;
   synchronized (mViews) {
       v = mViews.get(appWidgetId);
   }
   if (v != null) {
       v.updateAppWidget(views);
   }
}

根據appWidgetId找到對應的AppWidgetHostView,然後調用AppWidgetHostView的updateAppWidget來根據RemoteView來更新AppWidgetHostView:


/**
* Process a set of {@link RemoteViews} coming in as an update from the
* AppWidget provider. Will animate into these new views as needed
*/
public void updateAppWidget(RemoteViews remoteViews) {

   boolean recycled = false;
   View content = null;
   Exception exception = null;

   // Capture the old view into a bitmap so we can do the crossfade.
    ... 省去old view to bitmap

   if (remoteViews == null) {
       if (mViewMode == VIEW_MODE_DEFAULT) {
           // We've already done this -- nothing to do.
           return;
       }
       content = getDefaultView();  // 默認的View
       mLayoutId = -1;
       mViewMode = VIEW_MODE_DEFAULT;
   } else {
       // Prepare a local reference to the remote Context so we're ready to
       // inflate any requested LayoutParams.
       mRemoteContext = getRemoteContext();
       int layoutId = remoteViews.getLayoutId();

       // If our stale view has been prepared to match active, and the new
       // layout matches, try recycling it
       if (content == null && layoutId == mLayoutId) {
           try {
               remoteViews.reapply(mContext, mView, mOnClickHandler);
               content = mView;
               recycled = true;
               if (LOGD) Log.d(TAG, "was able to recycled existing layout");
           } catch (RuntimeException e) {
               exception = e;
           }
       }

       // Try normal RemoteView inflation
       if (content == null) {
           try {
               content = remoteViews.apply(mContext, this, mOnClickHandler);
               if (LOGD) Log.d(TAG, "had to inflate new layout");
           } catch (RuntimeException e) {
               exception = e;
           }
       }
       mLayoutId = layoutId;
       mViewMode = VIEW_MODE_CONTENT;
   }
   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();  // 在失敗的情況下,會使用ErrorView。
       mViewMode = VIEW_MODE_ERROR;
   }

   if (!recycled) {
       prepareView(content);
       addView(content);
   }

   if (mView != content) {
       removeView(mView);
       mView = content;
   }

   if (CROSSFADE) {
       if (mFadeStartTime < 0) {
           // if there is already an animation in progress, don't do anything --
           // the new view will pop in on top of the old one during the cross fade,
           // and that looks okay.
           mFadeStartTime = SystemClock.uptimeMillis();
           invalidate();
       }
   }
}


代碼有點長,但我覺得值得認真一看,可以從源碼中發現當我們的AppWidget在桌面顯示異常時究竟可能是什麼原因。更新view的整個策略主要就是:

如果remoteView為空,則看是否已經使用了默認視圖,如果已經使用了直接返回,如果沒有則使用默認的視圖。

如果remoteView不為空,則看layoutid是否跟現在已經使用的視圖的layoutid一致,一致則重用舊的視圖,調用remoteView.reapply方法重用視圖,並且設置視圖內容。

如果layoutid跟現使用的視圖不一致,則調用remoteView.apply方法得到新的視圖。

如果上面的步驟都沒有得到視圖,則使用錯誤視圖。

如果新的視圖與舊的視圖不一致,則添加新的視圖,刪除舊的視圖。

其實這裡更新視圖的策略非常簡單,盡量重用已有的視圖。裡面會有三種視圖,一種是我們自己設置的,一種是默認的,一種是有錯誤的情況的。當我們在桌面上看到我們的AppWidget顯示異常時,應該還是有兩種不同的表現的,一種是錯誤情況,一種是是用來額默認的視圖,如果remoteView為null的時候會使用默認視圖,如果是從remoteView中讀取視圖失敗時,則會使用錯誤視圖。

所以看到錯誤視圖時,我們可能需要考慮remoteView裡面的View設置是否合理。如果看到的是默認視圖,我們應該想想是否在AppWidgetProvider中調用了AppWidgetManager.updateAppWidget方法,是否remoteView參數為null?可能有的手機兩種視圖是一樣的。

總結

整個updateAppWidget的分析就到這裡了,AppWidget其他的更新也是一樣的,整個更新過程跨越了三個進程,而RemoteView作為一種View跨進程傳遞的媒介。另外我覺得從AppWidget去理解Binder機制的使用可能也是一個非常好的切入點。因為這部分我們在應用開發當中經常使用,而且是View,能夠看到效果。接下來我想寫一篇關於Binder多線程的理解。我看AppWidget這部分的源碼,也是項目中的AppWidget存在問題,熟悉AppWidget才能解決好問題,預測代碼中可能潛在的問題。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved