Android 屏幕旋转流程分析

一、概述

        Android 系统的屏幕能旋转的前提是设备需要具有sensor硬件设备,sensor实时将设备的旋转数据上报给上层,上层对上包上来的数据进行处理,并且改变屏幕的坐标和方向,最后呈现在我们面前的就是屏幕的正常旋转。所以下面将从Android Setting中自动旋转开关、AndroidManifest中Activity指定screenOrientation标签、通过Activity的setRequestedOrientation方法设置以及底层sensor上报到上层屏幕旋转四个流程分析屏幕的旋转,并且会在第六步分析常见的需求。

二、Android Setting中自动旋转开关流程

        Android 原生Settings中的辅助功能中有很多关于显示的接口,比如三指放大等等,而屏幕旋转的开关也在其中。具体就是AccessibilitySettings.java(packages/apps/Settings//src/com/android/settings/accessibility/AccessibilitySettings.java)中,整体的流程图如下图2-1所示,下面从Setting模块开始分析自动旋转开关流程。

Android 屏幕旋转流程分析_第1张图片

图2-1 屏幕自动旋转开关流程
 //packages/apps/Settings//src/com/android/settings/accessibility/AccessibilitySettings.java
 @Override                                                                               
 public boolean onPreferenceTreeClick(Preference preference) {                           
     if (mToggleHighTextContrastPreference == preference) {                              
         handleToggleTextContrastPreferenceClick();                                      
         return true;                                                                    
     ....  
     } else if (mToggleLockScreenRotationPreference == preference) {                     
         handleLockScreenRotationPreferenceClick();                                      
         return true;                                                                    
     ....                                    
     }                                                                                   
     return super.onPreferenceTreeClick(preference);                                     
 }     
//最后Settings是调用RotationPolicy中的相关方法进一步处理
private void handleLockScreenRotationPreferenceClick() {          
    RotationPolicy.setRotationLockForAccessibility(getActivity(), 
            !mToggleLockScreenRotationPreference.isChecked());    
}                                                                 

        Settings中实现很简单最后调用到 RotationPolicy.java(frameworks/base/core/java/com/android/internal/view/RotationPolicy.java)中的setRotationLockForAccessibility方法实现屏幕是否自动旋转。

//frameworks/base/core/java/com/android/internal/view/RotationPolicy.java 
public static final int NATURAL_ROTATION = getNaturalRotation();
 //通过ro.primary_display.user_rotation属性来获取默认的屏幕方向
private static int getNaturalRotation() {                                           
     int rotation = SystemProperties.getInt("ro.primary_display.user_rotation", 0);  
     switch (rotation) {                                                             
         case 90:                                                                    
             return Surface.ROTATION_90;                                             
         case 180:                                                                   
             return Surface.ROTATION_180;                                            
         case 270:                                                                   
             return Surface.ROTATION_270;                                            
         default:                                                                    
             break;                                                                  
     }                                                                               
     return Surface.ROTATION_0;                                                      
 }                                                                                   
public static void setRotationLockForAccessibility(Context context, final boolean enabled) {   
     Settings.System.putIntForUser(context.getContentResolver(),                                
             Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, enabled ? 1 : 0,      
                     UserHandle.USER_CURRENT);                                                  
     //最后调用setRotationLock来实现,NATURAL_ROTATION为默认的屏幕方向                                                                              
     setRotationLock(enabled, NATURAL_ROTATION);                                                
 }                                                                                              

        该方法中会设置Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY字段:

  • 0 表示自动旋转屏幕打开,屏幕解冻,可以自动旋转
  • 1 表示自动旋转屏幕关闭,屏幕固定锁死

可以通过下面手动adb 命令打开和关闭屏幕旋转功能

adb shell settings put system hide_rotation_lock_toggle_for_accessibility 0

        我们继续回到setRotationLockForAccessibility方法中,最后调用setRotationLock这个接口继续执行,注意此方法的第二个参数是默认的屏幕方向,是通过surfaceFlinger在初始化时候的设置的ro.primary_display.user_rotation这个属性获得。

//frameworks/base/core/java/com/android/internal/view/RotationPolicy.java 
private static void setRotationLock(final boolean enabled, final int rotation) {        
     new AsyncTask<Boolean, Void, Void>() {                                              
         @Override                                                                       
         protected Void doInBackground(Boolean... params){                               
             try {                                                                       
                 IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); 
                 //这里的params[0]就是传入的第一个参数enable,如果为true,就是冻结屏幕,否则解冻 	
                 if (params[0]) {                                                        
                     wm.freezeRotation(rotation);                                        
                 } else {                                                                
                     wm.thawRotation();                                                  
                 }                                                                       
             } catch (RemoteException exc) {                                             
                 Log.w(TAG, "Unable to save auto-rotate setting");                       
             }                                                                           
             return null;                                                                
         }                                                                               
     }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, enabled);                       
 }                                                                                       

        IWindowManager的实现类为WMS(WindowMangerService),最后调用到WMS中的freezeRotation方法进行冻结屏幕,防止屏幕自动旋转,调用thawRotation方法解冻屏幕,注意此时freezeRotation方法传入屏幕旋转的方向,作为固定的屏幕的方向,例如 冻结前是竖屏,冻结后就是竖屏,反之为横屏。

//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public void freezeRotation(int rotation) {                                             
    freezeDisplayRotation(Display.DEFAULT_DISPLAY, rotation);                          
}   

@Override                                                                              
public void freezeDisplayRotation(int displayId, int rotation) {                       
    // TODO(multi-display): Track which display is rotated.                            
    ....                                     
    try {                                                                              
        synchronized (mGlobalLock) {                                                   
            //1: 调用DisplayContent的getDisplayRotation()获取到DisplayRotation
            //然后调用DisplayRotation的freezeRotation方法设置当前用户选择的屏幕方向
            display.getDisplayRotation().freezeRotation(rotation);                     
        }                                                                              
    } finally {                                                                        
        Binder.restoreCallingIdentity(origId);                                         
    }                                                                           
    //2: 判断屏幕是否旋转,通知Config发生变化,如果布局改变,需要重新绘制布局                               
    updateRotationUnchecked(false, false);                                             
}


//解冻屏幕的方式类似
@Override                                                                             
public void thawRotation() {                                                          
    thawDisplayRotation(Display.DEFAULT_DISPLAY);                                     
}                                                                                     
                                                                                      
/**                                                                                   
 * Thaw rotation changes.  (Disable "rotation lock".)                                 
 * Persists across reboots.                                                           
 */                                                                                   
@Override                                                                             
public void thawDisplayRotation(int displayId) {                                      
    ...                                                                                                           
    long origId = Binder.clearCallingIdentity();                                      
    try {                                                                             
        synchronized (mGlobalLock) {                                                  
            ...
            //1: 调用DisplayRotation的thawRotation()方法解冻屏幕
            display.getDisplayRotation().thawRotation();
        }                                                                             
    } finally {                                                                       
        Binder.restoreCallingIdentity(origId);                                        
    }                                                                                 
    //2:判断屏幕是否旋转,通知Config发生变化,如果布局改变,需要重新绘制布局                              
    updateRotationUnchecked(false, false);                                            
}                                                                                                                             

        WMS中屏幕锁定和屏幕自动旋转两个实现很相似,主要是调用DisplayRotation的freezeRotation方法锁定用户指定的屏幕方向,调用thawRotation方法,解锁用户固定屏幕,恢复屏幕自动旋转。最后调用updateRotationUnchecked,发送新的Configuration变化,以及如果布局发生变化,也会重新计算布局。

//frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java 
void freezeRotation(int rotation) {                                        
    rotation = (rotation == -1) ? mDisplayContent.getRotation() : rotation;
    setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation);   
}                                                                          
                                                                    
void thawRotation() {                                                      
    setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE, mUserRotation);
}                                                                          

        DisplayRotation中最后都是调用setUserRotation方法处理,传入的参数不通,第一个参数为用户选择的选择模式(自动旋转或者屏幕锁定);第二个参数为用户选择的屏幕方向。

//frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java
/** When not otherwise specified by the activity's screenOrientation, rotation should be 
 * determined by the system (that is, using sensors). */                                 
public final int USER_ROTATION_FREE = 0;    
/** When not otherwise specified by the activity's screenOrientation, rotation is set by 
 * the user. */                                                                          
public final int USER_ROTATION_LOCKED = 1;                                                                               
  • WindowManagerPolicy.USER_ROTATION_LOCKED: 屏幕的旋转将和系统的sensor保持一致。值得注意的是,这个生效的前提是activity没有设置screenOrientation属性。
  • WindowManagerPolicy.USER_ROTATION_FREE :屏幕的旋转是由用户选择而定,前提也是需要activity没有设置screenOrientation属性。
//frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java 
private void setUserRotation(int userRotationMode, int userRotation) {                 
    //1: 默认屏幕显示,通过settings数据库的监听,不需要更新内部的值。
    if (isDefaultDisplay) {                                                                    
    // We'll be notified via settings listener, so we don't need to update internal values.
    final ContentResolver res = mContext.getContentResolver();                             
    final int accelerometerRotation =                                                      
            userRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED ? 0 : 1;          
    Settings.System.putIntForUser(res, Settings.System.ACCELEROMETER_ROTATION,             
            accelerometerRotation, UserHandle.USER_CURRENT);                               
    Settings.System.putIntForUser(res, Settings.System.USER_ROTATION, userRotation,        
            UserHandle.USER_CURRENT);                                                      
    return;                                                                                
    }                                                                                                                       
    boolean changed = false;                                                           
    if (mUserRotationMode != userRotationMode) {                                       
        mUserRotationMode = userRotationMode;                                          
        changed = true;                                                                
    }                                                                                  
    if (mUserRotation != userRotation) {                                               
        mUserRotation = userRotation;                                                  
        changed = true;                                                                
    }
    //2: 将屏幕显示配置信息保存到/data/system/display_settings.xml中
    mDisplayWindowSettings.setUserRotation(mDisplayContent, userRotationMode,          
            userRotation);                                                             
    if (changed) {  
        //3: 调用WMS的updateRotation更新屏幕的状态信息
        mService.updateRotation(true /* alwaysSendConfiguration */,                    
                false /* forceRelayout */);                                            
    }                                                                                  
}                                                                                      

如果是默认屏幕,不会修改内部的状态,而是直接通过设置监听Settings数据库实现,对应的Settings数据库字段主要两个:“accelerometer_rotation”和“user_rotation”。

  • accelerometer_rotation: 屏幕锁定为0,支持自动旋转为1;
  • user_rotation:屏幕旋转的方向:0表示竖屏,1表示横屏
//可以通过adb 命令获取该setting字段的值
adb shell settings get system accelerometer_rotation 0
adb shell settings get system user_rotation

        如果不是默认屏幕,则会修改内部的状态值,首先判断用户旋转模式或者屏幕方向是否改变,只要有一个改变就会,下面第三部都是需要通过WMS来更新屏幕的状态的信息(Configuration的改变和是否重新计算布局)。下面调用DisplayWindowSettings的setUserRotation方法在/data/system/display_settings.xml中保存用户设置的屏幕显示状态信息。最后调用WMS的updateRotation来更新屏幕的状态信息。而updateRotation最终还是调用WMS的updateRotationUnchecked方法,只是传入的第一参数为true,即总是需要通知其他组件Configuration的改变。

//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) { 
    ...                                                                                        
    try {                                                                                      
        synchronized (mGlobalLock) {                                                           
            boolean layoutNeeded = false;                                                      
            final int displayCount = mRoot.mChildren.size();
            //这里主要考虑是多屏幕的操作
            for (int i = 0; i < displayCount; ++i) {                                           
                final DisplayContent displayContent = mRoot.mChildren.get(i);                  
                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display"); 
                //1: 调用DisplayContent的updateRotationUnchecked方法更新屏幕旋转状态,返回屏幕旋转是否
                //改变,每个屏幕都有唯一一个DisplayContent实例对应
                final boolean rotationChanged = displayContent.updateRotationUnchecked();      
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);                                      
                                                                                               
                if (!rotationChanged || forceRelayout) {                                       
                    displayContent.setLayoutNeeded();                                          
                    layoutNeeded = true;                   
                } 
                //2:如果屏幕旋转角度改变,或者需要发送Configuration改变,都会调用DisplayContent的
                //sendNewConfiguration方法,通知Configuration的改变
                if (rotationChanged || alwaysSendConfiguration) {                              
                    displayContent.sendNewConfiguration();                                     
                }                                                                              
            }                                                                                                                                                                        
            if (layoutNeeded) {                                                                
                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,                                     
                        "updateRotation: performSurfacePlacement");
                //3: 如果需要重新计算和绘制布局,则调用WindowSurfacePlacer的performSurfacePlacement
                //计算和重新布局layout
                mWindowPlacerLocked.performSurfacePlacement();                                 
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);                                      
            }                                                                                  
        }                                                                                      
    } finally {                                                                                
        Binder.restoreCallingIdentity(origId);                                                 
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);                                              
    }                                                                                          
}                                                                                              

        和屏幕旋转相关的主要是第一点,DisplayContent的updateRotationUnchecked方法更新屏幕旋转状态,并且触发屏幕旋转的动画等一系列动作。

//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java 
boolean updateRotationUnchecked(boolean forceUpdate) {                                           
     ScreenRotationAnimation screenRotationAnimation;
     //1: 如果不是强制状态更新,则会有以下三种情况,不会更新状态
     //  a:屏幕旋转暂停状态时,不能旋转;
     //  b:屏幕旋转动画还没有结束时,不能旋转;
     //  c: 屏状态没有完全更新完,不能旋转
     if (!forceUpdate) {                                                                          
         if (mDeferredRotationPauseCount > 0) {                                                   
             // Rotation updates have been paused temporarily.  Defer the update until            
             // updates have been resumed.                                                        
             if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused.");    
             return false;                                                                        
         }                                                                                        
                                                                                                  
         screenRotationAnimation =                                                                
                 mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);               
         if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {          
             // Rotation updates cannot be performed while the previous rotation change           
             // animation is still in progress.  Skip this update.  We will try updating          
             // again after the animation is finished and the display is unfrozen.                
             if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress."); 
             return false;                                                                        
         }                                                                                        
         if (mWmService.mDisplayFrozen) {                                                         
             // Even if the screen rotation animation has finished (e.g. isAnimating              
             // returns false), there is still some time where we haven't yet unfrozen            
             // the display. We also need to abort rotation here.                                 
             if (DEBUG_ORIENTATION) Slog.v(TAG_WM,                                                
                     "Deferring rotation, still finishing previous rotation");                    
             return false;                                                                        
         }                                                                                        
     }                                                                                            
      
     //2: 如果显示处于disable状态,没法旋转。
     if (!mWmService.mDisplayEnabled) {                                                           
         // No point choosing a rotation if the display is not enabled.                           
         if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled.");    
         return false;                                                                            
     }                                                                                            
                                                                                                  
     final int oldRotation = mRotation;                                                           
     final int lastOrientation = mLastOrientation;   
     //3: 通过屏幕方向得到屏幕旋转角度,即从orientation到rotation
     final int rotation = mDisplayRotation.rotationForOrientation(lastOrientation, oldRotation);                                                 
     boolean mayRotateSeamlessly = mDisplayPolicy.shouldRotateSeamlessly(mDisplayRotation,        
             oldRotation, rotation);                                                              
                                                                                                  
     if (mayRotateSeamlessly) {                                                                   
         final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);              
         if (seamlessRotated != null && !forceUpdate) {                                           
             // We can't rotate (seamlessly or not) while waiting for the last seamless rotation  
             // to complete (that is, waiting for windows to redraw). It's tempting to check      
             // w.mSeamlessRotationCount but that could be incorrect in the case of               
             // window-removal.                                                                   
             return false;                                                                        
         }                                                                                        
                                                                                                  
         // In the presence of the PINNED stack or System Alert                                   
         // windows we unfortunately can not seamlessly rotate.                                   
         if (hasPinnedStack()) {                                                                  
             mayRotateSeamlessly = false;                                                         
         }                                                                                        
         for (int i = 0; i < mWmService.mSessions.size(); i++) {                                  
             if (mWmService.mSessions.valueAt(i).hasAlertWindowSurfaces()) {                      
                 mayRotateSeamlessly = false;                                                     
                 break;                                                                           
             }                                                                                    
         }                                                                                        
     }   
     final boolean rotateSeamlessly = mayRotateSeamlessly;                                                                   
                                                                                      
    if (oldRotation == rotation) {                                                    
        // No change.                                                                 
        return false;                                                                 
    }                                                                                                              
                                                                                      
    if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {                   
        mWaitingForConfig = true;                                                     
    }                                                                                 
                                                                                      
    mRotation = rotation;                                                             
                                                                                      
    mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;              
    mWmService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT, 
            this, WINDOW_FREEZE_TIMEOUT_DURATION);                                    
                                                                                      
    setLayoutNeeded();   
    //4:启动屏幕旋转动画
    final int[] anim = new int[2];                                                    
    mDisplayPolicy.selectRotationAnimationLw(anim);                                   
                                                                                      
    if (!rotateSeamlessly) {                                                          
        mWmService.startFreezingDisplayLocked(anim[0], anim[1], this);                
        // startFreezingDisplayLocked can reset the ScreenRotationAnimation.          
    } else {                                                                          
        // The screen rotation animation uses a screenshot to freeze the screen       
        // while windows resize underneath.                                           
        // When we are rotating seamlessly, we allow the elements to transition       
        // to their rotated state independently and without a freeze required.        
        mWmService.startSeamlessRotation();                                           
    }                                                                                 
                                                                                      
    return true;                                                                      
}                                                                                     

该方法首先说了以下四种情况不能进行屏幕旋转:

  • 如果不是强制状态更新,屏幕旋转暂停状态时,不能旋转;

  • 如果不是强制状态更新,屏幕旋转动画还没有结束时,不能旋转;

  • 如果不是强制状态更新,屏状态没有完全更新完,不能旋转;

  • 如果显示处于disable状态,没法旋转。

        除了以上四种情况外,都是可以进行屏幕旋转,然后,调用DisplayRotation的rotationForOrientation方法,从屏幕旋转角度得到屏幕的方向,即从rotation得到orientation。

//frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
int rotationForOrientation(int orientation, int lastRotation) {  
     //1: 如果是屏幕是用户锁定,禁止自动旋转,则直接返回用户设置的rotation。
     //这个接口主要是通过adb shell wm set-fix-to-user-rotation调用
     if (isFixedToUserRotation()) {                                                      
         return mUserRotation;                                                           
     }
     //2:获取底层sensor上报的rotation
     int sensorRotation = mOrientationListener != null                                   
             ? mOrientationListener.getProposedRotation() // may be -1                   
             : -1;                                                                       
     if (sensorRotation < 0) {                                                           
         sensorRotation = lastRotation;                                                  
     }                                                                                   
     //Dock,HDMI以及Lid等一些状态的设置                                                                                
     final int lidState = mDisplayPolicy.getLidState();                                  
     final int dockMode = mDisplayPolicy.getDockMode();                                  
     final boolean hdmiPlugged = mDisplayPolicy.isHdmiPlugged();                         
     final boolean carDockEnablesAccelerometer =                                         
             mDisplayPolicy.isCarDockEnablesAccelerometer();                             
     final boolean deskDockEnablesAccelerometer =                                        
             mDisplayPolicy.isDeskDockEnablesAccelerometer();                            
      
     //3 获取surfaceFlinger设置的默认的rotation
     int user_rotation = SystemProperties.getInt("ro.primary_display.user_rotation", 0); 
     int defaultRotation = Surface.ROTATION_0;                                           
     final int preferredRotation;                                                        
     switch (user_rotation) {                                                            
         case 90:                                                                        
             defaultRotation = Surface.ROTATION_90;                                      
             break;                                                                      
         case 180:                                                                       
             defaultRotation = Surface.ROTATION_180;                                     
             break;                                                                      
         case 270:                                                                       
             defaultRotation = Surface.ROTATION_270;                                     
             break;                                                                      
         default:                                                                        
             break;                                                                      
     } 
    //4: 根据不同的情况,获取preferredRotation,作为后面获取最终rotation的依据
    if (!isDefaultDisplay) {                                                                  
       // For secondary displays we ignore things like displays sensors, docking mode and    
       // rotation lock, and always prefer user rotation. 
       //多屏幕是不支持旋转,只有默认显示屏上支持(display 0)
       preferredRotation = mUserRotation;      
      //中间主要HDMI,VR,Dock的一些特殊情况处理
      ...
      //应用在activity标签中自定义了screenOrientation与上一次保持一致
    } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {                       
       // Application just wants to remain locked in the last rotation.                      
       preferredRotation = lastRotation;
     //如果不支持自动旋转,则忽略sensor和settings的设置
     //mSupportAutoRotationton是通过config.xml中的config_supportAutoRotation配置得到
    } else if (!mSupportAutoRotation) {                                                       
       // If we don't support auto-rotation then bail out here and ignore                    
       // the sensor and any rotation lock settings.                                         
        preferredRotation = -1; 
    //当Settings中屏幕可以自动旋转时,且满足一下条件中任意一条,rotation取决于sensor数据,否则为上一次rotation
    } else if ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE                   
                 && (orientation == ActivityInfo.SCREEN_ORIENTATION_USER                   
                         || orientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED     
                         || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE  
                         || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT   
                         || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER))     
         || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR                          
         || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR                     
         || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE                
         || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {           
         if (sensorRotation != Surface.ROTATION_180                                           
             || mAllowAllRotations == 1                                                   
             || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR                
             || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {               
               preferredRotation = sensorRotation;                                              
          } else {                                                                             
               preferredRotation = lastRotation;                                                
         }
     //如果Settings中屏幕可以自动旋转关闭,且ActivityInfo为SCREEN_ORIENTATION_NOSENSOR时,
     //使用用户设置的rotation
     } else if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED                 
         && orientation != ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {                    
       // Apply rotation lock.  Does not apply to NOSENSOR.                                 
       // The idea is that the user rotation expresses a weak preference for the direction  
       // of gravity and as NOSENSOR is never affected by gravity, then neither should      
       // NOSENSOR be affected by rotation lock (although it will be affected by docks).    
       preferredRotation = mUserRotation;  
    //其他情况都由应用自己决定
    } else {                                                                                 
       // No overriding preference.                                                         
       // We will do exactly what the application asked us to do. 
       preferredRotation = -1;                                                              
    }                                                                                        
 //5: 计算最后的rotation                                                                                      
 switch (orientation) {
     //如果应用设置了SCREEN_ORIENTATION_PORTRAIT,则roation有两种情况
     //mPortraitRotation或者mUpsideDownRotation
     case ActivityInfoDE .SCREEN_ORIENTATION_PORTRAIT:                                       
         // Return portrait unless overridden.                                            
         if (isAnyPortrait(preferredRotation)) {                                          
             return preferredRotation;                                                    
         }                                                                                
         return mPortraitRotation;                                                        
     //如果应用设置了SCREEN_ORIENTATION_LANDSCAPE,则roation有两种情况
     //mLandscapeRotation或者mSeascapeRotation                                                                                   
     case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:                                      
         // Return landscape unless overridden.                                           
         if (isLandscapeOrSeascape(preferredRotation)) {                                  
             return preferredRotation;                                                    
         }                                                                                
         return mLandscapeRotation;                                                       
     //如果应用设置了SCREEN_ORIENTATION_REVERSE_PORTRAIT,则roation有两种情况
     //mUpsideDownRotation或者mPortraitRotation                                                                                  
     case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:                               
         // Return reverse portrait unless overridden.                                    
         if (isAnyPortrait(preferredRotation)) {                                          
             return preferredRotation;                                                    
         }                                                                                
         return mUpsideDownRotation;                                                      
       //如果应用设置了SCREEN_ORIENTATION_REVERSE_LANDSCAPE,则roation有两种情况
       //mSeascapeRotation或者mLandscapeRotation                                                                                     
     case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:                              
         // Return seascape unless overridden.                                            
         if (isLandscapeOrSeascape(preferredRotation)) {                                  
             return preferredRotation;                                                    
         }                                                                                
         return mSeascapeRotation;                                                        
       //如果应用设置了SCREEN_ORIENTATION_SENSOR_LANDSCAPE或者SCREEN_ORIENTATION_USER_LANDSCAPE,
       //则roation有两种情况mLandscapeRotation、preferredRotation或者lastRotation                                                                                      
     case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:                               
     case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:                                 
         // Return either landscape rotation.                                             
         if (isLandscapeOrSeascape(preferredRotation)) {                                  
             return preferredRotation;                                                    
         }                                                                                
         if (isLandscapeOrSeascape(lastRotation)) {                                       
             return lastRotation;                                                         
         }                                                                                
         return mLandscapeRotation; 
     //如果应用设置了SCREEN_ORIENTATION_SENSOR_PORTRAIT或者SCREEN_ORIENTATION_USER_PORTRAIT,
     //则roation有两种情况mPortraitRotation、preferredRotation或者lastRotation 
     case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:                
     case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:                  
         // Return either portrait rotation.                              
         if (isAnyPortrait(preferredRotation)) {                          
             return preferredRotation;                                    
         }                                                                
         if (isAnyPortrait(lastRotation)) {                               
             return lastRotation;                                         
         }                                                                
             return mPortraitRotation;                                        
                                                                            
        default:                                                             
                // For USER, UNSPECIFIED, NOSENSOR, SENSOR and FULL_SENSOR,      
                // just return the preferred orientation we already calculated.  
                if (preferredRotation >= 0) {                                    
                    return preferredRotation;                                    
                }                                                                
                return defaultRotation;                                          
        }                                                                        
    }                                                                            

该方法主要功能有以下五点:

  1. 如果通过adb shell wm set-fix-to-user-rotation锁定了屏幕,直接返回用户设置的rotation;

  2. 通过OrientationListener获取底层sensor上报的屏幕旋转角度;

  3. 获取surfaceFlinger设置的默认的rotation,通过ro.primary_display.user_rotation属性获得;

  4. 根据不同的情况,计算preferredRotation,作为第5点中真正计算rotation使用;

  5. 真正计算rotation的情况,完全有应用自己的Activity标签决定,这个我们将在下一章详细分析

三、Activity标签指定screenOrientation属性作用

        Android应用程序中,android:screenOrientation用于控制activity启动时方向,取值主要下面的值:

  • unspecified,默认值,由系统决定,不同手机可能不一致;
  • landscape,强制横屏显示;
  • portrait,强制竖屏显;
  • behind,与前一个activity方向相同;
  • sensor,根据物理传感器方向转动,用户90度、180度、270度旋转手机方向,activity都更着变化;
  • sensorLandscape,横屏旋转,一般横屏游戏会这样设置;
  • sensorPortrait,竖屏旋转;
  • nosensor,旋转设备时候,界面不会跟着旋转。初始化界面方向由系统控制;
  • user,用户当前设置的方向;

        应用在扫描安装过程中解析AndroidManifest中各个标签,其中会调用PackageParser的parseActivity方法解析Activity的标签,其中screenOrientation属性标签就是从这个方法中获得,最后赋值给ActivityInfo的screenOrientation中。

//frameworks/base/core/java/android/content/pm/PackageParser.java
private Activity parseActivity(Package owner, Resources res,                                    
        XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs, 
        boolean receiver, boolean hardwareAccelerated)                                          
        throws XmlPullParserException, IOException {                                            
    TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivity); 
    Activity a = new Activity(cachedArgs.mActivityArgs, new ActivityInfo());
    ...                                                       
    a.info.screenOrientation = sa.getInt(                                             
        R.styleable.AndroidManifestActivity_screenOrientation,                    
        SCREEN_ORIENTATION_UNSPECIFIED);  
    ...
    return a;
}

而我们平时在AndroidManifest定义的screenOrientation属性的value值所对应的int值是在attrs_manifest.xml中定义。

                                                                
   <attr name="screenOrientation">                                                         
           
       <enum name="unspecified" value="-1" />                                              
             
       <enum name="landscape" value="0" />                                                 
              
       <enum name="portrait" value="1" />                                                  
                  
       <enum name="user" value="2" />                                                      
                
       <enum name="behind" value="3" />                                                    
                
       <enum name="sensor" value="4" />                                                    
              
       <enum name="nosensor" value="5" />                                                  
       
       <enum name="sensorPortrait" value="7" /> 
                
      <enum name="reverseLandscape" value="8" />                                               
          
      <enum name="reversePortrait" value="9" />                                                
               
      <enum name="fullSensor" value="10" />                                                    
            
      <enum name="userLandscape" value="11" />                                                 
             
      <enum name="userPortrait" value="12" />                                                  
                 
      <enum name="fullUser" value="13" />                                                      
                    
      <enum name="locked" value="14" />                                                        
  attr>                                                                                      

        下面我们从解析到的ActivityInfo中screenOrientation属性使用的地方来分析AndroidManifest中Activity的screenOrientation标签属性的作用。

3.1 在创建App Window token的过程中使用

我们知道Android 每一个Window对应于一个token,每一个应用有唯一的一个token对应,这个在后续分析应用的启动流程window篇中在继续分析。

//framework/base/services/core/java/com/android/server/wm/ActivityRecord.java
void createAppWindowToken() {                                        
      .....                                                                                     
        mAppWindowToken = createAppWindow(mAtmService.mWindowManager, appToken,                 
                task.voiceSession != null, container.getDisplayContent(),                       
                ActivityTaskManagerService.getInputDispatchingTimeoutLocked(this)               
                        * 1000000L, fullscreen,                                                 
                (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, appInfo.targetSdkVersion,          
                info.screenOrientation, mRotationAnimationHint,                                 
                mLaunchTaskBehind, isAlwaysFocusable());                                        
     .....                               
}                                                                                               

这里会创建AppWindowToken对象,并将对应的screenOrientation参数传入赋值给mOrientation成员变量。AppWindowToken里面用到orientation,都是这里获得,并且通过下面几个方法提供给其他模块。

//framework/base/services/core/java/com/android/server/wm/AppWindowToken.java
int getOrientation(int candidate) {                                                             
    if (candidate == SCREEN_ORIENTATION_BEHIND) {                                               
        // Allow app to specify orientation regardless of its visibility state if the current   
        // candidate want us to use orientation behind. I.e. the visible app on-top of this one 
        // wants us to use the orientation of the app behind it.                                
        return mOrientation;                                                                    
    }                                                                                           
                                                                                                
    // The {@link AppWindowToken} should only specify an orientation when it is not closing or  
    // going to the bottom. Allowing closing {@link AppWindowToken} to participate can lead to  
    // an Activity in another task being started in the wrong orientation during the transition.
    if (!(sendingToBottom || getDisplayContent().mClosingApps.contains(this))                   
            && (isVisible() || getDisplayContent().mOpeningApps.contains(this))) {              
        return mOrientation;                                                                    
    }                                                                                           
                                                                                                
    return SCREEN_ORIENTATION_UNSET;                                                            
}                                                                                               
                                                                                                
/** Returns the app's preferred orientation regardless of its currently visibility state. */    
int getOrientationIgnoreVisibility() {                                                          
    return mOrientation;                                                                        
}                                                                                               

在DisplayContent中会通过getOrientation来获取orientation给mLastOrientation。最后调用到 updateRotationUnchecked方法中,该方法已经在第一部分中分析,此处不再分析。值得注意的是,最后调用WMS的updateRotationUnchecked中调用sendNewConfiguration最后同样会调用DisplayContent的applyRotationLocked方法完成最后的设置

//framework/base/services/core/java/com/android/server/wm/DisplayContent.java
int getOrientation() {                                                                                                                          
    final WindowManagerPolicy policy = mWmService.mPolicy;                                                                                      
                                                                                                                                                
    if (mIgnoreRotationForApps) {                                                                                                               
        return SCREEN_ORIENTATION_USER;                                                                                                         
    }                                                                                                                                           
                                                                                                                                                
    if (mWmService.mDisplayFrozen) {                                                                                                            
        if (mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {                                                                   
            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId                                                                    
                    + " is frozen, return " + mLastWindowForcedOrientation);                                                                    
            // If the display is frozen, some activities may be in the middle of restarting, and                                                
            // thus have removed their old window. If the window has the flag to hide the lock                                                  
            // screen, then the lock screen can re-appear and inflict its own orientation on us.                                                
            // Keep the orientation stable until this all settles down.                                                                         
            return mLastWindowForcedOrientation;                                                                                                
        } else if (policy.isKeyguardLocked()) {                                                                                                 
            // Use the last orientation the while the display is frozen with the keyguard                                                       
            // locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED                                                 
            // window. We don't want to check the show when locked window directly though as                                                    
            // things aren't stable while the display is frozen, for example the window could be                                                
            // momentarily unavailable due to activity relaunch.                                                                                
            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display id=" + mDisplayId                                                                    
                    + " is frozen while keyguard locked, return " + mLastOrientation);                                                          
            return mLastOrientation;                                                                                                            
        }                                                                                                                                       
    } else {                                                                                                                                    
        int orientation = mAboveAppWindowsContainers.getOrientation();                                                                          
                                                                                                                                                
        if("homlet".equals(SystemProperties.get("ro.product.platform", "null"))){                                                               
            orientation = "1".equals(SystemProperties.get("ro.sf.disablerotation","0"))?ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:orientation;  
        }                                                                                                                                       
        if(SystemProperties.get("ro.product.name","DTEN_Mate").equals("DTEN_Mate")) {                                                           
            orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;                                                                     
        }                                                                                                                                       
                                                                                                                                                
        if (orientation != SCREEN_ORIENTATION_UNSET) {                                                                                          
            return orientation;                                                                                                                 
        }                                                                                                                                       
    }                                                                                                                                           
                                                                                                                                                
    // Top system windows are not requesting an orientation. Start searching from apps.                                                         
    return mTaskStackContainers.getOrientation();                                                                                               
}                                                                                                                                               

四 、调用setRequestedOrientation设置

每个应用也可以在自己的Activity中调用setRequestedOrientation方法设置屏幕方向。下面从Activity开始分析这个调用流程。

//framework/base/core/java/android/app/Activity.java
 public void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) { 
     if (mParent == null) {                                                                      
         try {                                                                                   
             ......                                                                                 
             ActivityTaskManager.getService().setRequestedOrientation(                           
                     mToken, requestedOrientation);                                              
         } catch (RemoteException e) {                                                           
             // Empty                                                                            
         }                                                                                       
     } else {                                                                                    
         mParent.setRequestedOrientation(requestedOrientation);                                  
     }                                                                                           
 }                                                                                               

这里很简单,ActivityTaskManager.getService()获取的是ActivityTaskManagerService的对象,调用其setRequestedOrientation最后调用是ActivityRecord的setRequestedOrientation方法。

//framework/base/services/core/java/com/android/server/wm/ActivityRecord.java
void setRequestedOrientation(int requestedOrientation) {                                         
    .....                                                                               
    setOrientation(requestedOrientation, mayFreezeScreenLocked(app));                            
    mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged( 
            task.taskId, requestedOrientation);                                                  
} 

private void setOrientation(int requestedOrientation, boolean freezeScreenIfNeeded) {   
    ....
    //调用AppWindowToken的setOrientation方法设置屏幕方向
    mAppWindowToken.setOrientation(requestedOrientation, binder, this);                 
    ....                                           
}                                                                                       

ActivityRecord的setRequestedOrientation方法最后调用AppWindowToken的setOrientation方法,我们直接分析setOrientation方法。

//framework/base/services/core/java/com/android/server/wm/WindowContainer
void setOrientation(int orientation) {                                                           
      setOrientation(orientation, null /* freezeDisplayToken */,                                   
              null /* ActivityRecord */);                                                          
  }                                                                                                
                                                                                                                                                                                             
  void setOrientation(int orientation, @Nullable IBinder freezeDisplayToken,                       
          @Nullable ConfigurationContainer requestingContainer) {                                  
      final boolean changed = mOrientation != orientation;                                         
      mOrientation = orientation;                                                                  
      ....                                                                                          
  }                                                                                                

其实最后仅仅是将最后设置的值赋值给mOrientation,通过getOrientation获取给其他模块使用。

五、相关需求

1 如果是自己开发的应用,则直接在AndroidManifest中将activity的screenOrientation属性设置为sensorLandscape。

2 如果是所有应用都需要修改,则需要在系统代码里面做过滤,根据上面介绍的流程需要做以下修改

//frameworks/base/core/java/android/content/pm/PackageParser.java
//解析应用AndroidMenifest中的Activity的标签
private Activity parseActivity(Package owner, Resources res,                                   
        XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs,
        boolean receiver, boolean hardwareAccelerated)                                         
        throws XmlPullParserException, IOException {                                           
    TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivity);  
    .....
    a.info.screenOrientation = sa.getInt(                                                               
        R.styleable.AndroidManifestActivity_screenOrientation,                                      
        SCREEN_ORIENTATION_UNSPECIFIED);                                                            
    //screenOrientation only display sensorLandscape for Mate                                           
   if(SystemProperties.get("ro.product.name","DTEN_Mate").equals("DTEN_Mate")) {                
      Slog.d(TAG,"DTEN Mate: force to sensorLandscape");                                              
      a.info.screenOrientation = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; 
    }
    .....
}

//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
//在用户主动设置地方过滤掉
void setRequestedOrientation(int requestedOrientation) {                                          
    /*aw: avoid setting orientation for some product*/                                            
    if(SystemProperties.get("ro.product.platform").equals("homlet")) {                            
        /*if (info.toString().contains("com.XX.Activity")*/                                       
        Slog.d(TAG_CONFIGURATION,"homlet skip setRequestedOrientation");                          
        return;                                                                                   
    }                                                                                             
    /*aw: end*/                                                                                   
    /*DTEN: avoid setting orientation for DTEN Mate product*/                                     
    if(SystemProperties.get("ro.product.name","DTEN_Mate").equals("DTEN_Mate")) {                 
        Slog.d(TAG_CONFIGURATION,"DTEN Mate skip setRequestedOrientation");                       
        return;                                                                                   
    }                                                                                             
    /*DTEN: end*/                                                                                 
    setOrientation(requestedOrientation, mayFreezeScreenLocked(app));                             
    mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged(  
            task.taskId, requestedOrientation);                                                   
}

//获取屏幕旋转状态时候,强制返回为SCREEN_ORIENTATION_SENSOR_LANDSCAPE
//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
  int getOrientation() {                                                                                                                           
        .....                                                                                             
         int orientation = mAboveAppWindowsContainers.getOrientation();                                                                
          /*DTEN: avoid setting orientation for DTEN Mate product*/                                                                                                                                        
         if(SystemProperties.get("ro.product.name","DTEN_Mate").equals("DTEN_Mate")) {                                                            
             orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;                                                                      
         }                                                                                                                                        
         /*DTEN: end*/                                                                                                                                       
         if (orientation != SCREEN_ORIENTATION_UNSET) {                                                                                           
             return orientation;                                                                                                                  
         }                                                                                                                                        
     }                                                                                                                                            
                                                                                                                                                  
     // Top system windows are not requesting an orientation. Start searching from apps.                                                          
     return mTaskStackContainers.getOrientation();                                                                                                
 }                                                

七、总结

        至此,Android 屏幕旋转的流程源码分析就结束了,后面会继续发布一些Android 系统源码框架的相关文章,欢迎大家批评指正。

参考:

https://blog.csdn.net/u010871962/article/details/108749099

你可能感兴趣的:(Android,显示系统框架源码分析,android,java,android,studio)