上篇讲到了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,并最终从数据库中删除。