这两天在解一个关于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 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 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 updatedViews = new ArrayList(); 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 当用户在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 items) { 83         final Bundle extras = getIntent().getExtras(); 84          85         // get and validate the extras they gave us 86         ArrayList customInfo = null; 87         ArrayList 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/////下面这句话救了我,太感谢了  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      * 

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      * 

143      * The intent will contain the following extras: 144      * 

145      *    146      *      147      *      150      *   151      * 
{@link #EXTRA_APPWIDGET_IDS}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.
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">
            
                
                

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:
? View Code XML
 xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.androidres.ConfigChangedTesting"
      android:versionCode="1"
      android:versionName="1.0.0">
     android:icon="@drawable/icon"android:label="@string/app_name">
         android:name=".ConfigChangedTesting"
                  android:label="@string/app_name"
                  android:configChanges="keyboardHidden|orientation">
            >
                 android:name="android.intent.action.MAIN" />
                 android:name="android.intent.category.LAUNCHER" />
            >
        >
    >
>

在Activity中添加了 android:configChanges属性,目的是当所指定属性(Configuration Changes)发生改变时,通知 程序 调用 onConfigurationChanged()函数。  创建一个Layout UI: 
? View Code XML
xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

        android:id="@+id/pick"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Pick"
    />

        android:id="@+id/view"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="View"
    />
>

这个简单的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/



更多相关文章

  1. Android中通过Messenger与Service实现进程间双向通信
  2. android 自定义AlertDialog 与Activity相互传递数据
  3. Android如何完全调试framework层代码
  4. 浅入浅出 Android(安卓)安全:第三章 Android(安卓)本地用户空间层
  5. android service
  6. android 开发提速
  7. Android高性能编程
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. Android本地数据存储之.txt文件存储读写
  2. 使用Kotlin开发Android 扩展函数(Extensio
  3. react-native 使用android DownloadManag
  4. android studio的使用说明
  5. Android开发规范以及注意事项
  6. 为Android Things构建Android IoT应用
  7. android 拍照或选择图片的实现方式//Atle
  8. Android6的Logger日志系统
  9. Android 中的拿来主义(编译,反编译,AXMLPri
  10. Android:使用pagerslidingtabstrip做Tab