【Android】安卓四大組件之內容提供者(ContentProvider):從基礎到進階

你手機里的通訊錄,存儲了所有聯系人的信息。如果你想把這些聯系人信息分享給其他App,就可以通過ContentProvider來實現。。

一、什么是 ContentProvider

?ContentProvider? 是 Android 四大組件之一,負責實現?跨應用程序的數據共享與訪問?,通過統一接口封裝數據存儲細節,提供標準化操作方式。其中主要功能包括:

  1. 數據抽象層:將應用內部的數據(如 SQLite 數據庫、文件等)封裝成統一的接口對外提供。
  2. 跨應用數據共享:允許其他應用安全地訪問和操作本應用的數據。
  3. 數據權限控制:通過 URI 和權限機制,精確控制數據的訪問范圍。
  4. 統一數據訪問:提供類似數據庫的 CRUD 操作接口,簡化數據使用。

二、ContentProvider 的核心概念

  1. URI(統一資源標識符)

    • 格式:content://authority/path/id
    • 示例:content://com.example.provider/users/1
    • authority:標識 ContentProvider,通常為應用包名 + provider 名
    • path:標識要訪問的數據集合
    • id:可選,標識具體記錄
  2. ContentResolver

    • 應用通過 ContentResolver 與 ContentProvider 通信
    • 提供 query ()、insert ()、update ()、delete () 等方法
  3. Cursor

    • 查詢結果的返回類型,類似數據庫查詢結果集
    • 通過 Cursor 獲取和遍歷數據

三、ContentProvider 的實現步驟

以下是實現一個簡單 ContentProvider 的完整步驟:

1.創建數據模型

// User.java
public class User {private int id;private String name;private int age;// getters and setters
}

2.創建 SQLiteOpenHelper 管理數據庫

// DatabaseHelper.java
public class DatabaseHelper extends SQLiteOpenHelper {private static final String DB_NAME = "user.db";private static final int DB_VERSION = 1;public static final String TABLE_NAME = "users";public DatabaseHelper(Context context) {super(context, DB_NAME, null, DB_VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL("CREATE TABLE " + TABLE_NAME + " (" +"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +"name TEXT, " +"age INTEGER);");}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);onCreate(db);}
}

3.實現 ContentProvider

// UserProvider.java
public class UserProvider extends ContentProvider {private DatabaseHelper dbHelper;public static final String AUTHORITY = "com.example.provider";public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/users");@Overridepublic boolean onCreate() {dbHelper = new DatabaseHelper(getContext());return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {SQLiteDatabase db = dbHelper.getReadableDatabase();return db.query(DatabaseHelper.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);}@Overridepublic Uri insert(Uri uri, ContentValues values) {SQLiteDatabase db = dbHelper.getWritableDatabase();long id = db.insert(DatabaseHelper.TABLE_NAME, null, values);return ContentUris.withAppendedId(CONTENT_URI, id);}@Overridepublic int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {SQLiteDatabase db = dbHelper.getWritableDatabase();return db.update(DatabaseHelper.TABLE_NAME, values, selection, selectionArgs);}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {SQLiteDatabase db = dbHelper.getWritableDatabase();return db.delete(DatabaseHelper.TABLE_NAME, selection, selectionArgs);}@Overridepublic String getType(Uri uri) {return "vnd.android.cursor.dir/vnd.com.example.provider.users";}
}

4.在 AndroidManifest.xml 中注冊 Provider

<providerandroid:name=".UserProvider"android:authorities="com.example.provider"android:exported="true"android:grantUriPermissions="true">
</provider>

四、ContentProvider 的使用示例

其他應用通過 ContentResolver 訪問該 Provider:

// 查詢所有用戶
Cursor cursor = getContentResolver().query(UserProvider.CONTENT_URI, null, null, null, null
);// 插入新用戶
ContentValues values = new ContentValues();
values.put("name", "John");
values.put("age", 30);
Uri newUri = getContentResolver().insert(UserProvider.CONTENT_URI, values);// 更新用戶
ContentValues updateValues = new ContentValues();
updateValues.put("age", 31);
int count = getContentResolver().update(UserProvider.CONTENT_URI, updateValues, "name=?", new String[]{"John"}
);// 刪除用戶
int deleted = getContentResolver().delete(UserProvider.CONTENT_URI, "age > ?", new String[]{"40"}
);

五、跨應用權限控制

配置目標實現方式
跨應用調用權限

調用方聲明<uses-permission>,Porvider方配置android:exported="true"。

動態權限申請針對dangerous級別權限,調用方需在運行時請求用戶授權
路徑級訪問控制Provider方通過<path-permission>細化權限,調用方需匹配聲明

1. 聲明 Provider 權限

<!-- 定義自定義權限 -->  
<permission android:name="com.example.READ_USERS"  android:protectionLevel="dangerous" />  
<permission android:name="com.example.WRITE_USERS"  android:protectionLevel="dangerous" />  <!-- 應用權限到 Provider --> 
<provider  android:name=".UserProvider" <!-- Provider 實現類的全路徑 -->  android:authorities="com.example.provider" <!-- 唯一標識符,與Contract類一致 -->  android:exported="true" <!-- 是否允許其他應用訪問(默認 false) -->   android:readPermission="com.example.READ_USERS"  android:writePermission="com.example.WRITE_USERS" />  
  • protectionLevel?設為?dangerous?表示需用戶手動授權。
  • readPermission/writePermission:自定義權限控制。

2.?路徑級權限細化(可選)

若 Provider 方通過?<path-permission>?限制特定路徑,調用方需確保擁有對應權限:

<!-- Provider 方配置 -->  
<provider ...>  <path-permission  android:pathPrefix="/admin"  android:permission="com.example.ADMIN_PERMISSION" />  
</provider>  

3. 調用方配置

<manifest ...>  <!-- 聲明權限 -->  <uses-permission android:name="com.example.READ_USERS" />  <uses-permission android:name="com.example.WRITE_USERS" />  <!-- 如果存在路徑細化,調用方需聲明額外權限 -->  <uses-permission android:name="com.example.ADMIN_PERMISSION" />  <application ...>  <!-- 無 Provider 聲明,直接通過 ContentResolver 調用 -->  </application>  
</manifest>  

4. 動態權限申請?

在調用方的 Activity/Fragment 中實現動態權限申請流程:

public class MainActivity extends AppCompatActivity {  private static final int REQUEST_READ_PERMISSION = 100;  @Override  protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  // 檢查權限  if (ContextCompat.checkSelfPermission(this, "com.example.READ_USERS")  != PackageManager.PERMISSION_GRANTED) {  // 權限未授予,顯示申請彈窗  ActivityCompat.requestPermissions(this,  new String[]{"com.example.READ_USERS"},  REQUEST_READ_PERMISSION);  } else {  // 已授權,執行數據訪問  queryData();  }  }  // 處理權限申請結果  @Override  public void onRequestPermissionsResult(int requestCode,  @NonNull String[] permissions, @NonNull int[] grantResults) {  super.onRequestPermissionsResult(requestCode, permissions, grantResults);  if (requestCode == REQUEST_READ_PERMISSION) {  if (grantResults.length > 0 && grantResults[0]== PackageManager.PERMISSION_GRANTED) {  queryData();  } else {  // 權限被拒絕,提示用戶  Toast.makeText(this, "權限被拒絕,無法讀取數據",Toast.LENGTH_SHORT).show();  }  }  }  private void queryData() {  // 通過 ContentResolver 訪問 Provider 數據  Cursor cursor = getContentResolver().query(  UserContract.CONTENT_URI,  null, null, null, null  );  // 處理查詢結果...  }  
}  

同一權限組內的權限只需申請一次(如?READ_CONTACTS?和?WRITE_CONTACTS?屬于同一組)

4.??用戶拒絕后引導設置?

若用戶勾選“不再詢問”,需引導用戶前往系統設置手動開啟權限(可通過?shouldShowRequestPermissionRationale?判斷)。

if (ActivityCompat.shouldShowRequestPermissionRationale(this,"com.example.READ_USERS")) {  // 用戶之前可能拒絕過權限但未勾選“不再詢問”// 展示解釋性彈窗后再次申請  
} else {  // 用戶勾選“不再詢問”或系統禁止權限(如廠商定制 ROM 限制)// 跳轉系統設置界面手動開啟權限 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);  intent.setData(Uri.parse("package:" + getPackageName()));  startActivity(intent);  
}  

若用戶?從未請求過該權限?,shouldShowRequestPermissionRationale() 也會返回 false。但此時代碼通常不會進入此分支(因首次請求時直接調用 requestPermissions())。

部分設備可能不支持 Settings.ACTION_APPLICATION_DETAILS_SETTINGS,需增加異常捕獲并提示用戶手動查找權限設置 。?

六、數據變更通知

角色職責
客戶端注冊ContentObserver并實現onChange回調邏輯(如刷新UI)
ContentProvider數據變更時調用notifyChange觸發通知
系統服務通過ContentService統一管理觀察者,完成消息分發

?1. 客戶端注冊觀察者

在使用數據的客戶端(如 Activity、Fragment)中,通過?ContentResolver?注冊?ContentObserver,并指定監聽的目標 URI,從而實時更新UI。

// 使用者(Activity)通過ContentResolver注冊觀察者  
getContentResolver().registerContentObserver(  UserContract.CONTENT_URI,  true,  // 是否監聽子 URI  new ContentObserver(new Handler()) {  @Override  public void onChange(boolean selfChange) {  // 數據變化時觸發回調}  }  
);
  • registerContentObserver?是客戶端主動調用的方法,用于綁定觀察者與目標數據 URI。
  • true?表示監聽該 URI 及其所有子路徑(如?content://com.example.provider/users/)的數據變更。

2. 提供者觸發通知

在 ?ContentProvider? 中,當數據發生變更(如?insertupdatedelete)時,需調用?notifyChange?方法觸發回調:

// 在 Provider 的 insert/update/delete 方法中  
getContext().getContentResolver().notifyChange(uri, null);  
  • notifyChange?會通知所有注冊了該 URI 的觀察者。
  • 可通過第二個參數?observer?指定跳過特定觀察者(通常設為?null)。

?3. 系統級支持?

  • ?ContentService?:負責管理所有注冊的觀察者,以樹形結構維護 URI 監聽關系,實現高效的跨進程通知分發。
  • ?Binder 機制?:底層通過 Binder 傳遞觀察者對象(封裝為?Transport?代理),確保跨進程通信的可行性。

客戶端需主動注冊觀察者監聽 URI,而通知觸發由 Provider 發起,兩者通過系統服務協同實現實時數據同步。

七、ContentProvider 的性能優化

1.使用 SQLite 事務

  • 批量操作時使用事務提高性能
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction();
try {// 執行多個操作db.setTransactionSuccessful();
} finally {db.endTransaction();
}

索引優化

  • 對經常查詢的字段添加索引
db.execSQL("CREATE INDEX IF NOT EXISTS idx_name ON users(name);");

避免在主線程進行耗時操作

  • 使用 Loader 或異步任務執行查詢
getSupportLoaderManager().initLoader(0, null, this);

八、ContentProvider 的安全注意事項

  1. 謹慎設置 android:exported

    • 僅在需要對外共享數據時設置為 true
    • 默認值為 false,可防止外部訪問
  2. 輸入驗證

    • 對傳入的 selection 和 projection 參數進行驗證
private void validateProjection(String[] projection) {if (projection != null) {for (String col : projection) {if (!allowedColumns.contains(col)) {throw new IllegalArgumentException("Invalid column: " + col);}}}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/913908.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/913908.shtml
英文地址,請注明出處:http://en.pswp.cn/news/913908.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Vue-19-前端框架Vue之應用基礎組件通信(二)

文章目錄 1 v-model(父子相傳)1.1 App.vue1.2 Father.vue1.2.1 v-model用在html標簽上1.2.2 v-model用在html標簽上(本質寫法)1.2.3 v-model用在組件標簽上1.2.4 v-model用在組件標簽上(本質寫法)1.3 MyInput(自定義的組件)1.4 修改modelValue1.4.1 Father.vue1.4.2 MyInput.vu…

寶塔下載pgsql適配spring ai

1.寶塔安裝pgvector 1.先去github下載pgvectorpgvector/pgvector: Open-source vector similarity search for Postgres 2.把壓縮包上傳到系統文件的/temp下解壓&#xff0c;重命名文件名為pgvector&#xff0c;之后命令操作 cd /tmp cd pgvector export PG_CONFIG/www/serv…

RK3568項目(八)--linux驅動開發之基礎外設(上)

目錄 一、引言 二、準備工作 ------>2.1、驅動加載/卸載命令 三、字符設備驅動開發 ------>3.1、驅動模塊的加載和卸載 ------>3.2、外部模塊編譯模板 Makefile ------>3.3、cdev 四、LED驅動 ------>4.1、原理圖 ------>4.2、驅動 五、設備樹 -…

BUUCTF在線評測-練習場-WebCTF習題[GXYCTF2019]BabySQli1-flag獲取、解析

解題思路打開靶場&#xff0c;題目提示是sql注入輸入數據&#xff0c;判斷下閉合11123報錯&#xff1a;Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 1 at line 1報錯提示…

“AI 曼哈頓計劃”:科技競賽還是人類挑戰?

美國國會下屬的經濟與安全審查委員會已將“推動建立并資助一項堪比曼哈頓計劃的通用人工智能研發項目”列為其對國會的核心建議之一&#xff0c;明確顯示出對AI競賽戰略意義的高度重視。與此同時&#xff0c;美國能源部在近幾個月中多次公開將人工智能的突破比作“下一場曼哈頓…

音頻信號的預加重:提升語音清晰度

一、預加重介紹預加重是一種信號處理技術&#xff0c;主要用于增強音頻信號中的高頻成分。由于人類語音的頻譜特性&#xff0c;尤其是在輔音和音調的表達上&#xff0c;高頻成分對于語音的清晰度至關重要。然而&#xff0c;在錄音和傳輸過程中&#xff0c;這些高頻成分往往會受…

WebSocket實戰:實現實時聊天應用 - 雙向通信技術詳解

目錄一、WebSocket&#xff1a;實時通信的"高速公路"1.1 HTTP的短板&#xff1a;永遠的"單相思"1.2 WebSocket的優勢&#xff1a;真正的"雙向對話"二、30分鐘搭建聊天服務器2.1 環境準備2.2 WebSocket配置類2.3 核心消息處理器三、前端實現&…

宏集案例 | 基于CODESYS的自動化控制系統,開放架構 × 高度集成 × 遠程運維

??案例概況客戶&#xff1a;MACS Sterilisationsanlagen GmbH&#xff08;Ermafa Environmental Technologies GmbH 旗下&#xff09; 應用場景&#xff1a;醫療與感染性廢棄物的無害化處理控制系統應用產品&#xff1a;宏集Berghof高性能控制器設備&#xff08;一&#xff0…

學習JNI 二

創建一個名為Learn1項目&#xff08;Android Studio&#xff09;。一、項目結構二、配置 build.gradlebuild.gradle.kts(:app)plugins {alias(libs.plugins.android.application)alias(libs.plugins.jetbrains.kotlin.android) }android {namespace "com.demo.learn1&quo…

基于Spring Boot+Vue的DIY手工社預約管理系統(Echarts圖形化、騰訊地圖API)

2.10 視頻課程管理功能實現2.11手工互動&#xff08;視頻彈幕&#xff09;2.8預約設置管理功能實現&#x1f388;系統亮點&#xff1a;Echarts圖形化、騰訊地圖API&#xff1b;文檔包含功能結構圖、系統架構圖、用例圖、實體屬性圖、E-R圖。一.系統開發工具與環境搭建1.系統設計…

leetcode 每日一題 1353. 最多可以參加的會議數目

更多技術訪問 我的個人網站 &#xff08;免費服務器&#xff0c;沒有80/443端口&#xff09; 1353. 最多可以參加的會議數目 給你一個數組 events&#xff0c;其中 events[i] [startDayi, endDayi] &#xff0c;表示會議 i 開始于 startDayi &#xff0c;結束于 endDayi 。 …

AI+智慧園區 | 事件處置自動化——大模型重構園區治理邏輯

在智慧園區的建設浪潮中&#xff0c;事件管理一直是園區高效運營的關鍵環節。考拉悠然所推出的大模型 智慧園區解決方案&#xff0c;在事件智能閉環管理方面獨樹一幟&#xff0c;為園區的日常運營編織了一張嚴密、高效、智能的管理網絡&#xff0c;實現了從事件感知到處置的全…

FFmpeg Windows安裝

FFmpeg 用于音頻文件轉換 Builds - CODEX FFMPEG gyan.dev ffmpeg-release-full.7z 下載完成之后 zip解壓 大概就是 ffmpeg/ └── bin/ └── ffmpeg.exe 配置環境變量 ffmpeg -version 有可能idea還是找不到命令 就把命令路徑寫在程序里 例如

【2025/07/10】GitHub 今日熱門項目

GitHub 今日熱門項目 &#x1f680; 每日精選優質開源項目 | 發現優質開源項目&#xff0c;跟上技術發展趨勢 &#x1f4cb; 報告概覽 &#x1f4ca; 統計項&#x1f4c8; 數值&#x1f4dd; 說明&#x1f4c5; 報告日期2025-07-10 (周四)GitHub Trending 每日快照&#x1f55…

JVM 基礎 - JVM 內存結構

前言 本文主要對JVM 內存結構進行講解&#xff0c;注意不要和Java內存模型混淆了。 運行時數據區 內存是非常重要的系統資源&#xff0c;是硬盤和 CPU 的中間倉庫及橋梁&#xff0c;承載著操作系統和應用程序的實時運行。JVM 內存布局規定了 Java 在運行過程中內存申請、分配…

【案例】二手車交易價格預測-472

二手車交易價格預測 數據來源數據特征探索構建模型參考數據來源 天池 https://tianchi.aliyun.com/competition/entrance/231784/information 數據特征探索 目標特征工程做好之后,能同時進行 lightgbm catboost 神經網絡等模型,所以盡量都轉換為數值類特征。 如果僅僅是使用…

【Spring】Java SPI機制及Spring Boot使用實例

目錄 一、SPI是什么 1.1 SPI 和 API 有什么區別&#xff1f; 二、使用場景 三、使用介紹 四、Spring Boot實例運用 五、總結 一、SPI是什么 SPI全稱Service Provider Interface&#xff0c;是Java提供的一套用來被第三方實現或者擴展的API&#xff0c;它可以用來啟用框架…

多維度數據資產測繪技術在安全管控平臺中的應用實踐

一、數據資產治理困境&#xff1a;從 “黑箱” 到 “可見性” 的行業挑戰在數字化轉型加速的當下&#xff0c;企業數據資產呈現爆發式增長&#xff0c;而傳統資產梳理手段因維度單一、時效性差&#xff0c;導致 “資產黑箱” 問題頻發。某省級運營商曾在安全評估中發現&#xf…

搭建react18+項目過程中遇到的問題(vite)

問題1. 頁面中使用import.meta.env獲取環境變量有紅色波浪線提示錯誤按提示給ts.config.ts文件中的compilerOptions增加了"module": “esnext” (es2020 | es2022 | system)這幾個也不行 但是另一個問題出現了安裝的第三方庫引入報錯了 按照提示我們將module改成了’…

Linux epoll簡介與C++TCP服務器代碼示例

Linux epoll 簡介與示例 TCP 服務器 1. 為什么要用 epoll select/poll 每次調用都把全部文件描述符從用戶態拷貝到內核態,隨連接數增長而線性變慢;epoll 采用事件驅動+就緒隊列的方式,內核只把“已就緒”的描述符返回給用戶態,O(1) 規模擴展;支持 邊沿觸發 Edge-Triggere…