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模块开始分析自动旋转开关流程。

在这里插入图片描述

图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中定义。

  <!--frameworks/base/core/res/res/values/attrs_manifest.xml-->                                                              
   <attr name="screenOrientation">                                                         
       <!-- No preference specified: let the system decide the best                        
            orientation.  This will either be the orientation selected                     
            by the activity below, or the user's preferred orientation                     
            if this activity is the bottom of a task. If the user                          
            explicitly turned off sensor based orientation through settings                
            sensor based device rotation will be ignored. If not by default                
            sensor based orientation will be taken into account and the                    
            orientation will changed based on how the user rotates the device.             
            Corresponds to                                                                 
            {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}. -->    
       <enum name="unspecified" value="-1" />                                              
       <!-- Would like to have the screen in a landscape orientation: that                 
            is, with the display wider than it is tall, ignoring sensor data.              
            Corresponds to                                                                 
            {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}. -->      
       <enum name="landscape" value="0" />                                                 
       <!-- Would like to have the screen in a portrait orientation: that                  
            is, with the display taller than it is wide, ignoring sensor data.             
            Corresponds to                                                                 
            {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_PORTRAIT}. -->       
       <enum name="portrait" value="1" />                                                  
       <!-- Use the user's current preferred orientation of the handset.                   
            Corresponds to                                                                 
            {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}. -->           
       <enum name="user" value="2" />                                                      
       <!-- Keep the screen in the same orientation as whatever is behind                  
            this activity.                                                                 
            Corresponds to                                                                 
            {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_BEHIND}. -->         
       <enum name="behind" value="3" />                                                    
       <!-- Orientation is determined by a physical orientation sensor:                    
            the display will rotate based on how the user moves the device.                
            Ignores user's setting to turn off sensor-based rotation.                      
            Corresponds to                                                                 
            {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_SENSOR}. -->         
       <enum name="sensor" value="4" />                                                    
       <!-- Always ignore orientation determined by orientation sensor:                    
            the display will not rotate when the user moves the device.                    
            Corresponds to                                                                 
            {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_NOSENSOR}. -->       
       <enum name="nosensor" value="5" />                                                  
       <!-- Would like to have the screen in landscape orientation, but can                
            use the sensor to change which direction the screen is facing.                 
            Corresponds to                                                                 
            {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_SENSOR_LANDSCAPE}. --
       <enum name="sensorLandscape" value="6" />                                           
       <!-- Would like to have the screen in portrait orientation, but can                 
            use the sensor to change which direction the screen is facing.                 
            Corresponds to                                                                 
            {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_SENSOR_PORTRAIT}. -->
       <enum name="sensorPortrait" value="7" /> 
             <!-- Would like to have the screen in landscape orientation, turned in                   
           the opposite direction from normal landscape.                                       
           Corresponds to                                                                      
           {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_REVERSE_LANDSCAPE}. -->   
      <enum name="reverseLandscape" value="8" />                                               
      <!-- Would like to have the screen in portrait orientation, turned in                    
           the opposite direction from normal portrait.                                        
           Corresponds to                                                                      
           {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_REVERSE_PORTRAIT}. -->    
      <enum name="reversePortrait" value="9" />                                                
      <!-- Orientation is determined by a physical orientation sensor:                         
           the display will rotate based on how the user moves the device.                     
           This allows any of the 4 possible rotations, regardless of what                     
           the device will normally do (for example some devices won't                         
           normally use 180 degree rotation).                                                  
           Corresponds to                                                                      
           {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_FULL_SENSOR}. -->         
      <enum name="fullSensor" value="10" />                                                    
      <!-- Would like to have the screen in landscape orientation, but if                      
           the user has enabled sensor-based rotation then we can use the                      
           sensor to change which direction the screen is facing.                              
           Corresponds to                                                                      
           {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER_LANDSCAPE}. -->      
      <enum name="userLandscape" value="11" />                                                 
      <!-- Would like to have the screen in portrait orientation, but if                       
           the user has enabled sensor-based rotation then we can use the                      
           sensor to change which direction the screen is facing.                              
           Corresponds to                                                                      
           {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER_PORTRAIT}. -->       
      <enum name="userPortrait" value="12" />                                                  
      <!-- Respect the user's sensor-based rotation preference, but if                         
           sensor-based rotation is enabled then allow the screen to rotate                    
           in all 4 possible directions regardless of what                                     
           the device will normally do (for example some devices won't                         
           normally use 180 degree rotation).                                                  
           Corresponds to                                                                      
           {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_FULL_USER}. -->           
      <enum name="fullUser" value="13" />                                                      
      <!-- Screen is locked to its current rotation, whatever that is.                         
           Corresponds to                                                                      
           {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LOCKED}. -->              
      <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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GitFranc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值