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;
}
}
}
}
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;
}
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);
}
}
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;
}
...
}
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;
}
/**
* Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView}
* which correctly captures all long-press events. This ensures that users can
* always pick up and move widgets.
*/
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);
}
}
private AppWidgetManager mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
mAppWidgetManager = AppWidgetManager.getInstance(this);
mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
mAppWidgetHost.startListening();
/**
* Updates AppWidget state; gets information about installed AppWidget providers and other
* AppWidget related state.
*/
public class AppWidgetManager {
static WeakHashMap> sManagerCache = new WeakHashMap();
static IAppWidgetService sService;
/**
* Get the AppWidgetManager instance to use for the supplied {@link android.content.Context
* Context} object.
*/
public static AppWidgetManager getInstance(Context context) {
synchronized (sManagerCache) {
if (sService == null) {
IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
sService = IAppWidgetService.Stub.asInterface(b);
}
WeakReference ref = sManagerCache.get(context);
AppWidgetManager result = null;
if (ref != null) {
result = ref.get();
}
if (result == null) {
result = new AppWidgetManager(context);
sManagerCache.put(context, new WeakReference(result));
}
return result;
}
}
private AppWidgetManager(Context context) {
mContext = context;
mDisplayMetrics = context.getResources().getDisplayMetrics();
}
frameworks/base/ core / java / android / appwidget / AppWidgetHost.java
public AppWidgetHost(Context context, int hostId) {
mContext = context;
mHostId = hostId;
mHandler = new UpdateHandler(context.getMainLooper());
synchronized (sServiceLock) {
if (sService == null) {
IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
sService = IAppWidgetService.Stub.asInterface(b);
}
}
}
可以看到AppWidgetHost有自己的HostId,Handler,和sService
mHandler = new UpdateHandler(context.getMainLooper());
这是啥用法呢 ,参数为Looper,即消息处理放到此Looper的MessageQueue中,有哪些消息呢?
static final int HANDLE_UPDATE = 1;
static final int HANDLE_PROVIDER_CHANGED = 2;
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();
}
public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED);
msg.arg1 = appWidgetId;
msg.obj = info;
msg.sendToTarget();
}
}
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;
}
}
}
}
/**
* Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
*/
protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
}
if (v != null) {
v.updateAppWidget(null, AppWidgetHostView.UPDATE_FLAGS_RESET);
}
}
void updateAppWidgetView(int appWidgetId, RemoteViews views) {
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
}
if (v != null) {
v.updateAppWidget(views, 0);
}
}
那么此消息是何时由谁发送的呢?
/**
* Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity
* becomes visible, i.e. from onStart() in your Activity.
*/
public void startListening() {
int[] updatedIds;
ArrayList updatedViews = new ArrayList();
try {
if (mPackageName == null) {
mPackageName = mContext.getPackageName();
}
updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
final int N = updatedIds.length;
for (int i=0; i
最终调用AppWidgetService中的方法startListening方法,并把mCallbacks传过去,由Service负责发送消息
/**
* Handle the action clicked in the "Add to home" dialog.
*/
public void onClick(DialogInterface dialog, int which) {
Resources res = getResources();
cleanup();
switch (which) {
case AddAdapter.ITEM_SHORTCUT: {
// Insert extra item to handle picking application
pickShortcut();
break;
}
case AddAdapter.ITEM_APPWIDGET: {
int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId();
Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
// start the pick activity
startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
break;
}
/**
* 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);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
}
Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
// start the pick activity
startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
这段代码之后,代码将会怎么执行呢,根据Log信息,可以看到代码将会执行到Setting应用中packages/apps/Settings/ src / com / android / settings / AppWidgetPickActivity.java此类将会通过AppWidgetService获取到当前系统已经安装的Widget,并显示出来
/**
* Create list entries for any custom widgets requested through
* {@link AppWidgetManager#EXTRA_CUSTOM_INFO}.
*/
void putCustomAppWidgets(List items) {
final Bundle extras = getIntent().getExtras();
// get and validate the extras they gave us
ArrayList customInfo = null;
ArrayList customExtras = null;
try_custom_items: {
customInfo = extras.getParcelableArrayList(AppWidgetManager.EXTRA_CUSTOM_INFO);
if (customInfo == null || customInfo.size() == 0) {
Log.i(TAG, "EXTRA_CUSTOM_INFO not present.");
break try_custom_items;
}
int customInfoSize = customInfo.size();
for (int i=0; i
从上述代码中可以看到,可以放置用户自己定义的伪Widget
/**
* {@inheritDoc}
*/
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = getIntentForPosition(which);
int result;
if (intent.getExtras() != null) {
// If there are any extras, it's because this entry is custom.
// Don't try to bind it, just pass it back to the app.
setResultData(RESULT_OK, intent);
} else {
try {
mAppWidgetManager.bindAppWidgetId(mAppWidgetId, intent.getComponent());
result = RESULT_OK;
} catch (IllegalArgumentException e) {
// This is thrown if they're already bound, or otherwise somehow
// bogus. Set the result to canceled, and exit. The app *should*
// clean up at this point. We could pass the error along, but
// it's not clear that that's useful -- the widget will simply not
// appear.
result = RESULT_CANCELED;
}
setResultData(result, null);
}
finish();
}
将会mAppWidgetManager.bindAppWidgetId(mAppWidgetId, intent.getComponent());如果此次添加的Widget是intent.getComponent()的第一个实例,将会发送如下广播
/**
* Sent when an instance of an AppWidget is added to a host for the first time.
* This broadcast is sent at boot time if there is a AppWidgetHost installed with
* an instance for this provider.
*
* @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context)
*/
public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED";
紧接着会发送UPDATE广播
/**
* Sent when it is time to update your AppWidget.
*
* This may be sent in response to a new instance for this AppWidget provider having
* been instantiated, the requested {@link AppWidgetProviderInfo#updatePeriodMillis update interval}
* having lapsed, or the system booting.
*
*
* The intent will contain the following extras:
*
*
* {@link #EXTRA_APPWIDGET_IDS}
* The appWidgetIds to update. This may be all of the AppWidgets created for this
* provider, or just a subset. The system tries to send updates for as few AppWidget
* instances as possible.
*
*
*
* @see AppWidgetProvider#onUpdate AppWidgetProvider.onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
*/
public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";
待用户选择完要添加的widget之后,将会回到Launcher.java中的函数onActivityResult中
case REQUEST_PICK_APPWIDGET:
addAppWidget(data);
break;
上述addAppWidget中做了哪些事情呢?
void addAppWidget(Intent data) {
// TODO: catch bad widget exception when sent
int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
if (appWidget.configure != null) {
// Launch over to configure widget, if needed
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
intent.setComponent(appWidget.configure);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
} else {
// Otherwise just add it
onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data);
}
}
首 先获取appWidgetId,再通过AppWidgetManager获取AppWidgetProviderInfo,最后判断此Widget是否存 在ConfigActivity,如果存在则启动ConfigActivity,否则直接调用函数onActivityResult
case REQUEST_CREATE_APPWIDGET:
completeAddAppWidget(data, mAddItemCellInfo);
break;
通过函数completeAddAppWidget把此widget的信息插入到数据库中,并添加到桌面上
/**
* Add a widget to the workspace.
*
* @param data The intent describing the appWidgetId.
* @param cellInfo The position on screen where to create the widget.
*/
private void completeAddAppWidget(Intent data, CellLayout.CellInfo cellInfo) {
Bundle extras = data.getExtras();
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
if (LOGD) Log.d(TAG, "dumping extras content=" + extras.toString());
AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
// Calculate the grid spans needed to fit this widget
CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen);
int[] spans = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight);
// Try finding open space on Launcher screen
final int[] xy = mCellCoordinates;
if (!findSlot(cellInfo, xy, spans[0], spans[1])) {
if (appWidgetId != -1) mAppWidgetHost.deleteAppWidgetId(appWidgetId);
return;
}
// Build Launcher-specific widget info and save to database
LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
launcherInfo.spanX = spans[0];
launcherInfo.spanY = spans[1];
LauncherModel.addItemToDatabase(this, launcherInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
if (!mRestoring) {
mDesktopItems.add(launcherInfo);
// Perform actual inflation because we're live
launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
launcherInfo.hostView.setTag(launcherInfo);
mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1],
launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
}
}
Launcher中删除widget 长按一个widget,并拖入到DeleteZone中可实现删除,具体代码在DeleteZone中
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,并最终从数据库中删除。
iff --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">
android:configChanges 如果配置了这个属性,当我们横竖屏切换的时候会直接调用onCreate方法中的onConfigurationChanged方法,而不会重新执行onCreate方法,那当然如果不配置这个属性的话就会重新调用onCreate方法了
在Activity中添加了 android:configChanges属性,目的是当所指定属性(Configuration Changes)发生改变时,通知程序调用 onConfigurationChanged()函数。 创建一个Layout UI:
这个简单的UI包含两个按钮,其中一个是通过Contact列表选择一个联系人,另外一个是查看当前选择联系人的详细内容。
项目的Java源代码:
.import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Contacts.People;
import android.view.View;
import android.widget.Button;
public class ConfigChangedTesting extends Activity {
/** Called when the activity is first created. */
static final int PICK_REQUEST = 1337;
Button viewButton=null;
Uri contact = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
setupViews();
}
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setupViews();
}
/* (non-Javadoc)
* @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
//super.onActivityResult(requestCode, resultCode, data);
if(requestCode == PICK_REQUEST){
if(resultCode==RESULT_OK){
contact = data.getData();
viewButton.setEnabled(true);
}
}
}
private void setupViews(){
setContentView(R.layout.main);
Button pickBtn = (Button)findViewById(R.id.pick);
pickBtn.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
// TODO Auto-generated method stub
Intent i=new Intent(Intent.ACTION_PICK,People.CONTENT_URI);
startActivityForResult(i,PICK_REQUEST);
}
});
viewButton =(Button)findViewById(R.id.view);
viewButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
startActivity(new Intent(Intent.ACTION_VIEW, contact));
}
});
viewButton.setEnabled(contact!=null);
}
}