先看一下app widget的添加代码,此处直接引用http://www.cnblogs.com/wanqieddy/archive/2012/05/05/2484533.html的代码
package com.qin.addappwidget; import android.app.Activity; import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private static String TAG = "AddAppWidget" ; private Button btAddShortCut; private LinearLayout linearLayout ; // 装载Appwidget的父视图 private static final int MY_REQUEST_APPWIDGET = 1; private static final int MY_CREATE_APPWIDGET = 2; private static final int HOST_ID = 1024 ; private AppWidgetHost mAppWidgetHost = null ; AppWidgetManager appWidgetManager = null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btAddShortCut = (Button) findViewById(R.id.bt_addShortcut); linearLayout = (LinearLayout)findViewById(R.id.linearLayout) ; //其参数hostid大意是指定该AppWidgetHost 即本Activity的标记Id, 直接设置为一个整数值吧 。 mAppWidgetHost = new AppWidgetHost(MainActivity.this, HOST_ID) ; //为了保证AppWidget的及时更新 , 必须在Activity的onCreate/onStar方法调用该方法 // 当然可以在onStop方法中,调用mAppWidgetHost.stopListenering() 停止AppWidget更新 mAppWidgetHost.startListening() ; //获得AppWidgetManager对象 appWidgetManager = AppWidgetManager.getInstance(MainActivity.this) ; btAddShortCut.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //显示所有能创建AppWidget的列表 发送此 ACTION_APPWIDGET_PICK 的Action Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK) ; //向系统申请一个新的appWidgetId ,该appWidgetId与我们发送Action为ACTION_APPWIDGET_PICK // 后所选择的AppWidget绑定 。 因此,我们可以通过这个appWidgetId获取该AppWidget的信息了 //为当前所在进程申请一个新的appWidgetId int newAppWidgetId = mAppWidgetHost.allocateAppWidgetId() ; Log.i(TAG, "The new allocate appWidgetId is ----> " + newAppWidgetId) ; //作为Intent附加值 , 该appWidgetId将会与选定的AppWidget绑定 pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, newAppWidgetId) ; //选择某项AppWidget后,立即返回,即回调onActivityResult()方法 startActivityForResult(pickIntent , MY_REQUEST_APPWIDGET) ; } }); } // 如果 protected void onActivityResult(int requestCode, int resultCode, Intent data) { //直接返回,没有选择任何一项 ,例如按Back键 if(resultCode == RESULT_CANCELED) return ; switch(requestCode){ case MY_REQUEST_APPWIDGET : Log.i(TAG, "MY_REQUEST_APPWIDGET intent info is -----> "+data ) ; int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID , AppWidgetManager.INVALID_APPWIDGET_ID) ; Log.i(TAG, "MY_REQUEST_APPWIDGET : appWidgetId is ----> " + appWidgetId) ; //得到的为有效的id if(appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID){ //查询指定appWidgetId的 AppWidgetProviderInfo对象 , 即在xml文件配置的<appwidget-provider />节点信息 AppWidgetProviderInfo appWidgetProviderInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) ; //如果配置了configure属性 , 即android:configure = "" ,需要再次启动该configure指定的类文件,通常为一个Activity if(appWidgetProviderInfo.configure != null){ Log.i(TAG, "The AppWidgetProviderInfo configure info -----> " + appWidgetProviderInfo.configure ) ; //配置此Action Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE) ; intent.setComponent(appWidgetProviderInfo.configure) ; intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); startActivityForResult(intent , MY_CREATE_APPWIDGET) ; } else //直接创建一个AppWidget onActivityResult(MY_CREATE_APPWIDGET , RESULT_OK , data) ; //参数不同,简单回调而已 } break ; case MY_CREATE_APPWIDGET: completeAddAppWidget(data) ; break ; } } //向当前视图添加一个用户选择的 private void completeAddAppWidget(Intent data){ Bundle extra = data.getExtras() ; int appWidgetId = extra.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID , -1) ; //等同于上面的获取方式 //int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID , AppWidgetManager.INVALID_APPWIDGET_ID) ; Log.i(TAG, "completeAddAppWidget : appWidgetId is ----> " + appWidgetId) ; if(appWidgetId == -1){ Toast.makeText(MainActivity.this, "添加窗口小部件有误", Toast.LENGTH_SHORT) ; return ; } AppWidgetProviderInfo appWidgetProviderInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) ; AppWidgetHostView hostView = mAppWidgetHost.createView(MainActivity.this, appWidgetId, appWidgetProviderInfo); //linearLayout.addView(hostView) ; int widget_minWidht = appWidgetProviderInfo.minWidth ; int widget_minHeight = appWidgetProviderInfo.minHeight ; //设置长宽 appWidgetProviderInfo 对象的 minWidth 和 minHeight 属性 LinearLayout.LayoutParams linearLayoutParams = new LinearLayout.LayoutParams(widget_minWidht, widget_minHeight); //添加至LinearLayout父视图中 linearLayout.addView(hostView,linearLayoutParams) ; } }
大致流程如下:
1. 实例化AppWidgetHost, 具体实例化代码如下:
public AppWidgetHost(Context context, int hostId) { this(context, hostId, null, context.getMainLooper()); } /** * @hide */ public AppWidgetHost(Context context, int hostId, OnClickHandler handler, Looper looper) { mContext = context; mHostId = hostId; mOnClickHandler = handler; mHandler = new UpdateHandler(looper); mDisplayMetrics = context.getResources().getDisplayMetrics(); bindService(); }
该AppWidgetHost保存应用的Context对象,hostId,同时实例化一个Handler(和Launcher主线程共用一个MessageQueue),实例化一个IAppWidgetServer对象用于与AppWidgetService服务通信。
2. 通知AppWidgetService自身可以接收消息
/** * Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity * becomes visible, i.e. from onStart() in your Activity. */ public void startListening() { Log.d(TAG, "startListening"); int[] updatedIds; ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>(); final int userId = mContext.getUserId(); try { if (mPackageName == null) { mPackageName = mContext.getPackageName(); } updatedIds = sService.startListening( mCallbacks, mPackageName, mHostId, updatedViews, userId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); } final int N = updatedIds.length; for (int i=0; i<N; i++) { if (updatedViews.get(i) != null) { updatedViews.get(i).setUser(new UserHandle(userId)); } updateAppWidgetView(updatedIds[i], updatedViews.get(i), userId); } }
sService.startListening最终调用在AppWidgetServiceImpl中,
public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, List<RemoteViews> updatedViews) { log("startListening"); if (!mHasFeature) { return new int[0]; } int callingUid = enforceCallingUid(packageName); synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); host.callbacks = callbacks; updatedViews.clear(); ArrayList<AppWidgetId> instances = host.instances; int N = instances.size(); int[] updatedIds = new int[N]; for (int i = 0; i < N; i++) { AppWidgetId id = instances.get(i); updatedIds[i] = id.appWidgetId; updatedViews.add(cloneIfLocalBinder(id.views)); } return updatedIds; } }
在AppWidgetService中mHost保存所有已注册的Host,此处会查找已经存在的或者添加一个新Host对象,同时记录AppWidgetHost的callback,更新updateViews列表为所有已添加到Host的插件的RemoteViews,最后返回该Host中所有插件的id。
appWidgetHost在取得自身所有已经添加的插件 id后,会先为对应的RemoteViews设定UserHandle,然后更新对应的AppWidgetHostView。
3. 获取新分配的appWidget id
/** * Get a appWidgetId for a host in the calling process. * * @return a appWidgetId */ public int allocateAppWidgetId() { try { if (mPackageName == null) { mPackageName = mContext.getPackageName(); } return sService.allocateAppWidgetId(mPackageName, mHostId, mContext.getUserId()); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); } }AppWidgetServiceImpl会分配一个appWidget id,然后实例化一个AppWidgetId对象,保存到该Host和mAppWidgetIds中,同时也保存到xml文件中(缺少provider元素)。
4. bind appwidgetid
在上例代码中是通过发送intent的方式完成,当用户选择需要添加的widget后则会调用AppWidgetManager.bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider, Bundle options) 方法,具体的实现在AppWidgetServiceImpl中
public boolean bindAppWidgetIdIfAllowed( String packageName, int appWidgetId, ComponentName provider, Bundle options) { if (!mHasFeature) { return false; } try { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET, null); } catch (SecurityException se) { if (!callerHasBindAppWidgetPermission(packageName)) { return false; } } bindAppWidgetIdImpl(appWidgetId, provider, options); return true; } private void bindAppWidgetIdImpl(int appWidgetId, ComponentName provider, Bundle options) { if (DBG) log("bindAppWidgetIdImpl appwid=" + appWidgetId + " provider=" + provider); final long ident = Binder.clearCallingIdentity(); try { synchronized (mAppWidgetIds) { AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); Provider p = lookupProviderLocked(provider); // ...检查数据 id.provider = p; id.options = options; p.instances.add(id); int instancesSize = p.instances.size(); if (instancesSize == 1) { // tell the provider that it's ready 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 schedule the next one, // we add updatePeriodMillis to its start time. That time will have some slop, // but that's okay. sendUpdateIntentLocked(p, new int[] { appWidgetId }); // schedule the future updates registerForBroadcastsLocked(p, getAppWidgetIds(p)); saveStateAsync(); } } finally { Binder.restoreCallingIdentity(ident); } }
可以看到bindAppWidgetId主要是保存新Provider信息,同时发送enable和update广播通知应用Provider处理,最后将插件信息写入磁盘。
5. configure appWidget
此处略过
6. 显示appWidget
bind和configure成功后就可以显示appWidget到AppWidgetHostView中,整个添加过程结束。
AppWidgetProviderInfo appWidgetProviderInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) ; AppWidgetHostView hostView = mAppWidgetHost.createView(MainActivity.this, appWidgetId, appWidgetProviderInfo); //linearLayout.addView(hostView) ; int widget_minWidht = appWidgetProviderInfo.minWidth ; int widget_minHeight = appWidgetProviderInfo.minHeight ; //设置长宽 appWidgetProviderInfo 对象的 minWidth 和 minHeight 属性 LinearLayout.LayoutParams linearLayoutParams = new LinearLayout.LayoutParams(widget_minWidht, widget_minHeight); //添加至LinearLayout父视图中 linearLayout.addView(hostView,linearLayoutParams) ;
下一篇再介绍createView的具体流程以及应用如何通知桌面来刷新数据。
前面的步骤3中有一个疑问:在AppWidgetServiceImpl中分配了一个widget id,同时保存了数据,但是如果后期用户后悔,取消添加插件会如何?基本做法应该为在接收到RESULT_CANCELED结果时通过AppWidgetHost删除已分配但是未添加的插件。