【Android】跨程序共享数据------内容提供器
简介
内容提供器(Content Provider)是Android中的一个组件,用于在应用程序之间共享数据。它提供了一种标准机制,使得一个应用可以暴露其数据,并允许其他应用访问这些数据。内容提供器在访问和存储数据时提供了安全性和一致性,通常用于访问结构化的数据集。
程序运行时申请权限
首先创建一个RuntimePermissionTest项目
修改activity中的代码:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/make_call"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Make Call"/>
</LinearLayout>
下来修改MainActivity中的代码:
java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button makecall = (Button) findViewById(R.id.make_call);
makecall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
} catch (SecurityException e) {
e.printStackTrace();
}
}
});
}
}
在按钮点击事件中构建了一个隐式Intent,Intent指定的Action为Intent.ACTION_CALL,是一个系统内置的打电话的动作
修改AndroidManifest文件:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.CALL_PHONE"
tools:ignore="PermissionImpliesUnsupportedChromeOsHardware" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.RuntimePermissionTest"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
此时在低于Android6.0系统行都是可以正常运行的,但是高于6.0却不行,因为6.0及以上系统在使用危险权限时都必须进行运行时权限处理
修改MainActivity中的代码:
java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button makecall = (Button) findViewById(R.id.make_call);
makecall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1);
} else {
call();
}
}
});
}
private void call() {
try {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
} catch (SecurityException e) {
e.printStackTrace();
}
}
@SuppressLint("MissingSuperCall")
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 1) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
call();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
}
}
}
第一步先判断用户是否授权,借助了ContextCompat.checkSelfPermission()方法,接收两个参数,第一个为Context,第二个为具体的权限名,比如拨打电话的权限名就是Manifest.permission.CALL_PHONE
我们把打电话的逻辑封装在requestPermission()方法中来向用户申请授权,接收三个参数,第一个是Activity实例,第二个是String数组,第三个是请求码
访问其他程序中的数据
ContentResolver的用法
标准格式的URI写法:
xml
content://com.example.app.provider/table1
content://com.example.app.provider/table2
在得到了内容URI字符串之后,我们还需要将它解析成Uri对象才可以作为参数传入:
java
Uri uri = Uri.parse("content://com.examole.app.provider/tabel")
只需要调用Uri.parse()方法,就可以将内容URI字符串解析成Uri对象了。
现在可以使用这个Uri对象来查询tabel表中的数据了:
java
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);
读取数据:
java
if(cursor != null){
do {
String name = cursor.getString(cursor.getColumnIndex("name"));
}while (cursor.moveToNext());
}
增加数据:
java
ContentValues values = new ContentValues();
values.put("name","This is a book");
getContentResolver().insert(uri,values);
删除数据:
java
ContentValues values1 = new ContentValues();
values.put("name","This is a book");
getContentResolver().delete(uri,"name = ?",new String[]{"This is a book"});
修改数据:
java
ContentValues values1 = new ContentValues();
values.put("name","This is a book1");
getContentResolver().update(uri,values,"name = ?",new String[]{"This is a book"});
读取系统联系人
新建ContactsTest项目
修改activity_main中的代码:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/contacts_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
下来修改MainActivity中的代码:
java
public class MainActivity extends AppCompatActivity {
// 声明一个 ArrayAdapter 对象
ArrayAdapter<String> adapter;
// 声明一个存储联系人列表的 List 对象
List<String> contactsList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取 ListView 控件,并设置适配器
ListView contactsView = (ListView) findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contactsList);
contactsView.setAdapter(adapter);
// 检查是否有读取联系人权限,如果没有则请求权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
} else {
// 如果已经有权限,则读取联系人
readContacts();
}
}
// 读取联系人信息的方法
private void readContacts() {
Cursor cursor = null;
try {
// 查询联系人数据
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
// 获取联系人姓名和电话号码,并添加到联系人列表
@SuppressLint("Range") String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
@SuppressLint("Range") String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName + '\n' + number);
}
// 通知适配器数据已更改
adapter.notifyDataSetChanged();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 确保游标被关闭
if (cursor != null) {
cursor.close();
}
}
}
// 权限请求结果的回调方法
@SuppressLint("MissingSuperCall")
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 1) {
// 如果权限被授予,则读取联系人;否则,显示拒绝权限的提示
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
}
}
}
最后还需要申请权限:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ContactsTest"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
创建自己的内容提供器
新建MyProvider继承ContentProvider:
java
public class Myprovider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return "";
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
各个方法的作用:
- onCreate() :
- 用于初始化
ContentProvider
。这个方法会在ContentProvider
第一次创建时调用。 - 返回
true
表示ContentProvider
初始化成功,返回false
表示初始化失败。
- 用于初始化
- query() :
- 用于查询
ContentProvider
中的数据。 - 参数解释:
uri
: 查询的 URI。projection
: 查询的列。selection
: 查询条件。selectionArgs
: 查询条件的参数。sortOrder
: 排序方式。
- 返回一个
Cursor
对象,指向查询结果。如果没有查询结果,返回null
。
- 用于查询
- getType() :
- 获取指定 URI 对应的数据的 MIME 类型。
- 参数解释:
uri
: 查询的 URI。
- 返回一个表示 MIME 类型的字符串。如果没有匹配的类型,返回空字符串。
- insert() :
- 向
ContentProvider
中插入数据。 - 参数解释:
uri
: 插入数据的 URI。values
: 要插入的数据。
- 返回新插入数据的 URI。如果插入失败,返回
null
。
- 向
- delete() :
- 从
ContentProvider
中删除数据。 - 参数解释:
uri
: 删除数据的 URI。selection
: 删除条件。selectionArgs
: 删除条件的参数。
- 返回删除的行数。如果删除失败,返回
0
。
- 从
- update() :
- 更新
ContentProvider
中的数据。 - 参数解释:
uri
: 更新数据的 URI。values
: 要更新的数据。selection
: 更新条件。selectionArgs
: 更新条件的参数。
- 返回更新的行数。如果更新失败,返回
0
。
- 更新
*
:表示匹配任意长度的任意字符#
:表示匹配任意长度的数字
修改MyProvider中的代码:
java
public class Myprovider extends ContentProvider {
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_ITEM = 3;
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provider", "table2", TABLE2_DIR);
uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_ITEM);
}
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
if (uriMatcher.match(uri) == TABLE1_DIR) {
;//查询table1表中的所有数据
} else if (uriMatcher.match(uri) == TABLE1_ITEM){
;//查询table1表中的单条数据
} else if (uriMatcher.match(uri) == TABLE2_DIR) {
;//查询table2表中的所有数据
} else if (uriMatcher.match(uri) == TABLE2_ITEM) {
;//查询table2表中的单条数据
}
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return "";
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
有一个方法
getType()
,它是所有内容提供器都必须提供的第一个方法,用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要有3部分构成:
- 必须以vnd开头
- 如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/
- 最后接上vnd.<authority>.<path>
继续完善MyProvider中的内容:
java
public class Myprovider extends ContentProvider {
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_ITEM = 3;
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provider", "table2", TABLE2_DIR);
uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_ITEM);
}
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
if (uriMatcher.match(uri) == TABLE1_DIR) {
;//查询table1表中的所有数据
} else if (uriMatcher.match(uri) == TABLE1_ITEM) {
;//查询table1表中的单条数据
} else if (uriMatcher.match(uri) == TABLE2_DIR) {
;//查询table2表中的所有数据
} else if (uriMatcher.match(uri) == TABLE2_ITEM) {
;//查询table2表中的单条数据
}
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
case TABLE1_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
case TABLE2_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
}
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
已经到底啦!!