Android Обеспечивает несколько способов взаимодействия различных приложений между собой на платформе. Приложение Android может обмениваться данными с другими приложениями, которые могут использоваться другими приложениями для построения собственной логики. Чтобы привести пример, вам может потребоваться запросить контактную информацию по телефону. В Android рекомендуемый способ обмена данными — через контент-провайдеров . Поставщик контента является владельцем определенного контента, он предоставляет четко определенные API для чтения, вставки, обновления и удаления этих данных. Поставщик контента может внутренне использовать любое место для хранения своих данных, например, локальный файл, локальную базу данных или некоторую удаленную службу. В этой статье мы узнаем, как мы можем создать нашего собственного поставщика контента и получить доступ к данным из другого приложения.
Найдите окончательный проект на GitHub .
Создание класса провайдера контента
Мы начнем с создания в нашем приложении класса провайдера контента, который может содержать элементы метаданных изображения. Мы будем использовать локальную базу данных SQLite для хранения данных, но вы можете использовать любую, где вам нравится, для хранения данных.
Сначала создайте класс ImagesProvider
расширяющий класс ImagesProvider
и переопределяющий следующие методы.
import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; public class ImagesProvider extends ContentProvider { @Override public String getType(Uri uri) { return ""; } @Override public boolean onCreate() { return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } }
В приведенном выше коде мы создали класс ImagesProvider
который переопределяет необходимые методы для создания контент-провайдера.
Мы начнем с реализации метода getType
. Метод getType
возвращает тип Mime данных в виде строки.
Возвращаемый тип MIME должен иметь формат vnd.<uri pattern>./vnd.<name>.<type>
. Где <uri pattern>
для одной строки должен быть android.cursor.item
, для нескольких строк android.cursor.dir
и <name>
должны быть глобально уникальными (используйте имя пакета). <type>
должен быть уникальным для соответствующего URI. Итак, теперь давайте обновим наш код для реализации getType
следующим образом:
import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; public class ImagesProvider extends ContentProvider { private static final String PROVIDER_NAME = "androidcontentproviderdemo.androidcontentprovider.images"; private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/images"); private static final int IMAGES = 1; private static final int IMAGE_ID = 2; private static final UriMatcher uriMatcher = getUriMatcher(); private static UriMatcher getUriMatcher() { UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME, "images", IMAGES); uriMatcher.addURI(PROVIDER_NAME, "images/#", IMAGE_ID); return uriMatcher; } @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { case IMAGES: return "vnd.android.cursor.dir/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images"; case IMAGE_ID: return "vnd.android.cursor.item/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images"; } return ""; } @Override public boolean onCreate() { return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } }
Реализация функций onCreate и query
Для хранения контента нам понадобится вспомогательный класс базы данных для нашего провайдера контента. Создайте класс ImageDataBase
следующим образом
import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; public class ImageDataBase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "ImagesDatabase.db"; private static final String TABLE_NAME = "imagestore"; private static final String SQL_CREATE = "CREATE TABLE " + TABLE_NAME + " (_id INTEGER PRIMARY KEY, IMAGETITLE TEXT , IMAGEURL TEXT , IMAGEDESC TEXT )"; private static final String SQL_DROP = "DROP TABLE IS EXISTS " + TABLE_NAME ; ImageDataBase(Context context) { super(context, DATABASE_NAME, null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(SQL_DROP); onCreate(db); } public Cursor getImages(String id, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder(); sqliteQueryBuilder.setTables(TABLE_NAME); if(id != null) { sqliteQueryBuilder.appendWhere("_id" + " = " + id); } if(sortOrder == null || sortOrder == "") { sortOrder = "IMAGETITLE"; } Cursor cursor = sqliteQueryBuilder.query(getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder); return cursor; } }
Приведенный выше класс расширяет SQLiteOpenHelper
и создает таблицу с именем imagestore в базе данных ImagesDatabase.db со столбцами id, IMAGETITLE, IMAGEURL и IMAGEDESC. Функция getImages
запрашивает эту базу данных, используя класс SQLiteQueryBuilder
и возвращает курсор результата. Теперь давайте реализуем функции onCreate
и query
в нашем контент-провайдере.
import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; public class ImagesProvider extends ContentProvider { private static final String PROVIDER_NAME = "androidcontentproviderdemo.androidcontentprovider.images"; private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/images"); private static final int IMAGES = 1; private static final int IMAGE_ID = 2; private static final UriMatcher uriMatcher = getUriMatcher(); private static UriMatcher getUriMatcher() { UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME, "images", IMAGES); uriMatcher.addURI(PROVIDER_NAME, "images/#", IMAGE_ID); return uriMatcher; } private ImageDatabase imageDataBase = null; @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { case IMAGES: return "vnd.android.cursor.dir/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images"; case IMAGE_ID: return "vnd.android.cursor.item/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images"; } return ""; } @Override public boolean onCreate() { Context context = getContext(); imageDataBase = new ImageDataBase(context); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { String id = null; if(uriMatcher.match(uri) == IMAGE_ID) { //Query is for one single image. Get the ID from the URI. id = uri.getPathSegments().get(1); } return imageDataBase.getImages(id, projection, selection, selectionArgs, sortOrder); } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } }
В функции onCreate
мы создаем объект класса ImageDatabase
и передаем ему контекст. Мы храним эту переменную, чтобы использовать ее для различных операций нашего контент-провайдера. В функции query
мы проверяем, является ли запрос одним id
. В этом случае мы выбираем id
а если нет, то сохраняем его как ImageDatabase:getImages
и передаем эти значения в ImageDatabase:getImages
чтобы получить данные изображений и вернуть курсор результата.
Вставка, удаление, обновление данных в вашем контент-провайдере
После завершения функции запроса мы реализуем другие функции в поставщике контента. Для insert
, delete
и update
нам потребуется добавить вспомогательную функцию в класс ImageDatabase
как ImageDatabase
ниже
import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import java.sql.SQLException; public class ImageDataBase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "ImagesDatabase.db"; private static final String TABLE_NAME = "imagestore"; private static final String SQL_CREATE = "CREATE TABLE " + TABLE_NAME + " (_id INTEGER PRIMARY KEY, IMAGETITLE TEXT , IMAGEURL TEXT , IMAGEDESC TEXT )"; private static final String SQL_DROP = "DROP TABLE IS EXISTS " + TABLE_NAME ; ImageDataBase(Context context) { super(context, DATABASE_NAME, null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(SQL_DROP); onCreate(db); } public Cursor getImages(String id, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder(); sqliteQueryBuilder.setTables(TABLE_NAME); if(id != null) { sqliteQueryBuilder.appendWhere("_id" + " = " + id); } if(sortOrder == null || sortOrder == "") { sortOrder = "IMAGETITLE"; } Cursor cursor = sqliteQueryBuilder.query(getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder); return cursor; } public long addNewImage(ContentValues values) throws SQLException { long id = getWritableDatabase().insert(TABLE_NAME, "", values); if(id <=0 ) { throw new SQLException("Failed to add an image"); } return id; } public int deleteImages(String id) { if(id == null) { return getWritableDatabase().delete(TABLE_NAME, null , null); } else { return getWritableDatabase().delete(TABLE_NAME, "_id=?", new String[]{id}); } } public int updateImages(String id, ContentValues values) { if(id == null) { return getWritableDatabase().update(TABLE_NAME, values, null, null); } else { return getWritableDatabase().update(TABLE_NAME, values, "_id=?", new String[]{id}); } } }
Все новые функции обращаются к базе данных и вызывают функции insert
, delete
и update
соответственно, передавая id и значения содержимого. Теперь мы обновляем класс ImagesProvider
как ImagesProvider
ниже
import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; import android.util.Log; public class ImagesProvider extends ContentProvider { private static final String PROVIDER_NAME = "androidcontentproviderdemo.androidcontentprovider.images"; private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/images"); private static final int IMAGES = 1; private static final int IMAGE_ID = 2; private static final UriMatcher uriMatcher = getUriMatcher(); private static UriMatcher getUriMatcher() { UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME, "images", IMAGES); uriMatcher.addURI(PROVIDER_NAME, "images/#", IMAGE_ID); return uriMatcher; } private ImageDatabase imageDataBase = null; @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { case IMAGES: return "vnd.android.cursor.dir/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images"; case IMAGE_ID: return "vnd.android.cursor.item/vnd.com.androidcontentproviderdemo.androidcontentprovider.provider.images"; } return ""; } @Override public boolean onCreate() { Context context = getContext(); imageDataBase = new ImageDatabase(context); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { String id = null; if(uriMatcher.match(uri) == IMAGE_ID) { //Query is for one single image. Get the ID from the URI. id = uri.getPathSegments().get(1); } return imageDataBase.getImages(id, projection, selection, selectionArgs, sortOrder); } @Override public Uri insert(Uri uri, ContentValues values) { try { long id = imageDataBase.addNewImage(values); Uri returnUri = ContentUris.withAppendedId(CONTENT_URI, id); return returnUri; } catch(Exception e) { return null; } } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { String id = null; if(uriMatcher.match(uri) == IMAGE_ID) { //Delete is for one single image. Get the ID from the URI. id = uri.getPathSegments().get(1); } return imageDataBase.deleteImages(id); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { String id = null; if(uriMatcher.match(uri) == IMAGE_ID) { //Update is for one single image. Get the ID from the URI. id = uri.getPathSegments().get(1); } return imageDataBase.updateImages(id, values); } }
В приведенном выше коде, если URI для простого изображения, мы получаем значение идентификатора, в противном случае мы оставляем идентификатор как нулевое. Затем передайте это значение соответствующей соответствующей функции в классе ImageDataBase
.
Объявление вашего контент-провайдера в AndroidManifest
Нам нужно объявить нашего провайдера контента в файле AndroidManifest.xml с помощью тега provide
.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.androidcontentproviderdemo.androidcontentprovider"> <application android:allowBackup="true" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:theme="@style/AppTheme"> <provider android:name="com.androidcontentproviderdemo.androidcontentprovider.ImagesProvider" android:authorities="androidcontentproviderdemo.androidcontentprovider.images"> </provider> </application> </manifest>
В приведенном выше <provider>
свойство android:name
должно представлять собой класс Content Provider, а android:authorities
должны быть URI для идентификации содержимого.
Использование контент-провайдера
После завершения вышесказанного мы готовы протестировать нашего контент-провайдера. Для этого мы создадим тестовое приложение в текущем основном действии, снова изменив подходящие значения для имени вашего приложения.
import android.app.Activity; import android.content.ContentValues; import android.content.CursorLoader; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.Toast; public class MainActivity extends Activity { private static final String PROVIDER_NAME = "androidcontentproviderdemo.androidcontentprovider.images"; private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/images"); private ListView listView; private SimpleCursorAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.lstViewImages); adapter = new SimpleCursorAdapter(getBaseContext(), R.layout.list_layout, null, new String[] { "IMAGETITLE", "IMAGEURL", "IMAGEDESC"}, new int[] { R.id.imgTitle , R.id.imgUrl, R.id.imgDesc }, 0); listView.setAdapter(adapter); refreshValuesFromContentProvider(); } private void refreshValuesFromContentProvider() { CursorLoader cursorLoader = new CursorLoader(getBaseContext(), CONTENT_URI, null, null, null, null); Cursor c = cursorLoader.loadInBackground(); adapter.swapCursor(c); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } public void onClickAddImage(View view) { ContentValues contentValues = new ContentValues(); contentValues.put("IMAGETITLE", ((EditText) findViewById(R.id.edtTxtImageTitle)).getText().toString()); contentValues.put("IMAGEURL" , ((EditText)findViewById(R.id.edtImageUrl)).getText().toString()); contentValues.put("IMAGEDESC", ((EditText) findViewById(R.id.edtImageDesc)).getText().toString()); Uri uri = getContentResolver().insert(CONTENT_URI, contentValues); Toast.makeText(getBaseContext(), uri.toString(), Toast.LENGTH_LONG).show(); refreshValuesFromContentProvider(); } }
Это действие относится к двум макетам, один для основного представления содержимого и один для представления списка, которые перечислены ниже:
activitymain.xml
<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".ContentProviderUsageActivity"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerHorizontal="true"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Image Title" android:id="@+id/txtViewImageTitle" android:layout_gravity="center_horizontal" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/edtTxtImageTitle" android:layout_gravity="center_horizontal" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Image URL" android:id="@+id/txtViewImageUrl" android:layout_gravity="center_horizontal" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/edtImageUrl" android:layout_gravity="center_horizontal" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Image description" android:id="@+id/txtViewImageDesc" android:layout_gravity="center_horizontal" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/edtImageDesc" android:layout_gravity="center_horizontal" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Add Image" android:id="@+id/btnAddImage" android:layout_gravity="center_horizontal" android:onClick="onClickAddImage" /> <ListView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/lstViewImages" android:layout_gravity="center_horizontal" /> </LinearLayout> </RelativeLayout>
listlayout.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/imgTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/imgUrl" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/imgDesc" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
В приведенном выше коде функция onCreate
извлекает все содержимое из поставщика содержимого с помощью функции CursorLoader
и передает ему CONTENT_URI
.
Чтобы вставить значения, getContentResolver().insert
, который передает URI содержимого и ContentValues
для вставки. Если мы запустим это действие, оно будет выглядеть следующим образом
Вывод
Поставщик контента в Android обеспечивает чистый и систематический способ обмена приложениями и использования данных из других приложений. Он стандартизирует, как контент используется и используется несколькими приложениями. В Android существует множество встроенных поставщиков контента, а API позволяет легко создавать свой собственный поставщик контента или использовать его.
Получайте удовольствие от создания вашего следующего поставщика контента и дайте мне знать, если у вас есть какие-либо отзывы.