1. 程式人生 > >Android:程序間通訊互動

Android:程序間通訊互動

Intent 的 ComponentName

Intent作為我們最常用的資料傳輸渠道,特別是通過Intent開啟一個Activity,想必每個人都不會陌生。通常我們用到的都是通過Intent開啟同一個程序(App)內部的Activity,如果想實現跨程序通訊,就需要把Intent物件傳送到另一個(App)中,並解析出來,這時就需要ComponentName來為我們做這件事情了。既然可以傳送資料到另外的程序,也就可以實現不同程序間的互動了。
注意事項:如果A要開啟另一個程序中的B中的Activity,那麼要在B專案中的AndroidManifest檔案中,把要開啟的Activity的exported設定為true ,否則將會報錯。

android:exported="true"

我們將要用到ComponentName的建構函式如下:

    public ComponentName(String pkg, String cls) {
        if (pkg == null) throw new NullPointerException("package name is null");
        if (cls == null) throw new NullPointerException("class name is null");
        mPackage = pkg;
        mClass = cls;
    }

它需要兩個引數,第一個引數是一個存在的pakage(包),第二個是這個包中你要開啟的類的名字(注意是帶完整包名的),下面是使用例子:
程序AndroidAIDL(com.example.androidaidl)中的MainActivity

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //接收傳過來的Intent
if (getIntent() != null){ System.out.println("----------"+getIntent().getIntExtra("id", 0)+"----------"); } }

另一個程序AndroidTest,在這裡的MainActivity中我們寫如下程式碼:

/**指定包名和帶包名的Activity的名字*/
ComponentName componentName = new ComponentName("com.example.androidaidl", "com.example.androidaidl.MainActivity");
Intent intent = new Intent();
intent.putExtra("id", 1001);
intent.setComponent(componentName);
startActivity(intent);

執行上面程式碼後,會看到LogCat中打印出 1001,表示接收AndroidTest程序傳來的Intent正常。

注意事項:上面程式碼中我們傳遞的是基本的資料型別,對於基本資料型別,比如Int,String等接收時可以像上面那樣直接讀取,但是如果傳送的是複雜的物件,該物件需要實現Serializable或者Parcelable介面。
比如我們定義一個 SendData 物件作為傳遞物件,它實現 Parcelable 介面:

public class SendData implements Parcelable{
    int id;
    String content;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public static final Parcelable.Creator<SendData> CREATOR = new Parcelable.Creator<SendData>() {

        @Override
        public SendData createFromParcel(Parcel source) {
            // TODO Auto-generated method stub
            SendData data = new SendData();
            data.setId(source.readInt());
            data.setContent(source.readString());
            return data;
        }

        @Override
        public SendData[] newArray(int size) {
            // TODO Auto-generated method stub
            return new SendData[size];
        }

    };

    @Override
    public int describeContents() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        // TODO Auto-generated method stub
        dest.writeInt(id);
        dest.writeString(content);
    }

}

傳送程式碼:

Intent intent = new Intent();
SendData data = new SendData();
data.setId(1001);
data.setContent("hellow world");
//傳送序列化物件
intent.putExtra("data", data);
startActivity(intent);

接收程式碼

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (getIntent() != null){
        if (getIntent().getParcelableExtra("data") == null)
            return;
        //讀取序列化物件,並轉化為SendData型別
        SendData data = (SendData)getIntent().getParcelableExtra("data");
        System.out.println("----------"+data.getContent()+"-----------");
    }
}

如果在同一個程序的話,上面的方法沒有任何問題,如果在不同程序中,就要注意,SendData 這個bean所在的包名,在各個專案中必須一樣,否則接收方,無法解析

廣播-BroadcastReceiver

Android的廣播是系統級的,只要傳遞的Action一樣(下面的例子中,都使用 Action_Test),就可以接收到其他程序廣播的訊息,廣播中可以通過Intent傳遞資料。

傳送方程式碼:

Intent intent = new Intent("Action_Test");
SendData data = new SendData();
data.setId(1001);
data.setContent("hellow world");
intent.putExtra("data", data);
intent.putExtra("id", 1001);
getActivity().sendBroadcast(intent);

接收方程式碼(動態註冊廣播):

innerReceiver = new InnerReceiver();
IntentFilter filter = new IntentFilter("Action_Test");
registerReceiver(innerReceiver, filter);

class InnerReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO Auto-generated method stub
        if (intent.getAction().equals("Action_Test")){
            SendData data = (SendData)intent.getParcelableExtra("data");
            System.out.println("-----------"+data.getContent()+"------------");
        }
    }   
}

因為是通過Intent傳遞資料,所以對複雜物件的要求和第一種方式一樣,要求bean所在的包名一樣

ContentProvider

ContentProvider通常用來操作資料集,Android本身就提供了不少的ContentProvider訪問,比如聯絡人、相簿等。
訪問ContentProvider,需要通過Uri,需要以“content://”開頭。下面看看使用方法:
在程序AndroidAIDL(com.example.androidaidl),我們定義一個繼承自ContentProvider的類,需要過載它的方法,這裡我們以query為例

public class ProviderTest extends ContentProvider {
    private static final UriMatcher MATCHER = new UriMatcher(  
            UriMatcher.NO_MATCH);  
    static{
    //新增訪問字串,對應配置檔案中的android:authorities屬性
        MATCHER.addURI("com.mh.getdata", "stock", 10001);
    }

    @Override
    public boolean onCreate() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    /**
     * @param uri URI 路徑
     * @param projection 要保護的列集合,如果傳 null,所有列都會被包含進去.
     * @param selection 用來過濾資料集的規則,null表示不篩選.
     * @param selectionArgs 類似字串的格式化,selection引數中可以包含 ?s, 這個將被selectionArgs的內容替換
     * @param sortOrder 排序.
     */
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        // TODO Auto-generated method stub
        System.out.println("--------------------query----------------------");
        return null;
    }

    @Override
    public String getType(Uri uri) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        // TODO Auto-generated method stub
        return 0;
    }

}

定義了這個類後,需要在AndroidManifest對它進行宣告,android:exported=true,記得要寫,否則會提示許可權錯誤

        <provider android:name="ProviderTest" android:authorities="com.mh.getdata"
            android:exported="true">            
        </provider>

上面的配置有兩個屬性
android:name就是類名成
android:authorities這個就是前面提到的Uri,外界需要通過這個來訪問Provider

下面看看呼叫者,在另一個程序中,我們有如下程式碼:

ContentResolver resolver = getActivity().getContentResolver();
/**com.mh.getdata/stock這個要和Provider所在程序中新增的Uri一致*/
Uri uri = Uri.parse("content://com.mh.getdata/stock");
Cursor cursor = resolver.query(uri, null, null, null, null);

呼叫上面程式碼後,LogCat中打印出 “query”字樣,表名Provider呼叫成功,我們通過resolver就可以和AndroidAIDL程序中的Provider物件進行互動了。

AIDL

一種介面定義語言,Android會自動生成通訊程式碼,通過AIDL我們可以在一個程序中,呼叫另一個程序中的方法,據說一些大公司的殺不死的Service就是通過這個AIDL來保活的,可以好好研究下。個人認為AIDL更適合做外掛化系統,或者有多個app共同組成一個系統,比如WebView因為記憶體洩露比較嚴重,所以一些公司就單獨把WebView封裝成一個app,單獨呼叫,這時,我們通過AIDL技術,就可以呼叫到這個app中的介面,來操作WebView的行為。
下面開始介紹使用方法:
在兩個專案中新建普通檔案(Eclipse中是:new ->General->File),記得同時寫上字尾名(aidl),兩個專案中這個檔案所在的包名要保持一致,內容也要一樣,如圖

這裡寫圖片描述
編譯之後, 會在gen目錄下,自動產生同名的,字尾為 java 的檔案。裡面有我們要用到的 Stub類。

public static abstract class Stub extends android.os.Binder implements com.example.aidl.AidlFunctions

在介面檔案AIDLFunctions.aidl中,我們定義一個方法 show

interface AidlFunctions{
    void show();
}

服務端:
AIDL的使用,需要一個Service配合,所以我們在服務端還要宣告一個Service

public class AIDLService extends Service {
//stub就是系統自動產生的
    AidlFunctions.Stub binder;

    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        binder = new AidlFunctions.Stub() {

            @Override
            //這裡是我們在介面中宣告的方法的實現
            public void show() throws RemoteException {
                // TODO Auto-generated method stub
                System.out.println("--------------------收到----------------------");
            }
        };
        return binder;
    }   
}

在AndroidManifest宣告Service

        <service android:name="com.example.androidaidl.AIDLService">
            <intent-filter>
                <action android:name="com.example.androidaidl.AIDLService" />
            </intent-filter>
        </service>

客戶端:

//繫結服務,要用到ServiceConnection 
private ServiceConnection serviceConnection;
//自定義的介面,和服務端一樣
private AidlFunctions aidlFunctions;

serviceConnection = new ServiceConnection() {

    @Override
    public void onServiceDisconnected(ComponentName name) {
        // TODO Auto-generated method stub
        System.out.println("--------------------ServiceDisconnected----------------------");
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // TODO Auto-generated method stub
        System.out.println("--------------------ServiceConnected----------------------");
        aidlFunctions = AidlFunctions.Stub.asInterface(service);
    }
};
Intent intent = new Intent("com.example.androidaidl.AIDLService");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
//呼叫show方法
try {
    aidlFunctions.show();
} catch (RemoteException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}