如何全局检测屏幕旋转变化?

10

问题

在 Android 服务中,我想检测屏幕旋转的任何更改。通过旋转,我不仅指纵向和横向; 我的意思是 任何 屏幕旋转的更改。此类更改示例包括:

  • 纵向
  • 反向纵向
  • 横向
  • 反向横向

请注意,本问题与设备方向的更改无关。它只涉及屏幕方向/旋转。

我尝试了什么

  • 通过 ACTION_CONFIGURATION_CHANGED 监听 Configuration 更改。这仅涵盖纵向和横向之间的更改,因此 180° 更改不会触发此操作。

为什么我要这样做

我正在开发一款自定义屏幕方向管理应用。

3个回答

5

已批准的答案是可行的,但如果您想要更高分辨率的检测(或支持API 3之前的版本),请尝试OrientationEventListener,它可以报告手机的方向(以度为单位)。

mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

OrientationEventListener orientationEventListener = new OrientationEventListener(this,
        SensorManager.SENSOR_DELAY_NORMAL) {
    @Override
    public void onOrientationChanged(int orientation) {
        Display display = mWindowManager.getDefaultDisplay();
        int rotation = display.getRotation();
        if(rotation != mLastRotation){
             //rotation changed
             if (rotation == Surface.ROTATION_90){} // check rotations here
             if (rotation == Surface.ROTATION_270){} //
        }
        mLastRotation = rotation;
    }
};

if (orientationEventListener.canDetectOrientation()) {
    orientationEventListener.enable();
}

2
是的,我看过 OrientationEventListener,但它监测的是设备的方向。正如我在问题中所说,*"这个问题不涉及设备方向的变化"*。这并不能涵盖诸如因切换应用程序、切换自动旋转或更改 USER_ROTATION 而导致方向改变的情况。 - Sam

4

使用隐藏API

您可以使用名为IWindowManager.watchRotation(IRotationWatcher)的隐藏API实现此操作。从我的测试中,它似乎接受一个回调函数,每次屏幕旋转时都会调用该回调函数。该回调函数还会给出当前屏幕的旋转方向。

作为一个隐藏API,您不能直接调用它。如何使用隐藏API是一个独立的主题。当然,从可维护性的角度来看,这可能不如普通API可靠。

此外,onRotationChanged并不在与您调用watchRotation相同的线程中被调用。一旦进入onRotationChanged,您可能需要将一些工作委托给其他线程,例如UI线程。

兼容性

已在API 14-28中进行测试并工作正常。

示例

以下是使其正常工作的一种方法:

  1. Copy android.view.IRotationWatcher into your app. Make sure to keep it in its original package. This seems to cause the development tools to think your code has access to it while also causing the operating system to still use the real one rather than your own copy.
  2. Use reflection to call watchRotation:

    try {
        Class<?> serviceManager = Class.forName("android.os.ServiceManager");
        IBinder serviceBinder = (IBinder)serviceManager.getMethod("getService", String.class).invoke(serviceManager, "window");
        Class<?> stub = Class.forName("android.view.IWindowManager$Stub");
        Object windowManagerService = stub.getMethod("asInterface", IBinder.class).invoke(stub, serviceBinder);
        Method watchRotation;
        if (Build.VERSION.SDK_INT >= 26)
            watchRotation = windowManagerService.getClass().getMethod("watchRotation", IRotationWatcher.class, int.class);
        else
            watchRotation = windowManagerService.getClass().getMethod("watchRotation", IRotationWatcher.class);
    
        //This method seems to have been introduced in Android 4.3, so don't expect to always find it
        Method removeRotationWatcher = null;
        try {
            removeRotationWatcher = windowManagerService.getClass().getMethod("removeRotationWatcher", IRotationWatcher.class);
        }
        catch (NoSuchMethodException ignored) {}
    
        IRotationWatcher.Stub screenRotationChanged = new IRotationWatcher.Stub() {
            @Override
            public void onRotationChanged(int rotation) throws RemoteException {
                //Do what you want here
                //WARNING: This isn't called in the same thread you were in when you called watchRotation
            }
        };
    
        //Start monitoring for changes
        if (Build.VERSION.SDK_INT >= 26)
            watchRotation.invoke(windowManagerService, screenRotationChanged, Display.DEFAULT_DISPLAY);
        else
            watchRotation.invoke(windowManagerService, screenRotationChanged);
    
        //Stop monitoring for changes when you're done
        if (removeRotationWatcher != null) {
            removeRotationWatcher.invoke(windowManagerService, screenRotationChanged);
    } catch (ClassNotFoundException | ClassCastException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
        throw new RuntimeException(e);
    }
    

我的onRotationChanged从未被调用过。 - MSpeed
@MSpeed,你看过代码中的 "//Stop monitoring for changes when you're done" 吗?你必须将该代码部分移动到适当的位置。(我只是为了测试而添加了注释。) - OneWorld
我的新的IRotationWatcher源代码,因为grep.com已经崩溃了:https://code.yawk.at/android/android-9.0.0_r35/android/view/IRotationWatcher.java - OneWorld
我正在Android 4.3、5和13上接收事件。 - OneWorld

-2

另一种方法(而不是上面学院提供的方法)是在onCreate中手动检查旋转角度。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    Display display = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
    int rotation = display.getRotation();
    if (rotation == Surface.ROTATION_180) { // reverse portrait
        setContentView(R.layout.main_reverse_portrait);
    } else {  // for all other orientations
        setContentView(R.layout.main);
    }
    ...
}

这并没有告诉我屏幕方向何时改变; 它只告诉我在那一时刻的当前状态。 - Sam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接