天天看點

Android中程序間通信(IPC)方式總結1.使用Bundle2.使用檔案共享3.使用Messenger4.使用AIDL:5.使用ContentProvider:6.使用廣播(Broadcast)7.使用Socket:

      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:

Android中程式間通信(IPC)方式總結1.使用Bundle2.使用檔案共享3.使用Messenger4.使用AIDL:5.使用ContentProvider:6.使用廣播(Broadcast)7.使用Socket:

    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主要還是應用于網絡通信,感興趣的讀者可以自行了解一下。

    真心的希望上面的總結對大家有幫助,哪裡有問題還希望大家批評指正,謝謝!