当前位置: 首页 > news >正文

Android跨进程传大图思考及实现——附上原理分析

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 { cmp=com.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.cppIPCThreadState.cppProcessState.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::Result<int> 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 { cmp=com.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 ArrayMap<String, Object> val) {......for (int i=0; i<N; 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_cast<BitmapWrapper*>(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 it's 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;}//mutableCopy=true:表示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全家桶篇(内含Compose):https://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

相关文章:

Android跨进程传大图思考及实现——附上原理分析

1.抛一个问题 这一天&#xff0c;法海想锻炼小青的定力&#xff0c;由于Bitmap也是一个Parcelable类型的数据&#xff0c;法海想通过Intent给小青传个特别大的图片 intent.putExtra("myBitmap",fhBitmap)如果“法海”(Activity)使用Intent去传递一个大的Bitmap给“…...

【动态规划part13】| 300.最长递增子序列、674.最长连续递增序列、718.最长重复数组

目录 &#x1f388;LeetCode 300.最长递增子序列 &#x1f388;LeetCode 674. 最长连续递增序列 &#x1f388;LeetCode 718. 最长重复子数组 &#x1f388;LeetCode 300.最长递增子序列 链接&#xff1a;300.最长递增子序列 给你一个整数数组 nums &#xff0c;找到其…...

QMainWindow

文章目录 QMainWindow基本元素QMainWindow函数介绍简单的示例效果图 QMainWindow QMainWindow是一个为用户提供主窗口程序 的类&#xff0c;包含一个菜单栏(menu bar)、多个工具栏 (tool bars)、多个锚接部件(dock widgets)、―个 状态栏(status bar )及一个中心部件(central …...

PV操作解决经典进程同步问题

一.经典同步问题 在学习《操作系统》时&#xff0c;会接触到进程的概念&#xff0c;其中不可避免的接触到进程同步问题&#xff0c;今天我们用熟悉的PV操作解决一些经典的进程同步问题。 二.生产者-消费者问题 1.问题描述 问题描述&#xff1a;一组生产者进程和一组消费者进…...

一文3000字从0到1使用Selenium进行自动化测试

对于很多刚入门的测试新手来说&#xff0c;大家都将自动化测试作为自己职业发展的一个主要阶段。可是&#xff0c;在成为一名合格的自动化测试工程师之前&#xff0c;我们不仅要掌握相应的理论知识&#xff0c;还要进行大量的实践&#xff0c;积累足够的经验&#xff0c;以便快…...

基于开源IM即时通讯框架MobileIMSDK:RainbowChat v9.0版已发布

关于MobileIMSDK MobileIMSDK 是一套专门为移动端开发的开源IM即时通讯框架&#xff0c;超轻量级、高度提炼&#xff0c;一套API优雅支持UDP 、TCP 、WebSocket 三种协议&#xff0c;支持iOS、Android、H5、标准Java平台&#xff0c;服务端基于Netty编写。 工程开源地址是&am…...

交叉编译----宿主机x86 ubuntu 64位-目标机ARMv8 aarch64

1.交叉编译是什么&#xff0c;为什么要交叉编译 编译&#xff1a;在一个平台上生成在该平台上的可执行代码交叉编译&#xff1a;在一个平台上生成在另一个平台上的可执行代码交叉编译的例子&#xff1a;如51单片机的可执行代码&#xff08;hex文件&#xff09;是在集成环境kei…...

安防监控视频汇聚平台EasyCVR修改录像计划等待时间较长是什么原因?

安防监控视频EasyCVR视频融合汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存储、回放与检…...

深度学习调参指南

1. 选择合适的模型架构 模型的结构(层数和宽度)&#xff0c;参数配置&#xff0c;尽量用已经有效的模型 2. 选择优化器 针对具体的问题&#xff0c;从选择常用的优化器开始&#xff0c;进行比较 3. 选择BatchSize 1). Batch Size决定训练速度&#xff0c;但是不影响验证集…...

MYSQL 优化常用方法

1、选取最适用的字段属性 MySQL可以很好的支持大数据量的存取&#xff0c;但是一般说来&#xff0c;数据库中的表越小&#xff0c;在它上面执行的查询也就会越快。因此&#xff0c;在创建表的时候&#xff0c;为了获得更好的性能&#xff0c;我们可以将表中字段的宽度设得尽可…...

isp调试工具环境搭建及其介绍!

一、isp调试环境搭建&#xff1a; 后期调试isp&#xff0c;是在rv1126提供的RKISP2.x Tuner工具上进行调试&#xff0c;所以我们大前提必须要把这个环境和一些操作先搞熟悉来&#xff0c;后面有一些专用术语&#xff0c;我们遇到了再去看&#xff0c;现在专门看一些专用术语&am…...

word显示书签并给书签添加颜色

CTRg 定位书签 在 Word 的用户界面中&#xff0c;没有直接的选项可以批量为所有书签设置颜色。但你可以使用 VBA 宏或者编写自定义的功能来实现这个需求。这里给出一个简单的 VBA 宏&#xff0c;它可以设置当前文档中所有书签内文本的颜色&#xff1a;vba Sub ColorAllBookmark…...

Rust系列(四) trait备忘录(持续更新)

上一篇&#xff1a;Rust系列(三) 类型系统与trait 基于官方文档进行简单学习记录&#xff0c;保证所有示例是可运行的基本单元。测试rust程序除了使用官方的playground之外&#xff0c;还可以通过定义[[example]]来运行程序。 文章目录 1. Deref2. DerefMut 1. Deref 用于不可…...

贪心算法总结及其leetcode题目N道

1 我为什么要写这个总结 1.1 字节笔试题 小明在玩一场通关游戏&#xff0c;初始血量为1&#xff0c;关卡有怪兽或者有血包&#xff08;正数就是血包可回血数&#xff0c;负数说明是怪兽的伤害值&#xff09;&#xff0c;当捡到血包时会加血量&#xff0c;碰到怪兽时会掉血&am…...

k8s的namespace一直处于terminating的解法

先试了强制替换&#xff0c;无法替换掉&#xff0c;强制删除&#xff0c;也删除不掉namespace [rootmaster k8s-study]# vi ns-demo.yaml [rootmaster k8s-study]# kubectl create -f ns-demo.yaml namespace/demo created [rootmaster k8s-study]# kubectl get -f ns-demo.ya…...

JAVA面试总结-Redis篇章(六)——数据过期策略

Java面试总结-Redis篇章&#xff08;六&#xff09;——数据过期策略 Redis数据删除策略——惰性删除Redis数据删除策略——定期删除 Redis数据删除策略——惰性删除 Redis数据删除策略——定期删除...

【LLM】大语言模型学习之LLAMA 2:Open Foundation and Fine-Tuned Chat Model

大语言模型学习之LLAMA 2:Open Foundation and Fine-Tuned Chat Model 快速了解预训练预训练模型评估微调有监督微调(SFT)人类反馈的强化学习(RLHF)RLHF结果局限性安全性预训练的安全性安全微调上手就干使用登记代码下载获取模型转换模型搭建Text-Generation-WebUI分发模型…...

Android是如何识别USB信号的

Android设备通过USB接口与外部设备通信时&#xff0c;会通过USB控制器&#xff08;USB Controller&#xff09;与USB设备进行通信。USB控制器是Android设备的一个硬件组件&#xff0c;它负责管理USB总线并控制所有USB设备的连接和通信。 当一个USB设备被插入Android设备的USB接…...

机器学习前言

1.机器学习和统计学关系 2.机器学习的发展 3.机器学习与深度学习的相同点与不同点 4.机器学习和深度学习优缺点 一、机器学习和统计学关系 机器学习和统计学密切相关&#xff0c;可以说机器学习是统计学在计算机科学和人工智能领域的应用。机器学习和统计学在方法论和技术上有…...

Java另一种debug方法(not remote jmv debug),类似python远程debug方式

这种Debug类似python的debug方式&#xff0c;是运行时将业务代码及依赖推送到Linux并使用Linux的java运行运行程。只要本地能运行&#xff0c;就能自动将代码推送到Linux运行&#xff0c;不需打包及设置远程debug jvm参数&#xff0c;适合一些项目Debug调试 运行时会推送一些依…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

QT3D学习笔记——圆台、圆锥

类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体&#xff08;对象或容器&#xff09;QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质&#xff08;定义颜色、反光等&#xff09;QFirstPersonC…...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...