参考:
《疯狂Android讲义》第四版,第九章
问题解决,参考:
https://stackoverflow.com/questions/63563995/failed-to-find-content-provider-in-api-30
测试包含两个应用程序:FirstContentProvider
与FirstContentResolver
,下面分别对两者进行说明
注意:本次开发在Android 11 (API 30)下完成
build.gradle
关于版本的内容:android { compileSdkVersion 30 buildToolsVersion "30.0.0" defaultConfig { applicationId "com.example.contentprovidertest" minSdkVersion 16 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" }
FirstContentProvider
新建应用程序项目后(创建时可以选择Empty Activity
),修改AndroidManifest.xml
,增加如下内容:
<provider
android:authorities="com.example.provider"
android:name=".FirstProvider"
android:exported="true" />
android:authorities="com.example.provider"
字段可以自定义
android:name=".FirstProvider"
中的FirstProvider
就是需要创建的类,在MainActivity
所在的包下新建该类:
package com.example.firstcontentprovider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class FirstProvider extends ContentProvider {
@Override
public boolean onCreate() {
Log.d("--oncreate--", "--onCreate--方法被调用");
return false;
}
@Nullable
@Override
public Cursor query(
@NonNull Uri uri,
@Nullable String[] strings,
@Nullable String s,
@Nullable String[] strings1,
@Nullable String s1) {
Log.d("--query--", "--query--方法被调用" + "," + " where参数为:" + s);
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
Log.d("--insert--", "--insert--方法被调用" + "," + " value参数为:" + contentValues);
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
Log.d("--delete--", "--delete--方法被调用" + "," + " where参数为:" + s);
return 0;
}
@Override
public int update(
@NonNull Uri uri,
@Nullable ContentValues contentValues,
@Nullable String s,
@Nullable String[] strings) {
Log.d(
"--update--",
"--update--方法被调用" + "," + " where参数为:" + s + ", " + "value参数为: " + contentValues);
return 0;
}
}
修改activity_main.xml
,只是修改TextView
的文本内容(不影响程序功能,这一步可做可不做):
FirstContentResolver
activity_main.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="query"
android:id="@+id/btn_query" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="insert"
android:id="@+id/btn_insert" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="update"
android:id="@+id/btn_update" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="delete"
android:id="@+id/btn_delete" />
</LinearLayout>
MainActivity.java
:
package com.example.firstcontentresolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private Uri uri = Uri.parse("content://com.example.provider");
private Button mBtnQuery, mBtnInsert, mBtnUpdate, mBtnDelete;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnQuery = findViewById(R.id.btn_query);
mBtnInsert = findViewById(R.id.btn_insert);
mBtnUpdate = findViewById(R.id.btn_update);
mBtnDelete = findViewById(R.id.btn_delete);
mBtnQuery.setOnClickListener(
view -> {
Cursor cursor = getContentResolver().query(uri, null, "query where", null, null);
Toast.makeText(this, "远程ContentProvider返回的Cursor为:" + cursor, Toast.LENGTH_SHORT).show();
});
mBtnInsert.setOnClickListener(
view -> {
ContentValues values = new ContentValues();
values.put("name", "android_insert");
Uri newUri = getContentResolver().insert(uri, values);
Toast.makeText(this, "远程ContentProvider新插入记录的Uri为:" + newUri, Toast.LENGTH_SHORT).show();
});
mBtnUpdate.setOnClickListener(
view -> {
ContentValues values = new ContentValues();
values.put("name", "android_update");
int count = getContentResolver().update(uri, values, "update_where", null);
Toast.makeText(this, "远程ContentProvider更新记录数为:" + count, Toast.LENGTH_SHORT).show();
});
mBtnDelete.setOnClickListener(
view -> {
int count = getContentResolver().delete(uri, "delete_where", null);
Toast.makeText(this, "远程ContentProvider删除记录数为:" + count, Toast.LENGTH_SHORT).show();
});
}
}
碰到的问题:Unknown URL content
如果编写完上述两个源文件,就运行两个程序,那么FristContentResolvert
在点击主界面的按钮后,程序会崩溃,查看日志得到如下错误:
经过一番搜索,这是在 Android 11 下才会出现的问题,简单来说,就是出于安全考虑,Android 11 要求应用事先说明需要访问的其他软件包
可以使用:adb shell dumpsys package queries
查看哪些软件包是可以访问的,使用grep
可以过滤出我们想要看的软件包,例如:
解决的方法:使用<queries>标签
说明需要访问的其他软件包
在AndroidManifest.xml
中添加<queries>
:
<queries>
<package android:name="com.example.firstcontentprovider" />
</queries>
运行程序进行测试
启动两个程序,打开FirstContentProvider
的LogCat
:
按图中箭头标注的条件进行过滤
依次点击FirstContentResolver
的四个按钮,可以看到:
至此,测试完成