怎样加强公司网站建设,公司网站主页设计,v9双语版网站怎么做,wordpress开启伪静态无法登陆1.抛一个问题
这一天#xff0c;法海想锻炼小青的定力#xff0c;由于Bitmap也是一个Parcelable类型的数据#xff0c;法海想通过Intent给小青传个特别大的图片
intent.putExtra(myBitmap,fhBitmap)如果“法海”(Activity)使用Intent去传递一个大的Bitmap给“…1.抛一个问题
这一天法海想锻炼小青的定力由于Bitmap也是一个Parcelable类型的数据法海想通过Intent给小青传个特别大的图片
intent.putExtra(myBitmap,fhBitmap)如果“法海”(Activity)使用Intent去传递一个大的Bitmap给“小青”(Activity)如果你的图片够大会出现类似下面这样的错误请继续往下看
Caused by: android.os.TransactionTooLargeException: data parcel size 8294952 bytesat android.os.BinderProxy.transactNative(Native Method)at android.os.BinderProxy.transact(BinderProxy.java:535)at android.app.IActivityTaskManager$Stub$Proxy.startActivity(IActivityTaskManager.java:3904)at android.app.Instrumentation.execStartActivity(Instrumentation.java:1738)至于是什么样的大图这个只有法海知道了(小青:好羞涩啊) 所以TransactionTooLargeException这玩意爆出来的地方在哪呢
2.问题定位分析
我们可以看到错误的日志信息里面看到调用了BinderProxy.transactNative这个transactNative是一个native方法
//android.os.BinderProxy
/*** Native implementation of transact() for proxies
*/
public native boolean transactNative(int code, Parcel data, Parcel reply,int flags) throws RemoteException;在Android Code Search全局搜索一下android_os_BinderProxy_transact
//frameworks/base/core/jni/android_util_Binder.cppstatic jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{......status_t err target-transact(code, *data, reply, flags);......if (err NO_ERROR) { //如果匹配成功直接拦截不往下面执行了return JNI_TRUE;} else if (err UNKNOWN_TRANSACTION) {return JNI_FALSE;}signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data-dataSize());return JNI_FALSE;
}我们打开signalExceptionForError方法看看里面的内容
//frameworks/base/core/jni/android_util_Binder.cpp
//处理异常的方法
void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,bool canThrowRemoteException, int parcelSize)
{switch (err) {//其他异常大家可以自行阅读了解//如没有权限异常文件太大错误的文件描述符等等........case FAILED_TRANSACTION: {const char* exceptionToThrow;char msg[128];//官方在FIXME中写道事务过大是FAILED_TRANSACTION最常见的原因//但它不是唯一的原因Binder驱动可以返回 BR_FAILED_REPLY//也有其他原因可能是事务格式不正确文件描述符FD已经被关闭等等//parcelSize大于200K就会报错canThrowRemoteException传递进来的是trueif (canThrowRemoteException parcelSize 200*1024) {// bona fide large payloadexceptionToThrow android/os/TransactionTooLargeException;snprintf(msg, sizeof(msg)-1, data parcel size %d bytes, parcelSize);} else {..........}//使用指定的类和消息内容抛出异常jniThrowException(env, exceptionToThrow, msg);} break;........}
}此时我们看到: parcelSize大于200K就会报错难道一定是200K以内先别着急着下结论继续往下看
3.提出疑问
法海我有个疑问我看到文档写的1M大小啊
许仙别急妹夫来先看一下文档的解释看一下使用说明 官方TransactionTooLargeException的文档中描述到Binder 事务缓冲区有一个有限的固定大小目前为 1MB由进程所有正在进行的事务共享 可以看到写的是共享事务的缓冲区
如来佛祖汝等别急我们简单测试一下Intent传递201*1024个字节数组我们发现可以正常传递过去Logcat仅仅输出了一个Error提示的日志信息,还是可以正常传递的
E/ActivityTaskManager: Transaction too large, intent: Intent { cmpcom.melody.test/.SecondActivity (has extras) }, extras size: 205848, icicle size: 0我们再测试一个值intent传递800*1024个字节数组我们发现会崩溃
android.os.TransactionTooLargeException: data parcel size 821976 bytesat android.os.BinderProxy.transactNative(Native Method)at android.os.BinderProxy.transact(BinderProxy.java:540)at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)at android.app.servertransaction.ClientTransaction.schedule(ClientTransaction.java:136)不要着急我们继续往下看分析
4.解答疑问
我们来看一下下面两行代码
//frameworks/base/core/jni/android_util_Binder.cpp
//这个方法android_os_BinderProxy_transact里面的
IBinder* target getBPNativeData(env, obj)-mObject.get();
status_t err target-transact(code, *data, reply, flags);从上面的分析和测试结果我们从target-transact这里来找err返回值 先根据头文件搜索对应的cpp类我们看一下这几个cpp类BpBinder.cpp、 IPCThreadState.cpp、ProcessState.cpp
//frameworks/native/libs/binder/ProcessState.cpp// (1 * 1024 * 1024) - (4096 *2)
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
#define DEFAULT_MAX_BINDER_THREADS 15//下面两个注释
//引用自官方文档https://source.android.google.cn/devices/architecture/hidl/binder-ipc
#ifdef __ANDROID_VNDK__
//供应商/供应商进程之间的IPC使用 AIDL 接口
const char* kDefaultDriver /dev/vndbinder;
#else
// /dev/binder 设备节点成为框架进程的专有节点
const char* kDefaultDriver /dev/binder;
#endif//构造函数初始化一些变量Binder最大线程数等
ProcessState::ProcessState(const char* driver): mDriverName(String8(driver)),mDriverFD(-1),mVMStart(MAP_FAILED),......mMaxThreads(DEFAULT_MAX_BINDER_THREADS),mStarvationStartTimeMs(0),mThreadPoolStarted(false),mThreadPoolSeq(1),mCallRestriction(CallRestriction::NONE) {......//打开驱动base::Resultint opened open_driver(driver);if (opened.ok()) {//映射(1M-8k)的mmap空间mVMStart mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,opened.value(), 0);......}......
}点击查看sysconf.cpp getauxval(AT_PAGESZ) 4096可以得出Binder内存限制BINDER_VM_SIZE 1M-8kb
这里为什么不是1M而是1M-8K? 最开始的时候官方写的是1M后来他们内部自己优化了 来看这里官方提交的ProcessState.cpp提交的log日志:允许内核更有效地利用其虚拟地址空间
我们知道微信的MMKV、美团的Logan的日志组件都是基于mmap来实现的
binder驱动的注册逻辑在Binder.c中我们看一下binder_mmap方法
//kernel/msm/drivers/android/binder.c
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{int ret;struct binder_proc *proc filp-private_data;const char *failure_string;if (proc-tsk ! current-group_leader)return -EINVAL;//这里可以看到映射空间最多4Mif ((vma-vm_end - vma-vm_start) SZ_4M)vma-vm_end vma-vm_start SZ_4M;......//初始化指定的空间vma用于分配绑定缓冲区ret binder_alloc_mmap_handler(proc-alloc, vma);......
}这里能看到映射空间最多4M我们再来看一下binder_alloc_mmap_handler这个方法点击查看binder_alloc.c
//kernel/msm/drivers/android/binder_alloc.c
//由binder_mmap()调用来初始化指定的空间vma用于分配绑定缓冲区
int binder_alloc_mmap_handler(struct binder_alloc *alloc,struct vm_area_struct *vma)
{......//buffer_size最大4Malloc-buffer_size vma-vm_end - vma-vm_start;......//异步事务的空闲缓冲区大小最大2Malloc-free_async_space alloc-buffer_size / 2;......
}从上面的分析得出结论: 1.Binder驱动给每个进程最多分配4M的buffer空间大小 2.异步事务的空闲缓冲区空间大小最多为2M 3.Binder内核内存上限为1M-8k; 4.异步事务缓冲区空间大小等于buffer_size/2具体值取决于buffer_size; 同步、异步是定义在AIDL文件中的我们看上面测试的两个例子其中有一个传了800*1024个字节数组崩溃如下
android.os.TransactionTooLargeException: data parcel size 821976 bytesat android.os.BinderProxy.transactNative(Native Method)at android.os.BinderProxy.transact(BinderProxy.java:540)at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)点击查看IApplicationThread.aidl 查看AIDL里面的内容我们看到scheduleTransaction是一个异步的方法 因为oneway修饰在interface之前会让interface内所有的方法都隐式地带上oneway
由于oneway异步调用我们这个时候修改一下传递(1M-8k)/2大小之内的数据测试一下
// ((1024 * 1024 - 8 * 1024)/2)-1E/ActivityTaskManager: Transaction too large, intent: Intent { cmpcom.melody.test/.SecondActivity (has extras) }, extras size: 520236, icicle size: 0Exception when starting activity com.melody.test/.SecondActivityandroid.os.TransactionTooLargeException: data parcel size 522968 bytesat android.os.BinderProxy.transactNative(Native Method)at android.os.BinderProxy.transact(BinderProxy.java:540)at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)可以看到还是会报错说明异步事务的可用空间不够仔细看一下为什么不够细心的同学可能发现了 警告的日志打印extras size: 520236 崩溃的日志打印data parcel size: 522968 大小相差2732 约等于 2.7k
如果这个时候我们用Intent传递一个ByteArray比之前的大小减去3k ByteArray((1024*1024 - (8 * 1024))/2 - 3 * 1024)
startActivity(Intent(this,SecondActivity::class.java).apply {putExtra(KEY,ByteArray((1024*1024 - (8 * 1024))/2 - 3 * 1024))
})这个时候发现(1M-8k)/2 -3k可以成功传递数据说明有其他数据占用了这部分空间。 我们上面写了不要忘记共享事务的缓冲区这里减去3k仅测试用的我们继续往下分析
找一下异步事务的空闲缓冲区空间大小比较的地方打开binder_alloc.c找到binder_alloc_new_buf方法
//kernel/msm/drivers/android/binder_alloc.c
//分配一个新缓冲区
struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,size_t data_size,size_t offsets_size,size_t extra_buffers_size,int is_async,int pid)
{......buffer binder_alloc_new_buf_locked(alloc, data_size, offsets_size,extra_buffers_size, is_async, pid);.......
}我们来看一下binder_alloc_new_buf_locked方法
//kernel/msm/drivers/android/binder_alloc.c
static struct binder_buffer *binder_alloc_new_buf_locked( struct binder_alloc *alloc,size_t data_size,size_t offsets_size,size_t extra_buffers_size,int is_async,int pid)
{......//如果是异步事务检查所需的大小是否在异步事务的空闲缓冲区区间内if (is_async alloc-free_async_space size sizeof(struct binder_buffer)) {return ERR_PTR(-ENOSPC);}
}分析了这么多不论是同步还是异步都是共享事务的缓冲区如果有大量数据需要通过Activity的Intent传递数据大小最好维持在200k以内 上面测试的时候超出200k数据传递的时候LogCat已经给我们打印提示“Transaction too large”了但是只要没有超出异步事务空闲的缓冲区大小就不会崩溃 如果Intent传递大量的数据完全可以使用别的方式方法
5.Intent设置Bitmap发生了什么?
5.1-Intent.writeToParcel
Intent数据写入到parcel中在writeToParcel方法里面Intent把Bundle写入到Parcel中
//android.content.Intentpublic void writeToParcel(Parcel out, int flags) {......//把Bundle写入到Parcel中out.writeBundle(mExtras);
}打开out.writeBundle方法
//android.os.Parcel#writeBundle
public final void writeBundle(Nullable Bundle val) {if (val null) {writeInt(-1);return;}//执行Bundle自身的writeToParcel方法val.writeToParcel(this, 0);
}5.2-Bundle.writeToParcel
//android.os.Bundlepublic void writeToParcel(Parcel parcel, int flags) {final boolean oldAllowFds parcel.pushAllowFds((mFlags FLAG_ALLOW_FDS) ! 0);try {//这里官方注释已经写的很详细了://将Bundle内容写入Parcel通常是为了让它通过IBinder连接传递super.writeToParcelInner(parcel, flags);} finally {//把mAllowFds值设置回来parcel.restoreAllowFds(oldAllowFds);}
}点击查看Parcel.cpp我们看一下里面的pushAllowFds方法
//frameworks/native/libs/binder/Parcel.cpp
bool Parcel::pushAllowFds(bool allowFds)
{const bool origValue mAllowFds;if (!allowFds) {mAllowFds false;}return origValue;
}如果Bundle设置了不允许带描述符当调用pushAllowFds之后Parcel中的内容也不带描述符 在文章开头我们举的例子中通过Intent去传递一个Bitmap在执行到Instrumentation#execStartActivity的时候我们发现Intent有个prepareToLeaveProcess方法在此方法里面调用了Bundle#setAllowFds(false)
//android.app.Instrumentation
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {try {......intent.prepareToLeaveProcess(who);......} catch (RemoteException e) {throw new RuntimeException(Failure from system, e);}return null;}5.3-Parcel.writeArrayMapInternal
刚刚上面Bundle.writeToParcel方法里面super.writeToParcelInner触发下面方法
//android.os.BaseBundle
void writeToParcelInner(Parcel parcel, int flags) {......parcel.writeArrayMapInternal(map);......
}我们看一下writeArrayMapInternal方法
void writeArrayMapInternal(Nullable ArrayMapString, Object val) {......for (int i0; iN; i) {writeString(val.keyAt(i));//根据不同数据类型调用不同的write方法writeValue(val.valueAt(i));}}5.4-writeValue
文章一开头我们使用的是intent.putExtra(bmp,法海bitmap)
//android.os.Parcel
public final void writeValue(Nullable Object v) {......if (v instanceof Parcelable) {writeInt(VAL_PARCELABLE);writeParcelable((Parcelable) v, 0);} ......
}
public final void writeParcelable(Nullable Parcelable p, int parcelableFlags) {......writeParcelableCreator(p);p.writeToParcel(this, parcelableFlags);
}因为传入的是Bitmap我们看Bitmap.writeToParcel
5.5-Bitmap.writeToParcel
//android.graphics.Bitmap
public void writeToParcel(Parcel p, int flags) {noteHardwareBitmapSlowCall();//打开Bitmap.cpp找对应的native方法if (!nativeWriteToParcel(mNativePtr, mDensity, p)) {throw new RuntimeException(native writeToParcel failed);}
}点击打开Bitmap.cpp查看Bitmap_writeToParcel方法
//frameworks/base/libs/hwui/jni/Bitmap.cppstatic jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,jlong bitmapHandle, jint density, jobject parcel) {......//获得Native层的对象android::Parcel* p parcelForJavaObject(env, parcel);SkBitmap bitmap;auto bitmapWrapper reinterpret_castBitmapWrapper*(bitmapHandle);//获取SkBitmapbitmapWrapper-getSkBitmap(bitmap);//写入parcelp-writeInt32(!bitmap.isImmutable());......p-writeInt32(bitmap.width());p-writeInt32(bitmap.height());p-writeInt32(bitmap.rowBytes());p-writeInt32(density);// Transfer the underlying ashmem region if we have one and its immutable.android::status_t status;int fd bitmapWrapper-bitmap().getAshmemFd();if (fd 0 bitmap.isImmutable() p-allowFds()) {//AshmemFd大于等于0 bitmap不可变 parcel允许带Fd//符合上述条件将fd写入到parcel中status p-writeDupImmutableBlobFileDescriptor(fd);if (status) {doThrowRE(env, Could not write bitmap blob file descriptor.);return JNI_FALSE;}return JNI_TRUE;}//mutableCopytrue表示bitmap是可变的const bool mutableCopy !bitmap.isImmutable();//返回像素存储所需的最小内存size_t size bitmap.computeByteSize();android::Parcel::WritableBlob blob;//获取到一块blob缓冲区往下翻有代码分析status p-writeBlob(size, mutableCopy, blob);......
}我们来看看writeBlob里面做了什么事情
5.6-Parcel::writeBlob
//frameworks/native/libs/binder/Parcel.cppstatic const size_t BLOB_INPLACE_LIMIT 16 * 1024; // 16kstatus_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
{status_t status;if (!mAllowFds || len BLOB_INPLACE_LIMIT) {//如果不允许带FD 或者 数据小于等于16k则直接将图片写入到parcel中status writeInt32(BLOB_INPLACE);if (status) return status;void* ptr writeInplace(len);if (!ptr) return NO_MEMORY;outBlob-init(-1, ptr, len, false);return NO_ERROR;}//不满足上面的条件即允许Fd len 16k//创建一个新的ashmem区域并返回文件描述符FD//ashmem-dev.cpp里面有注释说明//https://cs.android.com/android/platform/superproject//master:system/core/libcutils/ashmem-dev.cppint fd ashmem_create_region(Parcel Blob, len);if (fd 0) return NO_MEMORY;//设置ashmem这块区域是“可读可写”int result ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);if (result 0) {status result;} else {//根据fd映射 “len大小” 的mmap的空间void* ptr ::mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);......if (!status) {//将fd写入到parcel中status writeFileDescriptor(fd, true /*takeOwnership*/);if (!status) {outBlob-init(fd, ptr, len, mutableCopy);return NO_ERROR;}}......}......
}看到这里大家应该知道我们为什么先分析Intent传递数据大小的上限了吧 在目录5下面的 5.2-Bundle.writeToParcel已经说明清楚了Intent启动Activity的时候禁用掉了文件描述符 所以: 在执行writeBlob方法只能执行到第一个分支直接将图片写入到parcel中我们在目录4给出Intent传递数据大小限制的结论
那么如何不受Intent禁用文件描述符和数据大小的限制
6.跨进程传大图
在Parcel类中看到writeValue方法里面有个分支判断当前value是不是IBinder如果是IBinder类型的会调用writeStrongBinder把这个对象写入到Parcel中
所以我们可以使用Bundle的putBinder来把IBinder对象写入到Parcel中通过putBinder不会受Intent禁用文件描述符的影响数据大小也没有限制,Bitmap写入到parcel中默认是true可以使用匿名共享内存(Ashmem)
6.1-单进程下putBinder用法
//定义一个IntentBinder此方法仅在『同一个进程』下有效哦切记切记
class IntentBinder(val imageBmp:Bitmap? null): Binder()//------------------------使用如下--------------------------//
//com.xxx.xxx.MainActivity
val bitmap BitmapFactory.decodeStream(...)
startActivity(Intent(this,SecondActivity::class.java).putExtras(Bundle().apply {putBinder(myBinder,IntentBinder(bitmap))
}))//------------------------获取Bitmap并显示如下--------------------------//
//com.xxx.xxx.SecondActivity
val bundle: Bundle? intent.extras
val imageBinder:IntentBinder? bundle?.getBinder(myBinder) as IntentBinder?
//拿到Binder中的Bitmap
val bitmap imageBinder?.imageBmp
//自行压缩后显示到ImageView上.....注意: 这个用法不能跨进程喜欢动手的同学可以试一试给SecondActivity配置一个android:process:remote你会发现会报一个强制转换的异常错误
//错误的用在多进程场景下报错如下
java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.xxx.xxx.IntentBinder为什么可以通过这种方式传递对象 Binder会为我们的对象创建一个全局的JNI引用,点击查看android_util_Binder.cpp
//frameworks/base/core/jni/android_util_Binder.cpp
......
static struct bindernative_offsets_t
{// Class state.jclass mClass;jmethodID mExecTransact;jmethodID mGetInterfaceDescriptor;// Object state.jfieldID mObject;} gBinderOffsets;
......
static const JNINativeMethod gBinderMethods[] {/* name, signature, funcPtr */// CriticalNative{ getCallingPid, ()I, (void*)android_os_Binder_getCallingPid },// CriticalNative{ getCallingUid, ()I, (void*)android_os_Binder_getCallingUid },......{ getExtension, ()Landroid/os/IBinder;, (void*)android_os_Binder_getExtension },{ setExtension, (Landroid/os/IBinder;)V, (void*)android_os_Binder_setExtension },
};const char* const kBinderPathName android/os/Binder;//调用下面这个方法完成Binder类的注册
static int int_register_android_os_Binder(JNIEnv* env)
{//获取Binder的class对象jclass clazz FindClassOrDie(env, kBinderPathName);//内部创建全局引用并将clazz保存到全局变量中gBinderOffsets.mClass MakeGlobalRefOrDie(env, clazz);//获取Java层的Binder的成员方法execTransactgBinderOffsets.mExecTransact GetMethodIDOrDie(env, clazz, execTransact, (IJJI)Z);//获取Java层的Binder的成员方法getInterfaceDescriptorgBinderOffsets.mGetInterfaceDescriptor GetMethodIDOrDie(env, clazz, getInterfaceDescriptor,()Ljava/lang/String;);//获取Java层的Binder的成员变量mObjectgBinderOffsets.mObject GetFieldIDOrDie(env, clazz, mObject, J);//注册gBinderMethods中定义的函数return RegisterMethodsOrDie(env, kBinderPathName,gBinderMethods, NELEM(gBinderMethods));
}
......6.2-多进程下putBinder用法
//先定义一个IGetBitmapService.aidl
package com.xxx.aidl;
interface IGetBitmapService {Bitmap getIntentBitmap();
}//------------------------使用如下--------------------------//
//com.xxx.xxx.MainActivity 进程A
val bitmap BitmapFactory.decodeStream(...)
startActivity(Intent(this,SecondActivity::class.java).putExtras(Bundle().apply {putBinder(myBinder,object: IGetBitmapService.Stub() {override fun getIntentBitmap(): Bitmap {return bitmap}})
}))//------------------------获取Bitmap并显示如下--------------------------//
//com.xxx.xxx.SecondActivity 进程B
val bundle: Bundle? intent.extras
//返回IGetBitmapService类型
val getBitmapService IGetBitmapService.Stub.asInterface(bundle?.getBinder(myBinder))
val bitmap getBitmapService.intentBitmap
//自行压缩后显示到ImageView上.....Android 学习笔录
Android 性能优化篇https://qr18.cn/FVlo89 Android 车载篇https://qr18.cn/F05ZCM Android 逆向安全学习笔记https://qr18.cn/CQ5TcL Android Framework底层原理篇https://qr18.cn/AQpN4J Android 音视频篇https://qr18.cn/Ei3VPD Jetpack全家桶篇内含Composehttps://qr18.cn/A0gajp Kotlin 篇https://qr18.cn/CdjtAF Gradle 篇https://qr18.cn/DzrmMB OkHttp 源码解析笔记https://qr18.cn/Cw0pBD Flutter 篇https://qr18.cn/DIvKma Android 八大知识体https://qr18.cn/CyxarU Android 核心笔记https://qr21.cn/CaZQLo Android 往年面试题锦https://qr18.cn/CKV8OZ 2023年最新Android 面试题集https://qr18.cn/CgxrRy Android 车载开发岗位面试习题https://qr18.cn/FTlyCJ 音视频面试题锦https://qr18.cn/AcV6Ap