[TOC]
1. Что такое контент-провайдер?
Контент-провайдер (Content Provider) в основном используется для реализации функции совместного использования данных между различными приложениями.Он предоставляет полный набор механизмов, позволяющих одной программе получать доступ к данным в другой программе, и в то же время, также может быть гарантировано для доступа. Безопасность данных, в настоящее время с помощью поставщика контента + Android для достижения стандартного способа обмена данными между программами. Поставщик контента может выбрать только часть данных для обмена, чтобы обеспечить безопасность нашего данные.
2. Как использовать контент-провайдеров для получения данных из других приложений
2.1 Заявление о разрешении
Разрешения во время выполнения: новая функция, представленная в системе 6.0 для лучшей защиты конфиденциальности пользователей.
Обычная декларация разрешения требует только добавления разрешения, которое будет использоваться в MainFest.Для некоторых разрешений в 6.0 и более поздних версиях вам необходимо проверить, есть ли это разрешение в коде при запуске, иначе появится диалоговое окно для применения. Если вы откажетесь, вы не можете использовать.
** Не все разрешения требуют разрешений во время выполнения, требуются только те, которые связаны с конфиденциальностью пользователя. В дополнение к объявлению в ManiFest, оно должно быть повторно запрошено в коде. Если разрешения во время выполнения не используются, это вызовет броски этого приложения исключение SecurityException() **
public class MainActivity extends AppCompatActivity {
private String TAG = "TAG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkSelf();//检查权限
}
private void checkSelf() {
//如果检查多个权限的话,可以将要检查的权限放入数组或者集合当中,遍历检查即可
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
/**
* 通过checkSelfPermission可以检查当前这个应用有没有获取到指定的那个权限,没有的话就调用请求权限的那个方法.
* 根据返回值来判断状态 0表示权限已给予,-1表示没有获取到权限.
*/
Log.d(TAG, "checkSelf: 权限允许");
} else {
/**
* 通过ActivityCompat.requestPermissions动态的申请权限.
* 第一个参数:当前的上下文
* 第二个参数:需要申请的权限的字符串,保存在数组中.
* 第三个参数:查询码,请求权限的最终结果会通过回调的方式->onRequestPermissionsResult();在这个方法中,告诉你最后请求成功了没有
*/
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_CALENDAR,Manifest.permission.CAMERA}, 1);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
/**
* 通过请求码来判断具体是申请了什么权限,以及结果;
* grantResult[] 保存着申请权限后,根据申请权限的先后顺序保存
*/
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "onRequestPermissionsResult: 已授权");
for (int i = 0; i < grantResults.length; i++) {
Log.d(TAG, "onRequestPermissionsResult: 授权情况:"+grantResults[i]);
}
} else {
Log.d(TAG, "onRequestPermissionsResult: 您拒绝了授权");
}
break;
case 2:
break;
default:
break;
}
}
}
Выше приведено основное использование разрешений во время выполнения.
2.2 Основное использование и принципы поставщиков контента
Как правило, существует два варианта использования поставщиков контента: один — использовать существующий поставщик контента для чтения и работы с данными в соответствующей программе, а другой — самостоятельно создать поставщика контента для предоставления внешнего интерфейса доступа к данным наша программа (это через поставщика контента, предоставленного другими (программами), для получения данных, которые другие (программы) хотят использовать для нас), собственные SMS системы, телефонная книга, медиатека и другие программы предоставляют аналогичные интерфейсы доступа, мы можно использовать это для разработки и использования снова.
Если вы хотите получить данные в контент-провайдере, вам нужно использовать класс Content-Resolver, а метод GetContentResolver в Context может получить экземпляр этого класса.Content-Resolver предоставляет метод, аналогичный классу SqLiteDatabase, который можно использовать для общих данных.Выполняют CRUD-операции, но параметры немного отличаются.
Поставщик контента использует Uri для поиска источников данных.Uri состоит из полномочий и пути; полномочия используются для различения различных программ, обычно с использованием имен пакетов, а путь используется для различения разных таблиц одной и той же программы.
Например, пакет с именем com.example.test имеет две таблицы: table1, table2;
Тогда стандартный формат Uri (содержимое перед :// — это протокол):
контент://com.example.test/table1
контент://com.example.test/table2
Таким образом, мы можем четко выразить данные, в какой таблице какой программы мы хотим получить доступ, поэтому CRUD контент-провайдера принимает только параметр Uri для определения местоположения.
Находить:
Uri uri=Uri.parse("content://com.example.test/table2");
Cursor cursor=getContentResolver().quert(uri,projection,selection,selectionArgs,sortOrder);
Параметры метода quert() | Соответствующая часть SQL | описывать |
---|---|---|
uri | from table_name | Укажите для запроса таблицы в приложении |
projection | select column1,column2 | Укажите имена столбцов для запроса |
selection | where column =value | Укажите, где ограничения |
selectionArgs | - | Укажите конкретные значения для заполнителей, где |
sortOrder | order by column1,column2 | Указывает, как сортируются результаты запроса. |
Получение значения из курсора такое же, как операция базы данных.Выберите тип данных, которые вы хотите получить, а затем выберите имя столбца для получения
...
String data=cursor.getString(cursor.getColumnIndex("columnName"));
...
вставлять:
Как и в случае с базой данных, речь идет о сборке данных в ContentValues.
ContentValues values=new ContentValues();
values.put("columnName1","value1");
values.put("columnName2","value2");
getContentResolver().insert(uri,values);
обновить:
ContentValues values=new ContentValues();
values.put("columnName1","value1");
getContentResolver().update(Uri.parse(""),contentValues,"where column1=?",new String[]{"1"});
Объяснение параметра в update(): первый параметр указывает местоположение данных, второй параметр указывает, какое значение обновлять, третий параметр указывает условие, четвертый параметр указывает значение по умолчанию в операторе условия where;
Удалить:
getContentResolver().delete(Uri.parse(""),"column=?",new String[]{"1"});
Смысл параметров аналогичен предыдущему, объяснять особо не буду.
2.3 Получение данных с помощью контент-провайдера
Давайте воспользуемся контент-провайдером, который нам предоставила система, чтобы получить телефонные номера имен в адресной книге, предварительно убедившись, что несколько телефонных номеров действительно сохранены в адресной книге.
1. Во-первых, вам нужно получить разрешение READ_CANTACTS, иначе данные не могут быть прочитаны и будет сообщено об ошибке.
2. Оператор запроса аналогичен использованию базы данных
public class MainActivity extends AppCompatActivity {
private String TAG = "TAG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
} else {
getData();
}
}
private void getData() {
Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
//传入的Uri是ContactsContract.CommonData-Kinds.Phone类已经帮我们封装好了的一个常量
//点开源代码可以看到 :
//public static final Uri CONTENT_URI = Uri.withAppendedPath(Data.CONTENT_URI,
"phones");
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME 也是一个常量,为保存该数据的类名.下同
String phone = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.d(TAG, "getData: " + name + "-" + phone);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getData();
} else {
Log.d(TAG, "onRequestPermissionsResult: 你点击了拒绝!");
}
break;
default:
break;
}
}
}
Запустите программу, чтобы увидеть распечатанные имя и номер телефона.
Основное содержание находится в методе getData();, а конкретные пояснения пишутся в комментариях.Поскольку вы знаете принцип, пользоваться им становится очень удобно.
3. Создайте собственного контент-провайдера
Базовые знания:
Создайте поставщика контента, создав новый класс для наследования ContentProvider, но вы должны переопределить шесть методов внутри;
Переопределенный метод: | возвращаемое значение | использовать |
---|---|---|
onCreate(); | boolean | Вызывается при инициализации поставщика содержимого. Обычно здесь завершается создание и обновление базы данных. Возвращает значение true, чтобы указать, что поставщик содержимого был инициализирован успешно, и значение false, если произошла ошибка. |
query(uri,projection, selection, selectionArgs, sortOrder); | Cursor | Найдите данные от поставщика контента, используйте параметр uri, чтобы определить, какую таблицу искать, проекцию, чтобы определить, какие столбцы искать, выборку, чтобы определить условия поиска, selectionArgs, чтобы заполнить значение условий по умолчанию в выборе sortOrder Используется для сортировки результатов запроса, результаты запроса возвращаются в Cursor; |
insert(uri, values); | Uri | Добавьте часть данных в поставщик контента, используйте uri для определения добавляемой таблицы и поместите добавляемые данные в параметр values; после успешного добавления верните Cursor для представления новой записи; |
update(uri, values, selection, selectionArgs); | int | Обновите существующие данные в контент-провайдере, используйте параметр Uri, чтобы определить, какие данные таблицы нужно обновить, новые данные сохраняются в значениях (обновляются только значения, записанные здесь), и используются параметры selection и selectIonArgs для ограничения того, какие строки будут обновлены; количество затронутых строк будет возвращено в качестве возвращаемого значения |
delete(uri, selection, selectionArgs) | int | Удалите данные в поставщике контента, параметр uri используется для определения того, какие данные таблицы удаляются, параметры selection и selectionArgs используются для ограничения того, какие строки удаляются, количество удаленных строк будет возвращено в качестве возвращаемого значения; |
getType(uri)(); | String | Вернуть соответствующую строку типа MIME в соответствии с входящим Uri |
Описание двух форматов Uri:
контент://com.example.test/table1/1;
Указывает, что вызывающий объект ожидает доступа к данным с идентификатором 1 в таблице table1 в приложении com.exampke.test.
контент://com.example.test/table1;
Указывает, что вызывающий объект ожидает доступа ко всем данным в таблице table1 в приложении com.exampke.test.
Однако для соответствия этим двум форматам обычно используются подстановочные знаки:
: означает соответствие числу любой длины
#* : означает соответствие любой длине символов
Вышеприведенный контент может быть записан как: content://com.example.test/* или content://com.example.test/table1/#
getType(); возвращает описание данных
контент://com.example.test/table1/1;
контент://com.example.test/table1;
Возьмем в качестве примера два приведенных выше.
Строка MIME: начинается с vnd + .+ android.cursor.dir/ (или android.cursor.item/) + vnd.+ AUTHORITY + .+ PATH
vnd.android.cursor.dir/vnd.com.example.test.table1;
vnd.android.cursor.item/vnd.com.example.test.table1
3.1 Основные этапы создания
Создайте класс, наследуемый от ContentProvider, и переопределите в нем шесть методов.
Контент-провайдер должен быть зарегистрирован в ManiFest.xml, иначе его нельзя будет использовать.
<provider
android:name=".MyProvider"
android:authorities="com.example.h.content_demo_provider"
android:enabled="true"
android:exported="true" />
Первый параметр: имя класса
Второй параметр: обычно используется с именем пакета, которое может различать поставщиков контента между разными программами.
Третий параметр: включить
Четвертый параметр: указывает, что к нему разрешен доступ другим приложениям.
Создайте нового поставщика контента:
public class MyProvider extends ContentProvider {
public static final int BOOK_DIT = 0;
public static final int BOOK_ITEM = 1;
public static final int CATEGORY_DIR = 2;
public static final int CATEGORT_ITEM = 3;
public static UriMatcher sUriMatcher;
public static final String AUTHORITY = "com.example.h.content_demo_provider";
private MyDatabaseHelper mMyDatabaseHelper;
private SQLiteDatabase db;
{
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(AUTHORITY, "book", BOOK_DIT);
sUriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
sUriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
sUriMatcher.addURI(AUTHORITY, "category/#", CATEGORT_ITEM);
//UriMatcher 可以匹配uri 通过调用他的match()方法 匹配到就会返回我们在上面添加uri时填入的第三个参数
}
@Override
/**
* 初始化内容提供器的时候调用,通常会在这里完成对数据库的创建和升级等操作
* 返回true表示内容提供器初始化成功,返回false则表示失败.
*/
public boolean onCreate() {
//对当前内容提供器需要的资源进行初始化
mMyDatabaseHelper = new MyDatabaseHelper(getContext(), "info.db", null, 1);
db = mMyDatabaseHelper.getWritableDatabase();
Log.d(TAG, "onCreate: 内容提供器初始化完成");
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String
selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
//查询方法,通过解析uri来判断想要查询哪个程序的哪个表.通过UriMatchder进行匹配 如果有就返回前面addUri()中填入的code
Cursor cursor = null;
switch (sUriMatcher.match(uri)) {
case BOOK_DIT:
cursor = db.query("book", projection, selection, selectionArgs, null, null,
sortOrder);
Log.d(TAG, "query: 查询整个表" + cursor);
return cursor;
case BOOK_ITEM:
String itemId = uri.getPathSegments().get(1);
cursor = db.query("book", projection, "id=?", new String[]{itemId}, null, null,
sortOrder);
/**
* .getPathSegments()它会将内容URI权限之后的部分以 / 进行分割,并把分割后的结果放入到一个字符串列表中,
* 返回的列表[0]存放的就是路径,[1]存放的就是id
*/
return cursor;
case CATEGORT_ITEM:
String itemId2 = uri.getPathSegments().get(1);
cursor = db.query("category", projection, "id=?", new String[]{itemId2}, null, null,
sortOrder);
return cursor;
case CATEGORY_DIR:
cursor = db.query("category", projection, selection, selectionArgs, null, null,
sortOrder);
return cursor;
default:
return cursor;
}
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (sUriMatcher.match(uri)) {
case BOOK_DIT:
return "vnd.android.cursor.dir/vnd.com.example.h.content_demo_provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.h.content_demo_provider.book";
case CATEGORT_ITEM:
return "vnd.android.cursor.item/vnd.com.example.h.content_demo_provider.category";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.h.content_demo_provider.category";
default:
return null;
}
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Uri uriReturn = null;
switch (sUriMatcher.match(uri)) {
case BOOK_DIT:
case BOOK_ITEM:
long value = db.insert("book", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + value);
return uriReturn;
//返回新插入行的行id,如果发生错误则返回-1
case CATEGORT_ITEM:
case CATEGORY_DIR:
long value2 = db.insert("category", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + value2);
return uriReturn;
default:
return uriReturn;
}
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[]
selectionArgs) {
int deleteRows = 0;
switch (sUriMatcher.match(uri)) {
case BOOK_DIT:
deleteRows = db.delete("book", selection, selectionArgs);
return deleteRows;
case BOOK_ITEM:
String itemId1 = uri.getPathSegments().get(1);
deleteRows = db.delete("book", "id=?", new String[]{itemId1});
return deleteRows;
case CATEGORT_ITEM:
String itemId2 = uri.getPathSegments().get(1);
deleteRows = db.delete("category", "id=?", new String[]{itemId2});
return deleteRows;
case CATEGORY_DIR:
deleteRows = db.delete("category", selection, selectionArgs);
return deleteRows;
default:
return deleteRows;
}
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String
selection, @Nullable String[] selectionArgs) {
int updateRows = 0;
switch (sUriMatcher.match(uri)) {
case BOOK_DIT:
updateRows = db.update("book", values, selection, selectionArgs);
return updateRows;
case BOOK_ITEM:
String itemId1 = uri.getPathSegments().get(1);
updateRows = db.update("book", values, "id=?", new String[]{itemId1});
return updateRows;
case CATEGORT_ITEM:
String itemId2 = uri.getPathSegments().get(1);
updateRows = db.update("category", values, "id=?", new String[]{itemId2});
return updateRows;
case CATEGORY_DIR:
updateRows = db.update("category", values, selection, selectionArgs);
return updateRows;
default:
return updateRows;
}
}
}
Новые методы:
uri.getPathSegments().get(1);//getPathSegments() возвращает коллекцию, которая разделяет содержимое за пределами полномочий в uri, то есть имя таблицы get(0) и значение идентификатора get(1)Извлеченный таким образом идентификатор можно использовать в качестве условия для выполнения условного запроса к базе данных.
3.2 Получение данных между программами
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn1, btn2, btn3, btn4;
private Uri mUriBook = Uri.parse("content://com.example.h.content_demo_provider/book");
private Uri mUriCategory = Uri.parse("content://com.example.h.content_demo_provider/category");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
//randomInsert();
iQuery();
}
private void initView() {
btn1 = findViewById(R.id.button);
btn2 = findViewById(R.id.button2);
btn3 = findViewById(R.id.button4);
btn4 = findViewById(R.id.button5);
btn4.setOnClickListener(this);
btn3.setOnClickListener(this);
btn2.setOnClickListener(this);
btn1.setOnClickListener(this);
}
private void iQuery() {
Cursor cursor = getContentResolver().query(mUriBook, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String id = cursor.getString(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
System.out.println(id + name + author);
}
} else {
System.out.println("为空");
}
}
private void randomInsert() {
ContentValues contentValues1 = new ContentValues();
ContentValues contentValues2 = new ContentValues();
for (int i = 0; i < 5; i++) {
contentValues1.put("name", "name" + i);
contentValues1.put("author", "author" + i);
System.out.println(getContentResolver().insert(mUriBook, contentValues1));
contentValues2.put("type", "type" + i);
contentValues2.put("code", "code" + i);
System.out.println(getContentResolver().insert(mUriCategory, contentValues2)); ;
}
//这个方法只是通过内容提供器对另一个程序中的数据库写一点数据进去方便我们进行后续的CRUD.
}
private void iDelete(String value) {
ContentValues contentValues = new ContentValues();
int x = 0;
Uri uri = Uri.parse("content://com.example.h.content_demo_provider/book/" + value);
x = getContentResolver().delete(uri, null, null);
Log.d("TAG", "iDelete: 删除后的返回值:" + x);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
iQuery();
break;
case R.id.button2:
break;
case R.id.button4:
iDelete("1");
break;
case R.id.button5:
break;
default:
break;
}
}
}
В основном, определяя Uri и передавая его поставщику контента, сообщая ему, что вы хотите сделать, и, наконец, он сообщит вам результат выполнения через возвращаемое значение, функцию обновления, а другие несколько не будут расширяться и анализироваться,