2025年8月

最近发现客户端中android4.3上GS4手机上的WebApp应用特别容易crash。分析了源代码之后发现,在ActivityThread中回收内存时会调用EGLImpl里边去,回收RenderThread,进而调用到计算CPU FPS的逻辑,进而crash:

```bash
java.lang.Error: signal 11 (Address not mapped to object) at address 0xbe59dff0 [at libPowerStretch.so:0x2d4c (_ZN11LucidConfig13calcTargetFPSEi+0x1b)]

at system.lib.libPowerStretch_so.0x2d4c(LucidConfig::calcTargetFPS(int):0x1b:0)

at system.lib.libPowerStretch_so.0x2f23(LucidConfig::isLucidActive(bool):0x86:0)
```

因为在问题出在系统层而android应用回收内存这个message是ActivityManager发出,为正常且必要的行为,无法规避。最终选择如下方式将其绕过:

```java
public class H5WebViewRenderPolicy {
public static boolean shouldDisableHardwareRenderInLayer() {
// case 1: samsung GS4 on android 4.3 is know to cause crashes at libPowerStretch.so:0x2d4c
// use GT-I95xx to match more GS4 series devices though GT-I9500 is the typical device
final boolean isSamsungGs4 = android.os.Build.MODEL != null && android.os.Build.MODEL.contains("GT-I95") && android.os.Build.MANUFACTURER != null && android.os.Build.MANUFACTURER.equals("samsung");
final boolean isJbMr2 = Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR2;
if (isSamsungGs4 && isJbMr2) {
return true;
}

return false;
}
}
```
以上定义一个渲染策略类(方便以后维护),针对GS4 + android 4.3这种组合在WebView layer层面关闭硬件加速(这样就不会存在RenderThread,自然也就没法触发上文的crash)。

之后在自定义WebView中利用上以上渲染策略类:

```java
final boolean meetApiLevel11 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
if (H5WebViewRenderPolicy.shouldDisableHardwareRenderInLayer() && meetApiLevel11) {
final View underlyingWebView = webView.getUnderlyingWebView();
if (underlyingWebView != null && webView.getType().equals(WebViewType.SYSTEM_BUILD_IN)) {
try {
underlyingWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
} catch (Exception globalException) {
globalException.printStackTrace();
}
}
}
```

更新 2015/2/1
-------------------------------------------------------------
该现象表现在多款三星制造的搭载android 4.3系统的手机上,不仅限于GS4

异常的堆栈如下:

```java
java.lang.IllegalArgumentException: bad parameter
at org.apache.http.client.utils.URLEncodedUtils.parse(URLEncodedUtils.java:139)
at org.apache.http.client.utils.URLEncodedUtils.parse(URLEncodedUtils.java:76)
at android.webkit.AccessibilityInjector.getAxsUrlParameterValue(AccessibilityInjector.java:412)
at android.webkit.AccessibilityInjector.shouldInjectJavaScript(AccessibilityInjector.java:327)
at android.webkit.AccessibilityInjector.onPageFinished(AccessibilityInjector.java:286)
at android.webkit.WebViewClassic.onPageFinished(WebViewClassic.java:4088)
at android.webkit.CallbackProxy.handleMessage(CallbackProxy.java:332)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4829)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
at dalvik.system.NativeStart.main(Native Method)
```
相关的google code android project里边的issue中论述如下:

"
A customer of mine running 4.1.2 just informed me that the issue is still
present! After this issue crashes my app, I have to pop up a dialog
telling them to disable all accessibility services.
Worst user experience ever, but what else can I do.
......
Fixed in an internal build, to be released in the next major release.
......
The bug doesn't occur here in 4.2.1 (Galaxy Nexus, JOP40D).
"

这属于android中最常见的TransactionTooLargeException的后果表现之一。

该问题较多出现在使用PackageManager查询系统的应用的信息,通过Intent来过滤匹配目标应用组件(android四大组件)时,典型堆栈如下:

```java
java.lang.RuntimeException: Package manager has died
at android.app.ApplicationPackageManager.getPackageInfo(ApplicationPackageManager.java:78)

......
Caused by: android.os.TransactionTooLargeException
at android.os.BinderProxy.transact(Native Method)
at android.content.pm.IPackageManager$Stub$Proxy.getPackageInfo(IPackageManager.java:1393)
at android.app.ApplicationPackageManager.getPackageInfo(ApplicationPackageManager.java:73)
... 17 more
android.os.TransactionTooLargeException
at android.os.BinderProxy.transact(Native Method)
at android.content.pm.IPackageManager$Stub$Proxy.getPackageInfo(IPackageManager.java:1393)
at android.app.ApplicationPackageManager.getPackageInfo(ApplicationPackageManager.java:73)

```
这是Java层的堆栈,原始抛出异常的地方在于文件frameworks/base/core/jni/android_util_Binder.cpp:

```c
case FAILED_TRANSACTION:
LOGE("!!! FAILED BINDER TRANSACTION !!!");
// TransactionTooLargeException is a checked exception, only throw from certain methods.
// FIXME: Transaction too large is the most common reason for FAILED_TRANSACTION
// but it is not the only one. The Binder driver can return BR_FAILED_REPLY
// for other reasons also, such as if the transaction is malformed or
// refers to an FD that has been closed. We should change the driver
// to enable us to distinguish these cases in the future.
jniThrowException(env, canThrowRemoteException
? "android/os/TransactionTooLargeException"
: "java/lang/RuntimeException", NULL);
break;
```
该FAILED_TRANSACTION错误码定义于:
/bionic/libc/kernel/common/linux/binder.h中的BR_FAILED_REPLY

```c
enum BinderDriverReturnProtocol {
BR_ERROR = _IOR_BAD('r', 0, int),

BR_OK = _IO('r', 1),

BR_TRANSACTION = _IOR_BAD('r', 2, struct binder_transaction_data),
BR_REPLY = _IOR_BAD('r', 3, struct binder_transaction_data),

BR_ACQUIRE_RESULT = _IOR_BAD('r', 4, int),

BR_DEAD_REPLY = _IO('r', 5),

BR_TRANSACTION_COMPLETE = _IO('r', 6),

BR_INCREFS = _IOR_BAD('r', 7, struct binder_ptr_cookie),
BR_ACQUIRE = _IOR_BAD('r', 8, struct binder_ptr_cookie),
BR_RELEASE = _IOR_BAD('r', 9, struct binder_ptr_cookie),
BR_DECREFS = _IOR_BAD('r', 10, struct binder_ptr_cookie),

BR_ATTEMPT_ACQUIRE = _IOR_BAD('r', 11, struct binder_pri_ptr_cookie),

BR_NOOP = _IO('r', 12),

BR_SPAWN_LOOPER = _IO('r', 13),

BR_FINISHED = _IO('r', 14),

BR_DEAD_BINDER = _IOR_BAD('r', 15, void *),

BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR_BAD('r', 16, void *),

BR_FAILED_REPLY = _IO('r', 17),

};
```
其原因总结如下,首先Android系统对于Binder IPC每进程的事务buffer上限是1M(为所有Binder事务共享,且不区分目标系统的ram配置等因素),在通过PackageManager中获取系统内应用时,比如应用的Icon是个bitmap,且不同应用所制作Icon的品质(像素数)上差异很大。会占用大量内存导致Binder事务内存超过上限。

Binder事务buffer的尺寸定义在ProcessState.cpp:

```c
#define BINDER_VM_SIZE((1 * 1024 * 1024) - (4096 * 2))
......
ProcessState::ProcessState(): mDriverFD(open_driver()), mVMStart(MAP_FAILED), mManagesContexts(false), mBinderContextCheckFunc(NULL), mBinderContextUserData(NULL), mThreadPoolStarted(false), mThreadPoolSeq(1) {
if (mDriverFD >= 0) {
// XXX Ideally, there should be a specific define for whether we
// have mmap (or whether we could possibly have the kernel module
// availabla).
#
if !defined(HAVE_WIN32_IPC)
// mmap the binder, providing a chunk of virtual address space to receive transactions.
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
if (mVMStart == MAP_FAILED) {
// *sigh*
LOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
close(mDriverFD);
mDriverFD = -1;
}#
else
mDriverFD = -1;#
endif
}

LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened. Terminating.");
}
```
App icon对应的数据模型如下:

```java
class AppInfo {

......

private BitmapDrawable icon

......

}
```
该问题是android系统的限制,只能通过上层来捕获该异常来绕过。当然也不仅限于PackageManager,其他系统服务,应用间的Binder调用也可以带来这一问题。

最后的最后,由于在c++向java层抛出这个异常的场景有多种(见上边c++代码中“FAILED_TRANSACTION”处的注释),所以当你收到了TransactionTooLargeException的时候,不意味着真正的Transaction is Too Large。

最近踩了一个坑,有个非全屏的dialog遮住我的WebApp容器时,WebApp容器中JS代码操纵dom对象显示文本(input框中)巨慢(俗话描述就是“软键盘中输入了字,但是上屏很慢”)。

debug了一天之后发现。在WebView的onPause方法调用后,其内部JS引擎执行JS代码会慢好几倍。所以是否需要跟随Activity的生命周期调用onPause方法需要根据应用场景来区分:

若是应用中严格依赖JS做一些比较紧要的事情,则不应该onPause WebView。否则应该onPause WebView以释放一部分系统资源。

由于容纳 RN view 的外围控件不确定,同时又要与其他view协调好尺寸和布局。
所以 RN 对 RootView(包含了一个模块的所有布局)的处理挺有技巧,RN 中有如下调用逻辑:

ReactActivity.createRootView -> setContentView(mReactRootView)

ReactRootView.onMeasure()

ReactInstanceManagerImpl.attachMeasuredRootViewToInstance

UIManagerModule.addMeasuredRootView

得到宽高:

```java
final int width;
final int height;
// If LayoutParams sets size explicitly, we can use that. Otherwise get the size from the view.
if (rootView.getLayoutParams() != null &&
rootView.getLayoutParams().width > 0 &&
rootView.getLayoutParams().height > 0) {
width = rootView.getLayoutParams().width;
height = rootView.getLayoutParams().height;
} else {
width = rootView.getWidth();
height = rootView.getHeight();
}

rootView.setOnSizeChangedListener(
new SizeMonitoringFrameLayout.OnSizeChangedListener() {
@Override
public void onSizeChanged(final int width, final int height, int oldW, int oldH) {
getReactApplicationContext().runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
updateRootNodeSize(tag, width, height);
}
});
}
});
```
通过这个 SizeMonitoringFrameLayout.OnSizeChangedListener 将 SizeMonitoringFrameLayout(实际扮演一个容器 ViewGroup 来监听布局的改变)的宽高传递给 css-layout 用于布局各个"盒"节点。