個人部落格:
http://www.milovetingting.cn
前言
壓力測試中,一般會用到自動化測試。準備寫一個APP,可以記錄螢幕上的點選事件,然後通過shell指令來模拟自動執行。shell指令,比較容易實作。那麼,關鍵的一步是擷取點選的坐标。對于Android來說,為便于開發者調試,Android系統中的"開發者選項"中,有一個"指針位置"的選項。打開這個選項,點選螢幕,就會顯示目前點選的位置坐标。接下來,來看一下打開選項的過程。
開發者選項頁面
"開發者選項"的源碼位于packages/apps/settings/src/com/android/settings/DevelopmentSettings.java檔案中。
private SwitchPreference mPointerLocation;
在onCreate()方法中初始化:
mPointerLocation = findAndInitSwitchPref(POINTER_LOCATION_KEY);
findAndInitSwitchPref()方法:
private SwitchPreference findAndInitSwitchPref(String key) {
SwitchPreference pref = (SwitchPreference) findPreference(key);
if (pref == null) {
throw new IllegalArgumentException("Cannot find preference with key = " + key);
}
mAllPrefs.add(pref);
mResetSwitchPrefs.add(pref);
return pref;
}
當點選選項開關切換後,會把目前的開關狀态存入Settings資料庫。
private void writePointerLocationOptions() {
Settings.System.putInt(getActivity().getContentResolver(),
Settings.System.POINTER_LOCATION, mPointerLocation.isChecked() ? 1 : 0);
}
PhoneWindowManager
PhoneWindowManager的源碼位于framework/base/services/core/java/com/android/server/policy/PhoneWindowManager.java檔案中。
PhoneWindowManager會監聽Settings.System.POINTER_LOCATION字段的變化。
class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
super(handler);
}
void observe() {
// Observe all users' changes
ContentResolver resolver = mContext.getContentResolver();
...
resolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.POINTER_LOCATION), false, this,
UserHandle.USER_ALL);
...
updateSettings();
}
@Override public void onChange(boolean selfChange) {
updateSettings();
updateRotation(false);
}
}
當這個值發生變化時,在updateSettings()方法中調用:
public void updateSettings() {
ContentResolver resolver = mContext.getContentResolver();
boolean updateRotation = false;
synchronized (mLock) {
...
if (mSystemReady) {
int pointerLocation = Settings.System.getIntForUser(resolver,
Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT);
if (mPointerLocationMode != pointerLocation) {
mPointerLocationMode = pointerLocation;
mHandler.sendEmptyMessage(pointerLocation != 0 ?
MSG_ENABLE_POINTER_LOCATION : MSG_DISABLE_POINTER_LOCATION);
}
}
...
}
synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
PolicyControl.reloadFromSetting(mContext);
}
if (updateRotation) {
updateRotation(true);
}
}
在這個方法中,會通過Handler能送一個Message去處理。
private class PolicyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ENABLE_POINTER_LOCATION:
enablePointerLocation();
break;
case MSG_DISABLE_POINTER_LOCATION:
disablePointerLocation();
break;
...
}
}
}
如果打開了"指針位置"的選項開關,那麼會調用enablePointerLocation()方法
private void enablePointerLocation() {
if (mPointerLocationView == null) {
mPointerLocationView = new PointerLocationView(mContext);
mPointerLocationView.setPrintCoords(false);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
lp.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
if (ActivityManager.isHighEndGfx()) {
lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
lp.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
}
lp.format = PixelFormat.TRANSLUCENT;
lp.setTitle("PointerLocation");
WindowManager wm = (WindowManager) mContext.getSystemService(WINDOW_SERVICE);
lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
wm.addView(mPointerLocationView, lp);
mWindowManagerFuncs.registerPointerEventListener(mPointerLocationView);
}
}
在這個方法中,首先初始化一個PointerLocationView對象,然後設定WindowManager.LayoutParams,然後将PointerLocationView執行個體添加到window中。再通過WindowManagerFuncs注冊監聽。
當螢幕上有點選時,會回調PointerLocationView的onPointerEvent()方法:
@Override
public void onPointerEvent(MotionEvent event) {
...
}
通過反射可以擷取到PointerLocationView的執行個體,但是無法擷取到WindowManagerFuncs執行個體。WindowManagerFuncs是在PhoneWindowManager的init()方法中初始化的。
@Override
public void init(Context context, IWindowManager windowManager,
WindowManagerFuncs windowManagerFuncs) {
mContext = context;
mWindowManager = windowManager;
mWindowManagerFuncs = windowManagerFuncs;
...
對于WindowManager的流程不了解。這種方法看來是行不通了。。。
在網上查了相關的資料,還有種方法是通過adb的getevent指令來擷取/dev/input/路徑下的event事件資料,然後解析相關資料。不過對于這塊也不熟悉,就沒有再深入研究。
總的來說,開發基于Android的模拟點選的應用是以失敗告終。後面有時間再研究下是否有其它方法可以實作。