導語:
html
在Android系統中,提供了獨特的匿名共享內存子系統Ashmem(Anonymous Shared Memory),它以驅動程序的形式實如今內核空間中。它有兩個特色:java
1.一是可以輔助內存管理系統來有效地管理再也不使用的內存塊linux
2.二是它經過Binder進程間通訊機制來實現進程間的內存共享。android
本文中,咱們將經過實例來簡要介紹Android系統的匿名共享內存的使用方法,使得咱們對Android系統的匿名共享內存機制有一個感性的認識,爲進一步學習它的源代碼實現打下基礎。算法
案例原理:
數據結構
Android系統的匿名共享內存子系統的主體是以驅動程序的形式存在。在系統運行時庫層和應用程序框架層提供了訪問接口,其中,在系統運行時庫層提供了C/C++調用接口,而在應用程序框架層提供了Java調用接口。app
這裏,咱們將直接經過應用程序框架層提供的Java調用接口來講明匿名共享內存子系統Ashmem的使用方法,畢竟咱們在Android開發應用程序時,是基於Java語言的,而實際上,應用程序框架層的Java調用接口是經過JNI方法來調用系統運行時庫層的C/C++調用接口,最後進入到內核空間的Ashmem驅動程序去的。框架
咱們在這裏舉的例子是一個名爲Ashmem的應用程序,它包含了一個Server端和一個Client端實現,ide
1. Server端是以Service的形式實現的,在這裏Service裏面,建立一個匿名共享內存文件函數
2. Client是一個Activity,這個Activity經過Binder進程間通訊機制得到前面這個Service建立的匿名共享內存文件的句柄,從而實現共享。
在Android應用程序框架層,提供了一個MemoryFile接口來封裝了匿名共享內存文件的建立和使用,它實如今frameworks/base/core/java/android/os/MemoryFile.java文件中。
下面,咱們就來看看Server端是如何經過MemoryFile類來建立匿名共享內存文件的以及Client是如何得到這個匿名共享內存文件的句柄的。
在MemoryFile類中,提供了兩種建立匿名共享內存的方法,咱們經過MemoryFile類的構造函數來看看這兩種使用方法:
public class MemoryFile { ...... /** * Allocates a new ashmem region. The region is initially not purgable. * * @param name optional name for the file (can be null). * @param length of the memory file in bytes. * @throws IOException if the memory file could not be created. */ public MemoryFile(String name, int length) throws IOException { mLength = length; mFD = native_open(name, length); mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE); mOwnsRegion = true; } /** * Creates a reference to an existing memory file. Changes to the original file * will be available through this reference. * Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail. * * @param fd File descriptor for an existing memory file, as returned by * {@link #getFileDescriptor()}. This file descriptor will be closed * by {@link #close()}. * @param length Length of the memory file in bytes. * @param mode File mode. Currently only "r" for read-only access is supported. * @throws NullPointerException if <code>fd</code> is null. * @throws IOException If <code>fd</code> does not refer to an existing memory file, * or if the file mode of the existing memory file is more restrictive * than <code>mode</code>. * * @hide */ public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException { if (fd == null) { throw new NullPointerException("File descriptor is null."); } if (!isMemoryFile(fd)) { throw new IllegalArgumentException("Not a memory file."); } mLength = length; mFD = fd; mAddress = native_mmap(mFD, length, modeToProt(mode)); mOwnsRegion = false; } ...... }
兩個構造函數的主要區別是第一個參數
1.第一種構造方法是以指定的字符串調用JNI方法native_open來建立一個匿名共享內存文件,獲得一個文件描述符,接着就以這個文件描述符爲參數調用JNI方法natvie_mmap把這個匿名共享內存文件映射在進程空間中,而後就能夠經過這個映射後獲得的地址空間來直接訪問內存數據了;
2.第二種構造方法是以指定的文件描述符來直接調用JNI方法natvie_mmap把這個匿名共享內存文件映射在進程空間中,而後進行訪問,而這個文件描述符就必需要是一個匿名共享內存文件的文件描述符,這是經過一個內部函數isMemoryFile來驗證的,而這個內部函數isMemoryFile也是經過JNI方法調用來進一步驗證的。前面所提到的這些JNI方法調用,最終都是經過系統運行時庫層進入到內核空間的Ashmem驅動程序中去,不過這裏咱們不關心這些JNI方法、系統運行庫層調用以及Ashmem驅動程序的具體實現,在接下來的兩篇文章中,咱們將會着重介紹,這裏咱們只關注MemoryFile這個類的使用方法。
前面咱們說到,咱們在這裏舉的例子包含了一個Server端和一個Client端實現,
1. Server端就是經過第一個構造函數來建立一個匿名共享內存文件
2. Client端過Binder進程間通訊機制來向Server請求獲取這個匿名共享內存的文件描述符,有了這個文件描述符以後,就能夠經過後面一個構造函數來共享這個內存文件了。 而後Client和Server之間就能夠經過這個這個匿名內存共享數據了。
案例實現:
首先在源代碼工程的packages/experimental目錄下建立一個應用程序工程目錄Ashmem。它定義了一個路徑爲shy.luo.ashmem的package,這個例子的源代碼主要就是實如今這裏了。
將會逐一介紹這個package裏面的文件。這裏要用到的Binder進程間通訊接口定義在src/shy/luo/ashmem/IMemoryService.java文件中:
package shy.luo.ashmem; import android.util.Log; import android.os.IInterface; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.RemoteException; public interface IMemoryService extends IInterface { public static abstract class Stub extends Binder implements IMemoryService { private static final String DESCRIPTOR = "shy.luo.ashmem.IMemoryService"; public Stub() { attachInterface(this, DESCRIPTOR); } public static IMemoryService asInterface(IBinder obj) { if (obj == null) { return null; } IInterface iin = (IInterface)obj.queryLocalInterface(DESCRIPTOR); if (iin != null && iin instanceof IMemoryService) { return (IMemoryService)iin; } return new IMemoryService.Stub.Proxy(obj); } public IBinder asBinder() { return this; } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getFileDescriptor: { data.enforceInterface(DESCRIPTOR); ParcelFileDescriptor result = this.getFileDescriptor(); reply.writeNoException(); if (result != null) { reply.writeInt(1); result.writeToParcel(reply, 0); } else { reply.writeInt(0); } return true; } case TRANSACTION_setValue: { data.enforceInterface(DESCRIPTOR); int val = data.readInt(); setValue(val); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements IMemoryService { private IBinder mRemote; Proxy(IBinder remote) { mRemote = remote; } public IBinder asBinder() { return mRemote; } public String getInterfaceDescriptor() { return DESCRIPTOR; } public ParcelFileDescriptor getFileDescriptor() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); ParcelFileDescriptor result; try { data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getFileDescriptor, data, reply, 0); reply.readException(); if (0 != reply.readInt()) { result = ParcelFileDescriptor.CREATOR.createFromParcel(reply); } else { result = null; } } finally { reply.recycle(); data.recycle(); } return result; } public void setValue(int val) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(DESCRIPTOR); data.writeInt(val); mRemote.transact(Stub.TRANSACTION_setValue, data, reply, 0); reply.readException(); } finally { reply.recycle(); data.recycle(); } } } static final int TRANSACTION_getFileDescriptor = IBinder.FIRST_CALL_TRANSACTION + 0; static final int TRANSACTION_setValue = IBinder.FIRST_CALL_TRANSACTION + 1; } public ParcelFileDescriptor getFileDescriptor() throws RemoteException; public void setValue(int val) throws RemoteException; }
public ParcelFileDescriptor getFileDescriptor() throws RemoteException; public void setValue(int val) throws RemoteException;
package shy.luo.ashmem; import java.io.FileDescriptor; import java.io.IOException; import android.os.Parcel; import android.os.MemoryFile; import android.os.ParcelFileDescriptor; import android.util.Log; public class MemoryService extends IMemoryService.Stub { private final static String LOG_TAG = "shy.luo.ashmem.MemoryService"; private MemoryFile file = null; public MemoryService() { try { file = new MemoryFile("Ashmem", 4); setValue(0); } catch(IOException ex) { Log.i(LOG_TAG, "Failed to create memory file."); ex.printStackTrace(); } } public ParcelFileDescriptor getFileDescriptor() { Log.i(LOG_TAG, "Get File Descriptor."); ParcelFileDescriptor pfd = null; try { pfd = file.getParcelFileDescriptor(); } catch(IOException ex) { Log.i(LOG_TAG, "Failed to get file descriptor."); ex.printStackTrace(); } return pfd; } public void setValue(int val) { if(file == null) { return; } byte[] buffer = new byte[4]; buffer[0] = (byte)((val >>> 24) & 0xFF); buffer[1] = (byte)((val >>> 16) & 0xFF); buffer[2] = (byte)((val >>> 8) & 0xFF); buffer[3] = (byte)(val & 0xFF); try { file.writeBytes(buffer, 0, 0, 4); Log.i(LOG_TAG, "Set value " + val + " to memory file. "); } catch(IOException ex) { Log.i(LOG_TAG, "Failed to write bytes to memory file."); ex.printStackTrace(); } } }
這裏還實現了IMemoryService.Stub的兩個接口getFileDescriptor和setVal,一個用來獲取匿名共享內存文件的文件描述符,一個來往匿名共享內存文件中寫入一個整數,其中,接口getFileDescriptor的返回值是一個ParcelFileDescriptor。在Java中,是用FileDescriptor類來表示一個文件描述符的,而ParcelFileDescriptor是用來序列化FileDescriptor的,以便在進程間調用時傳輸。
定義好本地服務好,就要定義一個Server來啓動這個服務了。這裏定義的Server實如今src/shy/luo/ashmem/Server.java文件中:
package shy.luo.ashmem; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; import android.os.ServiceManager; public class Server extends Service { private final static String LOG_TAG = "shy.luo.ashmem.Server"; private MemoryService memoryService = null; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { Log.i(LOG_TAG, "Create Memory Service..."); memoryService = new MemoryService(); try { ServiceManager.addService("AnonymousSharedMemory", memoryService); Log.i(LOG_TAG, "Succeed to add memory service."); } catch (RuntimeException ex) { Log.i(LOG_TAG, "Failed to add Memory Service."); ex.printStackTrace(); } } @Override public void onStart(Intent intent, int startId) { Log.i(LOG_TAG, "Start Memory Service."); } @Override public void onDestroy() { Log.i(LOG_TAG, "Destroy Memory Service."); } }
package shy.luo.ashmem; import java.io.FileDescriptor; import java.io.IOException; import shy.luo.ashmem.R; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.MemoryFile; import android.os.ParcelFileDescriptor; import android.os.ServiceManager; import android.os.RemoteException; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; public class Client extends Activity implements OnClickListener { private final static String LOG_TAG = "shy.luo.ashmem.Client"; IMemoryService memoryService = null; MemoryFile memoryFile = null; private EditText valueText = null; private Button readButton = null; private Button writeButton = null; private Button clearButton = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); IMemoryService ms = getMemoryService(); if(ms == null) { startService(new Intent("shy.luo.ashmem.server")); } else { Log.i(LOG_TAG, "Memory Service has started."); } valueText = (EditText)findViewById(R.id.edit_value); readButton = (Button)findViewById(R.id.button_read); writeButton = (Button)findViewById(R.id.button_write); clearButton = (Button)findViewById(R.id.button_clear); readButton.setOnClickListener(this); writeButton.setOnClickListener(this); clearButton.setOnClickListener(this); Log.i(LOG_TAG, "Client Activity Created."); } @Override public void onResume() { super.onResume(); Log.i(LOG_TAG, "Client Activity Resumed."); } @Override public void onPause() { super.onPause(); Log.i(LOG_TAG, "Client Activity Paused."); } @Override public void onClick(View v) { if(v.equals(readButton)) { int val = 0; MemoryFile mf = getMemoryFile(); if(mf != null) { try { byte[] buffer = new byte[4]; mf.readBytes(buffer, 0, 0, 4); val = (buffer[0] << 24) | ((buffer[1] & 0xFF) << 16) | ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF); } catch(IOException ex) { Log.i(LOG_TAG, "Failed to read bytes from memory file."); ex.printStackTrace(); } } String text = String.valueOf(val); valueText.setText(text); } else if(v.equals(writeButton)) { String text = valueText.getText().toString(); int val = Integer.parseInt(text); IMemoryService ms = getMemoryService(); if(ms != null) { try { ms.setValue(val); } catch(RemoteException ex) { Log.i(LOG_TAG, "Failed to set value to memory service."); ex.printStackTrace(); } } } else if(v.equals(clearButton)) { String text = ""; valueText.setText(text); } } private IMemoryService getMemoryService() { if(memoryService != null) { return memoryService; } memoryService = IMemoryService.Stub.asInterface( ServiceManager.getService("AnonymousSharedMemory")); Log.i(LOG_TAG, memoryService != null ? "Succeed to get memeory service." : "Failed to get memory service."); return memoryService; } private MemoryFile getMemoryFile() { if(memoryFile != null) { return memoryFile; } IMemoryService ms = getMemoryService(); if(ms != null) { try { ParcelFileDescriptor pfd = ms.getFileDescriptor(); if(pfd == null) { Log.i(LOG_TAG, "Failed to get memory file descriptor."); return null; } try { FileDescriptor fd = pfd.getFileDescriptor(); if(fd == null) { Log.i(LOG_TAG, "Failed to get memeory file descriptor."); return null; } memoryFile = new MemoryFile(fd, 4, "r"); } catch(IOException ex) { Log.i(LOG_TAG, "Failed to create memory file."); ex.printStackTrace(); } } catch(RemoteException ex) { Log.i(LOG_TAG, "Failed to get file descriptor from memory service."); ex.printStackTrace(); } } return memoryFile; } }
這個Activity在onCreate時,會經過startService接口來啓動咱們前面定義的Server進程。調用startService時,須要指定要啓動的服務的名稱,這裏就是"shy.luo.ashmem.server"了,後面咱們會在程序的描述文件AndroidManifest.xml看到前面的Server類是如何和名稱"shy.luo.ashmem.server"關聯起來的。關於調用startService函數來啓動自定義服務的過程,能夠參考Android系統在新進程中啓動自定義服務過程(startService)的原理分析一文。
內部函數getMemoryService用來獲取IMemoryService。若是是第一次調用該函數,則會經過ServiceManager的getService接口來得到這個IMemoryService接口,而後保存在類成員變量memoryService中,之後再調用這個函數時,就能夠直接返回memoryService了。
內部函數getMemoryFile用來從MemoryService中得到匿名共享內存文件的描述符。一樣,若是是第一次調用該函數,則會經過IMemoryService的getFileDescriptor接口來得到MemoryService中的匿名共享內存文件的描述符,而後用這個文件描述符來建立一個MemoryFile實例,並保存在類成員變量memoryFile中,之後再調用這個函數時,就能夠直接返回memoryFile了。
有了memoryService和memoryFile後,咱們就能夠在Client端訪問Server端建立的匿名共享內存了。點擊Read按鈕時,就經過memoryFile的readBytes接口把共享內存中的整數讀出來,並顯示在文本框中;點擊Write按鈕時,就經過memoryService這個代理類的setVal接口來調用MemoryService的本地實現類的setVal服務,從而把文本框中的數值寫到Server端建立的匿名共享內存中去;點擊Clear按鈕時,就會清空文本框的內容。這樣,咱們就能夠經過Read和Write按鈕來驗證咱們是否在Client和Server兩個進程中實現內存共享了。
如今,咱們再來看看Client界面的配置文件,它定義在res/layout/main.xml文件中:
這樣,界面的相關配置文件就介紹完了。
咱們還要再來看程序描述文件AndroidManifest.xml的相關配置,它位於Ashmem目錄下:
這樣,整個例子的源代碼實現就介紹完了,接下來就要編譯了。有關如何單獨編譯Android源代碼工程的模塊,以及如何打包system.img,請參考如何單獨編譯Android源代碼中的模塊一文。
執行如下命令進行編譯和打包:
再接下來,就是運行模擬器來運行咱們的例子了。關於如何在Android源代碼工程中運行模擬器,請參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文。
執行如下命令啓動模擬器:
點擊Ashmem圖標,啓動Ashmem應用程序,界面以下:
這樣,咱們就能夠驗證程序的功能了,看看是否實現了在兩個進程中經過使用Android系統的匿名共享內存機制來共享內存數據的功能。
經過這個例子的學習,相信讀者對Android系統匿名共享內存子系統Ashmem有了一個大概的認識,可是,這種認識仍是停留在表面上。咱們在文章開始時就提到,Android系統匿名共享內存子系統Ashmem兩個特色,一是可以輔助內存管理系統來有效地管理再也不使用的內存塊,二是它經過Binder進程間通訊機制來實現進程間的內存共享。第二個特色咱們在上面這個例子中看到了,可是彷佛還不夠深刻,咱們知道,在Linux系統中,文件描述符其實就是一個整數,它是用來索引進程保存在內核空間的打開文件數據結構的,並且,這個文件描述符只是在進程內有效,也就是說,在不一樣的進程中,相同的文件描述符的值,表明的多是不一樣的打開文件,既然是這樣,把Server進程中的文件描述符傳給Client進程,彷佛就沒有用了,可是不用擔憂,在傳輸過程當中,Binder驅動程序會幫咱們處理好一切,保證Client進程拿到的文件描述符是在本進程中有效的,而且它指向就是Server進程建立的匿名共享內存文件。至於第一個特色,咱們也準備在後續學習Android系統匿名共享內存子系統Ashmem時,再詳細介紹。