您的位置:首页 > 移动开发 > Android开发

android Widget添加过程和android添加widget不更新的问题分析解决

2015-12-08 19:21 225 查看
这两天在解一个关于widget的CR,由于之前也没有看过widget,借这个机会学习下widget,不过这个bug后来是另外一个同事fix的,这篇文章分为两部分:第一部分,分析android widget的添加过程,第二部分,分析该问题

第一部分: android widget 添加过程分析

Android中的AppWidget与google widget和中移动的widget并不是一个概念,这里的AppWidget只是把一个进程的控件嵌入到别外一个进程的窗口里的一种方法。View在另 外一个进程里显示,但事件的处理方法还是在原来的进程里。这有点像
X Window中的嵌入式窗口。

首先,我们需要了解RemoteViews, AppWidgetHost, AppWidgetHostView等概念

RemoteViews:并不是一个真正的View,它没有实现View的接口,而只是一个用于描述View的实体。比如:创建View需要的资源ID和各个控件的事件响应方法。RemoteViews会通过进程间通信机制传递给AppWidgetHost。

AppWidgetHost

AppWidgetHost是真正容纳AppWidget的地方,它的主要功能有两个:

1 . 监听来自AppWidgetService的事件:

class Callbacks extends IAppWidgetHost.Stub

{

public void updateAppWidget(int appWidgetId,RemoteViews views) { Message

msg = mHandler.obtainMessage(HANDLE_UPDATE);

msg.arg1 = appWidgetId;

msg.obj = views; msg.sendToTarget();

} //处理update事件,更新widget

public void providerChanged(int appWidgetId,AppWidgetProviderInfo info) {

Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED);

msg.arg1 = appWidgetId;

msg.obj = info;

msg.sendToTarget();

}//处理providerChanged事件,更新widget

}

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;

}

case HANDLE_PROVIDER_CHANGED{

onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);

break;

}

}

}

}

2 . 另外一个功能就是创建AppWidgetHostView。

前面我们说过RemoteViews不是真正的View,只是View的描述,而 AppWidgetHostView才是真正的View。这里先创建AppWidgetHostView,然后通过AppWidgetService查询 appWidgetId对应的RemoteViews,最后把RemoteViews传递给AppWidgetHostView去 updateAppWidget。

public final AppWidgetHostView createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget) {

AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);

view.setAppWidget(appWidgetId, appWidget);

synchronized (mViews) { mViews.put(appWidgetId, view); }

RemoteViews views = null;

try {

views = sService.getAppWidgetViews(appWidgetId);

} catch(RemoteException e) {

throw new RuntimeException("system server dead?", e);

}

view.updateAppWidget(views);

return view;

}

AppWidgetHost其实是一个容器,在这个容器中可以放置widget,我们平常熟悉的Lanuch 就可以放置widget,所以lanuch应该是继承AppWidgetHost的

public class LauncherAppWidgetHost extends AppWidgetHost {

public LauncherAppWidgetHost(Context context, int hostId) {

super(context, hostId);

}

@Override

protected AppWidgetHostView onCreateView(Context context, int appWidgetId,

AppWidgetProviderInfo appWidget) {

return new LauncherAppWidgetHostView(context);

}

}

AppWidgetHostView

AppWidgetHostView是真正的View,但它只是一个容器,用来容纳实际的AppWidget的View。这个AppWidget的View是根据RemoteViews的描述来创建。这是在updateAppWidget里做的:

public void updateAppWidget(RemoteViews remoteViews){

...

if (content == null && layoutId ==mLayoutId) {

try {

remoteViews.reapply(mContext, mView);

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);

if (LOGD) Log.d(TAG, "had to inflate new layout");

} catch(RuntimeException e) { exception = e; }

}

...

if (!recycled) {

prepareView(content);

addView(content);

}

if (mView != content) {

removeView(mView);

mView = content;

}

...

}

remoteViews.apply创建了实际的View,下面代码可以看出:

public View apply(Context context, ViewGroup parent) {

View result = null;

Context c =prepareContext(context);

Resources r = c.getResources();

LayoutInflater inflater =(LayoutInflater) c .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

inflater =inflater.cloneInContext(c);

inflater.setFilter(this);

result = inflater.inflate(mLayoutId,parent, false);

performApply(result);

return result;

}

Host的实现者

AppWidgetHost和AppWidgetHostView是在框架中定义的两个基类。应用程序可以利用这两个类来实现自己的Host。Launcher是缺省的桌面,它是一个Host的实现者。

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

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

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

第二部分:分析问题:

问题描述:在烧上新版本以及做完factory reset之后,概率性出现添加到桌面上的widget不更新的问题,如电量管理没有图片,但是功能不受影响,天气和时钟都不更新。

问题再现条件:手机插有sim卡 && 第一次开机或为恢复出产设置重启

问题再现概率:50%

原因:

由于手机第一次开机的时候,会有一个下面的启动流程:

Launcher启动->检测到sim卡->重启Launcher

上一个启动的Launcher的activity还没有退出的时候,新启的Launcher的activity已经起来了。

由于统一package的activity只能注册一个listener,新启动的activity在要注册listener时,

因为上一个启动的Launcher还没有完全退出,系统发现此listener已经存在,

所以直接就把前一个listener的引用传了出来。但那个已经存在的listener对新的activity来说只是昙花一现,

即将被上一个Launcher销毁掉,新的activity中的listener变为null,导致以后所有的widget更新消息都得不到 处 理。

对应方法:

只需要修改一行代码就能搞定。我这边测试了5次,基本确认没有问题。

请参考下面的diff结果修改Launcher2的AndroidManifest.xml文件:

diff --git a/AndroidManifest.xml b/AndroidManifest.xml

index 3706661..c92f502 100644

--- a/AndroidManifest.xml

+++ b/AndroidManifest.xml

@@ -75,7 +75,8 @@

android:stateNotNeeded="true"

android:theme="@style/Theme"

android:screenOrientation="nosensor"

- android:windowSoftInputMode="stateUnspecified|adjustPan">

+ android:windowSoftInputMode="stateUnspecified|adjustPan"

+ android:configChanges="mcc|mnc|keyboard|keyboardHidden|navigation|orientation|uiMode">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.HOME" />

android:configChanges 如果配置了这个属性,当我们横竖屏切换的时候会直接调用onCreate方法中的onConfigurationChanged方法,而不会重新执行onCreate方法,那当然如果不配置这个属性的话就会重新调用onCreate方法了

转下这个属性的使用方式

通过设置这个属性可以使Activity捕捉设备状态变化,以下是可以被识别的内容:

CONFIG_FONT_SCALE

CONFIG_MCC

CONFIG_MNC

CONFIG_LOCALE

CONFIG_TOUCHSCREEN

CONFIG_KEYBOARD

CONFIG_NAVIGATION

CONFIG_ORIENTATION

设置方法:将下列字段用“|”符号分隔开,例如:“locale|navigation|orientation



ValueDescription
“mcc“The IMSI mobile country code (MCC) has changed — that is, a SIM hasbeen detected and updated the MCC.移动国家号码,由三位数字组成,每个国家都有自己独立的MCC,可以识别手机用户所属国家。
“mnc“The IMSI mobile network code (MNC) has changed — that is, a SIM hasbeen detected and updated the MNC.移动网号,在一个国家或者地区中,用于区分手机用户的服务商。
“locale“The locale has changed — for example, the user has selected a new language that text should be displayed in.用户所在地区发生变化。
“touchscreen“The touchscreen has changed. (This should never normally happen.)
“keyboard“The keyboard type has changed — for example, the user has plugged in an external keyboard.键盘模式发生变化,例如:用户接入外部键盘输入。
“keyboardHidden“The keyboard accessibility has changed — for example, the user has slid the keyboard out to expose it.用户打开手机硬件键盘
“navigation“The navigation type has changed. (This should never normally happen.)
“orientation“The screen orientation has changed — that is, the user has rotated the device.设备旋转,横向显示和竖向显示模式切换。
“fontScale“The font scaling factor has changed — that is, the user has selected a new global font size.全局字体大小缩放发生改变
通过一个例子介绍这个属性的用法: 首先需要修改项目的manifest:

?View
Code XML

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.androidres.ConfigChangedTesting"

android:versionCode="1"

android:versionName="1.0.0">

<application android:icon="@drawable/icon"android:label="@string/app_name">

<activity android:name=".ConfigChangedTesting"

android:label="@string/app_name"

android:configChanges="keyboardHidden|orientation">

<intent-filter[b]>[/b]

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter[b]>[/b]

</activity[b]>[/b]

</application[b]>[/b]

</manifest[b]>[/b]
在Activity中添加了 android:configChanges属性,目的是当所指定属性(Configuration Changes)发生改变时,通知程序调用
onConfigurationChanged()函数。 创建一个Layout UI:

?View
Code XML

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

<Button

android:id="@+id/pick"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="Pick"

/>

<Button

android:id="@+id/view"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="View"

/>

</LinearLayout[b]>[/b]
这个简单的UI包含两个按钮,其中一个是通过Contact列表选择一个联系人,另外一个是查看当前选择联系人的详细内容。



项目的Java源代码:

01.import android.app.Activity;

02.import android.content.Intent;

03.import android.content.res.Configuration;

04.import android.net.Uri;

05.import android.os.Bundle;

06.import android.provider.Contacts.People;

07.import android.view.View;

08.import android.widget.Button;

09.

10.public class ConfigChangedTesting extends Activity {

11. /** Called when the activity is first created. */

12. static final int PICK_REQUEST = 1337;

13. Button viewButton=null;

14. Uri contact = null;

15. @Override

16. public void onCreate(Bundle savedInstanceState) {

17. super.onCreate(savedInstanceState);

18. //setContentView(R.layout.main);

19.

20. setupViews();

21. }

22.

23. public void onConfigurationChanged(Configuration newConfig) {

24. super.onConfigurationChanged(newConfig);

25.

26. setupViews();

27. }

28.

29. /* (non-Javadoc)

30. * @see android.app.Activity#onActivityResult(int, int, android.content.Intent)

31. */

32. @Override

33. protected void onActivityResult(int requestCode, int resultCode, Intent data) {

34. // TODO Auto-generated method stub

35. //super.onActivityResult(requestCode, resultCode, data);

36.

37. if(requestCode == PICK_REQUEST){

38.

39. if(resultCode==RESULT_OK){

40.

41. contact = data.getData();

42. viewButton.setEnabled(true);

43. }

44.

45. }

46.

47. }

48.

49. private void setupViews(){

50.

51. setContentView(R.layout.main);

52.

53. Button pickBtn = (Button)findViewById(R.id.pick);

54.

55. pickBtn.setOnClickListener(new View.OnClickListener(){

56.

57. public void onClick(View v) {

58. // TODO Auto-generated method stub

59.

60. Intent i=new Intent(Intent.ACTION_PICK,People.CONTENT_URI);

61. startActivityForResult(i,PICK_REQUEST);

62. }

63. });

64.

65. viewButton =(Button)findViewById(R.id.view);

66.

67. viewButton.setOnClickListener(new View.OnClickListener() {

68. public void onClick(View view) {

69. startActivity(new Intent(Intent.ACTION_VIEW, contact));

70. }

71. });

72.

73. viewButton.setEnabled(contact!=null);

74. }

75.}

转载自:

http://blog.163.com/dengjingniurou@126/blog/static/53989196201201910154151/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: