Android后台监听全局屏幕旋转

最近有个需求就是程序在后台监听手机的屏幕的旋转方向,废话不多说,先看看效果:

在这里插入图片描述
摸摸头~,既然是监听屏幕的旋转方向,那就需要要弄明白Android的屏幕是由谁控制的?,方向又是怎么控制的?

带着问题我们来一探究竟:

手机的屏幕管理类是WindowManager,那是不是可先去WindowManager类看看,从这个类里边并没法看到跟屏幕方向有关系方法~,在看该类的时候又发现了另一个类:

public class IWindowManagerImpl implements IWindowManager {
	...
}

通过对源码的一番撕扯,终于找到了一点蛛丝马迹:

public int watchRotation(IRotationWatcher arg0, int arg1) throws RemoteException

通过Android Studio看上边这个类会发现它实现的是IWindowManager,报红了,这个文件经过查询Android源代码发现是一个aidl文件,看看这个文件,最终发现屏幕的旋转方向监听是在IWindowManager.aidl文件中处理的,所以我们先看看这个文件:(sdk版本:7.1.1_r28)

package android.view;

/**
* System private interface to the window manager.
*
* {@hide}
*/
interface IWindowManager
{
    ....

    sdk<=25
    /**
     * Retrieve the current screen orientation, constants as per
     * {@link android.view.Surface}.
     */
    int getRotation();

    sdk<=25
    /**
     * Watch the rotation of the screen.  Returns the current rotation,
     * calls back when it changes.
     */
    int watchRotation(IRotationWatcher watcher);
    
    sdk>25
    /**
    * Watch the rotation of the specified screen.  Returns the current rotation,
    * calls back when it changes.
    */
    int watchRotation(IRotationWatcher watcher, int displayId);

    /**
     * Remove a rotation watcher set using watchRotation.
     * @hide
     */
    void removeRotationWatcher(IRotationWatcher watcher);

    /**
     * Lock the device orientation to the specified rotation, or to the
     * current rotation if -1.  Sensor input will be ignored until
     * thawRotation() is called.
     * @hide
     */
    void freezeRotation(int rotation);

       ....
}

这里主要看几个跟屏幕方向有关的方法,由于不同的sdk版本里边的函数也有所不同。所以我的想法就是通过这个IWindowManager接口来实现屏幕方向的监听,那么首先我们就需要获取到IWindowManager的实例。

再看看IWindowManager.aidl的实现类的部分代码,这里我们只需要关注如何获取IWindowManager的实例:


```java
public interface IWindowManager extends IInterface {
    void getBaseDisplaySize(int paramInt, Point paramPoint) throws RemoteException;


    void getInitialDisplaySize(int paramInt, Point paramPoint) throws RemoteException;


    void getRealDisplaySize(Point paramPoint) throws RemoteException;


    int getRotation() throws RemoteException;


    void removeRotationWatcher(IRotationWatcher paramIRotationWatcher) throws RemoteException;


    public static abstract class Stub extends Binder implements IWindowManager {
        private static final String DESCRIPTOR = "android.view.IWindowManager";


        static final int TRANSACTION_getBaseDisplaySize = 3;


        static final int TRANSACTION_getInitialDisplaySize = 1;


        static final int TRANSACTION_getRealDisplaySize = 2;


        static final int TRANSACTION_getRotation = 4;


        static final int TRANSACTION_removeRotationWatcher = 5;


        public Stub() {
            attachInterface(this, "android.view.IWindowManager");
        }


        public static IWindowManager asInterface(IBinder param1IBinder) {
            if (param1IBinder == null)
                return null;
            IInterface iInterface = param1IBinder.queryLocalInterface("android.view.IWindowManager");
            return (iInterface != null && iInterface instanceof IWindowManager) ? (IWindowManager) iInterface : new Proxy(param1IBinder);
        }


        public IBinder asBinder() {
            return (IBinder) this;
        }


        public boolean onTransact(int param1Int1, Parcel param1Parcel1, Parcel param1Parcel2, int param1Int2) throws RemoteException {
            Point point = new Point();
            if (param1Int1 != 1) {
                if (param1Int1 != 2) {
                    if (param1Int1 != 3) {
                        if (param1Int1 != 4) {
                            if (param1Int1 != 5) {
                                if (param1Int1 != 1598968902)
                                    return super.onTransact(param1Int1, param1Parcel1, param1Parcel2, param1Int2);
                                param1Parcel2.writeString("android.view.IWindowManager");
                                return true;
                            }
                            param1Parcel1.enforceInterface("android.view.IWindowManager");
                            removeRotationWatcher(IRotationWatcher.Stub.asInterface(param1Parcel1.readStrongBinder()));
                            param1Parcel2.writeNoException();
                            return true;
                        }
                        param1Parcel1.enforceInterface("android.view.IWindowManager");
                        param1Int1 = getRotation();
                        param1Parcel2.writeNoException();
                        param1Parcel2.writeInt(param1Int1);
                        return true;
                    }
                    param1Parcel1.enforceInterface("android.view.IWindowManager");
                    param1Int1 = param1Parcel1.readInt();
                    point = new Point();
                    getBaseDisplaySize(param1Int1, point);
                    param1Parcel2.writeNoException();
                    param1Parcel2.writeInt(1);
                    point.writeToParcel(param1Parcel2, 1);
                    return true;
                }
                point.enforceInterface("android.view.IWindowManager");
                point = new Point();
                getRealDisplaySize(point);
                param1Parcel2.writeNoException();
                param1Parcel2.writeInt(1);
                point.writeToParcel(param1Parcel2, 1);
                return true;
            }
            point.enforceInterface("android.view.IWindowManager");
            param1Int1 = point.readInt();
            Point point = new Point();
            getInitialDisplaySize(param1Int1, point);
            param1Parcel2.writeNoException();
            param1Parcel2.writeInt(1);
            point.writeToParcel(param1Parcel2, 1);
            return true;
        }


        private static class Proxy implements IWindowManager {
            private IBinder mRemote;


            Proxy(IBinder param2IBinder) {
                this.mRemote = param2IBinder;
            }


            public IBinder asBinder() {
                return this.mRemote;
            }


            public void getBaseDisplaySize(int param2Int, Point param2Point) throws RemoteException {
                Parcel parcel1 = Parcel.obtain();
                Parcel parcel2 = Parcel.obtain();
                try {
                    parcel1.writeInterfaceToken("android.view.IWindowManager");
                    parcel1.writeInt(param2Int);
                    this.mRemote.transact(3, parcel1, parcel2, 0);
                    parcel2.readException();
                    if (parcel2.readInt() != 0)
                        param2Point.readFromParcel(parcel2);
                    return;
                } finally {
                    parcel2.recycle();
                    parcel1.recycle();
                }
            }


            public void getInitialDisplaySize(int param2Int, Point param2Point) throws RemoteException {
                Parcel parcel1 = Parcel.obtain();
                Parcel parcel2 = Parcel.obtain();
                try {
                    parcel1.writeInterfaceToken("android.view.IWindowManager");
                    parcel1.writeInt(param2Int);
                    this.mRemote.transact(1, parcel1, parcel2, 0);
                    parcel2.readException();
                    if (parcel2.readInt() != 0)
                        param2Point.readFromParcel(parcel2);
                    return;
                } finally {
                    parcel2.recycle();
                    parcel1.recycle();
                }
            }


            public String getInterfaceDescriptor() {
                return "android.view.IWindowManager";
            }


            public void getRealDisplaySize(Point param2Point) throws RemoteException {
                Parcel parcel1 = Parcel.obtain();
                Parcel parcel2 = Parcel.obtain();
                try {
                    parcel1.writeInterfaceToken("android.view.IWindowManager");
                    this.mRemote.transact(2, parcel1, parcel2, 0);
                    parcel2.readException();
                    if (parcel2.readInt() != 0)
                        param2Point.readFromParcel(parcel2);
                    return;
                } finally {
                    parcel2.recycle();
                    parcel1.recycle();
                }
            }


            public int getRotation() throws RemoteException {
                Parcel parcel1 = Parcel.obtain();
                Parcel parcel2 = Parcel.obtain();
                try {
                    parcel1.writeInterfaceToken("android.view.IWindowManager");
                    this.mRemote.transact(4, parcel1, parcel2, 0);
                    parcel2.readException();
                    return parcel2.readInt();
                } finally {
                    parcel2.recycle();
                    parcel1.recycle();
                }
            }


            public void removeRotationWatcher(IRotationWatcher param2IRotationWatcher) throws RemoteException {
                Parcel parcel1 = Parcel.obtain();
                Parcel parcel2 = Parcel.obtain();
                try {
                    parcel1.writeInterfaceToken("android.view.IWindowManager");
                    if (param2IRotationWatcher != null) {
                        IBinder iBinder = param2IRotationWatcher.asBinder();
                    } else {
                        param2IRotationWatcher = null;
                    }
                    parcel1.writeStrongBinder((IBinder) param2IRotationWatcher);
                    this.mRemote.transact(5, parcel1, parcel2, 0);
                    parcel2.readException();
                    return;
                } finally {
                    parcel2.recycle();
                    parcel1.recycle();
                }
            }
        }
    }
}

代码可以看到,我们可以通过IWindowManager中的Stub的asInterface()方法返回IWindowManager的代理类Proxy,然后通过Proxy来调用相应的方法:

//加载得到ServiceManager类,然后得到方法getService。
Method getServiceMethod = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", new Class[]{String.class});
//通过getServiceMethod得到ServiceManager的实例(隐藏类,所以使用Object)
Object ServiceManager = getServiceMethod.invoke(null, new Object[]{"window"});
//通过反射的到Stub
Class<?> cStub = Class.forName("android.view.IWindowManager$Stub");
//得到Stub类的asInterface 方法
Method asInterface = cStub.getMethod("asInterface", IBinder.class);
//然后通过类似serviceManager.getIWindowManager的方法获取IWindowManager的实例
Object IWindowManager=asInterface.invoke(null, ServiceManager);

这里就拿到了IWindowManager的代理类,然后我们通过反射获取到watchRotation()函数:

Method watchRotation = IWindowManager.getClass().getDeclaredMethod("watchRotation",参数类型)

这里获取watchRotation函数需要知道它的参数类型以及参数的个数:

public static Class<?>[] getMethodParamTypes(Class<?> clazz, String methodName) {
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        Log.e("Service", "method = " + method.toGenericString());
        if (methodName.equals(method.getName())) {
            return method.getParameterTypes();
        }
    }
    return null;
}

修改watchRotation函数的获取方法:

Class<?> paramTypes = getMethodParamTypes(IWindowManager.getClass(),"watchRotation")
Method watchRotation = IWindowManager.getClass().getDeclaredMethod("watchRotation",paramTypes)

这里可以打印参数类型看看:

sdk>25
public int android.view.IWindowManager$Stub$Proxy.watchRotation(android.view.IRotationWatcher,int) throws android.os.RemoteException

sdk<=25
public int android.view.IWindowManager$Stub$Proxy.watchRotation(android.view.IRotationWatcher) throws android.os.RemoteException

需要一个IRotationWatcher和int类型的参数,IRotationWatcher接口后边讲。这里需要注意我们最开始说的版本兼容问题,因为我这里是在android 9手机上测试的,所以这里的watchRotation方法多了个int类型的参数,因此我们执行watchRotation方法的时候需要做版本适配:

public static int watchRotation(IRotationWatcher watcher) {
    int rotation = -1;
    try {
        Object iWindowManager = getIWindowManager();
        Class<?>[] paramTypes = ClassUtils.getMethodParamTypes(iWindowManager.getClass(), "watchRotation");
        Method watchRotation = ClassUtils.getMethod("watchRotation", iWindowManager.getClass(), paramTypes);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//这里做个适配
            rotation = (int) watchRotation.invoke(iWindowManager, watcher, 0);
        } else {
            rotation = (int) watchRotation.invoke(iWindowManager, watcher);
        }
        Log.e("Service", "rotation = " + rotation);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return rotation;
}

接下来看看IRotationWatcher这个接口,查询资料发现也是一个Binder对象。思考一下,手机屏幕的旋转显然是在系统进程中监听的,所以这里就需要跨进程通讯获取到屏幕方向,先看看这个文件:

//RotationWatcher.aidl

package android.view;

/**
* {@hide}
*/
interface IRotationWatcher {
    void onRotationChanged(int rotation);
}

这里的IRotationWatcher是一个aidl接口,我们通过这个接口实现屏幕方向旋转结果的回调监听,所以这里我们只需要将这个文件拷贝到我们项目的aidl包中,包名必须是android.view才行:

编译后生成IRotationWatcher.java文件,然后我们创建一个Service,在Service中监听屏幕的旋转:

public class AccessService extends Service {
    public AccessService() {
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e("Service", "onStartCommand rotation = " + WindowUtils.watchRotation(new RotationWatcher()));
        return super.onStartCommand(intent, flags, startId);
    }


    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }


    public static class RotationWatcher extends IRotationWatcher.Stub {


        @Override
        public void onRotationChanged(int rotation) throws RemoteException {
            Log.e("Service", "onRotationChanged = " + rotation);
        }


        @Override
        public IBinder asBinder() {
            return new RotationWatcher();
        }
    }
}

这里我们实现IRotationWatcher的回调方法onRotationChanged,然后创建IRotationWatcher的实例并传给watchRotation方法:

WindowUtils.watchRotation(new RotationWatcher())

ok,到此我们可以运行程序并切到后台后,然后切换手机的横竖屏,结果:

E/Service: onStartCommand rotation = 0

E/Service: onRotationChanged = 1
E/Service: onRotationChanged = 0
E/Service: onRotationChanged = 3

可以看到,我们成功的监听到了屏幕方向的旋转!

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
要在 Android 应用中实现全局监听屏幕点击,可以使用以下步骤: 1. 创建一个继承自 Service 的服务类 ScreenClickService。 2. 在 ScreenClickService 中创建一个继承自 GestureDetector.SimpleOnGestureListener 的手势监听器类 ScreenClickGestureListener。 3. 在 ScreenClickGestureListener 中重写 onSingleTapConfirmed 方法,该方法会在用户单击屏幕时被调用。 4. 在 ScreenClickService 中创建一个 WindowManager,并使用其 addView 方法添加一个全屏幕的透明 View。 5. 在添加的 View 上设置一个 OnTouchListener,将其与 ScreenClickGestureListener 关联。 6. 在 AndroidManifest.xml 文件中注册 ScreenClickService。 以下是示例代码: ```java public class ScreenClickService extends Service { private WindowManager windowManager; private View screenClickView; private GestureDetector gestureDetector; @Override public void onCreate() { super.onCreate(); // 创建一个全屏幕的透明 View screenClickView = new View(this); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); windowManager.addView(screenClickView, layoutParams); // 创建手势监听器 gestureDetector = new GestureDetector(this, new ScreenClickGestureListener()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); // 移除透明 View if (screenClickView != null) { windowManager.removeView(screenClickView); } } @Nullable @Override public IBinder onBind(Intent intent) { return null; } // 手势监听器类 private class ScreenClickGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onSingleTapConfirmed(MotionEvent e) { // 用户单击屏幕时触发的逻辑 Log.d("ScreenClickService", "Screen clicked"); return super.onSingleTapConfirmed(e); } } } ``` 记得在 AndroidManifest.xml 文件中注册 ScreenClickService: ```xml <service android:name=".ScreenClickService" /> ``` 以上代码实现了在 Android 应用中全局监听屏幕点击的功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Mr大伟哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值