IPC機制與面試題精選《Android開發藝術探索》筆記

參考鏈接

本文圖片與總結習題均來自釐米姑娘

IPC簡介

IPC的概念

Inter-Process Communication的縮寫。含義爲進程間通信或跨進程通信,是指兩個進程之間進行數據交換的過程。

進程與線程的區別

  • 線程是CPU調度的最小單元,同時線程是一種有限的資源。
  • 進程一般指一個執行單元,一個進程可以包含多個線程,所以他們之間是包含與被包含的關係。(比如Android中有主線程,而如果要執行耗時操作(網絡請求),就需要開一個新的線程去完成)

多進程的情況

  • 第一種情況是一個應用因爲某些原因自身需要採用多線程模式來實現。
  • 另一種情況是當前應用需要向其他應用獲取數據

開啓多進程模式的方法

多進程模式是指一個應用中存在多個進程

在AndroidMainfest中爲四大組件指定屬性:android:process。

沒有指定該屬性

沒有指定該屬性則運行在默認進程,其進程名就是包名。

以:命名

省略包名,如android:process=":remote",其全稱com.example.myapplication:remote

這是一種屬於當前應用的私有進程,其他進程的組件不能和它跑在同一進程。

完整命名的進程

android:process = "com.example.myapplication.remote"

這是一種全局進程,其他應用可以通過ShareUID方式和他跑在用一個進程中。

UID&ShareUID:

Android系統爲每個應用分配一個唯一的UID,具有相同UID的應用才能共享數據。
兩個應用通過ShareUID跑在同一進程的條件:ShareUID相同且簽名也相同。

滿足上述條件的兩個應用,無論是否跑在同一進程,它們可共享data目錄,組件信息。
若跑在同一進程,它們除了可共享data目錄、組件信息,還可共享內存數據。它們就像是一個應用的兩個部分。

查看進程的方法

通過DDMS視圖查看進程信息。
通過shell查看,命令爲:adb shell ps|grep 包名。

使用進程間通信的重要性(重點)

所有運行在不同進程的四大組件,想要通過內存來共享數據,就會共享失敗。

原因由於Android系統爲每個進程(應用)都分配了一個獨立的虛擬機,不同的虛擬機在內存上就有不同的地址空間,當對一個公共類進行修改時,就會造成在不同的虛擬機中訪問相同的類產生多份備份。

舉例說明:兩個進程中都有一個類,實際上這兩個類是互相不影響的,當你在進程1中更改了類中的屬性,只會影響進程1,而不會影響進程2。

多進程造成的影響有四點(重要)

  • 1.靜態成員和單例模式失效
    原因:因爲同一個類在多個進程中會多次加載。
  • 2.線程同步機制失效
    原因:不是一塊內存的話,無論鎖對象還是鎖全局類,都無法保證線程同步,因爲不同進程鎖的不是同一個對象。
  • 3.Sharedpreferences的可靠性下降
    原因:SharedPreferences不支持兩個進程同時進行讀寫操作,即不支持併發讀寫,有一定機率導致數據丟失。(SharedPreferences底層是通過讀寫XML文件實現的,不支持多個進程的併發操作)
  • 4.Application多次創建
    原因:Android系統會爲新的進程分配獨立虛擬機,相當於系統又把這個應用重新啓動了一次,就相當於創建了新的Application。

序列化

序列化的介紹

含義:序列化表示將一個對象轉換成可存儲或可傳輸的狀態。序列化後的對象可以在網絡上進行傳輸,也可以存儲到本地。

場景:需要通過Intent和Binder傳輸對象,就必須完成對象的序列化過程

兩種方式:實現Serializable/Parcelable接口。

Serializable與Parcelable的比較

在這裏插入圖片描述

Serialzable的例子

Serializable使用簡單,我們舉一個將Person對象從FirstActivity傳遞到SecondActivity的例子。

首先讓我們要用的類實現該接口,並給予一個serialVersionUID。

public class Person implements Serializable{
    private static final long serialVersionUID = 7382351359868556980L;
    ...
}

在FirstActivity中使用Intent的putExtra傳遞即可。

 				Intent open = new Intent(MainActivity.this,SecondActivity.class);
                Person person = new Person();
                person.setName("一去二三裏");
                person.setAge(18);
                // 傳輸方式一,intent直接調用putExtra
                // public Intent putExtra(String name, Serializable value)
                open.putExtra("put_ser_test", person);
                // 傳輸方式二,intent利用putExtras(注意s)傳入bundle
                /**
                Bundle bundle = new Bundle();
                bundle.putSerializable("bundle_ser",person);
                open.putExtras(bundle);
                 */
                startActivity(open);

在跳轉的Activity中可以通過如下方式獲取並轉化爲Person對象(反序列化過程)。

Intent intent = getIntent();
        // 關鍵方法:getSerializableExtra ,我們的類是實現了Serializable接口的,所以寫這個方法獲得對象
        // public class Person implements Serializable
        Person per = (Person)intent.getSerializableExtra("put_ser_test");
        //Person per = (Person)intent.getSerializableExtra("bundle_ser");
        mTvDate.setText("名字:"+per.getName()+"\\n"
                +"年齡:"+per.getAge());

serialVersionUID的作用

理論上來說serialVersionUID可以不賦予,一樣可以完成序列化與反序列化。但由於原則上序列化後的數據中的serialVersionUID要和當前類的serialVersionUID 相同才能正常的反序列化,還是要在類中賦予
serialVersionUID,防止當前類的serialVersionUID因類中成員變量更改而改變。

序列化的工作機制是這樣的:序列化的時候會把當前類的serialversionUID寫進序列化的文件中(也可能是其他中介),當反序列化的時候系統會去檢測文件中的serialversionUID看它是否和當前類的serialversionUID一致,如果一致就說明序列化的類的版本和當前類的版本是相同的,這個時候可以成功反序列化;

因此,如果我們對當前類進行修改,比如增加或者刪除了某些成員變量,那麼系統就會重新計算當前類的hash值並把它賦值給serialversionUID,這個時候當前類的serialversionUID就和序列化數據中的serialversionUID不一致,於是反序列化失敗。

Binder機制

概念

從API角度:是一個類,實現了IBinder接口。
從IPC角度:是Android用於實現多進程通信的方式
從Framework角度:是ServiceManager連接各種Manager和相應ManagerService的橋樑。
從應用層:是客戶端和服務端進行通信的媒介。客戶端通過它可獲取服務端提供的服務或者數據。

優點

Linux常用的IPC方式包括管道、Socket、共享內存、消息隊列

(1)傳輸速率快,拷貝次數少
相比管道、Socket與消息隊列的2次拷貝,Binder機制只有一次拷貝。這是因爲其他方式的拷貝是首先將數據從傳輸方的緩存區拷貝到內核的緩存區中,之後再從內核的緩存區中將數據拷貝至接收方的緩存區
在這裏插入圖片描述
而Binder機制中,傳輸方將數據從緩存區拷貝到內核的緩存區後,不需要再次拷貝,因爲內核中的緩存區與接收方的緩存區映射到同一塊物理地址。
在這裏插入圖片描述
(2)實現C/S架構方便
Linux常用IPC方式只有Socket是基於C/S架構,但通常用於網絡中,且傳輸速率慢。而binder機制具有C/S架構,且Server與Cilent相對獨立,穩定性好。

(3)較爲安全
傳統linux的IPC方式接收都無法獲得發送方的UID/PID,從而無法獲得對方的消息。而Binder機制爲每個進程配備了UID/PID,在通信時會根據UID/PID進行檢測。

Binder框架

其中包含Client、Server、ServiceManager、Binder驅動設備。其中Client、Server、ServiceManager處於用戶空間,而Binder驅動設備處於內核空間。
在這裏插入圖片描述
Service、Client:服務端與客戶端,在ServiceManager與驅動設備的支持下,完成進程間通信。

Service Manager:服務的管理者,將Service的Binder轉換爲Client對Binder的引用,使得Client可以通過Binder名字獲得Server中Binder實體的引用,進而完成Server與Client的通信。
在這裏插入圖片描述
Binder驅動設備

  • 與硬件設備沒有關係,其工作方式與設備驅動程序是一樣的,工作於內核態。
  • 提供open()、mmap()、poll()、ioctl()等標準文件操作。
  • 以字符驅動設備中的misc設備註冊在設備目錄/dev下,用戶通過/dev/binder訪問該它。
  • 負責進程之間binder通信的建立,傳遞,計數管理以及數據的傳遞交互等底層支持
  • 驅動和應用程序之間定義了一套接口協議,主要功能由ioctl()接口實現,由於ioctl()靈活、方便且能夠一次調用實現先寫後讀以滿足同步交互,因此不必分別調用write()和read()接口。
  • 其代碼位於linux目錄的drivers/misc/binder.c中。

Android中的IPC機制

1.Bundle機制

Bundle實現了Parcelable接口,可以在不同進程間傳遞信息,這是最常用的方法。

調用Bundle的putString放入key與value的對應,並最終使用intent.putExtra方法傳遞Bundle對象。在想獲取Bundle對象的位置,可以通過getIntent()獲取intent對象,並通過getExtra()方法獲取Bundle,進而獲取Bundle其中存入的數據。

注意傳入的value需要是實現了Serialzable或者Paracelable。

        Intent intent = new Intent(MainActivity.this,SecondActivity.class);
        Bundle data = new Bundle();
        data.putString("name","zhangsan");//將數據放入bundle
        intent.putExtra(data);
        startActivity(intent);
        //在SecondActivity中,將傳遞的數據取出
        Bundle data = getIntent().getExtra();//從bundle中取出數據
        String name = data.getString("name");

我們也可以省略Bundle,直接putExtra傳遞我們的的數據的key與value。獲取也是通過getIntent獲取Intent對象,之後直接通過intent.getStringExtra(key)來獲取我們想傳遞的value。

        //在MainActivity中存入數據:
        intent.putExtra(NAME_KEY,"zhangsan");
        intent.putExtra(AGE_KEY,25);
        intent.putExtra(IS_FEMALE_KEY,false);
        //在SecondActivity中取出數據:
        Intent intent = getIntent();
        String name = intent.getStringExtra(MainActivity.NAME_KEY);
        int age = intent.getIntExtra(MainActivity.AGE_KEY,30);
        boolean isfemale = intent.getBooleanExtra(MainActivity.IS_FEMALE_KEY,true);

2.使用文件共享

兩個進程通過讀/寫同一個文件來交換數據。比如A把數據寫入一個文件,B通過讀寫文件來獲取數據。

適用於對數據同步要求不太高的進程,同時要妥善處理併發讀和併發寫的問題。

3.使用AIDL

AIDL內部是實現的Binder機制。如果在一個進程中,需要調用另一個進程中對象的方法,可以使用AIDL生成可序列化的參數,AIDL會生成一個服務器端對象的代理類,通過它來實現客戶端間接調用服務端對象的方法。

AIDL所支持的數據類型
基本類型:int,boolean,float,double,byte,long,char;
CharSequence類型;
String類型;
List、Map中的數據類型都是AIDL所支持的類型;
實現Parcelable接口的對象;

注意其參數除了有數據類型外,還必須標有tag,tag包括:in 數據只允許從客戶端發往服務端;out數據只允許從服務端發送客戶端;inout:兩種方式都可以。

in Student student

AIDL的本質

這裏先放一個AIDL的實例:創建一個AIDL文件,IBookManager接口中包含一個getBookList方法,一個addBook方法

import com.ryg.chapter_2.aidl.Book;
interface IBookManager {
     List<Book> getBookList();
     void addBook(in Book book);
     }

本質是實現了Binder機制,其中包括以下幾個關鍵類與關鍵方法:

(1)Stub類Binder的實現類,服務端由它來提供方法。

(2)Proxy類:**服務端的本地代理,客戶端通過這個類來調用服務端的方法。**在本例子中該類包括兩個方法:Proxy#getBookList與Proxy#addList,他們都運行在客戶端。

(3)asInterface方法:**客戶端調用,將服務器返回的Binder對象轉換爲AIDL接口的對象,以供客戶端使用。**返回對象:如果服務端和客戶端時同一個進程,則直接返回Stu對象;否則返回的是封裝後的Stu.proxy對象。其中的DESCRIPTOR是AIDL的唯一標識。
在這裏插入圖片描述(4)asBinder方法:根據當前調用情況返回代理Proxy的Binder對象

(5) onTransact方法:該方法運行在服務端的Binder線程池中,當客戶端與服務端發起跨進程請求時會調用。public Boolean onTransact (int code,android.os.Pareel data,android.os.Pareel reply,int fiags)首先通過code來判斷客戶端請求的是哪個方法並通過data來獲取該方法所需要傳入的參數。然後執行目標方法,執行完畢後,會將結果(如果有的話)寫入reply中。如果該方法返回false,則說明客戶端請求失敗。

@Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code){
            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTORS);
                return true;
                //getBookList方法(沒有data有reply)
            case TRANSACTION_getBookList:
                data.enforceInterface(DESCRIPTORS);
                List<com.liuguilin.multiprocesssample.Book> result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(result);
                return true;
                //addBook方法(有data沒有reply)
            case TRANSACTION_addBook:
                data.enforceInterface(DESCRIPTORS);
                Book book;
                if(0!=data.readInt()){
                    book =  Book.CREATOR.createFromParcel(data);
                }else {
                    book = null;
                }
                this.addBook(book);
                reply.writeNoException();
                return true;
        }
        return super.onTransact(code, data, reply, flags);
    }

(6)transact()方法:當客戶端發起跨進程請求時,會首先調用transact方法,並將當前線程掛起。之後調用服務端的onTransact方法,直到返回結果,線程才繼續執行。

調用方法的流程以Proxy#getBookList爲例:當客戶端運行該方法後,會創建所需要的輸入型對象data,輸出值對象reply,以及返回值對象List,然後把該方法的參數信息寫入data中,接着調用transact方法來發起與服務端通信的請求,之後線程掛起,服務端會回調onTransact方法,當方法執行結束後,線程進行執行,並從reply中取出結果。

AIDL的使用

服務端

(1)創建一個AIDL文件

import com.ryg.chapter_2.aidl.Book;
interface IBookManager {
     List<Book> getBookList();
     void addBook(in Book book);
     }

(2)在Service中實現AIDL接口中的方法,並在onBind方法中返回Binder對象(AIDL接口的Stub對象該對象是個Binder,也是實現它內部的方法)。

服務端代碼:

public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();

        mBookList.add(new Book(1,"Android"));
        mBookList.add(new Book(1,"IOS"));
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

客戶端

(1)創建ServiceConnection,並在onServiceConnected方法中,將從服務端獲取的Binder對象轉化爲我們的AIDL接口asInterface(binder),進而調用接口中的方法

(2)調用bindService將服務端與客戶端綁定起來

可以看到我們使用Service的方法很類似。整體的思路爲:首先創建一個ServiceConnection,並重寫onServiceConnected方法,在該方法中要先將binder對象(service)轉化爲AIDL接口(IBookManager),然後就可以調用該接口中的方法了

之後在創建一個Intent並傳入當前Activity與Service類,使用bindService方法傳入intent,ServiceConnection,標識符 以建立服務端與客戶端的綁定。最後在onDestroy()方法中調用unbindService方法,斷開綁定。


public class BookManagerActivity extends AppCompatActivity {
    private static final String TAG = "BookManagerActivity";
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> list = bookManager.getBookList();
                Log.i(TAG, "list Type :" + list.getClass());
                getCanonicalName();
                Log.i(TAG, "list string : " + list.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
       ...
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }
}


IPC的整體流程(藉助AIDL來談)

(1)服務端將Binder對象發送至與其綁定的客戶端,客戶端通過使用AIDL的asInterface將Binder對象轉化爲AIDL接口,進而去調用接口中的方法。asInterface返回值根據服務端和客戶端是否在同一個進程,如果在的話就直接返回Stu對象,否則返回Stu.porxy。(Stu的代理)

(2)當調用接口方法後,會調用transact方法,並傳入data(包括輸入參數,輸出參數,返回值)並將線程掛起;

(3)之後會調用服務端的onTransact方法,當方法執行完成後,會將返回值reply返回客戶端,並喚醒線程
在這裏插入圖片描述
一個調用方法實例。
在這裏插入圖片描述

Binder連接池

當多個模塊都需要使用AIDL進行遠程通信,如果我們都爲其創建Service,就會導致消耗系統資源過多。

解決方法是使用Binder連接池。具體邏輯如下每個模塊都創建自己的AIDL文件並實現其中的接口。服務端創建一個AIDL文件,接口中包含一個binderQuery方法。創建一個BinderPool類,在該類中創建一個Binder內部類繼承自接口的Stub(實質是一個Binder類),內部類中重寫binderQuery方法,根據標識符創建對應的Binder對象並返回。在服務端onBind中返回這個Binder對象。當客戶端需要使用Binder時,會調用連接池(BindPool)的bindQuery方法(其實是調用的(內部類)Binder對象的binderQuery方法)並傳入標識符,就會返回Binder對象。最後對其使用asinstance方法轉化爲對應接口。
在這裏插入圖片描述
具體代碼:

服務端的AIDL文件,要實現它的queryBinder方法,

interface IBinderPool {
    IBinder queryBinder(int binderCode);
}

下面還剩下Binder連接池(BinderPool類)的具體實現了,在他的內部具體做以下兩個事情:

(1)要實現IBinderPool接口
新建一個BinderPoolImpl類並繼承自IBinderPool.Stub,在queryBinder方法中根據傳入的標識符來判斷返回模塊需要的Binder對象

(2)綁定遠程服務
在綁定成功後,客戶端就可以通過Binder連接池的queryBinder方法來獲取對應的Binder,拿到所需的Binder之後,不同業務模塊就可以操作了。**

public class BinderPool {
    private IBinderPool mIBinderPool;
	public IBinder queryBinder(int binderCode) {
        	IBinder binder = null;
        		try {
            		if (mIBinderPool != null) {
                	binder = mIBinderPool.queryBinder(binderCode);
           	 		}
        	} catch (RemoteException e) {
            	e.printStackTrace();
        	}
	        return binder;
    }
	
	public static class BinderPoolImpl extends IBinderPool.Stub{
        public BinderPoolImpl(){
            super();
        }
        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode){
                case BINDER_SECURUITY_CENTER:
                    binder = new SecurityCenterImpl();
                    break;
                case BINDER_COMPUTE:
                    binder = new ComputeImpl();
                    break;
            }
            return binder;
        }
    }
}

在Service中onBind方法返回BinderPool類的BinderPoolImpl對象。

public class BinderPoolService extends Service{

    private  static final String  TAG = "BinderPoolService";

    private Binder mBinderPool = new BinderPool.BinderPoolImpl();

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinderPool;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

最後在客戶端,需要獲取Binder時,需要傳入標識符。首先獲取之前BinderPool類,之後調用它的queryBinder方法並傳入標識符獲取當前模塊所需的Binder,最後通過asInterface將其轉換爲接口,以使用接口的方法。

BinderPool binderPool = BinderPool.getInstance(BinderActivity.this);
IBinder securityBinder = binderPool.queryBinder(BinderPool.BINDER_SECURUITY_CENTER);
mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder);

總結(複習必看)

1.Android中進程和線程的關係?區別?

線程是CPU的最小調度,比如Android的UI線程不能執行耗時操作,我們就需要再開一個線程執行耗時操作。

而進程是一個執行單元,可以包含多個線程。四大組件可以通過在AndroidManifest中通過更改android:process來開啓進程。

2.爲何需要進行IPC?多進程通信可能會出現什麼問題?

3.什麼是序列化?Serializable接口和Parcelable接口的區別?爲何推薦使用後者?

4.Android中爲何新增Binder來作爲主要的IPC方式?

5.使用Binder進行數據傳輸的具體過程?

6.Binder框架中ServiceManager的作用?

7.Android中有哪些基於Binder的IPC方式?簡單對比下?

8.是否瞭解AIDL?原理是什麼?如何優化多模塊都使用AIDL的情況?

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