Android中使用ContentProvider進行跨程序方法呼叫
需求背景
最近接到這樣一個需求,需要和別的 App 進行聯動互動,比如下載器 App 和桌面 App 進行聯動,桌面的 App 能直接顯示下載器 App 內的下載任務進度和狀態。
尋找解決方案
從需求上知道了,主要問題在如何解決跨程序的通訊上邊。
AIDL
AIDL 即 Android Interface Definition Language的縮寫,是專為 Android 中跨程序通訊介面的描述語言。優缺點很明顯,優點是穩定,快,Android 專門用於跨程序通訊設計的。缺點是比較麻煩,AIDL 是通訊的約定,參加通訊的雙方都需要把這個 AIDL 檔案都加入自己的程式碼中,然後建立 Service 來實現訪問和被訪問。
ContentProvider
作為 Android 四大基礎元件之一的 ContentProvider 本來它的作用只是提供內容性質的跨程序訪問。但是在 API 11 (Android 3.0) 中,ContentProvider 加入了一個新的方法,可以用來進行跨程序的方法呼叫,ContentProvider 中這個方法的定義如下:
Bundle call(String method, String arg, Bundle extras)
從易用性來講,這個沒有 AIDL 那麼麻煩,而且擴充套件性更強,也沒有 Broadcast 過於依賴系統,API 11 應該就是主要是缺點了,別的缺點暫時沒發現,歡迎補充。
Broadcast
廣播是最簡單的:優點是把分發訊息的任務全部交給 Android 系統了;缺點也是因為全交給系統了,很多地方不受控制。缺點:
- 雖然廣播可以通過指定包名來進行傳送指向性訊息,但是卻不能驗證訊息去向 App 的簽名。
- 系統重啟之後,在系統的廣播佇列裡邊的訊息就丟失了。
實現
為了簡要,主要講講 ContentProvider 吧。
ContentProvider
首先是下載器 App 的 ContentProvider 程式碼實現
package cn.hiroz.downloader.realname;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.util.Log;
public class DownloaderContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] strings, String s, String[] strings2, String s2) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues contentValues) {
return null;
}
@Override
public int delete(Uri uri, String s, String[] strings) {
return 0;
}
@Override
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
return 0;
}
@Override
public Bundle call(String method, String arg, Bundle extras) {
if ("DOWNLOAD".equals(method)) { // 當呼叫我下載的時候
Log.e("Downloader", "download: " + arg);
// 呼叫桌面 App 的方法來更新狀態
updateStatus("download");
} else ("PAUSE".equals(method)) { // 當呼叫我暫停的時候
Log.e("Downloader", "pause: " + arg);
// 呼叫桌面 App 的方法來更新狀態
updateStatus("pause");
}
return null;
}
// 我們要呼叫的對方的 ContentProvider 的 URI
private final Uri LAUNCHERCONTENTPROVIDER_URI = Uri.parse("content://cn.hiroz.launcher.LauncherContentProvider");
}
private void updateStatus(String status) {
getContext().getContentResolver().call(LAUNCHERCONTENTPROVIDER_URI, "UPDATE_STATUS", status, new Bundle());
}
在下載器 App 的 AndroidManifest.xml 中還需要新增 ContentProvider 的定義:
<provider
android:name="cn.hiroz.downloader.realname.DownloaderContentProvider"
android:authorities="cn.hiroz.downloader.DownloaderContentProvider"
android:exported="true"/>
我特地加了authorities設定,這樣在互動時候訪問的 ContentProvider 的 URI 會看起來不一樣,也不會暴露我真實的 ContentProvider 類
然後是桌面 App 的 ContentProvider 程式碼實現
package cn.hiroz.launcher.realname;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.util.Log;
public class LauncherContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] strings, String s, String[] strings2, String s2) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues contentValues) {
return null;
}
@Override
public int delete(Uri uri, String s, String[] strings) {
return 0;
}
@Override
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
return 0;
}
@Override
public Bundle call(String method, String arg, Bundle extras) {
// 當被呼叫“更新狀態”的時候
if ("UPDATE_STATUS".equals(method)) {
Log.e("Launcher", "update status: " + arg);
}
return null;
}
// 我們要呼叫的對方的 ContentProvider 的 URI
private final Uri DOWNLOADERCONTENTPROVIDER_URI = Uri.parse("content://cn.hiroz.downloader.DownloaderContentProvider");
}
public void download(String arg) {
getContext().getContentResolver().call(DOWNLOADERCONTENTPROVIDER_URI, "DOWNLOAD", status, new Bundle());
}
public void pause(String arg) {
getContext().getContentResolver().call(DOWNLOADERCONTENTPROVIDER_URI, "PAUSE", status, new Bundle());
}
}
在桌面 App 的 AndroidManifest.xml 中還需要新增 ContentProvider 的定義:
<provider
android:name="cn.hiroz.launcher.realname.LauncherContentProvider"
android:authorities="cn.hiroz.launcher.LauncherContentProvider"
android:exported="true"/>
然後在桌面 App 中,就可以通過 LauncherContentProvider 的 download
方法和 pause
方法來呼叫下載器 App 的功能了(這兩個方法寫在這裡不太合適,不過我僅僅是為了節省篇幅放一起了)。下載器 App 中被呼叫了方法,就會呼叫桌面 App 的更新狀態。
這裡只是演示了一個互動的過程,有更多問題歡迎大家一起討論學習~~
引申
找不到 ContentProvider 的時候需要做一下空指標保護
簽名校驗