《Android开发进阶:从小工到专家》——第1章,第1.4节ContentProvider(外共享数据)-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

《Android开发进阶:从小工到专家》——第1章,第1.4节ContentProvider(外共享数据)

简介:

本节书摘来自异步社区《Android开发进阶:从小工到专家》一书中的第1章,第1.4节ContentProvider(外共享数据),作者 何红辉,更多章节内容可以访问云栖社区“异步社区”公众号查看

1.4 ContentProvider(外共享数据)
ContentProvider在android中的作用是对外共享数据,也就是说可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider对应用中的数据进行添、删、改、查。使用ContentProvider对外共享数据的好处是,统一了数据的访问方式,它实际上是对SQliteOpenHelper的进一步封装,通过Uri映射来判断选择需要操作数据库中的哪个表,并且进行增、删、改、查处理。

首先我们先来学习Uri,Uri代表了要操作的数据表的绝对路径,Uri主要包含了两部分信息,一是需要操作的ContentProvider,二是对ContentProvider中的哪个表进行操作。对于ContentProvider来说,一个Uri由以下几部分组成,如图1-18所示。


964cd1d34ef068575c19a2caf776b030b5796d4d

ContentProvider的scheme已经由Android固定设置为content://,Authority用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。这里的path就是要操作的数据库表,最后的id关键字是可选字段,例如,我们要操作特定的数据项时就会指定一个查询条件,如所有联系人的Uri:content://contacts/people,某个联系人的Uri: content://contacts/people/5,这个5就是联系人的id,也就对应了这里的查询关键字。

如果要把一个字符串转换成Uri,可以使用Uri类中的parse()函数,如下:

Uri uri = Uri.parse("content://contacts /people");
Android系统根据Uri来定位注册到系统的ContentProvider中,找到ContentProvider之后会通过ContentResolver来操作对应的数据库。实现ContentProvider的第一步就是需要覆写ContentProvider的insert、query、upate、delete、getType函数。下面我们要创建一个ContentProvider,该ContentProvider存储了一些服务行业人员的电话信息,如一些系统中就存储了快递人员、肯德基订餐电话等信息。我们要做的就是实现类似的功能,首先定义一个ContentProvider,代码如下:

public class UserInfoProvider extends ContentProvider {
    private static final String CONTENT = "content://";
    public static final String AUTHORIY = "com.book.jtm.info";
    /**
     * 该ContentProvider所返回的数据类型定义、数据集合
     */
    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd." + AUTHORIY;
    /**
     * 单项数据
     */
    public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd." + AUTHORIY;
    /**
     * 数据集合操作时的Uri
     */
    public static final Uri POSTCODE_URI = Uri.parse(CONTENT + AUTHORIY + "/" +   
    UserInfoDbHelper.TABLE_USER_INFO);
    /**
     * 数据集合操作时的Uri
     */
    public static final Uri COMPANY_URI = Uri.parse(CONTENT + AUTHORIY + "/" +   
    UserInfoDbHelper.TABLE_COMPANY);

    private SQLiteDatabase mDatabase;

    static final int USER_INFOS = 1;
    static final int USER_INFO_ITEM = 2;
    static final int COMPANY = 3;
    static final int COMPANY_ITEM = 4;

    static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        uriMatcher.addURI(AUTHORIY, "userinfo", USER_INFOS);
        uriMatcher.addURI(AUTHORIY, "userinfo/*", USER_INFO_ITEM);
        uriMatcher.addURI(AUTHORIY, "company", COMPANY);
        uriMatcher.addURI(AUTHORIY, "company/#",COMPANY_ITEM);
    }

    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case USER_INFOS:
            case COMPANY:
                return CONTENT_TYPE;
            case USER_INFO_ITEM:
            case  COMPANY_ITEM:
                return CONTENT_TYPE_ITEM;
            default:
                throw new RuntimeException("错误的Uri");
        }
    }

    // 删除、更新的代码省略
}

我们需要在ContentProvider中根据Uri建立关系映射,通过UriMatcher管理不同Uri对应的Type类型,这个类型会在getType中被返回。当在ContentProvider中进行增、删、改、查操作时,就会根据这个类型选择对应的数据表。在这个例子中,我们通过UriMatcher映射了4种Uri类型, Uri 的格式主要有两种,以表名结尾就表示期望访问该表中所有的数据,以id结尾就表示期望访问该表中拥有相应 id 的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容Uri,“*”表示匹配任意长度的任意字符,“#”表示匹配任意长度的数字。因此,content://com.book.jtm.info /userinfo表示要查询userinfo表中的所有数据,而content://com.book.jtm.info /userinfo/#表示要根据一个数字id查询一个用户。

下面我们就完成数据库操作的相关代码:

    @Override
    public boolean onCreate() {
        mDatabase = new UserInfoDbHelper(getContext()).getWritableDatabase();
        return true;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        long newId = 0 ;
        Uri newUri = null;
        switch (uriMatcher.match(uri)) {
            case USER_INFOS:
                newId =  mDatabase.insert(UserInfoDbHelper.TABLE_USER_INFO, null, values);
                newUri = Uri.parse(CONTENT + AUTHORIY + "/"
                        + UserInfoDbHelper.TABLE_USER_INFO + "/" + newId);
                break;
            case COMPANY:
               newId = mDatabase.insert(UserInfoDbHelper.TABLE_COMPANY, null, values);
                newUri = Uri.parse(CONTENT + AUTHORIY + "/"
                        + UserInfoDbHelper.TABLE_COMPANY + "/" + newId);
                break;
        }
        if (newId > 0) {
            return newUri;
        }
        throw new IllegalArgumentException("Failed to insert row into" + uri);
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[]   
    selectionArgs, String sortOrder) {

        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
            case USER_INFOS:
                cursor = mDatabase.query(UserInfoDbHelper.TABLE_USER_INFO, projection,
                        selection, selectionArgs, null, null, sortOrder);
                break;

            case USER_INFO_ITEM:
                String tel = uri.getPathSegments().get(1);
                cursor = mDatabase.query(UserInfoDbHelper.TABLE_USER_INFO, projection,
                        "tel_num = ?", new String[]{tel}, null, null, sortOrder);
                break;
            case COMPANY:
                cursor = mDatabase.query(UserInfoDbHelper.TABLE_COMPANY, projection,
                        selection, selectionArgs, null, null, sortOrder);
                break;
            case COMPANY_ITEM:
                String cid = uri.getPathSegments().get(1);
                cursor = mDatabase.query(UserInfoDbHelper.TABLE_COMPANY, projection,
                        "id = ?", new String[]{cid}, null, null, sortOrder);
                break;
        }
        return cursor;
    }

// 删除、更新的代码省略
上述代码中,我们把传递进来的Uri通过UriMatcher进行解析,得到type之后,再根据type来判断要操作哪个表,根据它的数据类型是所有数据还是单个数据,然后执行对应的数据库操作。对数据库的操作通过UserInfoDbHelper类实现。代码如下:

public class UserInfoDbHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "userinfo.db";
    private static final int DB_VERSION = 1;

    public static final String TABLE_USER_INFO = "userinfo";
    public static final String TABLE_COMPANY = "company";

    public static final String TEL_COLUMN = "tel_num";
    public static final String DESC_COLUMN = "desc";
    public static final String COMP_ID_COLUMN = "comp_id";
    public static final String ID_COLUMN = "id";
    public static final String BUSINESS_COLUMN = "business";
    public static final String ADDR_COLUMN = "addr";

    private static final String POSTCODE_TABLE_SQL = "CREATE TABLE " + TABLE_USER_INFO + "   ("
            + TEL_COLUMN + " TEXT ,"
            + COMP_ID_COLUMN + " TEXT  , "
            + DESC_COLUMN + " TEXT "
            + ")";

    private static final String COMPANY_TABLE_SQL = "CREATE TABLE " + TABLE_COMPANY + " ("
            + ID_COLUMN + " TEXT PRIMARY KEY , "
            + BUSINESS_COLUMN + " TEXT , "
            + ADDR_COLUMN + " TEXT"
            +  ")";

    public UserInfoDbHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(POSTCODE_TABLE_SQL);
        db.execSQL(COMPANY_TABLE_SQL);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

在UserInfoDbHelper中我们建立了两个表,分别为userinfo和company,表结构表1-2和表1-3所示。


ba7e98195f546d1fef58489d53795799d764648b

完成ContentProvider代码之后,第二步需要在AndroidManifest.xml中使用对该ContentProvider进行配置,为了能让其他应用找到该ContentProvider ,ContentProvider采用了authorities对它进行唯一标识,示例如下:
<providerandroid:name=".provider.UserInfoProvider"
      android:authorities="com.book.jtm.info" />

此时,我们就可以使用该ContentProvider了。

我们新建一个ProviderActivity,在该Activity中存储、查询用户信息,代码如下所示:

public class ProviderActivity extends Activity {

    EditText mUserDescEdittext;     // 用户描述信息
    EditText mUserTelEdittext;      // 电话号码
    EditText mUserCompIdEdittext ;  // 用户所属的公司id
    Button mSubmitBtn;              // 提交按钮

    EditText mCompIdEdittext;       // 公司id
    EditText mCompBusinessEdittext; // 公司业务
    EditText mCompAddrEdittext;     // 公司地址
    Button mCompSubmitBtn;          // 提交按钮

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);
        initWidgets();
    }

    private void initWidgets() {
        // 用户信息相关的View
        mUserDescEdittext = (EditText) findViewById(R.id.desc_edit);
        mUserTelEdittext = (EditText) findViewById(R.id.tel_edit);
        mUserCompIdEdittext = (EditText) findViewById(R.id.comp_edit);

        mSubmitBtn = (Button) findViewById(R.id.submit_btn);
        mSubmitBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                saveUserInfoRecord();
                mSubmitBtn.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        queryPostCode();
                    }
                }, 1000) ;
            }
        });

        // 公司信息相关的View
        mCompAddrEdittext = (EditText) findViewById(R.id.comp_addr_edit);
        mCompIdEdittext = (EditText) findViewById(R.id.comp_id_edit);
        mCompBusinessEdittext = (EditText) findViewById(R.id.comp_business_edit);

        mCompSubmitBtn = (Button) findViewById(R.id.submit_comp_btn);
        mCompSubmitBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                saveCompanyRecord();
            }
        });
    }

    /**
     * 存储用户信息到ContentProvider
     */
    private void saveUserInfoRecord() {
        ContentValues newRecord = new ContentValues();
        newRecord.put(UserInfoDbHelper.DESC_COLUMN, 
            mUserDescEdittext.getText().toString());
        newRecord.put(UserInfoDbHelper.TEL_COLUMN, 
            mUserTelEdittext.getText().toString());
        newRecord.put(UserInfoDbHelper.COMP_ID_COLUMN, 
            mCompIdEdittext.getText().toString());
        getContentResolver().insert(UserInfoProvider.USERINFO_URI, newRecord);
    }

    /**
     * 存储公司信息到ContentProvider中
     */
    private void saveCompanyRecord() {
        ContentValues newRecord = new ContentValues();
        newRecord.put(UserInfoDbHelper.ADDR_COLUMN, 
            mCompAddrEdittext.getText().toString());
        newRecord.put(UserInfoDbHelper.BUSINESS_COLUMN, 
            mCompBusinessEdittext.getText().toString());
        newRecord.put(UserInfoDbHelper.ID_COLUMN, mCompIdEdittext.getText().toString());
        getContentResolver().insert(UserInfoProvider.COMPANY_URI, newRecord);
    }

    /**
     * 通过电话号码查询相关信息
     */
    private void queryPostCode() {
        Uri queryUri = Uri.parse("content://com.book.jtm.info/userinfo/123456");
        Cursor cursor = getContentResolver().query(queryUri, null, null, null, null);
        if (cursor.moveToFirst()) {
            Toast.makeText(this, "电话来自 : " 
            + cursor.getString(2), Toast.LENGTH_SHORT).show();
        }
    }
}

上述Activity中我们可以将用户信息和公司信息根据不同的Uri插入到同一个ContentProvider中,UserInfoProvider会根据Uri选择对应的表进行插入,查询也是同样的道理。首先我们插入一条用户数据,该用户的电话为123456,描述为顺风快递员吴老二,公司id为11,插入之后我们可通过电话号码123456到ContentProvider中查询该用户的信息,此时就会得到该用户的描述信息,如图1-19所示。


01c12cce4e8a8564038f893e80af99154bbd8777

然后我们再插入该公司的信息,此时该ConentProvider的数据库中就有了数据,我们将数据库导出到PC中,结果如图1-20和图1-21所示。


cd7831311b566a50af9101c64803510382f1449e

正如本章前文所说,ContentProvider不过是对SQLiteOpenHelper的二次封装,通过UriMatcher将数据库中的表与Uri建立关联,当用户通过Uri操作ContentProvider时,ContentProvider会根据Uri选择对应的数据库表进行增、删、改、查操作。通过ContentProvider机制,使得数据可以在各应用之间共享,并且为用户提供了统一的API接口,降低了用户的使用成本。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章
最新文章
相关文章