在开发android应用的时候,有可能需要让应用程序随着系统设置而进行调整,比如判断系统的屏幕方向、判断系统方向的方向导航设备等。除此之外,还需要让应用程序监听系统设置的更改,对系统设置的更改作出响应。
如果系统需要监听系统设置的更改,则可以考虑重写Activity的onConfigurationChanged()方法,该方法是一个基于回调的事件处理方法;当系统的设置发生更改时,该方法会被自动触发。查阅AndroidAPI可以得知配置信息android:ConfigChanged实际对应的是Activity里的onConfigurationChanged()方法。
在一些特殊的情况中,你可能希望当一种或者多种配置改变时避免重新启动你的activity。你可以通过在manifest中设置android:configChanges属性来实现这点。你可以在这里声明activity可以处理的任何配置改变,当这些配置改变时不会重新启动activity,而会调它的onConfigurationChanged()方法。如果改变的配置中包含了你所无法处理的配置(在android:configChanges并未声明),你的activity仍然要被重新启动,onConfigurationChanged()将不会被调用。
android:configChanges=""中可以用的值,请查询具体的androidAPI。
本文主要是站在App的角度,分析屏幕旋转处理的一般使用方法和注意事项。然后简要介绍framework中的实现细节。
屏幕旋转处理的关键为获取屏幕旋转的信息,因此系统中就需要有一种机制,可以实时监测当前系统的屏幕角度。所以,首先,我们就关注一下Framework是如何实现实时监控系统中的oritentation状态的。
在Android的Settings->Display中有Orientation这一设置项。当选中时,屏幕会随设备旋转。
settings设置是在文件DisplaySettings.java中,该项对应的键字符串为:
private static final String KEY_ACCELEROMETER = "accelerometer";
其默认值保存在xml文件中,默认是Enable。UI程序初始化时会根据其值是否在复选框中打勾(代码在onCreate函数中):
public void onCreate(Bundle savedInstanceState)
{
Accelerometer = (CheckBoxPreference) findPreference(KEY_ACCELEROMETER);
mAccelerometer.setPersistent(false);
}
当用户改变了Android的Settings-> Display中有Orientation这一设置项时,会保存起来:
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
if (preference == mAccelerometer) {
RotationPolicy.setRotationLockForAccessibility(
getActivity(), !mAccelerometer.isChecked());
} else if (preference == mNotificationPulse) {
boolean value = mNotificationPulse.isChecked();
Settings.System.putInt(getContentResolver(), Settings.System.NOTIFICATION_LIGHT_PULSE,
value ? 1 : 0);
return true;
}
在文件frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindowManager.java中的mSettingsObserver会随时监控其值,对用户设置做出应:
mSettingsObserver的初始化代码如下:
mSettingsObserver = new SettingsObserver(mHandler);
mSettingsObserver.observe();
@Override public void onChange(boolean selfChange) {
updateSettings();
updateRotation(false);
}
同2.1.3节所述,也是在文件PhoneWindowManager.java中实现实时监控oritentation的状态的。是通过变量mOrientationListener监听oritentation状态的。
初始化代码如下:
mOrientationListener = new MyOrientationListener(mContext, mHandler);
try {
mOrientationListener.setCurrentRotation(windowManager.getRotation());
} catch (RemoteException ex) { }
一旦oritentation状态发生变化,mOrientationListener的onProposedRotationChanged会被调用。其code如下:
class MyOrientationListener extends WindowOrientationListener{
MyOrientationListener(Context context, Handler handler){
super(context, handler);
}
@Override
public voidonProposedRotationChanged(int rotation) {
if (localLOGV) Slog.v(TAG, "onProposedRotationChanged,rotation=" + rotation);
updateRotation(false);
}
}
我们不难发现,onProposedRotationChanged()函数中会调用updateRotation(false),实现对屏幕旋转的处理。
以上就是android实现的如何实时监测oritentation的状态。至于针对该状态,framework做什么样的处理,后续通过继续分析updateRotation(false)来说明。
如果没有针对性地做任何处理的话,默认情况下,当用户手机的重力感应器打开后,旋转屏幕方向,会导致app的当前activity发生onDestroy->onCreate,会重新构造当前activity和界面布局。其实现机制为:当Configuration改变后,ActivityManagerService将会发送"配置改变"的广播,会要求ActivityThread重新启动当前focus的Activity,这是默认情况。
如果想很好地支持屏幕旋转,则建议在res中建立layout-land和layout-port两个文件夹,把横屏和竖屏的布局文件放入对应的layout文件夹中。
如果不申明android:configChanges="",按照Activity的生命周期,都会去执行一次onCreate()方法,而onCreate()方法通常会在显示之前做一些初始化工作。这样就有可能造成重复的初始化,必然降低程序效率,而且更有可能因为重复的初始化而导致数据的丢失。
android:screenOrientation属性的意义为,固定屏幕显示方向
主要的几个属性如下表:
"unspecified" |
默认值,由系统来选择方向。它的使用策略,以及由于选择时特定的上下文环境,可能会因为设备的差异而不同。 |
"user" |
使用用户当前首选的方向。 |
"behind" |
使用Activity堆栈中与该Activity之下的那个Activity的相同的方向。 |
"landscape" |
横向显示(宽度比高度要大) |
"portrait" |
纵向显示(高度比宽度要大) |
"reverseLandscape" |
与正常的横向方向相反显示,在APILevel 9中被引入 |
"reversePortrait" |
与正常的纵向方向相反显示,在APILevel 9中被引入 |
"sensor" |
显示的方向是由设备的方向传感器来决定的。显示方向依赖与用户怎样持有设备;当用户旋转设备时,显示的方向会改变。但是,默认情况下,有些设备不会在所有的四个方向上都旋转,因此要允许在所有的四个方向上都能旋转,就要使用fullSensor属性值。 |
"nosensor" |
屏幕的显示方向不会参照物理方向传感器。传感器会被忽略,所以显示不会因用户移动设备而旋转。除了这个差别之外,系统会使用与“unspecified”设置相同的策略来旋转屏幕的方向。 |
如上表所示,在AndroidManifest.xml对应的activity属性中,添加:
android:screenOrientation="landscape" //横屏 或者 android:screenOrientation="portrait" //竖屏
那么,应用启动后,会固定为指定的屏幕方向,即使屏幕旋转,Activity也不会出现销毁或者转向等任何反应。这是由于PhoneWindowManager通知WindowManagerService更新Orientation的时候,WindowManagerService在函数updateRotationUnchecked()中会通过判断当前的orientation是否有变化:
public void updateRotationUnchecked(t) {
boolean changed;
synchronized(mWindowMap) {
changed = updateRotationUncheckedLocked(false);
if (!changed || forceRelayout) {
getDefaultDisplayContentLocked().layoutNeeded =true;
performLayoutAndPlaceSurfacesLocked();
}
}
if (changed ||alwaysSendConfiguration) {
sendNewConfiguration();
}
}
由于android:screenOrientation将屏幕设定为固定值,因此函数updateRotationUncheckedLocked永远返回false,此时只是重新布局activity并绘制,而没有通知AMSconfiguration的变化。因此即使屏幕旋转,Activity也不会出现销毁或者转向等任何反应。
如果用户的手机没有开启重力感应器或者在AndroidManifest.xml中设置了android:screenOrientation,默认情况下,该Activity不会响应屏幕旋转事件。如果在这种情况下,依然希望Activity能响应屏幕旋转,则添加如下代码:
// activity的onCreate函数中
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
此时,尽管没有在manifest中设置android:configChanges,系统仍能够捕获屏幕旋转事件,这是由于函数setRequestedOrientation本质上是重新设置了当前window关于Orientation的配置属性,并更新oritentation状态。setRequestedOrientation核心代码(ActivityManagerService.java)如下:
setRequestedOrientation( ){
……
//更新Orientation的属性配置
mWindowManager.setAppOrientation(r.appToken, requestedOrientation);
//更新当前系统的oritentation状态
Configurationconfig =mWindowManager.updateOrientationFromAppTokens(
mConfiguration, r.mayFreezeScreenLocked(r.app) ? r.appToken : null);
……
}
当设置android:configChanges="orientation|screenSize"之后,屏幕方向发生改变时,就可以触发onConfigurationChanged事件。其基本的实现原理为:当Configuration改变后,ActivityManagerService会发送"配置改变"的广播,会要求ActivityThread重新启动当前focus的Activity。这是我们前面描述的默认情况。
如果我们配置Activity的android:configChanges信息,那么就可以避免对Activity销毁再重新创建,而是调用activity的onConfigurationChanged方法。由于这部分代码量很大,此处仅仅列出framework流程图:
由上面的流程图可以看出,如果希望捕获屏幕旋转事件,并且不希望activity 被销毁,应该采用如下的方法: (1)在AndroidManifest.xml对应的activity属性中,添加: android:configChanges="orientation|screenSize"
此时如果屏幕旋转,就会触发orientation状态监听器mOrientationListener的函数onProposedRotationChanged,最终导致调用Activity的onConfigurationChanged方法。
注意:如果你的开发API等级等于或高于13,你还需要设置screenSize,因为screenSize会在屏幕旋转时改变。这与android代码升级有关系,记住即可。 (2)在对应的activity中,重载函数onConfigurationChanged()即可。
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged (newConfig);
//……
}
至此,我们就分析完了屏幕旋转相关知识,包括旋转信息的捕获以及后续的处理。屏幕旋转是一个android应用开发中,经常遇到的一个问题。看似简单,但是有几点需要注意:
1.注意区分configChanges和screenOrientation的不同,很多同事可能之前没有关注过。
2.配置android:configChanges时,需要设置screenSize,因为screenSize会在屏幕旋转时改变。否则onConfigurationChanged不会被调用。