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使用了中壓縮了處理.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章