Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中進程間通信(IPC)方式總結

Android中進程間通信(IPC)方式總結

編輯:關於Android編程

IPC為進程間通信或跨進程通信,是指兩個進程進行進程間通信的過程。在PC和移動設備上一個進程指的是一個程序或者一個應用,所以我們可以將進程間通信簡單理解為不同應用之間的通信,當然這種說法並不嚴謹。

在Android中,為每一個應用程序都分配了一個獨立的虛擬機,或者說每個進程都分配一個獨立的虛擬機,不同虛擬機在內存分配上有不同的地址空間,這就導致在不同的虛擬機互相訪問數據需要借助其他手段。下面分別介紹一下在Android中進行實現IPC的方式。

 

1.使用Bundle

我們知道在Android中三大組件(Activity、Service、Receiver)都支持在Intent中傳遞Bundle數據,由於Bundle實現了Parcelable接口,所以他可以方便的在不同的進程之間進行傳輸。當我們在一個進程中啟動另外一個進程的Activity、Service、Receiver時,我們就可以在Bundle中附加我們所需要傳輸給遠程進程的信息並通過Intent發送出去。這裡注意:我們傳輸的數據必須能夠被序列化。

下面我們看一下利用Bundle進行進程間通信的例子:

 

        Intent intent = new Intent(MainActivity.this, TwoActivity.class);
        Bundle bundle = new Bundle();
        bundle.putString("data", "測試數據");
        intent.putExtras(bundle);
        startActivity(intent);

 

利用Bundle進行進程間通信是很容易的,大家應該也注意到,這種方式進行進程間通信只能是單方向的簡單數據傳輸,他的使用時有一定局限性的。

 

2.使用文件共享

共享文件也是以後總不錯的進程間通信方式,兩個進程通過讀/寫同一個文件來交換數據,比如A進程把數據寫入文件FILE,B進程可以通過讀取這個文件來獲取這個數據。通過這種方式,除了可以交換簡單的文本信息以外,我們還可以序列化一個對象到文件系統中,另一個進程可以通過反序列化恢復這個對象。

比如在A進程中創建一個線程進行寫數據:

        new Thread(new Runnable() {
			@Override
			public void run() {
				User user = new User(1, "user", false);
				File cachedFile = new File(CACHE_FILE_PATH);
				ObjectOutputStream objectOutputStream = null;
				try{
					objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
					objectOutputStream.writeObject(user);
				}catch(IOException e){
					e.printStackTrace();
				}finally{
					objectOutputStream.close();
				}
			}
		}).start();

在B進程中創建一個線程進行讀取數據:

        new Thread(new Runnable() {
			@Override
			public void run() {
				User user = null;
				File cachedFile = new File(CACHE_FILE_PATH);
				if(cachedFile.exists()){
					ObjectInputStream objectInputStream = null;
					try{
						objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile));
						user = objectInputStream.readObject(user);
					}catch(IOException e){
						e.printStackTrace();
					}finally{
						objectInputStream.close();
					}
				}
				
				try{
					objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
					objectOutputStream.writeObject(user);
				}catch(IOException e){
					e.printStackTrace();
				}finally{
					objectOutputStream.close();
				}
			}
		}).start();

通過文件共享的這種方式來共享數據對文件的格式師妹有句提要求的,比如可以是文本文件、也可以是XML文件,只要讀寫雙方約定數據格式即可。這種方式進行進程間通信雖然方便,可是也是有局限性的,比如並發讀/寫,這會導致比較嚴重的問題,如讀取的數據不完整或者讀取的數據不是最新的。因此通過文件共享的方式適合在對數據同步要求不高的進程之間通信,並且要妥善處理並發讀/寫問題。

 

3.使用Messenger

我們也可以通過Messenger來進行進程間通信,在Messenger中放入我們需要傳遞的數據,就可以輕松的實現進程之間數據傳遞了。Messenger是一種輕量級的IPC方案,他的底層實現是AIDL,關於AIDL我們在和面會介紹到。

Messenger的使用方法也是比較簡單的,實現一個Messenger有如下幾步,分為服務端和客戶端:

服務端進程:在A進程創建一個Service來處理其他進程的連接請求,同時創建一個Handler並通過他來創建一個Messenger對象,然後在Service的onBind中返回這大Messneger對象底層的Binder即可。

 

public class MessengerService extends Service{  
    private Handler MessengerHandler = new Handler(){  
  
        @Override  
        public void handleMessage(Message msg) {  
           //消息處理.......            
    };  
    //創建服務端Messenger  
    private final Messenger mMessenger = new Messenger(MessengerHandler);  
    @Override  
    public IBinder onBind(Intent intent) {   
        //向客戶端返回Ibinder對象,客戶端利用該對象訪問服務端  
        return mMessenger.getBinder();  
    }  
    @Override  
    public void onCreate() {  
        super.onCreate();  
    }  
}

 

客戶端進程:在進程B中首先綁定遠程進程的Service,綁定成功後,根據Service返回的IBinder對象創建Messenger對象,並使用此對象發送消息,為了能收到Service端返回的消息,客戶端也創建了一個自己的Messenger發送給Service端,Service端就可以通過客戶端的Messenger向客戶端發送消息了,具體代碼如下:

public class MessengerActivity extends Activity{  
    private ServiceConnection conn = new ServiceConnection(){  
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            //根據得到的IBinder對象創建Messenger  
            mService = new Messenger(service);  
            //通過得到的mService 可以進行通信
        }  
  
    };  
      
    //為了收到Service的回復,客戶端需要創建一個接收消息的Messenger和Handler  
    private Handler MessengerHander = new Handler(){  
        @Override  
        public void handleMessage(Message msg) {  
//消息處理
                    }  
    };  
    private Messenger mGetMessenger = new Messenger(MessengerHander);  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_messenger);  
        init();  
    }  
    private void init() {  
        intent = new Intent(MessengerActivity.this, MessengerService.class);  
       indService(intent, conn, Context.BIND_AUTO_CREATE);  
             
            }  
        });  
    }  
    @Override  
    protected void onDestroy(){  
        unbindService(conn);  
        super.onDestroy();  
    }  
} 

這裡畫一張Messenger的工作原理圖,以便於更好的理解Messenger:

\

 

Messenger內部消息處理使用Handler實現的,所以他是以串行的方式處理客戶端發送過來的消息的,如果有大量的消息發送給服務端,服務端只能一個一個處理,如果並發量大的話用Messenger就不合適了,而且Messenger的主要作用是為了傳遞消息的,很多時候我們需要跨進程調用服務端的方法,這種需求Messenger就無法做到了。

 

4.使用AIDL:

AIDL (Android Interface Definition Language)是一種IDL語言,用於生成可以在Android設備上兩個進程之間進行進程間通信(IPC)的代碼。如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL生成可序列化的參數。

AIDL是IPC的一個輕量級實現,用了對於Java開發者來說很熟悉的語法。Android也提供了一個工具,可以自動創建Stub(類構架,類骨架)。當我們需要在應用間通信時,我們需要按以下幾步走:
1. 定義一個AIDL接口
2. 為遠程服務(Service)實現對應Stub
3. 將服務“暴露”給客戶程序使用

官方文檔中對AIDL有這樣一段介紹:Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.

第一句最重要,“只有當你允許來自不同的客戶端訪問你的服務並且需要處理多線程問題時你才必須使用AIDL”,其他情況下你都可以選擇其他方法,如使用Messager,也能跨進程通訊。可見AIDL是處理多線程、多客戶端並發訪問的。而Messager是單線程處理。

AIDL很大的好處就是我們直接可以調用服務端進程所暴露出來的方法,下面簡單介紹一下AIDL的使用:

服務端

服務端首先要創建一個Service用來監聽客戶端的請求,然後創建一個AIDL文件,將暴露給客戶端的接口在這個AIDL文件中聲明,最後在Service中實現這個AIDL接口即可。

 

(1)創建aidl接口文件

AIDL使用簡單的語法來聲明接口,描述其方法以及方法的參數和返回值。這些參數和返回值可以是任何類型,甚至是其他AIDL生成的接口。重要的是必須導入所有非內置類型,哪怕是這些類型是在與接口相同的包中。

package com.example.android;
interface IRemoteService {
    int getPid();
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

(2)向客戶端暴露接口:

public class DDService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("DDService onCreate........" + "Thread: " + Thread.currentThread().getName());
    }
    @Override
    public IBinder onBind(Intent arg0) {
        System.out.println("DDService onBind");
        return mBinder;
    }
 
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            System.out.println("Thread: " + Thread.currentThread().getName());
            System.out.println("DDService getPid ");
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            System.out.println("Thread: " + Thread.currentThread().getName());
            System.out.println("basicTypes aDouble: " + aDouble +" anInt: " + anInt+" aBoolean " + aBoolean+" aString " + aString);
        }
    };
}

這樣我們的服務端就完成了,把服務端運行到模擬器(或者手機上),等一會可以看一下打印信息,重點看“線程名”

客戶端

客戶端所做的事情就要簡單很多了,首先需要綁定服務端Service,綁定成功後將服務端返回的Binder對象轉成AIDL接口所屬的類型,接著皆可以調用AIDL中的方法了。

public class MainActivity extends Activity {
    private IRemoteService remoteService;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
     
    ServiceConnection conn = new ServiceConnection() {
         
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
         
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            remoteService = IRemoteService.Stub.asInterface(service);
            try {
                int pid = remoteService.getPid();
                int currentPid = Process.myPid();
                System.out.println("currentPID: " + currentPid +"  remotePID: " + pid);
                remoteService.basicTypes(12, 1223, true, 12.2f, 12.3, "我們的愛,我明白");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            System.out.println("bind success! " + remoteService.toString());
        }
    };
         
    /**
     * 監聽按鈕點擊
     * @param view
     */
    public void buttonClick(View view) {
        System.out.println("begin bindService");
        Intent intent = new Intent("duanqing.test.aidl");
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }
}

這樣就實現了通過AIDL進行進程間通信了,是不是也很簡單,不過這個看似簡單,其實底層Android為我們做了很多的事情,核心就是Binder,感興趣的讀者可以學習一下Binder原理。

 

5.使用ContentProvider:

ContentProvider(內容提供者)是Android中的四大組件之一,為了在應用程序之間進行數據交換,Android提供了ContentProvider,ContentProvider是不同應用之間進行數據交換的API,一旦某個應用程序通過ContentProvider暴露了自己的數據操作接口,那麼不管該應用程序是否啟動,其他應用程序都可以通過接口來操作接口內的數據,包括增、刪、改、查等。ContentProvider分為系統的和自定義的,系統的也就是例如聯系人,圖片等數據。

開發一個ContentProvider的步驟很簡單:

定義自己的ContentProvider類,該類繼承ContentProvider基類;

在AndroidManifest.xml中注冊這個ContentProvider,類似於Activity注冊,注冊時要給ContentProvider綁定一個域名;

當我們注冊好ContentProvider後,其他應用就可以訪問ContentProvider暴露出來的數據了。

ContentProvider只是暴露出來可供其他應用操作的數據,其他應用則需要通過ContentReslover來操作ContentProvider所暴露出來的數據。Context提供了getContentResolver()方法來獲取ContentProvider對象,獲取之後皆可以對暴露出來的數據進行增、刪、改、查操作了。

使用ContentResolver操作數據的步驟也很簡單:

調用Activity的getContentResolver()獲取ContentResolver對象

根據調用的ContentResolver的insert()、delete()、update()、和query()方法操作數據庫即可。

代碼就不貼出來了,這是Android四大組建之一,相信讀者已經很熟悉了。

 

6.使用廣播(Broadcast)

廣播是一種被動跨進程通訊的方式。當某個程序向系統發送廣播時,其他的應用程序只能被動地接收廣播數據。這就象電台進行廣播一樣,聽眾只能被動地收聽,而不能主動與電台進行溝通。

BroadcasReceivert本質上是一個系統級的監聽器,他專門監聽各程序發出的Broadcast,因此他擁有自己的進程,只要存在與之匹配的Intent被廣播出來,BroadcasReceivert總會被激發。我們知道,只有先注冊了某個廣播之後,廣播接收者才能收到該廣播。廣播注冊的一個行為是將自己感興趣的IntentFilter注冊到Android系統的AMS(ActivityManagerService)中,裡面保存了一個IntentFilter列表。廣播發送者將自己的IntentFilter 的action行為發送到AMS中,然後遍歷AMS中的IntentFilter列表,看誰訂閱了該廣播,然後將消息遍歷發送到注冊了相應IntentFilter的Activity或者Service中-----也就是會調用抽象方法onReceive()方法。其中AMS起到了中間橋梁作用。
程序啟動BroadcasReceivert只需要兩步:

1)創建需要啟動的BroadcasReceivert的Intent;

2)調用Context的sendBroadcast()或sendOrderBroadcast()方法來啟動指定的BroadcasReceivert;

 

每當Broadcast事件發生後,系統會創建對應的BroadcastReceiver實例,並自動觸發onReceiver()方法,onReceiver()方法執行完後,BroadcastReceiver實例就會被銷毀。

注意:onReceiver()方法中盡量不要做耗時操作,如果onReceiver()方法不能在10秒之內完成事件的處理,Android會認為改程序無響應,也就彈出我們熟悉的ANR對話框。如果我們需要在接收到廣播消息後進行一些耗時的操作,我們可以考慮通過Intent啟動一個Server來完成操作,不應該啟動一個新線程來完成操作,因為BroadcastReceiver生命周期很短,可能新建線程還沒執行完,BroadcastReceiver已經銷毀了,而如果BroadcastReceiver結束了,他所在的進程中雖然還有啟動的新線程執行任務,可是由於該進程中已經沒有任何組件,因此系統會在內存緊張的情況下回收該進程,這就導致BroadcastReceiver啟動的子線程不能執行完成。

 

7.使用Socket:

Socaket也是實現進程間通信的一種方式,Socaket也成為“套接字”,是網絡通信中的概念,通過Socaket我們可以很方便的進行網絡通信,都可以實現網絡通信錄,那麼實現跨進程通信不是也是相同的麼。但是Socaket主要還是應用於網絡通信,感興趣的讀者可以自行了解一下。

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved