Android跨进程传图片或者大数据(解决TransactionTooLargeException)

跨进程传图片方案

  1. 直接intent传bitmap
  2. 使用文件读写
  3. intent传递自定义binder,binder中传递image
  4. 使用网络传输
一、直接intent传bitmap

优势

使用简单


劣势

相关代码可能有侵入性,必须在四大组件中接收。

  1. intent传递数据的总大小是1MB,其中还包括启动四大组件相关的信息。因此使用intent传递的图片不宜超过500KB,甚至应该更小,因为还可能会传递其他数据。
  2. 如果通过此方案传递大图片,必须先压缩后传输。开发者需要自己评估业务场景是否适用,毕竟很多场景不适合让图片质量下降。

如果intent传递的数据超过1MB时,就会报错TransactionTooLargeException。

二、使用文件读写

优势

  1. 使用相对简单
  2. 一定程度上可以避免逻辑耦合的问题,对于单独的模块来说只需要负责“读”或者“写”。

劣势

  1. 需要自己控制读写的时机。
  2. 读写操作相比直接传递效率更低,耗时更长。
三、intent传递自定义binder,binder中传递image

优势

  1. 效率相对最高
  2. 传递图片没有大小限制

劣势

  1. 使用相对麻烦,需要自定义aidl
  2. 相关代码可能有侵入性,必须在四大组件中接收。
四、使用网络传输

这个方案比较特殊,只有特殊场景才会使用。

一般存在两种情况:

  1. 两个进程都与服务端通信,一个进程传输,一个进程接收。如果是图片上传和下载的场景可以使用,但是效率肯定没有直接传输高。
  2. 两个进程一个作为服务端,一个作为客户端。 这个方案的关键在于这个“作为服务端的进程”,需要这个进程本身就是某种图片服务的提供者,且通过网络来对其他进程或模块提供服务度。


intent通过binder传递bitmap的Demo
有兴趣的读者可以自行看下Demo:

github地址
https://github.com/Double2hao/ProcessImageTest

intent通过binder传递bitmap的原理

bitmap在native层传递的时候会有两种方案:

1. 直接将图片写入进程的缓冲区。

缓冲区是进程在初始化的时候就已经申请了的,并且大小是一定的。因此如果写入的大小超过了缓冲区的大小,就会报错。

2. 使用共享内存,将共享内存的fd,也就是文件描述符写入缓冲区。

这样的好处就是传递图片的大小不会受限制。

intent直接传递bitmap对应方案1,intent通过binder传递bitmap对应方案2。

为什么intent传递bitmap不默认使用共享内存?

个人理解,缓冲区的大小是进程创建的时候就申请好的,如果能保证不超出缓冲区大小的情况下使用缓冲区,不需要再另外申请共享内存肯定是最好的。

如果默认就使用共享内存,而缓冲区资源又没人用的话,就造成了资源浪费。

因此如果开发者自己认为需要传递大文件的话,就使用共享内存,默认不使用。
 


Android 基于共享内存跨进程实时传输大量图片或数据

aidl传输文件有大小1M限制,单次传输不适合传递大数据,可以使用aidl传递共享内存引用ParcelFileDescriptor方式传递图片信息,具体实现如下。

一、service端

1.1 aidl文件IIpcService.aidl 定义,这里主要用到pfd参数
interface IIpcService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
// void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
// double aDouble, String aString);
void register2Server(String packageName,IIpcServiceListener ipcServiceListener);
void unregister2Server(String packageName);
String processClientRequest(String packageName,String clientRequest,inout ParcelFileDescriptor pfd);
}
1.2 service端 处理客户端传递的图片 流 引用ParcelFileDescriptor ,将获取的ParcelFileDescriptor转换成Bitmap 并回调给ui层显示
    public String processClientRequest(String packageName, String clientRequest, ParcelFileDescriptor pfd) {Log.i(TAG, "processClientRequest 11 packageName:" + packageName+ " clientRequest:" + clientRequest + " pfd:" + pfd);String ret = clientRequest;FileDescriptor fileDescriptor = pfd.getFileDescriptor();FileInputStream fis = null;try {fis = new FileInputStream(fileDescriptor);Bitmap rawBitmap = BitmapFactory.decodeStream(fis);ret += " process success!";Log.i(TAG, "processClientRequest 112 rawBitmap:" + rawBitmap + " mUiShow:" + mUiShow);if (null != mUiShow) {mUiShow.showBitmap(rawBitmap);}} catch (Exception e) {Log.i(TAG, "processClientRequest 22 error:" + e);e.printStackTrace();} finally {try {if (fis != null) {fis.close();}} catch (IOException e) {Log.i(TAG, "processClientRequest 33 error:" + e);}}Log.i(TAG, "processClientRequest 22 end ret:" + ret);return ret;}
1.3 也可以处理客户端传递的字节数组 数据引用,处理代码如下
    public String processClientRequest(String packageName, String clientRequest, ParcelFileDescriptor pfd) {Log.i(TAG, "processClientRequest 11 packageName:" + packageName+ " clientRequest:" + clientRequest + " pfd:" + pfd);String ret = clientRequest;FileDescriptor fileDescriptor = pfd.getFileDescriptor();FileInputStream fis = null;try {fis = new FileInputStream(fileDescriptor);byte[] content = new byte[5];fis.read(content);Log.i(TAG, "processClientRequest 111 content:" + content);for (int i = 0; i < content.length; i++) {Log.i(TAG, "processClientRequest 113 content[" + i + "]=" + content[i]);}}} catch(Exception e){Log.i(TAG, "processClientRequest 33 error:" + e);e.printStackTrace();} finally{try {if (fis != null) {fis.close();}} catch (IOException e) {Log.i(TAG, "processClientRequest 44 error:" + e);}}Log.i(TAG,"processClientRequest 55 end ret:"+ret);
return ret;
}

二客户端

2.1 客户端连接到service后,调用接口 传递图片文件引用 ParcelFileDescriptor
    String path = "/sdcard/lilei/20230207161749238.jpg";public ParcelFileDescriptor getPfd() {ParcelFileDescriptor pfd = null;try {pfd = ParcelFileDescriptor.open(new File(path), MODE_READ_WRITE);} catch (FileNotFoundException e) {throw new RuntimeException(e);}Log.i(TAG, "getPfd() pfd:" + pfd);return pfd;}public String sendFile(String requestJson) {Log.i(TAG, "sendFile() requestJson:" + requestJson);if (null != mFtIpcManager) {return mFtIpcManager.processClientRequest(requestJson, getPfd());}return "error";}
2.2 客户端也可以传递 byte数组
    public ParcelFileDescriptor getTextPfd() {ParcelFileDescriptor pfd = null;try {MemoryFile memoryFile = new MemoryFile("test", 1024);Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");FileDescriptor des = (FileDescriptor) method.invoke(memoryFile);pfd = ParcelFileDescriptor.dup(des);
//向内存中写入字节数组memoryFile.getOutputStream().write(new byte[]{1,2,5,4,3});
//关闭流memoryFile.getOutputStream().close();memoryFile.close();} catch (IOException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);a} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}Log.i(TAG, "getTextPfd() pfd:" + pfd);return pfd;}public String sendFile(String requestJson) {Log.i(TAG, "sendFile() requestJson:" + requestJson);if (null != mFtIpcManager) {return mFtIpcManager.processClientRequest(requestJson, getTextPfd());}return "error";}
2.3 客户端也可以传递Bitmap数据,需要先将Bitmap转换成 byte数组,service端接收同1.2

public class test {public ParcelFileDescriptor getBitmapPfd() {ParcelFileDescriptor pfd = null;Bitmap bitmap= BitmapFactory.decodeResource(FtClientApp.getAppContext().getResources(), R.drawable.btn_send);
//将Bitmap转成字节数组ByteArrayOutputStream stream = new ByteArrayOutputStream();bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);byte[] byteArray = stream.toByteArray();try {MemoryFile memoryFile = new MemoryFile("test", bitmap.getByteCount());Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");FileDescriptor des = (FileDescriptor) method.invoke(memoryFile);pfd = ParcelFileDescriptor.dup(des);
//向内存中写入字节数组memoryFile.getOutputStream().write(byteArray);
//关闭流memoryFile.getOutputStream().close();memoryFile.close();} catch (IOException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}Log.i(TAG, "getPfd() pfd:" + pfd);return pfd;}public String sendFile(String requestJson) {Log.i(TAG, "sendFile() requestJson:" + requestJson);if (null != mFtIpcManager) {return mFtIpcManager.processClientRequest(requestJson, getBitmapPfd());}return "error";}

PS:这里也可以共享内存传递大字符串,只是需要将字符串和字节数组转换一下再传递,转换实现如下。
1.string 字符串转换成 byte[] 数组
String str = "reagan";
byte[] srtbyte = str.getBytes();

2.byte[] 数组转换成 string字符串
String res = new String(srtbyte);
或者
String res = new String(srtbyte,"UTF-8");
System.out.println(res);


Android 跨进程传递大图片

跨进程传大图,有哪些方案?

通过IPC的方式转发图片数据。

  • Binder:性能很好,方便使用,但是有大小限制
  • Socket,管道:存在多次copy问题,性能差,也有大小限制
  • 共享内存:性能好

主要看两个指标

1. 性能,减少copy次数

2. 内存泄露,资源及时关闭

跨进程通信是需要buffer的,发送数据需要buffer,返回数据也需要buffer,buffer只有整个transaction结束时才释放,发送数据占用太多buffer的话,留给返回数据的buffer就很少了。事情buffer是吧就会跑TransactionTooLargeException

进程在启动binder机制时会映射一块内存,大小是1M,也就是说跨进程通信时申请缓冲区大小不大于1M,一个事务用太多的话,其他事务可用空间就变少。甚至事情100K都会跑TransactiionToolargeException。

第三条是官方推荐

binder_alloc_buffer: 分配data_size(parcel)大小的内存空间

Bitmap 是如何传输的

上面代码块,如果使用那个intent启动另外一个进程的Activity,会抛出TransactionTooLargeException, 是因为这个bitmap直接copy到缓冲区了,没有里有ashmem机制,因为allowFd机制没有打开

下面代码块不会抛出TransactionTooLargeException

bitmap超过16K时,使用的是匿名共享内存的方式

setAllowFds(false): 禁用了bundle的fd机制,bundle写入parcel时也会禁用parcel的allowFd机制

这两个底层都用到了共享内存 ,  适合跨进程大数据传输


Android 共享内存实现跨进程大文件传输(设计思路和Demo实现绕过Binder传输限制) 

项目链接  AndroidSharedMemoryDemo

下图是文件详情:13.7M

项目在客户端最终的显示效果:

本人建议可以下载下来直接查看就可以,对照着代码查看.

项目整体分为三个 部分

1.客户端clientapp:负责调用SDK测试

2.SDKjar包:mylibrary:扶着整体的共享内存的开辟以及读取操作.

3.服务端serverapp:当客户端请求数据时,往共享内存里面写数据.

本文不再对如何提供SDK给第三方项目使用的进行讲解,只针对部代码进行详解,如果想看项目的详解可以查看 Android 应用提供SDK Jar包给第三方使用 (设计思路 以及实现步骤) 和本项目的架构类似。

本项目的整体调用时序图如下:

本项目的类关系图:

MemoryFile简介:

MemoryFile是android在最开始就引入的一套框架,其内部实际上是封装了android特有的内存共享机制Ashmem匿名共享内存,简单来说,Ashmem在Android内核中是被注册成一个特殊的字符设备,Ashmem驱动通过在内核的一个自定义slab缓冲区中初始化一段内存区域,然后通过mmap把申请的内存映射到用户的进程空间中(通过tmpfs),这样子就可以在用户进程中使用这里申请的内存了,另外,Ashmem的一个特性就是可以在系统内存不足的时候,回收掉被标记为"unpin"的内存,这个后面会讲到,另外,MemoryFile也可以通过Binder跨进程调用来让两个进程共享一段内存区域。由于整个申请内存的过程并不再Java层上,可以很明显的看出使用MemoryFile申请的内存实际上是并不会占用Java堆内存的。

MemoryFile.java位置在如下,有兴趣的同学可以翻阅源码看一看

frameworks/base/core/java/android/os/MemoryFile.java

mylibrary简介:
本项目中 mylibrary负责整体的内存开辟以及读操作

MemoryFileHelper.java是开辟空间的具体操作类,具体拿到MemoryFIle用的是反射方法,核心方法如下:

    public static MemoryFile openMemoryFile(FileDescriptor fd, int length, int mode) {MemoryFile memoryFile = null;try {memoryFile = new MemoryFile("tem", 1);memoryFile.close();if (!Utils.isMoreThanAPI27()) {Class<?> c = MemoryFile.class;Method native_mmap = null;Method[] ms = c.getDeclaredMethods();for (int i = 0; ms != null && i < ms.length; i++) {if (ms[i].getName().equals("native_mmap")) {native_mmap = ms[i];}}ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mFD", fd);ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mLength", length);if (Utils.isMoreThanAPI21()) {long address = (long) ReflectUtils.invokeMethod(null, native_mmap, fd, length, mode);ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mAddress", address);} else {int address = (int) ReflectUtils.invokeMethod(null, native_mmap, fd, length, mode);ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mAddress", address);}} else {SharedMemory sharedMemory = SharedMemory.create("tem", 1);sharedMemory.close();ReflectUtils.setField("android.os.SharedMemory", sharedMemory, "mFileDescriptor", fd);ReflectUtils.setField("android.os.SharedMemory", sharedMemory, "mSize", length);ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mSharedMemory", sharedMemory);ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mMapping", sharedMemory.mapReadWrite());}} catch (Exception e) {e.printStackTrace();}return memoryFile;}

MyControllerImp.java负责开辟共享内存和负责通过Aidl和服务端交互的核心业务类.最核心的方法在链接建立之后,将自己创建的ParcelFileDescriptor对象传递给server这样保证了serverapp拿到的MemoryFile对象是同一个对象

@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.d("mysdk", " sdk  onServiceConnected  ");if (service == null) {if (mMyRemoteCtrl != null) {try {mMyRemoteCtrl.unlinkToDeath(mFrameDataCallBack.asBinder());} catch (RemoteException e) {e.printStackTrace();}}mMyRemoteCtrl = null;} else {mMyRemoteCtrl = IMyRemoteCtrl.Stub.asInterface(service);if (mMyRemoteCtrl != null) {try {mMyRemoteCtrl.linkToDeath(mFrameDataCallBack.asBinder());Log.d("mysdk", " sdk  onServiceConnected  setBackBufferCallBack ");if (mCallBack != null) {mMyRemoteCtrl.setParcelFileDescriptor(mMemoryFile.getParcelFileDescriptor());mMyRemoteCtrl.registerFrameByteCallBack(mFrameDataCallBack);mMemoryFile.setReadBufferCallBack(mCallBack);} else {mMyRemoteCtrl.unregisterFrameByteCallBack(mFrameDataCallBack);mMemoryFile.release();}Log.d("mysdk", " sdk  onServiceConnected  setBackBufferCallBack  eld ");} catch (RemoteException e) {e.printStackTrace();}}}}

而客户端注册的IReadBufferCallBack.java的对象也被MyControllerImp.java 设置到了MemoryFileImp.java中当,也就是说MemoryFileImp.java持有客户端注册的数据回调对象

mMemoryFile.setReadBufferCallBack(mCallBack);

serverapp简介:


服务端最核心的类ServerClientService.java中的内部类MyRemoteCtrlImpl.java负责和mylibrary 中的MyControllerImp.java通讯,用于接收传递过来的远端ParcelFileDescriptor对象和callBack.最核心的代码如下,因为没有持续的流可以写,就自己准备了一张在草原天路拍色的图片放在服务端的assets文件夹下 13M 绝对超出了Binder限制.

public class MyRemoteCtrlImpl extends IMyRemoteCtrl.Stub {
...........省略代码.......@Overridepublic void readFile(String msg) throws RemoteException {Log.d("mysdk"," mParcelFileDescriptor  = null ? " + (mParcelFileDescriptor == null));if (mParcelFileDescriptor != null) {memoryFile = MemoryFileHelper.openMemoryFile(mParcelFileDescriptor, MEMORY_SIZE, 0x3);}Log.d("mysdk"," memoryFile  = null ? " + (memoryFile == null));try {InputStream open = getResources().getAssets().open("IMG.JPG");byte[] buffer = new byte[open.available()];Log.d("mysdk"," 服务端 buffer " + buffer.length );open.read(buffer);readImage(buffer);open.close();} catch (IOException e) {e.printStackTrace();}}}
//写共享内存方法private void readImage(byte[] frame) {if (memoryFile != null) {try {memoryFile.readBytes(isCanRead, 0, 0, 1);if (isCanRead[0]== 0) {memoryFile.writeBytes(frame, 0, 1, frame.length);isCanRead[0] = 1;memoryFile.writeBytes(isCanRead, 0, 0, 1);}Log.d("mysdk"," 服务端 canReadFrameData " );mIReadDataCallBack.canReadFileData();} catch (Exception e ) {Log.d("mysdk"," 服务端 Exception  "  + e.getMessage()  );e.printStackTrace();}}}

clientapp简介

集成mylibrary的jar包 不知道如何打jar包的可以看 Android 应用提供SDK Jar包给第三方使用 (设计思路 以及实现步骤) 

核心代码就是读取数据进行显示MainActivity.java中

public class MainActivity extends AppCompatActivity {ImageView iv;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);iv = findViewById(R.id.iv);SharedMemoryLibSDK.getInstance().init(this);SharedMemoryLibSDK.getInstance().setBackBufferCallBack(new IReadBufferCallBack() {@Overridepublic void onReadBuffer(final byte[] bytes, int i) {Log.d("mysdk"," 客户端 读取到客户写到共享内存的大小为: " + bytes.length);runOnUiThread(new Runnable() {@Overridepublic void run() {Bitmap bitmap = byteToBitmap(bytes);iv.setImageBitmap(bitmap);}});}});}public static Bitmap byteToBitmap(byte[] imgByte) {  InputStream input = null;  Bitmap bitmap = null;  BitmapFactory.Options options = new BitmapFactory.Options();  options.inSampleSize = 8;  input = new ByteArrayInputStream(imgByte);SoftReference softRef = new SoftReference(BitmapFactory.decodeStream(  input, null, options));  bitmap = (Bitmap) softRef.get();  if (imgByte != null) {  imgByte = null;  }  try {if (input != null) {  input.close();  }  } catch (IOException e) {// TODO Auto-generated catch block  e.printStackTrace();  }  return bitmap;  }public void  readFIle(View view) {Log.d("mysdk"," 客户端  调用服务端的 readFIle  " );SharedMemoryLibSDK.getInstance().readFile("我是客户端");}
}


点击按钮的最后效果:因为数据太大在用byte生成BitMap的时候容易内存溢出,在客户端读取完成数据之后对生成的BitMap使用了中压缩了处理.

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/159747.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在Jupyter Lab中使用多个环境,及魔法命令简介

一、Jupyter Lab使用conda虚拟环境 1、给虚拟环境添加 ipykernel 方法一: 创建环境时直接添加ipykernel 方法&#xff1a;conda create -n 【虚拟环境名称】python3.8 ipykernel实例如下&#xff1a; conda create -n tensorflow_cpu python3.8 ipykernel 方法二&#xff…

VR全景:赋能城市园区建设,打造3DVR城市名片

近年来&#xff0c;很多城市都在大力发展数字化经济建设&#xff0c;以VR全景技术赋能现代化城市和园区建设&#xff0c;为城市园区展示带来了全新的可能性。借助3D、VR技术把现实城市和园区搬到互联网上进行全方位展示&#xff0c;将城市园区的形象、景观、规划布局等1&#x…

『亚马逊云科技产品测评』活动征文|AWS 存储产品类别及其适用场景详细说明

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 目录 前言、AWS 存储产品类别 1、Amazon Elastic Block Store (EBS) …

echarts 几千条分钟级别在小时级别图标上展示

需求背景解决效果ISQQW代码地址strategyChart.vue 需求背景 需要实现 秒级数据几千条在图表上显示&#xff0c;(以下是 设计图表上是按小时界别显示数据&#xff0c;后端接口为分钟级别数据) 解决效果 ISQQW代码地址 链接 strategyChart.vue <!--/** * author: liuk *…

编译安装redis及配置多实例

yum安装是这种十分简单的方法我们就不在提及了&#xff0c;今天我们来做一下redis的编译安装 Redis源码包官方下载链接&#xff1a;http://download.redis.io/releases/ 一、编译安装&#xff1a; 安装依赖包 dnf -y install make gcc jemalloc-devel systemd-devel如果是…

数据结构中树、森林 与 二叉树的转换

1 树转换为 二叉树 将树转换成二叉树的步骤是&#xff1a; 加线。在所有的兄弟结点之间加一条线。去线。对于树中的每个结点&#xff0c;只保留它与第一个孩子结点的连线&#xff0c;删除该结点其他孩子结点之间的连线。调整。以树的根结点为轴心&#xff0c;将整个树顺时针旋…

SMBGhost_RCE漏洞(CVE-2020-0796永恒之黑)

https://blog.csdn.net/qq_45372008/article/details/106980409 https://zhuanlan.zhihu.com/p/374949632 SMB 3.1.1协议处理某些请求的方式中存在远程执行代码漏洞&#xff0c;可能被攻击者利用远程执行任意代码。该漏洞的后果十分接近永恒之蓝系列&#xff0c;都利用Windows …

银行业务测试

1、商业银行四大类&#xff1a; 业务类系统、渠道类面试、MIS类系统、其他基础平台系统 2、银行系统开发流程&#xff08;UAT是行方&#xff09; 3、银行系统测试流程 4、对于不同的服务方式也不同&#xff0c;如:柜台、手机银行、网上银行&#xff0c;电话外呼&#xff0c;…

89. 格雷编码

89. 格雷编码 Java代码&#xff1a;2DFS class Solution {List<Integer> res new ArrayList<>();public List<Integer> grayCode(int n) {dfs(n, new StringBuffer(), new int[]{0, 1});return res;}public void dfs(int n, StringBuffer sb, int[] nums){i…

吴恩达《机器学习》9-7-9-8:综合起来、自主驾驶

在神经网络的使用过程中&#xff0c;需要经历一系列步骤&#xff0c;从网络结构的选择到训练过程的实施。以下是使用神经网络时的主要步骤的小结&#xff1a; 一、网络结构的选择 输入层&#xff1a; 第一步是选择网络结构&#xff0c;即确定神经网络的层数以及每层的单元数。…

CURL踩坑记录

因为项目使用的windows server&#xff0c;且没有安装Postman&#xff0c;所以对于在本地的Postman上执行的请求&#xff0c;要拷贝到服务器执行&#xff0c;只能先转化成为curl命令&#xff0c;操作也很简单&#xff0c;如下&#xff1a; 注意&#xff0c;Postman默认对url包围…

【钉钉】通过链接方式跳转到应用机器人聊天窗口

使用这个方式&#xff1a; dingtalk://dingtalkclient/action/jumprobot?dingtalkid可以通过机器人回调拿到chatbotUserId这个字段&#xff0c;这个就是dingtalkid。 示例&#xff1a;&#xff08;chatbotUserId是不规则字符串&#xff0c;链接拼上这个参数最好 urlencode一…

大图书馆 #9 《流计算系统图解》书评

上周&#xff0c;我收到清华大学出版社编辑寄来的新书《流计算系统图解》。趁着周末的功夫&#xff0c;我快速浏览了本书的主要内容。一句话评价&#xff1a;值得一读&#xff0c;尤其是对开始开发流计算任务或系统一到两年&#xff0c;初步实现过一些功能或作业&#xff0c;但…

二蛋赠书九期:《机器学习图解》

前言 大家好&#xff01;我是二蛋&#xff0c;一个热爱技术、乐于分享的工程师。在过去的几年里&#xff0c;我一直通过各种渠道与大家分享技术知识和经验。我深知&#xff0c;每一位技术人员都对自己的技能提升和职业发展有着热切的期待。因此&#xff0c;我非常感激大家一直…

基于C#实现AC自动机算法

我要检查一篇文章中是否有某些敏感词&#xff0c;这其实就是多模式匹配的问题。当然你也可以用 KMP 算法求出&#xff0c;那么它的时间复杂度为 O(c*(mn))&#xff0c;c&#xff1a;为模式串的个数。m&#xff1a;为模式串的长度,n:为正文的长度&#xff0c;那么这个复杂度就不…

Autocad2020切换经典界面

Autocad2020切换经典界面 1.更改1.1设置另存为 1.更改 1.1设置另存为

迅为RK3568开发板学习之Linux驱动篇第十三期输入子系统

驱动视频全新升级&#xff0c;并持续更新~更全&#xff0c;思路更科学&#xff0c;入门更简单。 迅为基于iTOP-RK3568开发板进行讲解&#xff0c;本次更新内容为第十三期&#xff0c;主要讲解输入子系统&#xff0c;共计24 讲。 关注B站&#xff1a;北京迅为电子&#xff0c;在…

赛轮集团SAILUN方程式赛车轮胎震撼登场,开启新篇章

11月初&#xff0c;在厦门国际赛车场&#xff0c;SAILUN方程式赛车轮胎展现出令人瞩目的实力&#xff0c;成功完成了首次震撼亮相。这一引人注目的表现为未来的赛车轮胎技术发展打开了崭新的一页。 在这次首次亮相的测试中&#xff0c;职业车手巧妙操控着SAILUN方程式赛车轮胎&…

解决Vision Transformer在任意尺寸图像上微调的问题:使用timm库

解决Vision Transformer在任意尺寸图像上微调的问题&#xff1a;使用timm库 文章目录 一、ViT的微调问题的本质二、Positional Embedding如何处理1&#xff0c;绝对位置编码2&#xff0c;相对位置编码3&#xff0c;对位置编码进行插值 三、Patch Embedding Layer如何处理四、使…

气膜体育馆:低碳环保体育新潮流

在追求健康生活的今天&#xff0c;体育运动的重要性无法忽视。为了满足人民日益增长的体育需求&#xff0c;气膜体育馆应运而生&#xff0c;成为体育场馆领域的一次革命性创新。这种新型体育馆解决了传统体育场馆建设中面临的审批难、周期长、门槛高等问题&#xff0c;为我们的…