最近一直忙着解决公司游戏的Bug,但有些问题是顽固分子,许多个迭代都没解决掉,但不处理总觉有一根刺,在扎着肉。
本篇是记录如何处理手机系统ROM中Framework中报错,可以适用不同的手机厂商。
bugly上的问题:
android.database.CursorWindowAllocationException
Cursor window could not be created from binder.
android.database.CursorWindow.(CursorWindow.java:137)
android.database.CursorWindow.(CursorWindow.java)
android.database.CursorWindow$1.createFromParcel(CursorWindow.java:685)
android.database.CursorWindow$1.createFromParcel(CursorWindow.java:684)
android.database.BulkCursorDescriptor.readFromParcel(BulkCursorDescriptor.java:75)
android.database.BulkCursorDescriptor$1.createFromParcel(BulkCursorDescriptor.java:34)
android.database.BulkCursorDescriptor$1.createFromParcel(BulkCursorDescriptor.java:32)
android.content.ContentProviderProxy.query(ContentProviderNative.java:424)
android.content.ContentResolver.query(ContentResolver.java:541)
android.content.ContentResolver.query(ContentResolver.java:476)
com.huawei.android.hwaps.OperateExperienceLib.isGameInfoExist(OperateExperienceLib.java:497)
com.huawei.android.hwaps.OperateExperienceLib.saveAPSResult(OperateExperienceLib.java:429)
com.huawei.android.hwaps.EventAnalyzed.setAdaptFPS(EventAnalyzed.java:1373)
com.huawei.android.hwaps.EventAnalyzed.processAnalyze(EventAnalyzed.java:1726)
android.view.HwNsdImpl.adaptPowerSave(HwNsdImpl.java:1182)
android.view.ViewRootImpl$EarlyPostImeInputStage.processPointerEvent(ViewRootImpl.java:4648)
android.view.ViewRootImpl$EarlyPostImeInputStage.onProcess(ViewRootImpl.java:4617)
android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4258)
android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6690)
android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6664)
android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6625)
android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6819)
android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:192)
android.os.MessageQueue.nativePollOnce(Native Method)
android.os.MessageQueue.next(MessageQueue.java:356)
android.os.Looper.loop(Looper.java:138)
android.app.ActivityThread.main(ActivityThread.java:6623)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)
从代码报错点来看是,cursor 过多未关闭,存在泄漏,产生的原因可能是cursor未关闭或者fd 过多或者是内存不足。
因报错点发生在华为ROM的framework层,因此要先考虑获取到对应的framework.dex, 查看代码调用流程,再来确定解决方案。
设备信息:
设备机型 | 系统版本 | ROM |
---|---|---|
WAS-AL00 | Android 8.0.0,level 26 | HuaWei/EMOTION |
可考虑下载华为固件Rom 或者通过adb pull 获取该机型的framework.dex, 详细操作,请阅读Android反编译之各大手机厂商的系统(adb pull和Rom包)
经过一些列操作,获取到framework有关的dex,如下图所示:
通过gui 工具打开华为rom 的framework 层源码。
先从ViewRootImpl#EarlyPostImeInputStage的onProcess()
开始入手,追查起。
android/view/ViewRootImpl
:
final class EarlyPostImeInputStage extends InputStage {
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
}
if ((q.mEvent.getSource() & 2) != 0) {
return processPointerEvent(q);
}
return 0;
}
private int processPointerEvent(QueuedInputEvent q) {
MotionEvent event = q.mEvent;
if (ViewRootImpl.this.mTranslator != null) {
ViewRootImpl.this.mTranslator.translateEventInScreenToAppWindow(event);
}
//若是rom 支持aps功能和包名和进程名匹配的情况下
if (HwFrameworkFactory.getHwNsdImpl().isSupportAps() && HwFrameworkFactory.getHwNsdImpl().isGameProcess(ViewRootImpl.this.mContext.getPackageName())) {
HwFrameworkFactory.getHwNsdImpl().initAPS(ViewRootImpl.this.mView.getContext(), ViewRootImpl.this.mView.getResources().getDisplayMetrics().widthPixels, Process.myPid());
HwFrameworkFactory.getHwNsdImpl().adaptPowerSave(event);
}
int action = event.getAction();
if (HwFrameworkFactory.getHwViewRootImpl().filterDecorPointerEvent(ViewRootImpl.this.mContext, event, action, ViewRootImpl.this.mWindowAttributes, ViewRootImpl.this.mDisplay)) {
return 1;
}
//...
return 0;
}
}
先来看下getHwNsdImpl()获取的对象,才能更好的了解源码调用过程。
android/common/HwFrameworkFactory
中:
public static IHwNsdImpl getHwNsdImpl() {
Factory obj = getImplObject();
if (obj != null) {
return obj.getHwNsdImpl();
}
return null;
}
private static Factory getImplObject() {
if (obj != null) {
return obj;
}
synchronized (mLock) {
try {// 反射获取真正的HwFrameworkFactory实现类
obj = (Factory) Class.forName("huawei.android.common.HwFrameworkFactoryImpl").newInstance();
} catch (Exception e) {
Log.e(TAG, ": reflection exception is " + e);
}
}
//...
return null;
}
HwFrameworkFactory 是一个封装的api 类,用于静态方法/工厂类方式,间接调用各种api。
接下来,继续看下huawei/android/common/HwFrameworkFactoryImpl
类中的getHwNsdImpl()
:
public HwNsdImpl getHwNsdImpl() {
return HwNsdImpl.getDefault();
}
接下来看下android/view/HwNsdImpl
:
public static HwNsdImpl getDefault() {
if (mInstance == null) {
mInstance = new HwNsdImpl();
}
return mInstance;
}
经过一些列的追踪,可以了解到ViewRootImpl对象中调用的是HwNsdImpl中的方法。
接下来看下,isGameProcess()
和isSupportAps()
和adaptPowerSave()
等几个方法。
public boolean isSupportAps() {
return SystemProperties.getInt("sys.aps.support", 0) > 0;
}
从上面代码可知,isSupportAps()
是检测系统rom 是否支持aps功能。
接下来看下isGameProcess()
:
private static IEventAnalyzed mEventAnalyzed = null;
public boolean isGameProcess(String pkgName) {
createEventAnalyzed();
if (mEventAnalyzed != null) {
return mEventAnalyzed.isGameProcess(pkgName);
}
return false;
}
public synchronized void createEventAnalyzed() {
if (mEventAnalyzed == null) {
mEventAnalyzed = HwapsWrapper.getEventAnalyzed();
}
}
接下来看下, com/huawei/android/hwaps/HwapsWrapper类中getEventAnalyzed():
public static IEventAnalyzed getEventAnalyzed() {
IHwapsFactory factory = getHwapsFactoryImpl();
return factory != null ? factory.getEventAnalyzed() : null;
}
private static synchronized IHwapsFactory getHwapsFactoryImpl() {
IHwapsFactory iHwapsFactory;
synchronized (HwapsWrapper.class) {
if (mFactory != null) {
iHwapsFactory = mFactory;
} else {
try {
mFactory = (IHwapsFactory) Class.forName("com.huawei.android.hwaps.HwapsFactoryImpl").newInstance();
} catch (ClassNotFoundException e) {
Log.e(TAG, "reflection exception is " + e);
} catch (InstantiationException e2) {
Log.e(TAG, "reflection exception is " + e2);
} catch (IllegalAccessException e3) {
Log.e(TAG, "reflection exception is " + e3);
}
if (mFactory == null) {
Log.e(TAG, "failes to get HwapsFactoryImpl");
}
iHwapsFactory = mFactory;
}
}
return iHwapsFactory;
}
从上面代码可知, HwapsWrapper是一个封装api的类, 其真正调用是HwapsFactoryImpl类。
com/huawei/android/hwaps/HwapsFactoryImpl
:
public class HwapsFactoryImpl implements IHwapsFactory {
//...
public IEventAnalyzed getEventAnalyzed() {
return new EventAnalyzed();
}
}
经过一些列的波折,最终调用链是ViewRootImpl->IHwNsdImpl->EventAnalyzed
类的方法:
com/huawei/android/hwaps/EventAnalyzed
//检查下是否是原始进程对应的包名
private boolean isIdentifyProcess(String strPkgName) {
if (!this.mHasIdentifyProcess) {
if (SystemProperties.get("debug.aps.process.name", "").equals(strPkgName)) {
this.mIsGameProcess = true;
} else {
this.mIsGameProcess = false;
}
this.mHasIdentifyProcess = true;
}
return this.mIsGameProcess;
}
public boolean isGameProcess(String pkgName) {
if (this.mHasIdentifyProcess && !this.mIsGameProcess) {
return this.mIsGameProcess;
}
if (isAPSReady()) {
return isIdentifyProcess(pkgName); // 通常下,app主进程 是返回true
}
return false;
}
从上面可见,若pkgName是app 主进程名,是返回true的。
这里注意点, EventAnalyzed 是com/huawei/android/hwaps/IEventAnalyzed接口的实现类,后面会用到。
public interface IEventAnalyzed {
//......
boolean isGameProcess(String str);
void processAnalyze(int i, long j, int i2, int i3, int i4, long j2);
void setHasOnPaused(boolean z);
}
HwNsdImpl#adaptPowerSave()
是调用EventAnalyzed#processAnalyze()
,因此,接下来看下processAnalyze()
:
public void processAnalyze(int aciton, long eventTime, int x, int y, int pointCount, long downTime) {
//...
else if (CUST_APP_TYPE != mGameType) {
//...
setAdaptFPS(gametype, openGLType, this.mLevel);
}
}
public void setAdaptFPS(int gameType, int openGLType, int level) {
//...
else if (!this.mHasSetFPS) {
mGameType = gameType;
int recommendFPS = computeRecommendFPS(gameType, openGLType, level);
if (gameType == 8 || gameType == 9) {
this.mOperateExLib.saveAPSResult(gameType, recommendFPS, recommendFPS);
} else {
this.mOperateExLib.saveAPSResult(gameType, recommendFPS, mMinFps);
}
//.....
}
}
接下来看下com/huawei/android/hwaps/OperateExperienceLib
的 saveAPSResult()
:
public void saveAPSResult(int gameType, int maxFPS, int minFPS) {
this.mGameType = gameType;
this.mMaxFPS = maxFPS;
this.mMinFPS = minFPS;
if (this.mIsExistInDataBase) {
updateAppInfo();
} else {
insertAppInfo();
}
}
这里发生了一点小变故,可能是同样的手机,同个大编号的系统rom 存在多个小版本,并没有找到isGameInfoExist()方法。
但并不影响,咱们继续分析。看到最后,发现最终是华为系统收集app game 的周期性的fps 信息, 通过广播形式跨进程通信到其他系统app中。
在通信前,会通过cursor查询下该游戏app是否存在,也就是bulgy上的报错点。解决方案,也是基于减少cursor的查询。
1.通过hook 方式,代理调用EventAnalyzed类调用processAnalyze();
2.经过上面的源码分析,可知HwNsdImpl类中mEventAnalyzed属性时hook 点;
3.因IEventAnalyzed是一个抽象接口,考虑动态代理拦截其中的方法调用。
public static void hookHwFrameworkFactory() {
if (isSafeHookHuawei()) {
try {
Class<?> hwNsdImplClass = Class.forName("android.view.HwNsdImpl");
Method getDefaultMethod = hwNsdImplClass.getDeclaredMethod("getDefault");
getDefaultMethod.setAccessible(true);
// 获取HwNsdImpl 类对象
Object hwNsdImplObject = getDefaultMethod.invoke(null);
// 调用createEventAnalyzed(),先构建出真正的EventAnalyzed对象
Method createEventAnalyzedMethod = hwNsdImplClass.getDeclaredMethod("createEventAnalyzed");
createEventAnalyzedMethod.setAccessible(true);
createEventAnalyzedMethod.invoke(hwNsdImplObject);
// 获取真正的EventAnalyzed对象
Field mEventAnalyzedFiled = hwNsdImplClass.getDeclaredField("mEventAnalyzed");
mEventAnalyzedFiled.setAccessible(true);
Object oldFactory = mEventAnalyzedFiled.get(null);
// 构建IEventAnalyzed的代理对类
Class<?> iEventAnalyzedClass = Class.forName("com.huawei.android.hwaps.IEventAnalyzed");
Object proxyFactory = CommonProxy.startHook(oldFactory, iEventAnalyzedClass, new ProxyCallBack() {
@Override
public Object interrupt(CommonProxy proxy, Method method, Object[] args) throws Throwable {
Object result = null;
if (method.getName().equals("processAnalyze")) {
//处理bugly上的问题:https://bugly.qq.com/v2/crash-reporting/crashes/1105308248/33050435/report?pid=1&crashDataType=undefined
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
//6.0到8.1的版本上屏蔽该方法的调用
} else {
result = proxy.doRealInvoke(method, args);
}
} else {
result = proxy.doRealInvoke(method, args);
}
return result;
}
});
mEventAnalyzedFiled.set(null, proxyFactory);
} catch (Exception e) {
e.printStackTrace();
}
}
}
在云真机进行验证发现,mEventAnalyzed是非静态,通过静态方式获取直接报错,可能每个Rom中代码又有新的变化:
08-29 19:39:29.633 W/System.err( 5126): java.lang.NullPointerException: null receiver
08-29 19:39:29.635 W/System.err( 5126): at java.lang.reflect.Field.get(Native Method)
08-29 19:39:29.635 W/System.err( 5126): at com.minitech.miniworld.HuaWeiHook.hookHwFrameworkFactory(HuaWeiHook.java:91)
08-29 19:39:29.635 W/System.err( 5126): at org.appplay.lib.GameBaseActivity$7.run(GameBaseActivity.java:625)
08-29 19:39:29.635 W/System.err( 5126): at android.os.Handler.handleCallback(Handler.java:761)
08-29 19:39:29.635 W/System.err( 5126): at android.os.Handler.dispatchMessage(Handler.java:98)
08-29 19:39:29.635 W/System.err( 5126): at android.os.Looper.loop(Looper.java:156)
08-29 19:39:29.635 W/System.err( 5126): at android.app.ActivityThread.main(ActivityThread.java:6623)
08-29 19:39:29.635 W/System.err( 5126): at java.lang.reflect.Method.invoke(Native Method)
08-29 19:39:29.635 W/System.err( 5126): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942)
08-29 19:39:29.635 W/System.err( 5126): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)
结合报错的机型,如下:
因此调整代码如下:
public class HuaWeiHook {
private static final String TAG = "HuaWeiHook";
/**
* 是否是华为rom
*
* @return
*/
public static boolean isHuWeiRoom() {
final String huawei_room_name = "huawei";
boolean result=false;
try {
String brand = Build.BRAND;
Log.i(TAG," room brand "+brand);
if (!TextUtils.isEmpty(brand)) {
brand = brand.toLowerCase();
}
result=brand.contains(huawei_room_name);
} catch (Exception e) {
e.printStackTrace();
}
if (!result){
try {
String manufacturer = Build.MANUFACTURER;
Log.i(TAG," room manufacturer "+ manufacturer);
if (!TextUtils.isEmpty(manufacturer)) {
manufacturer = manufacturer.toLowerCase();
}
result= manufacturer.contains(huawei_room_name);
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
/**
* 是否支持反射framework 层的api,9.0以下
*
* @return
*/
public static boolean isSupportHook() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.P;
}
/**
* 检查是否满足华为机型反射调用
*
* @return
*/
public static boolean isSafeHookHuawei() {
return isHuWeiRoom() && isSupportHook();
}
private static AtomicBoolean init = new AtomicBoolean(false);
public static void hookHwFrameworkFactory() {
if (init.compareAndSet(false, true)) {
boolean isSafe = isSafeHookHuawei();
Log.i(TAG, " room huawei " + isSafe);
if (isSafe) {// 非华为手机系统,不做处理
try {
Class<?> hwNsdImplClass = Class.forName("android.view.HwNsdImpl");
Method getDefaultMethod = hwNsdImplClass.getDeclaredMethod("getDefault");
getDefaultMethod.setAccessible(true);
// 获取HwNsdImpl 类对象
Object hwNsdImplObject = getDefaultMethod.invoke(null);
// 调用createEventAnalyzed(),先构建出真正的EventAnalyzed对象
Method createEventAnalyzedMethod = hwNsdImplClass.getDeclaredMethod("createEventAnalyzed");
createEventAnalyzedMethod.setAccessible(true);
createEventAnalyzedMethod.invoke(hwNsdImplObject);
// 获取真正的EventAnalyzed对象
Field mEventAnalyzedFiled = hwNsdImplClass.getDeclaredField("mEventAnalyzed");
mEventAnalyzedFiled.setAccessible(true);
//检查是否为静态属性
boolean static_access=false;
Object oldFactory =null;
try {
oldFactory=mEventAnalyzedFiled.get(null);
static_access=true;
}catch (Exception e){
}
if (oldFactory==null){
static_access=false;
oldFactory= mEventAnalyzedFiled.get(hwNsdImplObject);
}
// 构建IEventAnalyzed的代理对类
Class<?> iEventAnalyzedClass = Class.forName("com.huawei.android.hwaps.IEventAnalyzed");
Object proxyFactory = CommonProxy.startHook(oldFactory, iEventAnalyzedClass, new ProxyCallBack() {
@Override
public Object interrupt(CommonProxy proxy, Method method, Object[] args) throws Throwable {
Object result = null;
if (method.getName().equals("processAnalyze")) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
//6.0到8.1的版本上屏蔽该方法的调用
Log.i(TAG, " interrupt huawei processAnalyze method ");
} else {
result = proxy.doRealInvoke(method, args);
}
}
//...
else {
result = proxy.doRealInvoke(method, args);
}
return result;
}
});
if (!static_access){
mEventAnalyzedFiled.set(hwNsdImplObject, proxyFactory);
}else {
mEventAnalyzedFiled.set(null,proxyFactory);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* 构建一个通用的代理类
*/
public static class CommonProxy implements InvocationHandler {
protected Object oldObject;
protected ProxyCallBack callBack;
private CommonProxy(Object oldFactory, ProxyCallBack callBack) {
this.oldObject = oldFactory;
this.callBack = callBack;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return callBack != null ? callBack.interrupt(this, method, args) : doRealInvoke(method, args);
}
/**
* 调用原本的逻辑,继续走下去
*
* @param method
* @param args
* @return
* @throws Throwable
*/
public Object doRealInvoke(Method method, Object[] args) throws Throwable {
return method.invoke(oldObject, args);
}
public static Object startHook(Object oldFactory, Class<?> interfaceClass, ProxyCallBack callBack) throws Exception {
return Proxy.newProxyInstance(HuaWeiHook.class.getClassLoader(), new Class[]{interfaceClass}, new CommonProxy(oldFactory, callBack));
}
}
public static interface ProxyCallBack {
Object interrupt(CommonProxy proxy, Method method, Object[] args) throws Throwable;
}
}
另外一个问题:
java.lang.NullPointerException
Attempt to invoke interface method 'java.util.Iterator java.util.List.iterator()' on a null object reference
com.huawei.android.hwaps.EventAnalyzed.isBackground(EventAnalyzed.java:1932)
com.huawei.android.hwaps.EventAnalyzed$1.run(EventAnalyzed.java:1956)
android.os.Handler.handleCallback(Handler.java:743)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:150)
android.app.ActivityThread.main(ActivityThread.java:5621)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684)
通过一些列的反向逆推,找到调用链接。调用链接:EventAnalyzed#setHasOnPaused()->EventAnalyzed#checkBackground()->EventAnalyzed#isBackground()
。
先来看下, com/huawei/android/hwaps/EventAnalyzed#setHasOnPaused()
:
public void setHasOnPaused(boolean hasOnPaused) {
boolean haspause = mHasOnPaused;
mHasOnPaused = hasOnPaused;
checkBackground();
if (mApsThermal != null) {
mApsThermal.stop();
} else {
ApsCommon.logI(TAG, "setHasOnPaused-ApsThermal is null");
}
if (mApsUserFeedback != null) {
mApsUserFeedback.stop();
} else {
ApsCommon.logI(TAG, "setHasOnPaused-ApsUserFeedback is null");
}
//...
}
接下来看下checkBackground()
:
private void checkBackground() {
new Handler().postDelayed(new Runnable() {// 通过handler 延迟执行
public void run() {
if (EventAnalyzed.this.isBackground() && EventAnalyzed.mSdrController != null) {
ApsCommon.logI(EventAnalyzed.TAG, "SDR: stop SDR because the package is in background");
EventAnalyzed.mSdrController.stopSdrImmediately();
EventAnalyzed.this.mHasSetSdr = false;
}
}
}, 0);
}
接下来看下,isBackground()
:
public boolean isBackground() {
if (mActivityManager == null) {
return false;
}
for (RunningAppProcessInfo appProcess : mActivityManager.getRunningAppProcesses()) {// 报错点,在这里,返回list 为空
if (appProcess.processName.equals(mPkgName) && appProcess.importance != 100) {
ApsCommon.logD(TAG, "SDR: The pkg is in Background and pkg: " + mPkgName);
return true;
}
}
ApsCommon.logD(TAG, "SDR: The pkg is not in Background and pkg: " + mPkgName);
return false;
}
解决方法:
也是采用hook 方式,拦截IEventAnalyzed 是一个接口,可以考虑动态代理掉setHasOnPaused().
再结合报错的机型,如下:
关键代码:
else if (method.getName().equals("setHasOnPaused")) {
//EventAnalyzed#setHasOnPaused->EventAnalyzed#checkBackground()->EventAnalyzed#isBackground()
if (Build.VERSION_CODES.M == Build.VERSION.SDK_INT) {
// 在 华为机型的android 6.0 屏蔽该方法的调用
} else {
result = proxy.doRealInvoke(method, args);
}
}