Content Provider与SQLite结合使用
来源:互联网 发布:java培训讲师工资 编辑:程序博客网 时间:2024/06/09 19:37
前言
虽然推荐使用数据库保存结构化的复杂数据,但是数据共享是一个挑战,因为数据库只允许创建它的应用访问。
在Android中共享数据
在Android中,推荐使用content provider方法在不同包之间共享数据。content provider可以被视为一个数据仓库。它如何存储数据与应用如何使用它无关。然而,应用如何使用一致的编程接口在它的里面的数据非常重要。content provider的行为与数据库非常相似——你可以查询它、编辑它的内容、添加或者删除内容。然而,与数据库不同的是,一个content provider可以使用不同的方式存储它的数据。数据可以被存储在数据库中、在文件中、甚至在网络上。
Android提供了许多非常有用的content provider,包括以下几种:
除了许多内置的content provider以外,可以创建自定义的content provider。
要查询一个content provider,需要以统一的资源标识符(Uniform Resource Identifier,URI)的形式制定查询字符串,以及一个可选的特定行说明符。以下是查询URI的形式:
<standard_prefix>://<authority>/<data_path>/<id>
URI的各种组成部分如下:
content://
。 authority 指定content provider的名称。例如内置Contacts的content provider的名称为contacts。对于第三方content provider来说,它可以是完整的合法名称,例如com.wrox.provider。 data_path 指定请求的数据种类。例如,如果你要从Contacts的content provider中获取所有的通讯录,那么data_path应该是people,URI将类似与:content://contacts/people
。 id 指定请求特定的记录。例如,如果你要获取Contacts的content provider中第2条通讯录,URI将类似类似于:content://contact/people/2
。 使用content provider
理解content provider的最佳方法是在实践中使用它。
Main2Activity
public class Main2Activity extends ListActivity { final private int REQUEST_READ_CONTACTS = 123; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS); } else { listContacts(); } } @Override public void onRequestPermissionsResult(int requestCode , @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case REQUEST_READ_CONTACTS: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { listContacts(); } else { Toast.makeText(Main2Activity.this , "Permission Denied", Toast.LENGTH_SHORT).show(); } break; default: super.onRequestPermissionsResult(requestCode , permissions, grantResults); } } protected void listContacts() { Uri allContacts = Uri.parse("content://contacts/people"); CursorLoader cursorLoader = new CursorLoader( this, allContacts, null, null, null, null); Cursor cursor = cursorLoader.loadInBackground(); String[] columns = new String[]{ ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts._ID}; int[] views = new int[]{R.id.contactName, R.id.contactID}; SimpleCursorAdapter adapter; adapter = new SimpleCursorAdapter( this, R.layout.activity_main2, cursor, columns, views, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); this.setListAdapter(adapter); }}
本示例获取了存储在Contacts应用中的通讯录并且将其显示在ListView中。
首先指定访问Contacts应用的URI:
Uri allContacts = Uri.parse("content://contacts/people");
其次,检查应用是否由访问Contacts的权限:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS);} else { listContacts();}
如果没有访问权限,就会发出一条权限请求(使Android弹出一个权限请求对话框)。如果应用由相应的访问权限,ListContacts()方法会被调用。
getContentResolver()方法返回一个ContentResolver对象,它会使用适当content provider解析内容URI。
CursorLoader类(Android API level 11及以上版本可用)在后再线程中执行cursor查询操作,因此不会阻塞应用UI。
CursorLoader cursorLoader = new CursorLoader( this, allContacts, null, null, null, null);Cursor cursor = cursorLoader.loadInBackground();
SimpleCursorAdapter对象将一个Cursor数据映射到XML文件(activity_main2.xml)中定义的TextView(或者ImageView)。它将数据(对应于代码中的columns变量)映射到视图(对应于代码中的view变量)中:
String[] columns = new String[]{ ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts._ID};int[] views = new int[]{ R.id.contactName, R.id.contactID};SimpleCursorAdapter adapter = new SimpleCursorAdapter( this, R.layout.activity_main2, cursor, columns, views, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);this.setListAdapter(adapter);
类似于managedQuery()方法,SimpleCursorAdapter类的一个构造函数已经被弃用。对于运行Honeycomb及后续版本的设备,需要使用新的SimpleCursorAdapter类的构造函数,与旧版本相比,该构造函数多一个参数:
SimpleCursorAdapter adapter = new SimpleCursorAdapter( this, R.layout.activity_main2, cursor, columns, views, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
新的标志参数将该适配器注册为观察者,当Content Provider发生变化时,该适配器会被通知。
注意,如果你的应用要访问Contacts应用,需要在AndroidManifest.xml文件中添加READ_CONTACTS权限。
CursorLoader说明
CursorLoader (Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
预定义查询字符串常量
Uri allContacts = Uri.parse("content://contacts/people");
等同于:
Uri allContacts = ContactsContract.Contacts.CONTENT_URI;
在下面的示例中,通过访问ContactsContract.Contacts._ID
字段获取一条通讯录的ID,通过访问ContactsContract.Contacts.DISPLAY_NAME
字段获取一条通讯录的姓名。如果想要显示通讯录电话号码,可以再次查询content provider,因为这个信息存储在另外一个表中:
private void printContacts(Cursor c) { if (c.moveToFirst()) { do { String contactID = c.getString(c.getColumnIndex(ContactsContract.Contacts._ID)); String contactDisplayName = c.getString(c.getColumnIndex( ContactsContract.Contacts.DISPLAY_NAME)); Log.v("ContentProviders", contactID + ", " + contactDisplayName); // 获取电话号码 Cursor phoneCursor = getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactID, null, null); assert phoneCursor != null; while (phoneCursor.moveToNext()) { Log.v("ContentProviders", phoneCursor.getString(phoneCursor.getColumnIndex( ContactsContract.CommonDataKinds.Phone.NUMBER))); } phoneCursor.close(); } while (c.moveToNext()); }}
注意,要访问通讯录中的电话号码,需要使用保存在
ContactsContract.CommonDataKinds.Phone.CONTENT_URI
常量中的URI查询。
显示结果:
V/ContentProviders: 1, I think is okayV/ContentProviders: 100V/ContentProviders: 2, JavaV/ContentProviders: 1 234-567-9V/ContentProviders: 3, KotlinV/ContentProviders: 1 23V/ContentProviders: 4, ScalaV/ContentProviders: 45V/ContentProviders: 5, PythonV/ContentProviders: 78V/ContentProviders: 6, RubyV/ContentProviders: 36V/ContentProviders: 7, GradleV/ContentProviders: 258-V/ContentProviders: 8, JavaScriptV/ContentProviders: 1 4V/ContentProviders: 9, HaskellV/ContentProviders: 1 5V/ContentProviders: 10, C/C+V/ContentProviders: 35V/ContentProviders: 11, Html+CSSV/ContentProviders: 248-6
指定查询字段
CursorLoader类的第三个参数控制查询返回多少列。这个参数称为Projections(映射)。
String[] projection = new String[]{ ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME};CursorLoader cursorLoader = new CursorLoader( this, allContacts, projection, null, null, null);Cursor cursor = cursorLoader.loadInBackground();
筛选
CursorLoader类的第四和第五个参数指定一个SQL WHERE语句用来筛选查询的结果。例如,以下示例代码仅仅获取姓名以Lee
结尾的通讯录:
String[] projection = new String[]{ ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME};CursorLoader cursorLoader = new CursorLoader( this, allContacts, projection, ContactsContract.Contacts.DISPLAY_NAME + " LIKE ?", new String[]{"%Lee"}, null;Cursor cursor = cursorLoader.loadInBackground();
排序
CursorLoader类的最后一个参数指定SQL ORDER BY语句用来排序查询结果。例如,以下示例代码将通讯录姓名以升序排序:
String[] projection = new String[]{ ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME};CursorLoader cursorLoader = new CursorLoader( this, allContacts, projection, ContactsContract.Contacts.DISPLAY_NAME + " LIKE ?", new String[]{"%Lee"}, ContactsContract.Contacts.DISPLAY_NAME + " ASC");Cursor cursor = cursorLoader.loadInBackground();
自定义Content Provider
AndroidManifest.xml
先在AndroidManifest.xml
中添加如下语句:
<provider android:name=".BooksProvider" android:authorities="link_work.myapplication.provider.Books" android:exported="false" />
说明:android:authorities="<包名>.provider.Books"
BooksProvider
在自定义的content provider中,可以自由地选择如何存储数据——使用传统文件系统、XML文件、数据库,或者通过Web服务。在本示例中,使用的是SQLite数据库方案。
@SuppressLint("Registered")public class BooksProvider extends ContentProvider { /** * 常量 */ static final String PROVIDER_NAME = "link_work.myapplication.provider.Books"; static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/books"); static final String ID = "_id"; static final String TITLE = "title"; static final String ISBN = "isbn"; static final int BOOKS = 1; static final int BOOK_ID = 2; private static final UriMatcher URI_MATCHER; /* * * 使用URI_MATCHER对象解析内容URI,将内容URI通过ContentResolver传递给 * content provider。例如,以下内容URI表示请求content provider中的所 * 有图书。 * */ static { URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); URI_MATCHER.addURI(PROVIDER_NAME, "books", BOOKS); URI_MATCHER.addURI(PROVIDER_NAME, "books/#", BOOK_ID); } /** * ---数据库实例--- */ SQLiteDatabase booksDB; static final String DATABASE_NAME = "Books"; static final String DATABASE_TABLE = "titles"; static final int DATABASE_VERSION = 1; static final String DATABASE_CREATE = "create table " + DATABASE_TABLE + " (_id integer primary key autoincrement, " + "title text not null, isbn text not null);"; /** * 要删除一本书,需要重写delete方法。 * 同样,在删除成功后调用ContentResolver的notifyChange方法。 * 它会通知已注册的观察者有一条记录要删除。 * */ @Override public int delete(@NonNull Uri arg0, String arg1, String[] arg2) {// arg0 = uri// arg1 = selection// arg2 = selectionArgs int count; switch (URI_MATCHER.match(arg0)) { case BOOKS: count = booksDB.delete(DATABASE_TABLE, arg1, arg2); break; case BOOK_ID: String id = arg0.getPathSegments().get(1); count = booksDB.delete(DATABASE_TABLE, ID + " = " + id + (!TextUtils.isEmpty(arg1) ? " AND (" + arg1 + ')' : ""), arg2); break; default: throw new IllegalArgumentException("Unknown URI " + arg0); } getContext().getContentResolver().notifyChange(arg0, null); return count; } /** * 重写getType方法,为自定义Content Provider描述数据类型。使用UriMatcher对象解析 * URI,vnd.android.cursor.item/vnd.<包名>.books表示返回一条图书记录, * vnd.android.cursor.dir/vnd.<包名>.books表示返回多条图书记录。 * */ @Override public String getType(@NonNull Uri uri) { switch (URI_MATCHER.match(uri)) { //---获取所有的书籍--- case BOOKS: return "vnd.android.cursor.dir/vnd.link_work.myapplication.books "; //---获取指定的书籍--- case BOOK_ID: return "vnd.android.cursor.item/vnd.link_work.myapplication.books "; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } } /** * 默认情况下,查询的结果按照title字段排序。查询结果以Cursor对象返回。 * 为了能够向content Provider中插入新的图书记录,需要重写insert方法。 * 当数据插入成功后,调用ContentResolver的notifyChange方法。它会通 * 知已注册的观察者由一条记录更新。 * */ @Override public Uri insert(@NonNull Uri uri, ContentValues values) { //---添加一本书--- long rowID = booksDB.insert(DATABASE_TABLE, "", values); //---如果添加成功的话--- if (rowID > 0) { Uri tempUri = ContentUris.withAppendedId(CONTENT_URI, rowID); getContext().getContentResolver().notifyChange(tempUri, null); return tempUri; } //---添加不成功--- throw new SQLException("Failed to insert row into " + uri); } /** * 重写onCreate方法,当content Provider启动的时候,打开数据库连接。 * */ @Override public boolean onCreate() { Context context = getContext(); DatabaseHelper dbHelper = new DatabaseHelper(context); booksDB = dbHelper.getWritableDatabase(); return booksDB != null; } /** * 重写query方法,使得用户可以查询图书。 * */ @Override public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder(); sqlBuilder.setTables(DATABASE_TABLE); //---如果要获取制定的图书信息-- if (URI_MATCHER.match(uri) == BOOK_ID) { sqlBuilder.appendWhere(ID + " = " + uri.getPathSegments().get(1)); } if (sortOrder == null || Objects.equals(sortOrder, "")) { sortOrder = TITLE; } Cursor c = sqlBuilder.query( booksDB, projection, selection, selectionArgs, null, null, sortOrder); //---注册一个观察者来监视Uri的变化--- c.setNotificationUri(getContext().getContentResolver(), uri); return c; } /** * 要更新一本书,需要重写update方法。 * 如同insert方法和delete方法,更新成功后你需要调用ContentResolver * 的notifyChange方法。它会通知已注册的观察者有一条记录被更新。 * */ @Override public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count; switch (URI_MATCHER.match(uri)) { case BOOKS: count = booksDB.update( DATABASE_TABLE, values, selection, selectionArgs); break; case BOOK_ID: count = booksDB.update( DATABASE_TABLE, values, ID + " = " + uri.getPathSegments().get(1) + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } /** * 本示例中Content Provider使用SQLite数据库存储图书数据。 * */ private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(DATABASE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w("Provider database", "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS titles"); onCreate(db); } }}
使用自定义的Content Provider
Main3Activity
public class Main3Activity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main3); } public void onClickAddTitle(View view) { //---添加一本书--- ContentValues values = new ContentValues(); values.put(BooksProvider.TITLE, ((EditText) findViewById(R.id.txtTitle)).getText().toString()); values.put(BooksProvider.ISBN, ((EditText) findViewById(R.id.txtISBN)).getText().toString()); Uri uri = getContentResolver().insert(BooksProvider.CONTENT_URI, values); assert uri != null; Toast.makeText(getBaseContext(), uri.toString(), Toast.LENGTH_LONG).show(); } public void onClickRetrieveTitles(View view) { //---检索书名--- Uri allTitles = Uri.parse("content://link_work.myapplication.provider.Books/books"); CursorLoader cursorLoader = new CursorLoader( this, allTitles, null, null, null, // 默认以title列从大到小排序 "title desc" ); Cursor c = cursorLoader.loadInBackground(); if (c.moveToFirst()) { do { String string = c.getString(c.getColumnIndex(BooksProvider.ID)) + ", " + c.getString(c.getColumnIndex(BooksProvider.TITLE)) + ", " + c.getString(c.getColumnIndex(BooksProvider.ISBN)); Toast.makeText(this, string, Toast.LENGTH_SHORT).show(); } while (c.moveToNext()); } }}
activity_main3.xml
<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".Main3Activity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="ISBN" app:layout_constraintLeft_toLeftOf="@+id/activity_main" app:layout_constraintRight_toRightOf="@+id/activity_main" app:layout_constraintTop_toTopOf="@+id/activity_main" tools:layout_constraintLeft_creator="1" tools:layout_constraintRight_creator="1" /> <EditText android:id="@+id/txtISBN" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginTop="8dp" android:ems="10" android:inputType="text" app:layout_constraintBottom_toTopOf="@+id/textView2" app:layout_constraintHorizontal_bias="0.46" app:layout_constraintLeft_toLeftOf="@+id/activity_main" app:layout_constraintRight_toRightOf="@+id/activity_main" app:layout_constraintTop_toBottomOf="@+id/textView" app:layout_constraintVertical_bias="0.100000024" tools:layout_constraintLeft_creator="1" tools:layout_constraintRight_creator="1" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="142dp" android:text="Title" app:layout_constraintLeft_toLeftOf="@+id/activity_main" app:layout_constraintRight_toRightOf="@+id/activity_main" app:layout_constraintTop_toTopOf="@+id/activity_main" tools:layout_constraintLeft_creator="1" tools:layout_constraintRight_creator="1" tools:layout_constraintTop_creator="1" /> <EditText android:id="@+id/txtTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:ems="10" android:inputType="text" app:layout_constraintLeft_toLeftOf="@+id/activity_main" app:layout_constraintRight_toRightOf="@+id/activity_main" app:layout_constraintTop_toBottomOf="@+id/textView2" tools:layout_constraintLeft_creator="1" tools:layout_constraintRight_creator="1" /> <Button android:id="@+id/btnAdd" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="112dp" android:onClick="onClickAddTitle" android:text="添加标题" app:layout_constraintLeft_toLeftOf="@+id/activity_main" app:layout_constraintRight_toRightOf="@+id/activity_main" app:layout_constraintTop_toBottomOf="@+id/txtTitle" tools:layout_constraintLeft_creator="1" tools:layout_constraintRight_creator="1" /> <Button android:id="@+id/btnRetrieve" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:onClick="onClickRetrieveTitles" android:text="检索标题" app:layout_constraintLeft_toLeftOf="@+id/activity_main" app:layout_constraintRight_toRightOf="@+id/activity_main" app:layout_constraintTop_toBottomOf="@+id/btnAdd" tools:layout_constraintLeft_creator="1" tools:layout_constraintRight_creator="1" /></android.support.constraint.ConstraintLayout>
附录
- 《Beginning Android Programming with Android Studio, 4th Edition》
- Content Provider与SQLite结合使用
- Android content provider基础与使用
- Android content provider基础与使用
- Content Provider的创建与使用
- Content Provider 使用教程
- content provider 的使用
- Content Provider 使用入门
- android content provider 使用
- Content Provider使用
- 使用Content Provider
- content provider的使用!
- Content Provider 使用
- Android Content Provider使用
- Android Content Provider使用
- SQLite与AutoCompleteTextView结合使用
- SQLite与AutoCompleteTextView结合使用
- Android学习笔记-SQLite和Content Provider
- 总结Content Provider的使用
- 图像就是矩阵
- 数据结构实验之查找一:二叉排序树
- 【quickhybrid】如何实现一个Hybrid框架
- 搭建IDEA License Serve本地系统服务
- Foundation5(二十四)
- Content Provider与SQLite结合使用
- Foundation5(二十五)
- python3利用ctypes传入一个字符串类型的列表
- 常犯错误及各种小细节[不断更新]
- QEMU模拟vexpress-a9 搭建Linux kernel运行环境
- MyEclipse出现Failed to create the part's controls
- Android app漏洞挖掘初探(1)-android权限机制和默认场景配置
- sot23-6 随机数生成芯片,i2c接口
- 数据结构实验之排序三:bucket sort 桶排序