加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

Content Provider与SQLite结合使用

发布时间:2020-12-12 19:17:02 所属栏目:百科 来源:网络整理
导读:前言 虽然推荐使用数据库保存结构化的复杂数据,但是数据共享是一个挑战,因为数据库只允许创建它的应用访问。 在Android中共享数据 在Android中,推荐使用 content provider 方法在不同包之间共享数据。 content provider 可以被视为一个数据仓库。它如何存
Browser 存储浏览器数据,比如浏览器的书签、浏览器访问历史等。 CallLog 存储通话数据,比如未接电话、通话详情等。 Contacts 存储通讯录详情。 MediaStore 存储多媒体文件,比如音频、视频和图片。 Settings 存储设备的设置和偏好设置。

  除了许多内置的content provider以外,可以创建自定义的content provider。
  要查询一个content provider,需要以统一的资源标识符(Uniform Resource Identifier,URI)的形式制定查询字符串,以及一个可选的特定行说明符。以下是查询URI的形式:

<standard_prefix>://<authority>/<data_path>/<id>

  URI的各种组成部分如下:

content provider content provider的标准前缀是content://authority 指定content provider的名称。例如内置Contacts的content provider的名称为contacts。对于第三方content provider来说,它可以是完整的合法名称,例如com.wrox.provider。 data_path 指定请求的数据种类。例如,如果你要从Contacts的content provider中获取所有的通讯录,那么data_path应该是people,URI将类似与:content://contacts/peopleid 指定请求特定的记录。例如,如果你要获取Contacts的content provider中第2条通讯录,URI将类似类似于:content://contact/people/2。 content://media/internal/images 返回设备上保存在内部存储中的图片列表。 content://media/external/images 返回设备上保存在外部存储(例如SD卡)中的图片列表。 content://call_log/calls 返回登记在通话记录中的通话记录。 content://browser/bookmarks 返回存储在浏览器中的书签列表。

使用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);
        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,REQUEST_READ_CONTACTS);
} else {
    listContacts();
}

  如果没有访问权限,就会发出一条权限请求(使Android弹出一个权限请求对话框)。如果应用由相应的访问权限,ListContacts()方法会被调用。
  getContentResolver()方法返回一个ContentResolver对象,它会使用适当content provider解析内容URI。
  CursorLoader类(Android API level 11及以上版本可用)在后再线程中执行cursor查询操作,因此不会阻塞应用UI。

CursorLoader cursorLoader = new CursorLoader(
    this,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,CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
this.setListAdapter(adapter);

  类似于managedQuery()方法,SimpleCursorAdapter类的一个构造函数已经被弃用。对于运行Honeycomb及后续版本的设备,需要使用新的SimpleCursorAdapter类的构造函数,与旧版本相比,该构造函数多一个参数:

SimpleCursorAdapter adapter = new SimpleCursorAdapter(
    this,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)
context Context uri Uri Uri: The URI,using the content:// scheme,for the content to retrieve. This value must never be null. projection String String: A list of which columns to return. Passing null will return all columns,which is inefficient. selection String String: A filter declaring which rows to return,formatted as an SQL WHERE clause (excluding the WHERE itself). Passing null will return all rows for the given URI. selectionArgs String String: You may include ?s in selection,which will be replaced by the values from selectionArgs,in the order that they appear in the selection. The values will be bound as Strings.This value may be null. sortOrder String String: How to order the rows,formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null will use the default sort order,which may be unordered.

预定义查询字符串常量

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,ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " +
                contactID,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 okay
V/ContentProviders: 100
V/ContentProviders: 2,Java
V/ContentProviders: 1 234-567-9
V/ContentProviders: 3,Kotlin
V/ContentProviders: 1 23
V/ContentProviders: 4,Scala
V/ContentProviders: 45
V/ContentProviders: 5,Python
V/ContentProviders: 78
V/ContentProviders: 6,Ruby
V/ContentProviders: 36
V/ContentProviders: 7,Gradle
V/ContentProviders: 258-
V/ContentProviders: 8,JavaScript
V/ContentProviders: 1 4
V/ContentProviders: 9,Haskell
V/ContentProviders: 1 5
V/ContentProviders: 10,C/C+
V/ContentProviders: 35
V/ContentProviders: 11,Html+CSS
V/ContentProviders: 248-6

指定查询字段

  CursorLoader类的第三个参数控制查询返回多少列。这个参数称为Projections(映射)。

String[] projection = new String[]{
    ContactsContract.Contacts._ID,ContactsContract.Contacts.DISPLAY_NAME
};
CursorLoader cursorLoader = new CursorLoader(
    this,projection,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,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 + " 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

getType() 返回给定URI的数据的MIME类型。 onCreate() 当provider启动时调用。 query() 收到一条客户的请求。结果将以Cursor对象返回。 insert() 向content provider插入一条新的记录。 delete() 从content provider中删除一条已存在的记录。 update() 从content provider中更新一条已存在的记录。

  在自定义的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,selection,selectionArgs,sortOrder);
        //---注册一个观察者来监视Uri的变化---
        c.setNotificationUri(getContext().getContentResolver(),uri);
        return c;
    }

    /** * 要更新一本书,需要重写update方法。 * 如同insert方法和delete方法,更新成功后你需要调用ContentResolver * 的notifyChange方法。它会通知已注册的观察者有一条记录被更新。 * */
    @Override
    public int update(@NonNull Uri uri,ContentValues values,String[] selectionArgs) {
        int count;
        switch (URI_MATCHER.match(uri)) {
            case BOOKS:
                count = booksDB.update(
                        DATABASE_TABLE,values,selectionArgs);
                break;
            case BOOK_ID:
                count = booksDB.update(
                        DATABASE_TABLE,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,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,// 默认以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》

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

前言

  虽然推荐使用数据库保存结构化的复杂数据,但是数据共享是一个挑战,因为数据库只允许创建它的应用访问。

在Android中共享数据

  在Android中,推荐使用content provider方法在不同包之间共享数据。content provider可以被视为一个数据仓库。它如何存储数据与应用如何使用它无关。然而,应用如何使用一致的编程接口在它的里面的数据非常重要。content provider的行为与数据库非常相似——你可以查询它、编辑它的内容、添加或者删除内容。然而,与数据库不同的是,一个content provider可以使用不同的方式存储它的数据。数据可以被存储在数据库中、在文件中、甚至在网络上。
  Android提供了许多非常有用的content provider,包括以下几种:

类型 说明
组成 说明
查询字符串 描述
参数 类型 说明
方法 说明
    推荐文章
      热点阅读