ipC是 Inter-PRocess Communication的縮寫,含義為進程間通信或者跨進程通信,是指兩個進程之間進行數據交換的過程。 進程和線程是包含與被包含的關系。
1. 在Android中創建多進程的方法: ① 通過JNI在native層去fork一個新的進程(不常用) ② 給四大組件添加屬性 android:process = "進程名" 就可以開啟多進程模式,如果沒有指定這個屬性,那么默認進程名是包名 2. 給進程命名: ① android:process = ": 名字" ,這是一種簡寫的方法,完整的進程名為 "包名+名字";是屬于當前應用的私有進程,其他應用的組件不可以和它跑到同一個進程中
<service android:name=".services.BookManagerService" android:enabled="true" android:exported="true" android:process=":myaidl" />② android:process = "全名" ,屬于全局進程,其它的應用通過ShareUID方法可以和它跑到同一個進程中。
3. 一般來說,使用多進程會造成如下幾方面的問題: ① 靜態成員和單例模式完全失效: Android為每個進程都會分配一個獨立的虛擬機,這導致在不同的虛擬機中訪問同一個類的對象會產生多個副本 ② 線程同步機制完全失效 Android為每個進程都會分配一個獨立的虛擬機(內存) ③ SharedPreferences的可靠性下降 底層是通過xml文件來實現的,并發讀寫都可能出問題 ④ application會創建多次 運行在不同進程的組件是屬于不同的虛擬機和Application的
Android中的序列化機制
Serializable是java中的序列化接口,其使用起來簡單但是開銷很大,序列化、反序列化過程都需要大量的I/O操作 Parceable是Adnroid中的序列化接口,效率很高,就是使用起來麻煩一點
一、Serializable接口:
① 為該類指定 private static final long serialVersionUID = 465465464l; 作用:當版本升級后,可能刪除或者添加了某個成員變量,這個時候我們仍然能夠反序列化成功,程序能夠最大限度的恢復數據,相反,如果不指定serialVersionUID, 程序則會掛掉。 ② 采用ObjectOutputStream、ObjectInputStream實現序列化和反序列化操作:
File file = new File(Environment.getExternalStorageDirectory() , "test.text");//序列化Userss usersS = new UsersS(20 , "xiaoming" , true);try { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); out.writeObject(usersS); out.close();} catch (IOException e) { e.printStackTrace();}//反序列化try { ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); UsersS usersS1 = (UsersS) in.readObject(); in.close(); LogUtils.e("反序列化得到的名字是:" + usersS1.getName());} catch (IOException e) { e.printStackTrace();} catch (ClassNotFoundException e) { e.printStackTrace();}二、Parceable接口:
① 實現這個接口可以實現序列化,并可以通過Intent和Binder傳遞:
//返回當前對象的描述(一般返回0)@Overridepublic int describeContents() { return 0;}//將當前對象寫入序列化結構中@Overridepublic void writeToParcel(Parcel dest, int flags) { dest.writeInt(age); dest.writeString(name); dest.writeInt(isMale ? 1 : 0); dest.writeParcelable(usersS , 0);//users另外是一個序列化的類的對象;flag一般是0}public static final Creator<UsersP> CREATOR = new Creator<UsersP>() { //從序列化后的對象中創建原始對象 @Override public UsersP createFromParcel(Parcel in) { return new UsersP(in); } //創建指定長度的原始對象數組 @Override public UsersP[] newArray(int size) { return new UsersP[size]; }};protected UsersP(Parcel in) { age = in.readInt(); name = in.readString(); isMale = in.readInt() == 1; in.readParcelableArray(Thread.currentThread().getContextClassLoader());//反序列化過程需要傳入當前線程的上下文類加載器}Bundle
可以在Bundle上附加需要傳輸的信息并通過Intent發送出去 Intent intent = new Intent(); Bundle bundle = new Bundle(); intent.putExtra("1" , bundle);
文件共享
適合在對數據同步要求不高的進程之間進行通信,并且要妥善處理并發讀、寫的問題 例子:在兩個進程中分別進行序列化和反序列化一個java類,存在問題: 一個序列化、一個反序列化,那么反序列化出來的可能不是最新的; 如果兩個同時進行序列化就有可能出現更嚴重的問題;
Messenger(翻譯為“信使”,對AIDL做了封裝)
① 通過它可以在不同的進程中傳遞Message對象,在Message中可以放入我們需要傳遞的數據 ② 兩個構造函數表明它的底層實現是AIDL: public Messenger(Handler handler){ mTarget = target.getIMessenger(); } public Messenger(IBinder target){ mTarget = IMessenger.Stub.asInterface(target); } ③ 一次只處理一個請求,在服務端不需要考慮線程同步的問題。 ④ Messenger是串行的方式處理客戶端發來的消息,如果大量的消息同時發送到服務端,服務端仍然只能一個個處理,如果有大量的并發請求, 那么Messenger就不太合適了。 ⑤ Messenger的主要作用是為了傳遞消息,很多時候我們很可能需要跨進程調用服務端的方法,這種情形Messenger就無法做到了,aidl可以。
下面通過一個例子來加深理解:
客戶端通過bindService()來啟動服務,然后根據返回的IBinder對象得到Messenger對象:
private ServiceConnection conn = new ServiceConnection(){ @Override public void onServiceConnected(ComponentName name, IBinder service) { //通過返回的IBinder對象,得到一個Messenger對象 Messenger messenger = new Messenger(service); Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT); Bundle bundle = new Bundle(); bundle.putString("msg" , "this is test"); msg.setData(bundle); //將客戶端的Messenger通過Message的replyTo參數傳遞給服務端 msg.replyTo = mMessenger; try { messenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { //斷開連接會觸發此方法 }};服務端接收到客戶端傳來的信息,并返回數據:
private final Messenger messenger = new Messenger(new MessengerHandler());private static class MessengerHandler extends Handler{ @Override public void handleMessage(Message msg) { switch (msg.what){ case MyConstants.MSG_FROM_CLIENT: LogUtils.e("服務端:" + msg.getData().getString("msg")); //通過Message的replyTo得到客戶端傳遞過來的Messenger對象 Messenger replyTo = msg.replyTo; Message replyMsg = Message.obtain(null, MyConstants.MSG_FROM_SERVICE); Bundle bundle = new Bundle(); bundle.putString("reply" , "this is reply"); replyMsg.setData(bundle); try { replyTo.send(replyMsg); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } }}@Overridepublic IBinder onBind(Intent intent) { //綁定成功,返回Messenger對象底層的Binder return messenger.getBinder();}然后再在客戶端通過handler來進行處理:
switch (msg.what){ case MyConstants.MSG_FROM_SERVICE: LogUtils.e("服務端返回回來的消息:" + msg.getData().getString("reply")); break;AIDL
一、創建AIDL文件: ① 自定義的Parcelable對象和AIDL對象必須要顯示import進來,不管是否位于同一個包 ② aidl接口只支持方法,不支持靜態常量 ③ aidl中除了基本的數據類型,其他類型的參數必須標上方向:in 、out或者inout。其中: in: 表示輸入型的參數 out:表示輸出型的參數 inout:表示輸入輸出型的參數 ④ 如果aidl中用到了自定義的Parcelable對象,那么就必須新建一個和它同名的AIDL文件,并且在其中申明它為 Parcelable ⑤ build--->Rebuild project 生成對應的java文件,java文件路徑 build/generated/source/aidl/debug/XX.java 二、保持客戶端、服務端的aidl文件一致: AIDL的包結構在服務端和客戶端要保持一致,否則運行會出錯,這是因為客戶端需要反序列化服務端中和AIDL接口相關的所有類。如果類的完整路徑不一樣,就無法成功反序列化三、問題: ① 添加單個元素集合(list) CopyOnWriteArrayList:支持并發的讀、寫(會存在多個線程同時訪問的情形);相對應的ConcurrentHashMap
/** * CopyOnWriteArrayList: * 支持并發的讀、寫(會存在多個線程同時訪問的情形);相對應的ConcurrentHashMap */private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>(); ② 添加注冊注銷接口集合(map): RemoteCallbackList:是系統專門提供的用于刪除跨進程listener的接口,其內部實現了自動同步的功能。 int N = RemoteCallbackList.beginBroadcast(); RemoteCallbackList.finishBroadcast();必須配對使用/** * RemoteCallbackList: 是系統專門提供的用于刪除跨進程listener的接口,其內部實現了自動同步的功能。 * int N = RemoteCallbackList.beginBroadcast(); * RemoteCallbackList.finishBroadcast();必須配對使用 */private RemoteCallbackList<OnNewBookListener> mListeners = new RemoteCallbackList<>(); ② Binder意外死亡需要重新連接: binderDied():默認在子線程執行 onServiceDisconnected():在默認主線程執行四、添加aidl的訪問權限: 在客戶端和服務端都添加自定義的權限 <permission android:name="zidingyiquanxian" android:protectionLevel="normal"/> 在服務端進行權限驗證
@Overridepublic IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("自定義權限"); if(check == PackageManager.PERMISSION_DENIED){ //權限拒絕,直接返回null } return mBinder;}五、配置 builder.gradle文件:
//把src/main/aidl文件也作為Java.srcDirs, resources.srcDirs;否則找不到自定義的類sourceSets { main { manifest.srcFile 'src/main/AndroidManifest.xml' java.srcDirs = ['src/main/java', 'src/main/aidl'] resources.srcDirs = ['src/main/java', 'src/main/aidl'] aidl.srcDirs = ['src/main/aidl'] res.srcDirs = ['src/main/res'] assets.srcDirs = ['src/main/assets'] }}ContentProvider
這里以數據庫的存儲方式的例子來加深理解:
創建一個contentprovider:
/** * 定義單獨的uri和uri_code,并關聯在一起。當外界請求時可以根據請求的uri得到對應的uri_code,從而知道訪問哪一張表 */private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);static { matcher.addURI(AUTHORITIES , "book" , BOOK_URI_CODE); matcher.addURI(AUTHORITIES , "users" , USERS_URI_CODE);}/** * 代表contentprovider的創建,一般做初始化操作,運行在主線程,其他的方法運行在Binder線程池中 * @return */@Overridepublic boolean onCreate() { context = getContext(); ProviderSqliteOpenHelper providerSqliteOpenHelper = new ProviderSqliteOpenHelper(context , ProviderSqliteOpenHelper.DB_NAME , null , 1); db = providerSqliteOpenHelper.getWritableDatabase(); return true;}/** * @param uri Uri請求的路徑 * @return 請求對應的MIME類型(媒體類型),如果不關心這個選項,可以直接返回null或者"星號/星號" */@Overridepublic String getType(Uri uri) { return null;}@Overridepublic Uri insert(Uri uri, ContentValues values) { String tableName = getTableName(uri); if(tableName != null){ db.insert(tableName, null, values); context.getContentResolver().notifyChange(uri , null); return uri; } return null;}public String getTableName(Uri uri){ String table = null; switch (matcher.match(uri)){ case BOOK_URI_CODE: table = ProviderSqliteOpenHelper.BOOK_TABLE_NAME; break; case USERS_URI_CODE: table = ProviderSqliteOpenHelper.USER_TABLE_NAME; break; } return table;}在另外一個進程中訪問contentprovider:
Cursor cursor = contentResolver.query(bookUri, null, null, null, null);if (cursor.moveToFirst()){ int index = cursor.getColumnIndex("name"); String name = cursor.getString(index); LogUtils.e("查詢到的數據:name = " + name); while (cursor.moveToNext()){ int index1 = cursor.getColumnIndex("name"); String name1 = cursor.getString(index); LogUtils.e("查詢到的數據:name = " + name1); }}cursor.close();Socket(“套接字”)
① 流式套接字(網絡傳輸控制層中的TCP協議):面向連接的協議,提供穩定的雙向通信功能,TCP連接的建立需要經過“三次握手”才能完成,為了提供穩定的數據傳輸功能,其本身提供了超時重傳機制,因此具有很高的穩定性。 Socket必須在發送數據之前與目的地的Socket取得連接,一旦連接建立了,Socket就可以使用一個流接口進行打開、讀寫以及關閉操作。并且,所有發送的數據在另一端都會以相同的順序被接收。 ② 用戶數據報套接字(UDP協議):是無連接的,提供不穩定的單向通信功能(目的地址和要發送的內容),當然UDP也能實現雙向通信功能。
1、Socket的構造方法 Java在包java.NET中提供了兩個類Socket和ServerSocket,分別用來表示雙向連接的Socket客戶端和服務器端。 Socket的構造方法如下: (1)Socket(InetAddress address, int port); (2)Socket(InetAddress address, int port, boolean stream); (3)Socket(String host, int port); (4)Socket(String host, int port, boolean stream); (5)Socket(SocketImpl impl); (6)Socket(String host, int port, InetAddress localAddr, int localPort); (7)Socket(InetAddress address, int port, InetAddrss localAddr, int localPort); ServerSocket的構造方法如下: (1)ServerSocket(int port); (2)ServerSocket(int port, int backlog); (3)ServerSocket(int port, int backlog, InetAddress bindAddr); 其中,參數address、host和port分別是雙向連接中另一方的IP地址、主機名和端口號;參數stream表示Socket是流Socket還是數據報Socket;參數localAddr和localPort表示本地主機的IP地址和端口號;SocketImpl是Socket的父類, 既可以用來創建ServerSocket,也可以用來創建Socket。2、輸入流和輸出流 Socket提供了方法getInputStream()和getOutPutStream()來獲得對應的輸入流和輸出流,以便對Socket進行讀寫操作,這兩個方法的返回值分別是InputStream和OutPutStream對象。 為了便于讀寫數據,我們可以在返回的輸入輸出流對象上建立過濾流,如PrintStream、InputStreamReader和OutputStreamWriter等。3、關閉Socket 可以通過調用Socket的close()方法來關閉Socket。在關閉Socket之前,應該先關閉與Socket有關的所有輸入輸出流,然后再關閉Socket。
例子:
客戶端與服務端建立連接:
public void connectTcp(){ while (mSocket == null){ //和tcp服務建立連接 try { mSocket = new Socket("localhost" , 8688); printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream())) , true); isConnected = true; } catch (IOException e) { e.printStackTrace(); SystemClock.sleep(1000); isConnected = false; } if(isConnected) { //接收服務端的消息 try { br = new BufferedReader(new InputStreamReader(mSocket.getInputStream())); while (!SocketTcpActivity.this.isFinishing()){ String msg = br.readLine(); if(msg != null){ Message message = Message.obtain(); Bundle bundle = new Bundle(); bundle.putString("serverMsg" , msg); message.setData(bundle); message.what = MyConstants.ACCPT_SERVER_MSG; mHandler.sendMessage(message); } } } catch (IOException e) { e.printStackTrace(); } } } br.close(); printWriter.close(); mSocket.close();再次發送消息:
printWriter.println(msg);在服務端開一個線程監聽客戶端的連接請求:
public void run() { try { serverSocket = new ServerSocket(8688); } catch (IOException e) { e.printStackTrace(); return; } while (!isServiceDied){ try { //指定端口實例化一個ServerSocket,并調用ServerSocket的accept()方法在等待客戶端連接期間造成阻塞。 final Socket socket = serverSocket.accept(); new Thread(new Runnable() { @Override public void run() { responseClient(socket);public void responseClient(Socket socket){ try { //接收客戶端的消息 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); //用于向客戶端發送消息 PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true); while (!isServiceDied){ String str = in.readLine(); if(str == null){ //客戶端傳來的數據為空(可以做一些斷開連接、不回復消息等操作) break; } int i = new Random().nextInt(strMsg.length); String msg = strMsg[i]; out.println(packMessage(msg)); } out.close(); in.close(); socket.close();選擇合適的IPC機制
完整demo路徑
http://download.csdn.net/detail/fanghana/9762302
新聞熱點
疑難解答