launcher widget 添加过程分析2

 

上篇讲到了Launcher中自定义了两个类:

LauncherAppWidgetHostView: 扩展了AppWidgetHostView,实现了对长按事件的处理

LauncherAppWidgetHost: 扩展了AppWidgetHost,这里只是重载了onCreateView,创建LauncherAppWidgetHostView的实例

24  /**
25   * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView}
26   * which correctly captures all long-press events. This ensures that users can
27   * always pick up and move widgets.
28   */
29  public class LauncherAppWidgetHost extends AppWidgetHost {
30      public LauncherAppWidgetHost(Context context, int hostId) {
31          super(context, hostId);
32      }
33      
34      @Override
35      protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
36              AppWidgetProviderInfo appWidget) {
37          return new LauncherAppWidgetHostView(context);
38      }
39  }

 

首先在Launcher.java中定义了如下两个变量

174      private AppWidgetManager mAppWidgetManager;
175      private LauncherAppWidgetHost mAppWidgetHost;

在onCreate函数中初始化,

224          mAppWidgetManager = AppWidgetManager.getInstance(this);
225          mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
226          mAppWidgetHost.startListening();
上述代码,获取mAppWidgetManager的实例,并创建LauncherAppWidgetHost,以及监听
 
AppWidgetManager只是应用程序与底层Service之间的一个桥梁,是Android中标准的aidl实现方式
应用程序通过AppWidgetManager调用Service中的方法
frameworks/base /  core  /  java  /  android  /  appwidget  /  AppWidgetManager.java
35  /**
36   * Updates AppWidget state; gets information about installed AppWidget providers and other
37   * AppWidget related state.
38   */
39  public class AppWidgetManager {
197    static WeakHashMap<Context, WeakReference<AppWidgetManager>> sManagerCache = new WeakHashMap();
198      static IAppWidgetService sService;
 
204      /**
205       * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context
206       * Context} object.
207       */
208      public static AppWidgetManager getInstance(Context context) {
209          synchronized (sManagerCache) {
210              if (sService == null) {
211                  IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
212                  sService = IAppWidgetService.Stub.asInterface(b);
213              }
214
215              WeakReference<AppWidgetManager> ref = sManagerCache.get(context);
216              AppWidgetManager result = null;
217              if (ref != null) {
218                  result = ref.get();
219              }
220              if (result == null) {
221                  result = new AppWidgetManager(context);
222                  sManagerCache.put(context, new WeakReference(result));
223              }
224              return result;
225          }
226      }
227
228      private AppWidgetManager(Context context) {
229          mContext = context;
230          mDisplayMetrics = context.getResources().getDisplayMetrics();
231      }

以上代码是设计模式中标准的单例模式

 

frameworks/base/ core / java / android / appwidget / AppWidgetHost.java

90      public AppWidgetHost(Context context, int hostId) {
91          mContext = context;
92          mHostId = hostId;
93          mHandler = new UpdateHandler(context.getMainLooper());
94          synchronized (sServiceLock) {
95              if (sService == null) {
96                  IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
97                  sService = IAppWidgetService.Stub.asInterface(b);
98              }
99          }
100      }

可以看到AppWidgetHost有自己的HostId,Handler,和sService

93         mHandler = new UpdateHandler(context.getMainLooper());

这是啥用法呢?

参数为Looper,即消息处理放到此Looper的MessageQueue中,有哪些消息呢?

40      static final int HANDLE_UPDATE = 1;
41      static final int HANDLE_PROVIDER_CHANGED = 2;
 
48
49      class Callbacks extends IAppWidgetHost.Stub {
50          public void updateAppWidget(int appWidgetId, RemoteViews views) {
51              Message msg = mHandler.obtainMessage(HANDLE_UPDATE);
52              msg.arg1 = appWidgetId;
53              msg.obj = views;
54              msg.sendToTarget();
55          }
56
57          public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
58              Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED);
59              msg.arg1 = appWidgetId;
60              msg.obj = info;
61              msg.sendToTarget();
62          }
63      }
64
65      class UpdateHandler extends Handler {
66          public UpdateHandler(Looper looper) {
67              super(looper);
68          }
69          
70          public void handleMessage(Message msg) {
71              switch (msg.what) {
72                  case HANDLE_UPDATE: {
73                      updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
74                      break;
75                  }
76                  case HANDLE_PROVIDER_CHANGED: {
77                      onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
78                      break;
79                  }
80              }
81          }
82      }

通过以上可以看到主要有两中类型的消息,HANDLE_UPDATE和HANDLE_PROVIDER_CHANGED

处理即通过自身定义的方法

231      /**
232       * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
233       */
234      protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
235          AppWidgetHostView v;
236          synchronized (mViews) {
237              v = mViews.get(appWidgetId);
238          }
239          if (v != null) {
240              v.updateAppWidget(null, AppWidgetHostView.UPDATE_FLAGS_RESET);
241          }
242      }
243
244      void updateAppWidgetView(int appWidgetId, RemoteViews views) {
245          AppWidgetHostView v;
246          synchronized (mViews) {
247              v = mViews.get(appWidgetId);
248          }
249          if (v != null) {
250              v.updateAppWidget(views, 0);
251          }
252      }

 

那么此消息是何时由谁发送的呢?

从以上的代码中看到AppWidgetHost定义了内部类Callback,扩展了类IAppWidgetHost.Stub,类Callback中负责发送以上消息

Launcher中会调用本类中的如下方法,

102      /**
103       * Start receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity
104       * becomes visible, i.e. from onStart() in your Activity.
105       */
106      public void startListening() {
107          int[] updatedIds;
108          ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
109          
110          try {
111              if (mPackageName == null) {
112                  mPackageName = mContext.getPackageName();
113              }
114              updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews);
115          }
116          catch (RemoteException e) {
117              throw new RuntimeException("system server dead?", e);
118          }
119
120          final int N = updatedIds.length;
121          for (int i=0; i<N; i++) {
122              updateAppWidgetView(updatedIds[i], updatedViews.get(i));
123          }
124      }
最终调用AppWidgetService中的方法startListening方法,并把mCallbacks传过去,由Service负责发送消息
 
Launcher中添加Widget
在Launcher中添加widget,有两种途径,通过Menu或者长按桌面的空白区域,都会弹出Dialog,让用户选择添加
如下代码是当用户选择
1999          /**
2000           * Handle the action clicked in the "Add to home" dialog.
2001           */
2002          public void onClick(DialogInterface dialog, int which) {
2003              Resources res = getResources();
2004              cleanup();
2005
2006              switch (which) {
2007                  case AddAdapter.ITEM_SHORTCUT: {
2008                      // Insert extra item to handle picking application
2009                      pickShortcut();
2010                      break;
2011                  }
2012
2013                  case AddAdapter.ITEM_APPWIDGET: {
2014                      int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId();
2015
2016                      Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
2017                      pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2018                      // start the pick activity
2019                      startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
2020                      break;
2021                  }

当用户在Dialog中选择AddAdapter.ITEM_APPWIDGET时,首先会通过AppWidgethost分配一个appWidgetId,并最终调到AppWidgetService中去

同时发送Intent,其中保存有刚刚分配的appWidgetId,AppWidgetManager.EXTRA_APPWIDGET_ID

139      /**
140       * Get a appWidgetId for a host in the calling process.
141       *
142       * @return a appWidgetId
143       */
144      public int allocateAppWidgetId() {
145          try {
146              if (mPackageName == null) {
147                  mPackageName = mContext.getPackageName();
148              }
149              return sService.allocateAppWidgetId(mPackageName, mHostId);
150          }
151          catch (RemoteException e) {
152              throw new RuntimeException("system server dead?", e);
153          }
154      }
 
2016                      Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
2017                      pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2018                      // start the pick activity
2019                      startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
这段代码之后,代码将会怎么执行呢,根据Log信息,可以看到代码将会执行到Setting应用中
packages/apps/Settings/   src  /   com  /   android  /   settings  /   AppWidgetPickActivity.java
此类将会通过AppWidgetService获取到当前系统已经安装的Widget,并显示出来
78      /**
79       * Create list entries for any custom widgets requested through
80       * {@link AppWidgetManager#EXTRA_CUSTOM_INFO}.
81       */
82      void putCustomAppWidgets(List<PickAdapter.Item> items) {
83          final Bundle extras = getIntent().getExtras();
84          
85          // get and validate the extras they gave us
86          ArrayList<AppWidgetProviderInfo> customInfo = null;
87          ArrayList<Bundle> customExtras = null;
88          try_custom_items: {
89              customInfo = extras.getParcelableArrayList(AppWidgetManager.EXTRA_CUSTOM_INFO);
90              if (customInfo == null || customInfo.size() == 0) {
91                  Log.i(TAG, "EXTRA_CUSTOM_INFO not present.");
92                  break try_custom_items;
93              }
94
95              int customInfoSize = customInfo.size();
96              for (int i=0; i<customInfoSize; i++) {
97                  Parcelable p = customInfo.get(i);
98                  if (p == null || !(p instanceof AppWidgetProviderInfo)) {
99                      customInfo = null;
100                      Log.e(TAG, "error using EXTRA_CUSTOM_INFO index=" + i);
101                      break try_custom_items;
102                  }
103              }
104
105              customExtras = extras.getParcelableArrayList(AppWidgetManager.EXTRA_CUSTOM_EXTRAS);
106              if (customExtras == null) {
107                  customInfo = null;
108                  Log.e(TAG, "EXTRA_CUSTOM_INFO without EXTRA_CUSTOM_EXTRAS");
109                  break try_custom_items;
110              }
111
112              int customExtrasSize = customExtras.size();
113              if (customInfoSize != customExtrasSize) {
114                  Log.e(TAG, "list size mismatch: EXTRA_CUSTOM_INFO: " + customInfoSize
115                          + " EXTRA_CUSTOM_EXTRAS: " + customExtrasSize);
116                  break try_custom_items;
117              }
118
119
120              for (int i=0; i<customExtrasSize; i++) {
121                  Parcelable p = customExtras.get(i);
122                  if (p == null || !(p instanceof Bundle)) {
123                      customInfo = null;
124                      customExtras = null;
125                      Log.e(TAG, "error using EXTRA_CUSTOM_EXTRAS index=" + i);
126                      break try_custom_items;
127                  }
128              }
129          }
130
131          if (LOGD) Log.d(TAG, "Using " + customInfo.size() + " custom items");
132          putAppWidgetItems(customInfo, customExtras, items);
133      }
从上述代码中可以看到,可以放置用户自己定义的伪Widget
关于伪widget,个人有如下想法:
早期Android版本中的Google Search Bar就属于伪Widget,其实就是把widget做到Launcher中,但是用户体验与真widget并没有区别,个人猜想HTC的sense就是这样实现的。
优点:是不需要进程间的通信,效率将会更高,并且也可以规避点Widget开发的种种限制
缺点:导致Launcher代码庞大,不易于维护
 
用户选择完之后,代码如下
135      /**
136       * {@inheritDoc}
137       */
138      @Override
139      public void onClick(DialogInterface dialog, int which) {
140          Intent intent = getIntentForPosition(which);
141          
142          int result;
143          if (intent.getExtras() != null) {
144              // If there are any extras, it's because this entry is custom.
145              // Don't try to bind it, just pass it back to the app.
146              setResultData(RESULT_OK, intent);
147          } else {
148              try {
149                  mAppWidgetManager.bindAppWidgetId(mAppWidgetId, intent.getComponent());
150                  result = RESULT_OK;
151              } catch (IllegalArgumentException e) {
152                  // This is thrown if they're already bound, or otherwise somehow
153                  // bogus.  Set the result to canceled, and exit.  The app *should*
154                  // clean up at this point.  We could pass the error along, but
155                  // it's not clear that that's useful -- the widget will simply not
156                  // appear.
157                  result = RESULT_CANCELED;
158              }
159              setResultData(result, null);
160          }
161          finish();
162      }
 
将会 149                  mAppWidgetManager.bindAppWidgetId(mAppWidgetId, intent.getComponent());
如果此次添加的Widget是intent.getComponent()的第一个实例,将会发送如下广播
171      /**
172       * Sent when an instance of an AppWidget is added to a host for the first time.
173       * This broadcast is sent at boot time if there is a AppWidgetHost installed with
174       * an instance for this provider.
175       * 
176       * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context)
177       */
178    public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED";
紧接着会发送UPDATE广播
135      /**
136       * Sent when it is time to update your AppWidget.
137       *
138       * <p>This may be sent in response to a new instance for this AppWidget provider having
139       * been instantiated, the requested {@link AppWidgetProviderInfo#updatePeriodMillis update interval}
140       * having lapsed, or the system booting.
141       *
142       * <p>
143       * The intent will contain the following extras:
144       * <table>
145       *   <tr>
146       *     <td>{@link #EXTRA_APPWIDGET_IDS}</td>
147       *     <td>The appWidgetIds to update.  This may be all of the AppWidgets created for this
148       *     provider, or just a subset.  The system tries to send updates for as few AppWidget
149       *     instances as possible.</td>
150       *  </tr>
151       * </table>
152       * 
153     * @see AppWidgetProvider#onUpdate AppWidgetProvider.onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
154       */
155    public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";

 待用户选择完要添加的widget之后,将会回到Launcher.java中的函数onActivityResult中

538                  case REQUEST_PICK_APPWIDGET:
539                      addAppWidget(data);
540                      break;

上述addAppWidget中做了哪些事情呢?

1174      void addAppWidget(Intent data) {
1175          // TODO: catch bad widget exception when sent
1176          int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
1177          AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
1178
1179          if (appWidget.configure != null) {
1180              // Launch over to configure widget, if needed
1181              Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
1182              intent.setComponent(appWidget.configure);
1183              intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1184
1185              startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
1186          } else {
1187              // Otherwise just add it
1188              onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data);
1189          }
1190      }

首先获取appWidgetId,再通过AppWidgetManager获取AppWidgetProviderInfo,最后判断此Widget是否存在ConfigActivity,如果存在则启动ConfigActivity,否则直接调用函数onActivityResult

541                  case REQUEST_CREATE_APPWIDGET:
542                      completeAddAppWidget(data, mAddItemCellInfo);
543                      break;

通过函数completeAddAppWidget把此widget的信息插入到数据库中,并添加到桌面上

873      /**
874       * Add a widget to the workspace.
875       *
876       * @param data The intent describing the appWidgetId.
877       * @param cellInfo The position on screen where to create the widget.
878       */
879      private void completeAddAppWidget(Intent data, CellLayout.CellInfo cellInfo) {
880          Bundle extras = data.getExtras();
881          int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
882
883          if (LOGD) Log.d(TAG, "dumping extras content=" + extras.toString());
884
885          AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
886
887          // Calculate the grid spans needed to fit this widget
888          CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen);
889          int[] spans = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight);
890
891          // Try finding open space on Launcher screen
892          final int[] xy = mCellCoordinates;
893          if (!findSlot(cellInfo, xy, spans[0], spans[1])) {
894              if (appWidgetId != -1) mAppWidgetHost.deleteAppWidgetId(appWidgetId);
895              return;
896          }
897
898          // Build Launcher-specific widget info and save to database
899          LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
900          launcherInfo.spanX = spans[0];
901          launcherInfo.spanY = spans[1];
902
903          LauncherModel.addItemToDatabase(this, launcherInfo,
904                  LauncherSettings.Favorites.CONTAINER_DESKTOP,
905                  mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
906
907          if (!mRestoring) {
908              mDesktopItems.add(launcherInfo);
909
910              // Perform actual inflation because we're live
911              launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
912
913              launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
914              launcherInfo.hostView.setTag(launcherInfo);
915
916              mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1],
917                      launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
918          }
919      }

 

Launcher中删除widget

 长按一个widget,并拖入到DeleteZone中可实现删除

具体代码在DeleteZone中

92      public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
93              DragView dragView, Object dragInfo) {
94          final ItemInfo item = (ItemInfo) dragInfo;
95
96          if (item.container == -1) return;
97
98          if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
99              if (item instanceof LauncherAppWidgetInfo) {
100                  mLauncher.removeAppWidget((LauncherAppWidgetInfo) item);
101              }
102          } else {
103              if (source instanceof UserFolder) {
104                  final UserFolder userFolder = (UserFolder) source;
105                  final UserFolderInfo userFolderInfo = (UserFolderInfo) userFolder.getInfo();
106                  // Item must be a ShortcutInfo otherwise it couldn't have been in the folder
107                  // in the first place.
108                  userFolderInfo.remove((ShortcutInfo)item);
109              }
110          }
111          if (item instanceof UserFolderInfo) {
112              final UserFolderInfo userFolderInfo = (UserFolderInfo)item;
113              LauncherModel.deleteUserFolderContentsFromDatabase(mLauncher, userFolderInfo);
114              mLauncher.removeFolder(userFolderInfo);
115          } else if (item instanceof LauncherAppWidgetInfo) {
116              final LauncherAppWidgetInfo launcherAppWidgetInfo = (LauncherAppWidgetInfo) item;
117              final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost();
118              if (appWidgetHost != null) {
119                  appWidgetHost.deleteAppWidgetId(launcherAppWidgetInfo.appWidgetId);
120              }
121          }
122          LauncherModel.deleteItemFromDatabase(mLauncher, item);
123      }

删除时,判断删除的类型是否是AppWidget,如果是的话,要通过AppWidgetHost,删除AppWidetId,并最终从数据库中删除。

你可能感兴趣的:(android,service,null,扩展,dialog,callback)