关于 Android 4.4 系统屏幕旋转调研






Android 系统屏幕旋转调研

2015-4-20修订版)

 


目录

目录 2

前言 4

1.关于 5

1.1.Framework 层屏幕旋转原理 6

1.1.1.初识SystemUI 6

1.1.2.分析 SystemUI 代码 6

1.1.2.1.AndroidManifest.xml开始 6

1.1.2.2.SystemUIService分析 7

1.1.2.3.Statusbar分析 9

1.1.3.关于Settings.System 16

1.2.旋转监听机制分析 17

1.3.加速度传感器架构分析 17

1.3.1.内核驱动层 18

1.3.2.HAL硬件抽象层 18

1.3.3.JNI 18

1.3.4.Java Framework 18

2.修改快速设置中旋转屏幕按钮 19

2.1.需求: 19

2.2.功能上的修改: 19

2.3.关于图标: 19

2.4.关于图标标签: 20

3.系统设置添加选项 21

3.1.研究休眠菜单原理 21

3.2.模仿休眠菜单原理构建 22

3.2.1.调用读取键值部分 22

3.2.2.updateRotationPreferenceDescription方法的构建 22

3.2.3.设置标签 23

3.2.4.添加菜单标题 24

菜单的设置 24

4.应用程序设置屏幕旋转方法 25


前言

 

本文档是在全志A33平台 Android 系统中,关于屏幕旋转方面的技术调研。

李瀚

2015414


1.关于 

决定屏幕方向有两个层面,一个是 Linux 内核层的 FB 方向(这里暂不深入),驱动上做的修改,另一个是 Android OS 上的修改。

在 Linux 内核初始化以后,init进程启动,紧接着启动了Zygote。ZygoteDalvik虚拟机共享代码、低内存占用以及最小的启动时间成为可能。Zygote是一个虚拟器进程,正如我们在前一个步骤所说的在系统引 导的时候启动。Zygote预加载以及初始化核心库类。通常,这些核心类一般是只读的,也是Android SDK或者核心框架的一部分。在Java虚拟机中,每一个实例都有它自己的核心库类文件和堆对象的拷贝。

 
 
 

如上图,Zygote启动以后,初始化了 System.Settings,开始了SystemUI 和 Settings 的进程、监听屏幕旋转机制和传感器管理器。当来自 SystemUI 或者 Settings 对 System.Settings 数据库的设置生效,监听屏幕旋转机制就打开,开始读取来由自底层设备驱动经过HAL经过JNI到传感器管理器的数据,对屏幕进行旋转设置。

 

1.1.Framework 层屏幕旋转原理

最终实现状态栏的快速设置功能是在 SystemUI 上。所以先从 SystemUI 入手。

1.1.1.初识SystemUI

Android 4.4,是由 Google 公司制作和研发的代号为 KitKat 的手机操作系统,于北京时间 2013 年 9 月 4 日凌晨对外公布了该 Android 新版本的名称,为 Android 4.4 (代号 KitKat 奇巧)。

Android 4.4 同时适用于PhoneTablet(TV),因此,对于 Phone 来说 SystemUI 指的是:StatusBar(状态栏)、 NavigationBar(导航栏)。而对于 Tablet 或者是 TV 来说 SystemUI 指的是:CombinedBar(包括了 StatusBar 和 NavigationBar)

 

Android 的 Phone 的信号,蓝牙标志,Wifi 标志等等这些状态显示标志都会在 StatusBar 上显示。当我们的设备开机后,首先需要给用户呈现的就是各种界面同时也包括了我们的 SystemUI,因此对于整个 Android 系统来说,SystemUI 都有举足轻重的作用。

1.1.2.分析 SystemUI 代码

1.1.2.1.AndroidManifest.xml开始

 

在 Android 4.4 中,Google 整合了 Phone 和 Tablet(TV) 的 SystemUI,也就说可以根据设备的类型自动匹配相应的 SystemUI首先从 AndroidManifest.xml 入手。

 

(省略)

<!-- Wifi Display -->

<uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />

<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />

<uses-permission android:name="android.permission.RECORD_AUDIO" />

<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />

 

<application

android:persistent="true"

android:allowClearUserData="false"

android:allowBackup="false"

android:hardwareAccelerated="true"

android:label="@string/app_label"

android:icon="@*android:drawable/platlogo"

android:process="com.android.systemui"

android:supportsRtl="true">

 

<!-- Broadcast receiver that gets the broadcast at boot time and starts

up everything else.

TODO: Should have an android:permission attribute

-->

<service android:name="SystemUIService"

android:exported="true"

/>

 

<!-- started from PhoneWindowManager

TODO: Should have an android:permission attribute -->

<service android:name=".screenshot.TakeScreenshotService"

android:process=":screenshot"

android:exported="false" />

 

<service android:name=".LoadAverageService"

android:exported="true" />

 

<service android:name=".ImageWallpaper"

android:permission="android.permission.BIND_WALLPAPER"

android:exported="true" />

(省略)

根据以上代码我们可以发现这其中注册了很多 Service,同时也包括了广播。但这里我们只关注 SystemUIService。首先要找到 SystemUIService 是如何启动的。Service 的启动,不外乎 startService(intent) 和 bindService(intent),它们都是以 intent 为对象,由于 intent 的声明也需要 SystemUIService,因此我们可以据此搜索关键词"SystemUIService"

1.1.2.2.SystemUIService分析

经过搜索发现:

frameworks/base/services/java/com/android/server/SystemServer.java

 

static final void startSystemUi(Context context) {

Intent intent = new Intent();

intent.setComponent(new ComponentName("com.android.systemui",

"com.android.systemui.SystemUIService"));

//Slog.d(TAG, "Starting service: " + intent);

context.startServiceAsUser(intent, UserHandle.OWNER);

}

 

这部分是开始了 SystemUI,可以确认下面代码是上面服务的具体实现:

frameworks/base/packages/systemui/src/com/android/systemui/SystemUIService.java 

 

public class SystemUIService extends Service {

    private static final String TAG = "SystemUIService";

 

    /**

     * The classes of the stuff to start.

     */

    private final Class<?>[] SERVICES = new Class[] {

            com.android.systemui.recent.Recents.class,

            com.android.systemui.statusbar.SystemBars.class,

            com.android.systemui.usb.StorageNotification.class,

            com.android.systemui.power.PowerUI.class,

            com.android.systemui.media.RingtonePlayer.class,

            com.android.systemui.settings.SettingsUI.class,

};

 

    /**

     * Hold a reference on the stuff we start.

     */

    private final SystemUI[] mServices = new SystemUI[SERVICES.length];

 

    @Override

    public void onCreate() {

        HashMap<Class<?>, Object> components = 

            new HashMap<Class<?>, Object>();

        final int N = SERVICES.length;

        for (int i=0; i<N; i++) {

            Class<?> cl = SERVICES[i];

            Log.d(TAG, "loading: " + cl);

            try {

                mServices[i] = (SystemUI)cl.newInstance();

            } catch (IllegalAccessException ex) {

                throw new RuntimeException(ex);

            } catch (InstantiationException ex) {

                throw new RuntimeException(ex);

            }

            mServices[i].mContext = this;

            mServices[i].mComponents = components;

            Log.d(TAG, "running: " + mServices[i]);

            mServices[i].start();

        }

    }

(省略)

 

这里初始化了以下类

Recent(最近打开程序)

com.android.systemui.recent.Recents.class

Statusbar(状态栏)

com.android.systemui.statusbar.SystemBars.class

USBStorageNotificationUSB存储设备通知) com.android.systemui.usb.StorageNotification.class

PowerUI

com.android.systemui.power.PowerUI.class

RingtonePlayer

com.android.systemui.media.RingtonePlayer.class

SettingsUI(设置界面)

com.android.systemui.settings.SettingsUI.class

 

4.0以前版本,这里都是整合在一起的,4.4这里开始分离出来,分一个个模块。以后若是针对某个模块分析,在这里开始了分支。

1.1.2.3.Statusbar分析

接下来重点是 Statusbar(状态栏),这个类构建在:

/frameworks/base/packages/systemui/src/com/android/systemui/statusbar/SystemBars.java 分析得出接下来调用了frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java

// 创建一个快速设置菜单QuickSettings的实例 

QuickSettings mQS;

mQS = new QuickSettings(mContext, mSettingsContainer);

 

类原型来自:

frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettings.java 

这里出现了关键代码,添加删除下拉快速设置可以模仿如下内容添加:

// 声明下文所需的RotationLockController

private RotationLockController mRotationLockController;

// 声明下文所需的QuickSettingsModel 

private QuickSettingsModel mModel;

 

// R.bool.quick_settings_show_rotation_lock 作为一个配置设置项

// 位于frameworks/base/packages/systemui/res/value/Config.xml提供了原始的设置

// 由于自适应机制,这里真正调用的是

// 位于frameworks/base/packages/systemui/res/values-sw600dp/Config.xml

// 类似的按钮也可以在这里设置是否需要显示

// DEBUG_GONE_TILES设置调试时屏蔽选项

// 这里作为旋转屏幕部分的开始,用了两个配置项做约束条件

if (mContext.getResources().getBoolean(R.bool.quick_settings_show_rotation_lock)|| DEBUG_GONE_TILES) {

 

        // QuickSettingsBasicTile 提供了监听按钮是否按下的方法。

        // 原型位于:

        // frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettingsBasicTile.java 

        // 新建快速设置的按钮,这里新建了一个按钮的对象

final QuickSettingsBasicTile rotationLockTile

= new QuickSettingsBasicTile(mContext);

 

        // 设置按钮的Listenter,用于监听按钮是否被按下

rotationLockTile.setOnClickListener(new View.OnClickListener() {

          

          // 这里开始创建按钮按下的方法

      public void onClick(View view) {

 

              // 声明了一个locked变量用来表示屏幕当前的锁定状态

              // 取值自isRotationLocked 方法,该方法提供获取屏幕的锁定状态

              // 详见1.1.2.3.1.分析 RotationLockController.isRotationLocked 方法

      final boolean locked = 

                        mRotationLockController.isRotationLocked();

 

              // 调用了RotationLockController.setRotationLocked方法

              // 该方法提供提供了设置了设置屏幕旋转锁定与否的功能

              // 如果传入的参数,即locked的值

              // true,则锁定屏幕旋转

              // false,则解锁屏幕旋转

              // 这里locked取反,使得每次按下按钮时,锁定、解锁状态相互切换

              // 详见 1.1.2.3.2.分析RotationLockController.setRotationLocked方法

  mRotationLockController.setRotationLocked(!locked);

}

});

        

        // addRotationLockTile是由QuickSettingsModel提供的方法

        // 用来设置旋转按钮上面标签、图标的变化。

        // rotationLockTile mRotationLockController 两个类的上下文传入该方法

        // 并创建了刷新的回调函数

        // 详见 1.1.2.3.3.分析 QuickSettingsModel.addRotationLockTile方法

mModel.addRotationLockTile(rotationLockTile, mRotationLockController,

new QuickSettingsModel.RefreshCallback() {

 

                // QuickSettingsTileView 提供了设置按钮各种显示相关的方法和参数

                // State QuickSettingsModel中实现

                // static class State {

                //     int iconId;

                //     String label;

                //     boolean enabled = false;

                // }

                // 作为父类,其生成了很多用于各种设置的子类

                // 其中有QuickSettingsModel.RotationLockState

                // 实现刷新界面的方法,当按钮发生改变的时候

                // 这里被调用到用于刷新按钮上的图标和标签

public void refreshView(QuickSettingsTileView view, State state) {

                        // 声明rotationLockState 用于存储当前屏幕锁定状态

                        // 从传参得到的state

                        // 强制转换成QuickSettingsModel.RotationLockState

                        // 并将其赋值给rotationLockState 

QuickSettingsModel.RotationLockState rotationLockState =

(QuickSettingsModel.RotationLockState) state;

 

                        // Visibility的三个属性

                        // VISIBLE:设置控件可见

                        // INVISIBLE:设置控件不可见

                        // GONE:设置控件隐藏

                        // INVISIBLEGONE的主要区别是:

                        // 当控件visibility属性为INVISIBLE时,

                        // 界面保留了view控件所占有的空间;

                        // 而控件属性为GONE时,界面则不保留view控件所占有的空间。

                        // 这里设置当rotationLockState.visible

                        // true,往view.setVisibility传入View.VISIBLE

                        // false,则传入View.GONE

view.setVisibility(rotationLockState.visible ? View.VISIBLE : View.GONE);

 

                        // 检测图标状态设置是否存在,是的话

                        if (state.iconId != 0) {

// 首先清空已有的图标

                  rotationLockTile.setImageDrawable(null);

                                // 设置图标

rotationLockTile.setImageResource(state.iconId); }

 

                        // 检测标签状态设置是否存在,是的话

                        if (state.label != null) {

                                // 设置标签

                                rotationLockTile.setText(state.label);

}

}

});

        // 传入了按钮的句柄,把该按钮加入并显示出来

        // 详见 1.1.2.3.4.分析addView方法

parent.addView(rotationLockTile);

}

 

1.1.2.3.1.分析 RotationLockController.isRotationLocked 方法

用于判断屏幕旋转是否处于锁定状态

位于:

frameworks/base/packages/systemui/src/com/android/systemui/statusbar/policy/RotationLockController.java

源码:

public boolean isRotationLocked() {

         // 如果旋转开关被支持则调用RotationPolicy.isRotationLocked

         // 用来返回屏幕旋转锁定状态

         // 详见 1.1.2.3.1.1.分析 RotationPolicy.isRotationLocked 方法

         if (RotationPolicy.isRotationLockToggleSupported(mContext)) {

                 return RotationPolicy.isRotationLocked(mContext);

         }

         return false;

}

1.1.2.3.1.1.分析 RotationPolicy.isRotationLocked 方法

用于判断屏幕旋转是否处于锁定状态

位于:

frameworks/base/core/java/com/android/internal/view/RotationPolicy.java

源码:

public static boolean isRotationLocked(Context context) {

        return Settings.System.getIntForUser(

                      context.getContentResolver(),

                      Settings.System.ACCELEROMETER_ROTATION,

                      0,

                      UserHandle.USER_CURRENT) == 0;

}

// 这里调用了返回系统API Settings.System.getIntForUser读取系统设置

// 读取了ACCELEROMETER_ROTATION的值

// ACCELEROMETER_ROTATION 是 Settings.System 的一个键值,

// 决定了是否使用加速度计控制屏幕旋转角度。

// 详见 1.1.3.关于Settings.System

 

1.1.2.3.2.分析RotationLockController.setRotationLocked方法

用于设置屏幕旋转是否处于锁定状态

位于:

frameworks/base/packages/systemui/src/com/android/systemui/statusbar/policy/RotationLockController.java

源码:

public void setRotationLocked(boolean locked) {

        // 如果旋转开关被支持则返回RotationPolicy.isRotationLocked

        // 用于判断屏幕选装是否锁定

        // 详见 1.1.2.3.2.1.分析RotationLockController.setRotationLocked方法

        if (RotationPolicy.isRotationLockToggleSupported(mContext)) {

                RotationPolicy.setRotationLock(mContext, locked);

        }

}

1.1.2.3.2.1.分析RotationLockController.setRotationLocked方法

用于设置屏幕锁定与否

位于:

frameworks/base/core/java/com/android/internal/view/RotationPolicy.java

源码:

public static void setRotationLock(Context context, final boolean enabled) {

        // 向 Settings.System. 写入HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY

        // 如果0,那么不隐藏旋转锁切换的可访问性(尽管它可能因其他原因不可用)
        // 如果1,那么旋转锁切换隐藏。

        Settings.System.putIntForUser(

                context.getContentResolver(),

                Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 

                0,

                UserHandle.USER_CURRENT);

        // AsyncTask,android提供的轻量级的异步类,可以直接继承AsyncTask,

        // 在类中实现异步操作,并提供接口反馈当前异步执行的程度

        // (可以通过接口实现UI进度更新),        

        // 最后反馈执行的结果给UI主线程.

        AsyncTask.execute(new Runnable() {

            public void run() {

                try {

                    //获取系统WindowManager服务,该类提供了关于窗口管理方面的方法

                    IWindowManager wm = 

                            WindowManagerGlobal.getWindowManagerService();

                    // 传参为 true

                    // 则 wm.freezeRotation(-1) 锁定屏幕,并设置 -1 表示默认值

                    // 传参为 false

                    // 则 wm.thawRotation() 设定有加速度计决定屏幕方向

                    if (enabled) {

                        // freezeRotation 详见 1.1.2.3.2.1.分析freezeRotation

                        wm.freezeRotation(-1);

                    } else {

                        // thawRotation详见 1.1.2.3.2.2.分析thawRotation

                        wm.thawRotation();

                    }

                } catch (RemoteException exc) {

                    Log.w(TAG, "Unable to save auto-rotate setting");

                }

            }

        });

}

1.1.2.3.2.1.1.分析freezeRotation

用于锁定屏幕方向

位于:frameworks/base/services/java/com/android/server/wm/WindowManagerService.java

源码:

public void freezeRotation(int rotation) {

// 第一步,检查相对应的权限。

    if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,

"freezeRotation()")) {

throw new SecurityException("Requires SET_ORIENTATION permission");

}

 

    //第二步,如果传入的rotation值小于-1或者大于270度则抛出异常

if (rotation < -1 || rotation > Surface.ROTATION_270) {

throw new IllegalArgumentException("Rotation argument must be -1 or a v alid "+ "rotation constant.");

}

 

if (DEBUG_ORIENTATION)

            Slog.v(TAG, "freezeRotation: mRotation=" + mRotation);

 

long origId = Binder.clearCallingIdentity();

    try {

            // PhoneWindowManager.setUserRotationMode

            // 用于设置屏幕旋转锁定模式

            // 详见 1.1.2.3.2.1.1.进一步分析PhoneWindowManager.setUserRotationMode

mPolicy.setUserRotationMode(

                WindowManagerPolicy.USER_ROTATION_LOCKED,

    rotation == -1 ? mRotation : rotation);

    } finally {

Binder.restoreCallingIdentity(origId);

}

    // 更新旋转状态

    updateRotationUnchecked(false, false);

}

1.1.2.3.2.1.1.1.进一步分析PhoneWindowManager.setUserRotationMode

用于设置旋转模式

位于:

frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

源码:

public void setUserRotationMode(int mode, int rot) {

ContentResolver res = mContext.getContentResolver();

    // 如果传参等于 USER_ROTATION_LOCKED

if (mode == WindowManagerPolicy.USER_ROTATION_LOCKED) {

        // 通过 Settings.System 设置 Settings.System.USER_ROTATION

        // 为传参 rot 的值,即设置屏幕旋转角度

Settings.System.putIntForUser(res,

Settings.System.USER_ROTATION,

rot,

UserHandle.USER_CURRENT);

        // 通过 Settings.System 设置 Settings.System.ACCELEROMETER_ROTATION

        // 0,即设置不根据加速度计改变屏幕方向

     Settings.System.putIntForUser(res,

Settings.System.ACCELEROMETER_ROTATION,

0,

UserHandle.USER_CURRENT);

} else {

            // 通过 Settings.System 设置 Settings.System.ACCELEROMETER_ROTATION

            // 1,即设置根据加速度计改变屏幕方向

Settings.System.putIntForUser(res,

Settings.System.ACCELEROMETER_ROTATION,

1,

UserHandle.USER_CURRENT);

}

}

1.1.2.3.2.1.2.分析thawRotation

用于设置屏幕旋转由加速度计决定

位于:frameworks/base/services/java/com/android/server/wm/WindowManagerService.java

源码:

public void thawRotation() {

    // 第一步,检查权限。

    if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,

         "thawRotation()")) {

         throw new SecurityException("Requires SET_ORIENTATION permission");

    }

 

if (DEBUG_ORIENTATION)

     Slog.v(TAG, "thawRotation: mRotation=" + mRotation);

 

    long origId = Binder.clearCallingIdentity();

try {

        // PhoneWindowManager.setUserRotationMode 用于设置屏幕旋转锁定模式

    // 详见 1.1.2.3.2.1.1.进一步分析PhoneWindowManager.setUserRotationMode

        mPolicy.setUserRotationMode(

            WindowManagerPolicy.USER_ROTATION_FREE,

            777); // rot not used

    } finally {

        Binder.restoreCallingIdentity(origId);

}

    // 更新旋转状态

    updateRotationUnchecked(false, false);

}

1.1.2.3.3.分析 QuickSettingsModel.addRotationLockTile方法

调用了QuickSettingsModel.addRotationLockTile 值,用于设置快速设置的图标、标签,提供其刷新的方法

位于:

frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java

源码:

void addRotationLockTile(QuickSettingsTileView view,

            RotationLockController rotationLockController,

            RefreshCallback cb) {

        // 传入设置菜单的按钮

        mRotationLockTile = view;

        // 传入回调

        mRotationLockCallback = cb;

        // 传入旋转控制类

        mRotationLockController = rotationLockController;

        // 使旋转状态改变图标标签等状态

        // 详见 1.1.2.3.2.1.分析QuickSettingsModel.onRotationLockChanged 方法

        onRotationLockChanged();

}

 

1.1.2.3.1.分析QuickSettingsModel.onRotationLockChanged 方法

用于调用 QuickSettingsModel.onRotationLockChanged ,使旋转状态改变图标标签等状态

位于:

frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java

源码:

void onRotationLockChanged() {

        // 调用 QuickSettingsModel.onRotationLockStateChanged

        // 实现旋转状态改变图标标签等状态

        // 传入判断是否锁屏状态方法及判断按钮是否可见方法

        // 详见 1.1.2.3.2.1.1.分析QuickSettingsModel.onRotationLockStateChanged方法

        onRotationLockStateChanged(

                mRotationLockController.isRotationLocked(),

                mRotationLockController.isRotationLockAffordanceVisible());

}

1.1.2.3.1.1.分析QuickSettingsModel.onRotationLockStateChanged方法

实现旋转状态改变图标标签等状态

位于:

frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java

源码:

public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {

    // 传入锁屏状态是否可见

mRotationLockState.visible = affordanceVisible;

// 传入屏幕旋转是否是锁屏状态

mRotationLockState.enabled = rotationLocked;

// 判断屏幕状态,

// true,锁屏,则mRotationLockState.iconId赋值指向锁屏图标的资源

// false,锁屏,则mRotationLockState.iconId赋值指向旋转图标的资源

    mRotationLockState.iconId = rotationLocked

       ? R.drawable.ic_qs_rotation_locked

       : R.drawable.ic_qs_auto_rotate;

 

// 判断屏幕状态,

// true,锁屏,则mRotationLockState.iconId赋值指向锁屏标签的资源

// false,锁屏,则mRotationLockState.iconId赋值指向旋转标签的资源

    mRotationLockState.label = rotationLocked

       ? mContext.getString(R.string.quick_settings_rotation_locked_label)

       : mContext.getString(R.string.quick_settings_rotation_unlocked_label);

 

   // 刷新状态

   mRotationLockCallback.refreshView(mRotationLockTile, mRotationLockState);

}

1.1.2.3.4.分析addView方法

// 添加子视图。如果没有设置布局参数,那么使用默认值。

// 传入时,我们只传入一个child参数,这时候再加入index参数

public void addView(View child) {

    addView(child, -1);

}

 

// 加入index参数

public void addView(View child, int index) {

    // 尝试用getLayoutParams获取params参数

LayoutParams params = child.getLayoutParams();

// 如果获取不到params

if (params == null) {

    // 尝试使用generateDefaultLayoutParams获得

        params = generateDefaultLayoutParams();

        // 如果获取不到则抛出异常

        if (params == null) {

            throw new IllegalArgumentException(

                "generateDefaultLayoutParams() cannot return null");

        }

}

// 传入params到有三个参数的addView

    addView(child, index, params);

}

 

// 添加一个与指定的子视图布局参数

public void addView(View child, int index, LayoutParams params) {

        if (DBG) {

            System.out.println(this + " addView");

        }

 

        // addViewInner() 将会调用 child.requestLayout() 当设置一个布局参数

        // 所以调用之前调用 requestLayout() ,

        // 因此child的请求会被要求在这一层

        requestLayout();

        // 调用该方法可以是绘图缓存失效

        invalidate(true);

        // 实现显示绘图部分

        addViewInner(child, index, params, false);

}

1.1.3.关于Settings.System

Settings.System.提供类似Settings.System.putIntForUsergetIntForUser等系统设置的API,它的作用可以对系统的全局设置数据库进行操作。具体可以参考官方API文档,或者在阅读源码:

frameworks/base/core/java/android/provider/Settings.java


指令 USER_ROTATION 作用:
当没有其他设置有效时设置默认屏幕旋转方向。
当 ACCELEROMETER_ROTATION 为零,必须设置这个值。

指令 ACCELEROMETER_ROTATION 作用:

控制加速度计是否会被用来改变屏幕取向。如果为0,它将不能使用,除非显式地请求由应用程序;如果1,那么它将使用默认情况下,除非显式由应用程序禁用。

 

 

Settings.System 提供了一套键值对的形式,将一些特定的值以全局的模式保存到 Setting 的数据库中。我们可以通过它提供的 get 或者 put 的方法对其中的数据进行读写。我们也可以在 frameworks 中添加自己所需要的一些特定值。

在应用程序层面,如需操作在 AndroidMainfes.xml 添加权限:

<uses-permission android:name="android.permission.READ_SETTINGS" />

<uses-permission android:name="android.permission.WRITE_SETTINGS" />

 

Settings.System 由 frameworks/base/core/java/android/provider/Setting.java实现。

 

1.2.旋转监听机制分析

Zygote 启动了 WindowManager 以后,调用了 PhoneManager的 rotationForOrientationLw,其实际调用了 WindowOrientationListener,WindowOrientationListener 中监听了 Sensor 的值,对旋转方向进行了判断,实现、对四个不同方向做出响应 。

OrientationListener提供了一个抽象的旋转监听机制。

OrientationEventListener提供了具体的实现。

 

1.3.加速度传感器架构分析

Android 4.4 系统支持了20种传感器,其中加速度传感器提供了手机旋转的矢量。

Android 传感器系统由以下几部分构成:

类别

名称

代码

用户空间

Java应用程序

用户实现

Framework框架层

SensorManager.java

SensorListener.java

SensorEvent.Java

...

JNI

android_hardware_SensorManager.cpp

com_android_server_SensorService.java

HAL硬件抽象层

用户实现

内核空间

设备驱动程序

用户实现

具体硬件

比如加速度传感器

 

 

 

各部分之间架构图如下:

 

1.3.1.内核驱动层

一般加速度模块使用I2C对硬件进行操作,生成了设备节点,对上层提供例如open(), read(), write(), ioctl(), poll()等函数调用的方式

1.3.2.HAL硬件抽象层

与底层设备驱动程序进行交互。

用于交互的关键是文件描述符fdfd通过open()打开加速度传感器的设备节点而得到,

即 fd = open ("/dev/bma220", O_RDONLY);

这里的/dev/bma220这个设备节点是举个例子,因为目前设备上没有对应的硬件。

其他的函数调用如read(), write()等都通过该文件描述符fd加速度传感器进行操作。

1.3.3.JNI

JNI提供了从C++语言到JAVA语言的转换,它Java Framework层提供一系列接口,而这些接口函数的具体实现中,利用例如module->methods->open(),sSensorDevice->data_open(), sSensorDevice->poll()等回调函数与硬件抽象层进行交互。而这些open(), poll()回调函数在硬件抽象层中具体实现。

1.3.4.Java Framework

Framework层提供各种类和类的对象,可作为系统的守护进程运行,也可供上层应用程序的使用。

例如类SensorManager,它作为系统的守护进程在初始化的时候开始运行,其子类SensorThread中的子类 SensorThreadRunnable通过sensors_data_poll()实现了对G-sensor数据的轮询访问,而 sensors_data_poll()通过JNI层转换到硬件抽象层去具体实现poll()


2.修改快速设置中旋转屏幕按钮

2.1.需求:

把快速设置栏中的“屏幕锁定/自由旋转”按钮改成“旋转屏幕”按钮,实现每按一次屏幕旋转递增90°。

 

2.2.功能上的修改:

frameworks/base/core/java/com/android/internal/view/RotationPolicy.java

在 RotationPolicy 类里添加:

public static int rota = 0;

在 setRotationLock 方法里面,删除调用重力自由旋转的可能性:

//if (enabled) {

//    wm.freezeRotation(-1);

//} else {

//    wm.thawRotation();

//}

// 注释掉以上部分

final int cur = wm.getRotation();//获取当前屏幕旋转防线的值,4个方向用0~3表示

rota = cur;

rota++;

if(rota > 3)

    rota = 0;

wm.freezeRotation(rota);//每次被调用锁定旋转的时候设置新的锁定方向,即原来方向的下一个方向

 

2.3.关于图标:

frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java

做如下修改:

public void onRotationLockStateChanged(boolean rotationLocked, 

                                              boolean affordanceVisible) {

        mRotationLockState.visible = affordanceVisible;

        mRotationLockState.enabled = rotationLocked;

//        mRotationLockState.iconId = rotationLocked

//                ? R.drawable.ic_qs_rotation_locked

//                : R.drawable.ic_qs_auto_rotate;

// 注释掉以上部分

 

        // 强制的设置图标固定显示“旋转屏幕图标”

        mRotationLockState.iconId = R.drawable.ic_qs_auto_rotate;

 

 

//mRotationLockState.label = rotationLocked

//    ? mContext.getString(R.string.quick_settings_rotation_locked_label)

//    : mContext.getString(R.string.quick_settings_rotation_unlocked_label);

// 注释掉以上部分

 

        // 强制的设置图标固定显示“旋转屏幕标签”

        mRotationLockState.label = mContext.getString(R.string.quick_settings_rotation_sreen_label);

 

        mRotationLockCallback.refreshView(mRotationLockTile, mRotationLockState);

    }

 

 

2.4.关于图标标签:

frameworks/base/packages/SystemUI/res/values/strings.xml

添加:

<string name="quick_settings_rotation_sreen_label">Rotate Sreen</string>

图标标签的中文翻译:

android/frameworks/base/packages/SystemUI/res/values-zh-rCN/strings.xml

添加:

<string name="quick_settings_rotation_sreen_label">"旋转屏幕"</string>

 


3.系统设置添加选项

思路是模仿休眠菜单进行添加

3.1.研究休眠菜单原理

休眠部分读取设置菜单的主要原理:

packages/apps/Settings/src/com/android/settings/DisplaySettings.java

// 把实际数值更新到界面上

private void updateTimeoutPreferenceDescription(long currentTimeout) {

ListPreference preference = mScreenTimeoutPreference;

    // 声明一个字符串变量summary用于存储临时字符串

     String summary;

     // 若超时时间小于0,这种情况则置空临时字符串

    if (currentTimeout < 0) {

summary = "";

} else {

        // 声明变量entries ,存储读取自preference.getEntries,表示读取到的记录的内容

final CharSequence[] entries = preference.getEntries();

        // 声明变量values ,存储读取自preference.getEntryValues,表示读取到的记录所在的值

final CharSequence[] values = preference.getEntryValues();

        // 如果读取到的记录和记录长度为0,则置空字符串

if (entries == null || entries.length == 0) {

summary = "";

} else {

int best = 0;

 

for (int i = 0; i < values.length; i++) {

long timeout = Long.parseLong(values[i].toString());

        if (currentTimeout >= timeout) {

      best = i;

}

}

// 最后一项永不休眠

        // 如果到了最后一项

if (best == values.length - 1)

            // 设置字符串为“永远休眠”字样

summary = preference.getContext().getString(R.string.never_sleep);

else

            // 设置字符串为XX分钟休眠

summary = 

                 preference.getContext().getString(

                 R.string.screen_timeout_summary,

         entries[best]);

}

}

    // 把设置好的字符串显示到菜单项上

preference.setSummary(summary);

}

 

 

public boolean onPreferenceChange(Preference preference, Object objValue) {

final String key = preference.getKey();

if (KEY_SCREEN_TIMEOUT.equals(key)) {

int value = Integer.parseInt((String) objValue);

try {

            // 调用Settings.System.putInt

            // 设置SCREEN_OFF_TIMEOUT,用于设置屏幕关闭时间的

Settings.System.putInt(

                getContentResolver(), 

                SCREEN_OFF_TIMEOUT, 

                value);

            // 传入最新数值,更新最新的数值到界面上

updateTimeoutPreferenceDescription(value);

} catch (NumberFormatException e) {

Log.e(TAG, "could not persist screen timeout setting", e);

}

}

}

(省略...

3.2.模仿休眠菜单原理构建

3.2.1.调用读取键值部分

实现在设置菜单中设置屏幕旋转角度的核心方法:

使用Settings.System.putInt,对USER_ROTATION的值进行设置。

packages/apps/Settings/src/com/android/settings/DisplaySettings.java

if (KEY_SCREEN_ROTATION.equals(key)) {

    // 读取设置项上的值

int value = Integer.parseInt((String) objValue);

 

try {

        // 通过 Settings.System.putInt 写入系统的数据库

Settings.System.putInt(getContentResolver(), USER_ROTATION, value);

        // 把系统最新值显示到菜单上

updateRotationPreferenceDescription(value);

} catch (NumberFormatException e) {

Log.e(TAG, "could not ...ask Jangel", e);

}

}

3.2.2.updateRotationPreferenceDescription方法的构建

起着把最新设置的值显示到菜单上的作用

packages/apps/Settings/src/com/android/settings/DisplaySettings.java

import static android.provider.Settings.System.USER_ROTATION;

 

private static final int FALLBACK_SCREEN_ROTATION_VALUE = 0;

private static final String KEY_SCREEN_ROTATION = "screen_rotation";

private ListPreference mScreenRotationPreference;

 

mScreenRotationPreference = (ListPreference) findPreference(KEY_SCREEN_ROTATION);

final long currentRotation = Settings.System.getLong(resolver, USER_ROTATION,

FALLBACK_SCREEN_ROTATION_VALUE);

mScreenRotationPreference.setValue(String.valueOf(currentRotation));

mScreenRotationPreference.setOnPreferenceChangeListener(this);

disableUnusableTimeouts(mScreenRotationPreference);

updateRotationPreferenceDescription(currentRotation);

 

private void updateRotationPreferenceDescription(long currentRotation) {

ListPreference preference = mScreenRotationPreference;

String summary;

 

    // 处理不支持的值

if (currentRotation < 0) {

// Unsupported value

summary = "";

} else {

  final CharSequence[] entries2 = preference.getEntries();

    final CharSequence[] values2 = preference.getEntryValues();

  if (entries2 == null || entries2.length == 0) {

summary = "";

} else {

int best = 0;

for (int i = 0; i < values2.length; i++) {

long rota = Long.parseLong(values2[i].toString());

if (currentRotation >= rota) {

best = i;

     }

}

 

summary = preference.getContext().getString(R.string.screen_rotati on_summary,entries2[best]);

}

}

3.2.3.设置标签

添加了设置菜单选项,并为每个选项赋值:

packages/apps/Settings/res/values/arrays.xml

    <!--Display settings.Srceen_rotation. add by Jangel-->

    <string-array name="screen_rotation_entries">

        <item>0° </item> 

        <item>90° </item>

        <item>180° </item>

        <item>270° </item>

    </string-array>

 

    <!-- Do not translate.这部分内容是取其具体数值,不要对其进行语言翻译 -->

    <string-array name="screen_rotation_values" translatable="false">

        <!-- Do not translate. -->

        <item>0</item>

        <!-- Do not translate. -->

        <item>1</item>

        <!-- Do not translate. -->

        <item>2</item>

        <!-- Do not translate. -->

        <item>3</item>

</string-array>

3.2.4.添加菜单标题

packages/apps/Settings/res/values/strings.xml

<!--add by Jangel-->

    <string name="screen_rotation">Rotation</string>

<string name="screen_rotation_summary">Rotate screen <xliff:g id="rotation_description">%1$s</xliff:g></string>

同理,需要对以上xml文件进行中文翻译,这里不再详述。

菜单的设置

packages/apps/Settings/res/xml/display_settings.xml

<!--add by Jangel-->

        <ListPreference

                android:key="screen_rotation"

                android:title="@string/screen_rotation"

                android:summary="@string/screen_rotation_summary"

                android:persistent="false"

                android:entries="@array/screen_rotation_entries"

                android:entryValues="@array/screen_rotation_values" />

 

 

 


4.应用程序设置屏幕旋转方法

//设置为横屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
// 设置为竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

 

也可以通过xml实现:

 

//在配置文件中,设为横
android:screenOrientation="landscape"
//在配置文件中,设为竖屏
android:screenOrientation="portrait"

 

 

 


  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值