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

项目链接  AndroidSharedMemoryDemo

Demo简介

最近在学习Binder的东西,发现Binder对跨进程传输文件的大小有要求,系统的Binder传输文件大小的时候限制在1M左右,太大的文件会导致内存溢出,导致跨进程传输失败,当然实现大文件传输的时候我们也可以使用广播,当别人发广播给我们的时候我们可以将文件路径通过广播返回给调用者,今天实现的方式使用的是共享内存

共享内存的作用可以是大文件传输,也可以用于共享预览帧数据,比如我camera打开预览的时候,此时别的应用在后台也想使用预览帧,我就可以将预览帧放到内存当中,在我预览的时候别人也可以使用预览帧处理自己的逻辑.

本次设计的服务端有一个13M的图片 通过共享内存的方式传递到客户点并且可以显示出来

在网上找了半天没有找到大图片,于是我把去天路的高清大图拿来测试了,图片放在了Service端的assets文件夹下

下图是文件详情: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对象是同一个对象

@Override
    public 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 {
...........省略代码.......
        @Override
        public 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;
    @Override
    protected 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() {
            @Override
            public void onReadBuffer(final byte[] bytes, int i) {
                Log.d("mysdk"," 客户端 读取到客户写到共享内存的大小为: " + bytes.length);
                runOnUiThread(new Runnable() {
                    @Override
                    public 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使用了中压缩了处理.

原创文章 33 获赞 38 访问量 2万+

猜你喜欢

转载自blog.csdn.net/ChaoLi_Chen/article/details/106105894
今日推荐