一.多進程
1.創建多進程:
- 在android 中通過
android:process
爲四大組件創建新的進程 - 在特殊情況下也可以通過JNI去native層fork一個新的進程,但是一般不會這麼做
-
android:process = ":remote"
是私有進程(其他應用的組件不會和他跑在同一個進程中) -
android:process = "com.test.remote"
是全局進程(其他應用通過ShareUID和它跑在同一個進程中)
2.Share UID:
- android 系統會爲每一個應用都分配一個UID(鑰匙)
- 只有UID相同的才能共享數據(房間)
- 注意:要想在同一個進程中運行,除了UID要一樣,簽名也得一樣;這種情況下的兩個應用,不光能訪問私有數據(比如data目錄),還能共享內存數據
3.多進程的運行機制
- 系統會爲每個應用分配一個虛擬機,或者說,會爲每一個進程分配一個虛擬機
- 同一個應用內不同進程直接就像是複製品一樣,每個進程內的東西是完全一樣的,一個進程內變量或其他東西的改變,不影響其他進程,他們都有自己的內存空間
4.多進程的影響:
- 靜態成員變量和單例模式無效(原因就是3中所說的,每一個進程都有自己的內存空間了)
- SharePreference無效(SharePreference不允許多個進程同時進行讀寫操作)
- 線程同步機制無效(因爲不同進程鎖的都不是同一個對象)
- 會創建多個Application(創建一個新的進程,就要創建一個新的虛擬機,就相當於打開了一個新的應用,會重新創建一個application。可以這麼理解,在同一個進程中的應用,用同一個虛擬機和同一個Application)
二.IPC機制
1.Serizable接口
- 使用:
- 寫一個類實現Serializable接口
- 自己寫一個serialVersionUID或者是系統自動生成,自己寫的話可以保證序列化和反序列化正確
- 不參與序列化的有:
- 對於靜態的成員變量
- 用transient關鍵字標記的不參與
String fname = Environment.getExternalStorageDirectory() + "/test.txt";
//序列化
User user = new User(1,"lili",true);
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(fname));
outputStream.writeObject(user);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
Log.e("user","存入失敗:"+e.getMessage());
}
//反序列化
try {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(fname));
User user = (User) inputStream.readObject();
Log.e("user","讀取結果:"+user.toString());
inputStream.close();
textEdit.setText(user.toString());
} catch (IOException e) {
e.printStackTrace();
Log.e("user","讀取失敗:"+e.getMessage());
} catch (ClassNotFoundException e) {
e.printStackTrace();
Log.e("user","讀取失敗222:"+e.getMessage());
}
2.Parceable接口
- 寫一個類實現Parcelable接口
- 在序列化對象中有一個是實現了Parcelable的類,序列化時需要:
//序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(book,0);
}
//反序列化
protected User(Parcel in) {
//需要上傳當前上下文的線程
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
3.這兩個接口的區別
區別 | Serializable | Parcelable |
---|---|---|
平臺 | java的序列化接口 | android的序列化接口 |
原理 | 將一個對象轉成可存儲或可傳輸的狀態 | 將一個對象分解成若干個支持傳輸的類型 |
使用場景 | 將序列化對象存儲到設備上或者是用於網絡傳輸(推薦) | 內存序列化 |
優缺點 | 用到了大量的I/O操作(ObjectOutputStream/ObjectInoutStream),使用簡單,但是消耗過大 | 高效,但是使用複雜 |
4.Binder
4.1 理解
- Binder是android的一個類,實現了IBinder接口
- 從IPC角度:是Android中一種跨進程通信方式,可以理解爲一種虛擬的物理設備,它的驅動是/dev/binder
- 從Framework層,是ServiceManager連接各種Manager和相應ManagerService的橋樑
- 從應用層,是連接客戶端和服務端的橋樑,bindService的時候,服務端會返回一個服務端的Binder對象給客戶端,客戶端可以通過這個Binder可以獲取服務端提供的服務和數據
4.2 創建aidl
①創建Book.java實現Parceable接口
②創建Book.aidl(先取別的名字,之後再重命名)
// Book.aidl
package ly.com.artres.aidl;
parcelable Book;
③創建IBookManager.aidl
// IBookManager.aidl
package ly.com.artres.aidl;
//手動導包
import ly.com.artres.aidl.Book;
// Declare any non-default types here with import statements
interface IBookManager {
//獲取所有圖書列表
List<Book> getBookList();
//添加圖書:AIDL中除了基本數據類型,其他類型的參數必須標上方向:in、out、inout,in表示輸入型參數,
//out輸出型參數,inout表示輸入輸出型參數,AIDL中只支持方法,不支持聲明靜態常量,是不同於一般接口的
void addBook(in Book book);
}
④Build-->Make project之後在
build/generate/source/aidl中可以看到編譯後的IBookManager .java
4.3 手寫aidl
所有通過Binder傳輸的接口都要繼承IInterface接口
/**
* 手寫aidl實現
* 所有通過Binder傳輸的接口都要繼承IInterface接口
*/
public interface BookManager extends IInterface{
//1.1創建文件描述類
static final String DESCRIPTION = "ly.com.artres.aidl.BookManager";
//1.2創建兩個方法的標識
static final int TRANSACT_GETBOOKLIST = IBinder.FIRST_CALL_TRANSACTION + 0;
static final int TRANSACT_ADDBOOK = IBinder.FIRST_CALL_TRANSACTION + 1;
//1.3創建客戶端要調用的幾個方法
List<Book> getBookList() throws RemoteException;
void addBook(Book book) throws RemoteException;
//1.4創建內部類Stub,實現外部接口,實際上是一個Binder
public class BookManageImpl extends Binder implements BookManager{
//2.1要有一個構造函數和外部接口進行綁定
public BookManageImpl(){
this.attachInterface(this,DESCRIPTION);
}
//2.2 將服務端的Binder轉化爲客戶端需要的接口(BookManager)
public static BookManager asInterface(Binder binder){
if (binder == null){
//客戶端與服務端連接失敗
return null;
}
//根據描述,取出當前進程的接口
IInterface iin = binder.queryLocalInterface(DESCRIPTION);
if (iin != null && iin instanceof BookManager){
//相同進程,取出的接口和客戶端的接口相同,則返回服務端binder
return (BookManager) binder;
}
//不同進程
return new BookManageImpl.Proxy(binder);
}
//2.3 返回當前的binder對象
@Override
public IBinder asBinder() {
return this;
}
/**
* 2.4 客戶端發起RPC(遠程過程調用)請求--->運行在服務端的Binder線程池中,所以所有的Binder請求都要是同步的
* @param code : 請求的方法標識
* @param data : 請求參數
* @param reply : 請求的返回結果
* @param flags : 參數flags只有0和FLAG_ONEWAY兩種,默認的跨進程操作是同步的,所以transact()方法的執行會阻塞,
* 調用以同步的形式傳遞到遠程的transact(),等待遠端的transact()返回後繼續執行——最好理解的方式就是把
* 兩端的transact()看作一個方法,Binder機制的目標也就是這樣。指定FLAG_ONEWAY時,表示Client的transact()
* 是單向調用,執行後立即返回,無需等待Server端transact()返回
* @return 返回false,則客戶端請求失敗
* @throws RemoteException
*/
@Override
public boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
//判斷請求的哪個方法
switch (code){
case TRANSACT_GETBOOKLIST:
//獲取圖書列表
//有請求參數,讀取請求參數(沒有)
data.enforceInterface(DESCRIPTION);
//調用請求方法
List<Book> list = this.getBookList();
//請求完成後,需要返回值,則寫到reply中
reply.writeNoException();
reply.writeTypedList(list);
return true;
case TRANSACT_ADDBOOK:
//添加圖書
//有請求參數,讀取請求參數(有)
data.enforceInterface(DESCRIPTION);
Book _arg0;
if (0 != data.readInt()){
//將之前我們所存入的數據按照順序進行獲取,反序列化
_arg0 = Book.CREATOR.createFromParcel(data);
}else {
_arg0 = null;
}
//調用方法
this.addBook(_arg0);
//請求完成後,需要返回值,則寫到reply中(沒有返回值)
reply.writeNoException();
return true;
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTION);
return true;
default:
break;
}
return super.onTransact(code, data, reply, flags);
}
//2.4創建代理類
private static class Proxy implements BookManager{
IBinder mRemote;
/**
* 服務端的binder
* @param binder
*/
Proxy(IBinder binder){
this.mRemote = binder;
}
/**
* 運行在客戶端
* @return
* @throws RemoteException
*/
@Override
public List<Book> getBookList() throws RemoteException {
Parcel _data = Parcel.obtain();//創建輸入型對象
Parcel _reply = Parcel.obtain();//創建輸出型對象
List<Book> _result = null;//創建結果對象
try {
//寫入請求參數(爲空)
_data.writeInterfaceToken(DESCRIPTION);
//發起RPC請求
mRemote.transact(TRANSACT_GETBOOKLIST,_data,_reply,0);
//返回結果(list)
_reply.readException();
_result = _reply.createTypedArrayList(Book.CREATOR);
}catch (Exception e){
e.getMessage();
}finally {
//回收
_reply.recycle();
_data.recycle();
}
return _result;
}
/**
* 運行在客戶端
* @param book
* @throws RemoteException
*/
@Override
public void addBook(Book book) throws RemoteException {
Parcel _data = Parcel.obtain();//創建輸入型對象
Parcel _reply = Parcel.obtain();//創建輸出型對象
try {
//寫入請求參數
_data.writeInterfaceToken(DESCRIPTION);
if (book != null){
//序列化
_data.writeInt(1);
book.writeToParcel(_data,0);
}else {
_data.writeInt(0);
}
//發起RPC請求
mRemote.transact(TRANSACT_ADDBOOK,_data,_reply,0);
//返回結果(爲空)
_reply.readException();
}catch (Exception e){
e.getMessage();
}finally {
//回收
_reply.recycle();
_data.recycle();
}
}
@Override
public IBinder asBinder() {
return mRemote;
}
public String getInterfaceDescription(){
return DESCRIPTION;
}
}
@Override
public List<Book> getBookList() throws RemoteException {
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
}
}
}
4.4 兩個重要方法
- unlinkToDeath()
private IBinder.DeathRecipient mRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mManage == null){
//斷開之前的連接
mManage.asBinder().unlinkToDeath(mRecipient,0);
mManage = null;
}
}
};
- linkToDeath():
binder.linkToDeath(mRecipient,0);
三、Android中的IPC方式:
- Bundle
- ContentProvider
- AIDL
- Messenger
- 文件共享
- Socket
3.1 Bundle
- Bundle實現了Parcelable接口,可以通過Intent傳遞
- Bundle可以放基本數據類型,實現了Parcelale或Serializable接口的類,還有特殊數據類型
Intent intent = new Intent(this,BActivity.class);
Bundle bundle = new Bundle();
bundle.putString("key1","你在幹啥");
intent.putExtra("intentkey",bundle);
startActivity(intent);
- 特殊使用場景:
進程A中的計算結果要在進程B中展示,且這個計算結果不支持Bundle傳輸,可以通過Intent啓動B的一個Service組件,然後B在後臺進行計算,計算完成後在B中顯示
3.2 Messenger(信使)
- 是一種輕量級的ipc方案
- 底層是AIDL
- 串行的方式處理消息,客戶端發一個消息,服務端處理一個,當客戶端發了大量的消息的時候,服務端也只能一個一個處理,效率低,不適合用Messenger處理。
- Messenger主要是用來傳遞消息,當要調用服務端的方法時就不適用了。
原理分析:
①client和server綁定後
②client在onServiceConnected中通過IBinder生成一個Messenger發送Message消息
③server接收到client發來的消息後,會在Handler中進行處理
④server回覆消息給client,也是通過Messenger,他發送的消息需要客戶端處理,所以這個Messenger得是client的,怎麼創建客戶端的Messenger呢?只需要在②中,發送消息的時候,通過msg.replyTo = xx,就可以把client的messenger傳過來了
- 注意:
在跨進程中不能傳遞自定義的Parceable對象
3.3 AIDL
-
(1)支持的數據類型:
自定義的Parcelable對象和AIDL都要手動import (2)解註冊跨進程的接口時:
@Override
protected void onDestroy() {
if (mRemoteManager != null && mRemoteManager.asBinder().isBinderAlive()){
//註銷
Log.e("testaidl","onDestroy unregister listener:"+mNewBookListener);
try {
mRemoteManager.unregisterListener(mNewBookListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
這樣寫的時候會解註冊失敗,因爲這時候的manager和service端的都不是同一個了,aidl不能傳輸對象,這裏的manager相當於兩個新對象。
要刪除這個跨進程的接口,要用到RemoteCallBackListener;
使用方法:
//用於刪除跨進程的接口
private RemoteCallbackList<IOnNewBookArrivedLisneer> mListennerist = new RemoteCallbackList<>();
@Override
public void registerListener(IOnNewBookArrivedLisneer listener) throws RemoteException {
mListennerist.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedLisneer listener) throws RemoteException {
mListennerist.unregister(listener);
}
注意:
在使用RemoteCallbackList時要注意:
//獲取元素個數
int N = mListennerist.beginBroadcast();
for (int i=0;i<N;i++){
IOnNewBookArrivedLisneer lisneer = mListennerist.getBroadcastItem(i);
if (lisneer != null){
try {
lisneer.onNewBookArrived(book);
}catch (Exception e){
e.printStackTrace();
}
}
}
//要和beginBroadcast配套使用
mListennerist.finishBroadcast();
(3)在UI線程中調用遠程端的耗時方法(互調):
- 服務端中的方法都運行在Binder線程池中,客戶端調用服務端中的耗時方法時,會造成ANR,所以要在客戶端開啓一個子線程才能調用服務端的方法;
- 服務端的調用客戶端Binder線程池中的某一個耗時方法時,也要開始一個線程才行;
(4)Binder意外死亡的時候:
- 可以在onServiceDisconnected中重新連接
- 可以在DeathRecipient 中斷開之前的連接,再重新綁定
IBinder.DeathRecipient recipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
}
};
- 他們二者的區別:
onServiceDisconnected是運行在UI線程中,而DeathRecipient 是運行在客戶端的Binder線程池中的
(5)權限驗證:
聲明權限
<!--權限驗證,驗證通過才能連接服務器-->
<permission android:name="ly.com.artres.aidl.ACCESS_BOOK_SERVICE" android:protectionLevel="normal"/>
使用權限:
<uses-permission android:name="ly.com.artres.aidl.ACCESS_BOOK_SERVICE"/>
- 可以在onBinder()中驗證權限
@Override
public IBinder onBind(Intent intent) {
//權限驗證
int permission = checkCallingOrSelfPermission("ly.com.artres.aidl.ACCESS_BOOK_SERVICE");
if (permission == PackageManager.PERMISSION_DENIED){
return null;
}
return mBinder;
}
可以在onTransact()中通過權限驗證或者Uid,Pid進行驗證
總結:創建一個service和AIDL接口,創建一個類實現AIDL接口中的Stub類並實現裏面的抽象方法,在service的onBind()中返回這個類的對象,然後在客戶端就可以綁定服務端Service,繼而訪問遠程接口
3.4 ContentProvider
- 底層是Binder
- 寫一個類繼承ContentProvider
要實現6個方法:
①boolen onCreate():創建,做初始化工作,運行在UI線程中;
②String getType(Uri uri):根據uri獲取對應的MIME(媒體)類型:文字/圖片/視頻,在主線程;
③Uri insert(@NonNull Uri uri, @Nullable ContentValues values):插入數據,在binder線程池中運行;
④int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs):刪除數據,在binder線程池中運行;
⑤int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs):更新數據,在binder線程池中運行;
⑥Cursor query(...):查詢數據,在binder線程池中運行;
實現步驟:
- 在manifest中定義這個provider
<provider
android:name=".contentproviderdemo.BookProvider"
android:authorities="ly.com.artres.provider"//唯一標識
android:permission="ly.com.PROVIDER"//權限
android:process=":process" />//這個可以不寫
根據Uri-->得到UriCode--->再判斷是哪一張表
- 將uri和uricode進行綁定
//創建Uri和UriCode
private static String AUTHORITY = "ly.com.artres.provider";
private static Uri BOOK_URI = Uri.parse("content://"+AUTHORITY+"/book");
private static Uri USER_URI = Uri.parse("content://"+AUTHORITY+"/user");
public static final int BOOK_CODE = 0;
private static final int USER_CODE = 1;
//創建urimatch
private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY,"book",BOOK_CODE);
uriMatcher.addURI(AUTHORITY,"user",USER_CODE);
}
/**
* 根據Uri得到Uricode,再判斷是哪一張表
* @param uri
* @return
*/
private String getTableName(Uri uri){
String tableName = null;
switch (uriMatcher.match(uri)){
case BOOK_CODE:
tableName = DBOpenHelper.BOOK_TABLE_NAME;
break;
case USER_CODE:
tableName = DBOpenHelper.USER_TABLE_NAME;
break;
default:
break;
}
return tableName;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.e("testcp","insert():"+Thread.currentThread().getName());
String tableName = getTableName(uri);
if (tableName == null){
throw new IllegalArgumentException("insert unsupport uri:"+uri);
}
mDb.insert(tableName,null,values);
mContext.getContentResolver().notifyChange(uri,null);
return null;
}
SQLite數據庫:
- 底部實現了線程同步,如果provider不使用sqlite數據庫,要注意在CRUD中使用線程同步;
- 自定義一個類繼承SQLiteOpenHelper,必須要有一個構造方法;
- SQLiteOpenHelper用於創建升級數據庫;
public class DBOpenHelper extends SQLiteOpenHelper {
public static final String DB_NAME = "book.db";
public static final String BOOK_TABLE_NAME = "booktb";
public static final String USER_TABLE_NAME = "usertb";
public static final int DB_VERSION = 1;
private String CREATE_BOOK = "create table if not exists "+BOOK_TABLE_NAME+"(_id integer primary key,name text)";
private String CREATE_USER = "create table if not exists "+USER_TABLE_NAME+"(_id integer primary key,name text)";
/**
* 必須要有一個構造函數
* @param context
*/
public DBOpenHelper(Context context) {
super(context,DB_NAME,null,DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
//創建表
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_USER);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
根據sqliteopenhelprt可以創建sqlitedatabase,然後在contentprovider的CRUD方法中調用sqlitedatabase的增刪改查的方法;
SQLiteDatabase mDb = new DBOpenHelper(mContext).getWritableDatabase();
在Activity中往provider中插入數據時:
Uri uri = Uri.parse("content://ly.com.artres.provider/book");
ContentValues values = new ContentValues();
values.put("_id",4);
values.put("name","語文");
getContentResolver().insert(uri,values);
在Activity中從provider中查詢數據時:
Uri uri = Uri.parse("content://ly.com.artres.provider/book");
Cursor bookCursor = getContentResolver().query(uri,new String[]{"_id","name"},null,null,null);
while (bookCursor.moveToNext()){
int id = bookCursor.getInt(0);
String name = bookCursor.getString(1);
Log.e("testcp","query book結果:"+id+",name:"+name+"\n");
}
bookCursor.close();
3.5 文件共享
- 實現Serializable接口
- 通過ObjectOutputStream()和ObjectInputStream()實現
3.6 Socket(套接字)
1.分爲兩種:
- 流式套接字-->對應TCP(面向連接,提供穩定的雙方通信功能,要建立三次握手,本身具有超時重連機制)
- 用戶數據報套接字-->對應UDP(也能實現雙方通信功能,性能會更高,但是數據不一定能傳輸成功,)
2.可以傳輸任意字節流
3.使用:
- 要有權限(不能在主線程訪問網絡)
<!-- socket要的權限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- 步驟:
服務端:
①服務端創建Socket
ServerSocket serverSocket = new ServerSocket(8688);
②接收客戶端請求:
final Socket client = serverSocket.accept();
③接收客戶端請求
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
String str = in.readLine();
④向客戶端發送消息
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);
out.println("歡迎來到聊天室");
客戶端:
①連接服務端
Socket socket = new Socket("localhost",8688);
②向服務端發送請求:
PrintWriter mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
mPrintWriter.println(“hello”);
③接收服務端的消息:
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = in.readLine();