最近有个需求就是程序在后台监听手机的屏幕的旋转方向,废话不多说,先看看效果:
摸摸头~,既然是监听屏幕的旋转方向,那就需要要弄明白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
可以看到,我们成功的监听到了屏幕方向的旋转!