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

这两天在解一个关于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的实例。

/**
  * 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);
     }
 }

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

 private AppWidgetManager mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;

在onCreate函数中初始化

 mAppWidgetManager = AppWidgetManager.getInstance(this);
  mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
mAppWidgetHost.startListening();

上述代码,获取mAppWidgetManager的实例,并创建LauncherAppWidgetHost,以及监听
AppWidgetManager只是应用程序与底层Service之间的一个桥梁,是Android中标准的aidl实现方式
应用程序通过AppWidgetManager调用Service中的方法
frameworks/base/ core / java / android / appwidget / AppWidgetHost.java

/**
 * Updates AppWidget state; gets information about installed AppWidget providers and other
 * AppWidget related state.
 */
public class AppWidgetManager {
    static WeakHashMap<Context, WeakReference<AppWidgetManager>> 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<AppWidgetManager> 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;
            }
        }
    }
}

通过以上可以看到主要有两中类型的消息,HANDLE_UPDATE和HANDLE_PROVIDER_CHANGED
处理即通过自身定义的方法
   /**
 * 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);
    }
}
 
那么此消息是何时由谁发送的呢?
从以上的代码中看到AppWidgetHost定义了内部类Callback,扩展了类IAppWidgetHost.Stub,类Callback中负责发送以上消息
Launcher中会调用本类中的如下方法,

/**
 * 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<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
    
    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<N; i++) {
        updateAppWidgetView(updatedIds[i], updatedViews.get(i));
    }
}
最终调用AppWidgetService中的方法startListening方法,并把mCallbacks传过去,由Service负责发送消息
 
Launcher中添加Widget
在Launcher中添加widget,有两种途径,通过Menu或者长按桌面的空白区域,都会弹出Dialog,让用户选择添加
如下代码是当用户选择

 /**
 * 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;
        }

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


同时发送Intent,其中保存有刚刚分配的appWidgetId,AppWidgetManager.EXTRA_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);
    }
    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<PickAdapter.Item> items) {
   final Bundle extras = getIntent().getExtras();
   
   // get and validate the extras they gave us
   ArrayList<AppWidgetProviderInfo> customInfo = null;
   ArrayList<Bundle> 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<customInfoSize; i++) {
           Parcelable p = customInfo.get(i);
           if (p == null || !(p instanceof AppWidgetProviderInfo)) {
               customInfo = null;
                Log.e(TAG, "error using EXTRA_CUSTOM_INFO index=" + i);
                break try_custom_items;
            }
        }

        customExtras = extras.getParcelableArrayList(AppWidgetManager.EXTRA_CUSTOM_EXTRAS);
        if (customExtras == null) {
            customInfo = null;
            Log.e(TAG, "EXTRA_CUSTOM_INFO without EXTRA_CUSTOM_EXTRAS");
            break try_custom_items;
        }

        int customExtrasSize = customExtras.size();
        if (customInfoSize != customExtrasSize) {
            Log.e(TAG, "list size mismatch: EXTRA_CUSTOM_INFO: " + customInfoSize
                    + " EXTRA_CUSTOM_EXTRAS: " + customExtrasSize);
            break try_custom_items;
        }


        for (int i=0; i<customExtrasSize; i++) {
            Parcelable p = customExtras.get(i);
            if (p == null || !(p instanceof Bundle)) {
                customInfo = null;
                customExtras = null;
                Log.e(TAG, "error using EXTRA_CUSTOM_EXTRAS index=" + i);
                break try_custom_items;
            }
        }
    }

    if (LOGD) Log.d(TAG, "Using " + customInfo.size() + " custom items");
    putAppWidgetItems(customInfo, customExtras, items);
}
从上述代码中可以看到,可以放置用户自己定义的伪Widget
关于伪widget,个人有如下想法:
早期Android版本中的Google Search Bar就属于伪Widget,其实就是把widget做到Launcher中,但是用户体验与真widget并没有区别,个人猜想HTC的sense就是这样实现的。
优点:是不需要进程间的通信,效率将会更高,并且也可以规避点Widget开发的种种限制
缺点:导致Launcher代码庞大,不易于维护
 
用户选择完之后,代码如下

    /**
 * {@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.
  *
  * <p>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.
  *
  * <p>
  * The intent will contain the following extras:
  * <table>
  *   <tr>
  *     <td>{@link #EXTRA_APPWIDGET_IDS}</td>
  *     <td>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.</td>
  *  </tr>
  * </table>
  * 
 * @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,并最终从数据库中删除。

第二部分:分析问题:
问题描述:在烧上新版本以及做完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文件:

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">
             <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

Value Description
“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:
<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>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
在Activity中添加了 android:configChanges属性,目的是当所指定属性(Configuration Changes)发生改变时,通知程序调用 onConfigurationChanged()函数。 创建一个Layout UI: 
<LinearLayout xmlns: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>
这个简单的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);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值