1. 程式人生 > >[Android]ContentProvider內容提供器

[Android]ContentProvider內容提供器

本文原始碼:https://github.com/gitEkko/MyApplication.git

一、內容提供者是什麼

內容提供者(Content Provider)主要用於在不同的應用程式之間實現資料共享的功能,它提供了一套完整的機制,允許一個程式訪問另一個程式中的資料,同時還能保證被訪資料的安全性。目前,使用內容提供者是Android實現跨程式共享資料的標準方式。

 

原理: ContentProvider的底層是採用 Android中的Binder機制

二、ContentProvider的使用

 1.統一資源識別符號-URI

 定義:Uniform Resource Identifier,即統一資源識別符號

 作用:唯一標識ContentProvider以及其中的資料

  • 外界程序通過URI找到對應的ContentProvider,再進行資料操作。

 URI分為:

  1.  系統預置:系統內建的資料,如通訊錄,簡訊,日程表等資料。
  2.  自定義:即自定義的資料庫,提供給外界程序對自定義資料庫進行操作。 

  需要將URI字串解析成URI物件才可以使用。

Uri uri = Uri.parse("content://com.example.myinterview.myprovider/person");

 2.MIME資料型別

 作用:指定某個副檔名的檔案用某種應用程式來開啟。 

 ContentProvider根據URI來返回MIME資料型別:ContentProvider.geType(uri) ;

 有兩種形式:

  •  單條記錄:"vnd.android.cursor.item/vnd.com.example.myinterview.myprovider.person";
  •  多條記錄:"vnd.android.cursor.dir/vnd.com.example.myinterview.myprovider.person";

三、ContentProvider類 

<-- 4個核心方法 -->
  public Uri insert(Uri uri, ContentValues values) 
  // 外部程序向 ContentProvider 中新增資料

  public int delete(Uri uri, String selection, String[] selectionArgs) 
  // 外部程序 刪除 ContentProvider 中的資料

  public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
  // 外部程序更新 ContentProvider 中的資料

  public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,  String sortOrder)  
  //  外部應用 獲取 ContentProvider 中的資料

// 注:
// 1. 上述4個方法由外部程序回撥,並執行在ContentProvider程序的Binder執行緒池中(不是主執行緒)
// 2. 存在多執行緒併發訪問,需要實現執行緒同步
// a. 若ContentProvider的資料儲存方式是使用SQLite & 一個,則不需要,因為SQLite內部實現好了執行緒同步,若是多個SQLite則需要,因為SQL物件之間無法進行執行緒同步
// b. 若ContentProvider的資料儲存方式是記憶體,則需要自己實現執行緒同步

<-- 2個其他方法 -->
public boolean onCreate() 
// ContentProvider建立後 或 開啟系統後其它程序第一次訪問該ContentProvider時 由系統進行呼叫
// 注:執行在ContentProvider程序的主執行緒,故不能做耗時操作

public String getType(Uri uri)
// 得到資料型別,即返回當前 Url 所代表資料的MIME型別

在ContentProvider類中要對資料庫進行操作,所以要結合SQLiteOpenHelper。

ContentProvider類並不會直接與外部程序進行互動,而是通過ContentResolver類。

四、ContentResolver類

 通過URI來操作不同的ContentProvider。

  •  一般來說,一款應用要使用多個ContentProvider,若需要了解每個ContentProvider的不同實現從而再完成資料互動,操作成本高 & 難度大
  • 所以再ContentProvider類上加多了一個 ContentResolver類對所有的ContentProvider進行統一管理。

 ContentResolver 類提供了與ContentProvider類相同名字 & 作用的4個方法

// 向 ContentProvider 中新增資料
public Uri insert(Uri uri, ContentValues values)  

// 刪除 ContentProvider 中的資料
public int delete(Uri uri, String selection, String[] selectionArgs)

// 更新 ContentProvider 中的資料
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)  

// 獲取 ContentProvider 中的資料
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

 使用:

 Uri uri = Uri.parse("content://" + AUTOHORITY + "/person");
 ContentResolver resolver = getContentResolver();
 Cursor cursor = resolver.query(uri, null, null, null, null);

五、UriMatcher類

 作用:

  1. 在ContentProvider 中註冊URI
  2. 根據 URI 匹配 ContentProvider 中對應的資料表 

 具體使用:

// 步驟1:初始化UriMatcher物件
    UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); 
    //常量UriMatcher.NO_MATCH  = 不匹配任何路徑的返回碼
    // 即初始化時不匹配任何東西

// 步驟2:在ContentProvider 中註冊URI(addURI())
    int URI_CODE_a = 1;
    int URI_CODE_b = 2;
    matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a); 
    matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b); 
    // 若URI資源路徑 = content://cn.scu.myprovider/user1 ,則返回註冊碼URI_CODE_a
    // 若URI資源路徑 = content://cn.scu.myprovider/user2 ,則返回註冊碼URI_CODE_b

// 步驟3:根據URI 匹配 URI_CODE,從而匹配ContentProvider中相應的資源(match())

@Override   
    public String getType(Uri uri) {   
      Uri uri = Uri.parse(" content://cn.scu.myprovider/user1");   

      switch(matcher.match(uri)){   
     // 根據URI匹配的返回碼是URI_CODE_a
     // 即matcher.match(uri) == URI_CODE_a
      case URI_CODE_a:   
        return tableNameUser1;   
        // 如果根據URI匹配的返回碼是URI_CODE_a,則返回ContentProvider中的名為tableNameUser1的表
      case URI_CODE_b:   
        return tableNameUser2;
        // 如果根據URI匹配的返回碼是URI_CODE_b,則返回ContentProvider中的名為tableNameUser2的表
    }   
}

六、ContentObserver類

定義:內容觀察者

作用:觀察 Uri 引起 ContentProvider 中的資料變化 & 通知外界(即訪問該資料訪問者)

  • ContentProvider 中的資料發生變化(增、刪 & 改)時,就會觸發該 ContentObserver

具體使用:

// 步驟1:註冊內容觀察者ContentObserver
    getContentResolver().registerContentObserver(uri);
    // 通過ContentResolver類進行註冊,並指定需要觀察的URI

// 步驟2:當該URI的ContentProvider資料發生變化時,通知外界(即訪問該ContentProvider資料的訪問者)
    public class UserContentProvider extends ContentProvider { 
      public Uri insert(Uri uri, ContentValues values) { 
      db.insert("user", "userid", values); 
      getContext().getContentResolver().notifyChange(uri, null); 
      // 通知訪問者
   } 
}

// 步驟3:解除觀察者
 getContentResolver().unregisterContentObserver(uri);
    // 同樣需要通過ContentResolver類進行解除

 


七、Demo例項

   通過訪問自定義的ContentProvider 來操作資料庫進行增刪改查。並且利用ContentObserver監聽資料庫如果發生變化,就進行     更新UI操作。

 1.建立資料庫類:

   SQLiteHelper.java:複用上篇程式碼:https://blog.csdn.net/Gods_magic/article/details/84706386

 2.自定義ContentProvider類

/**
 * 內容提供者
 * ContentProvider是不同應用程式之間進行資料交換的標準API,
 * ContentProvide以Uri的形式對外提供資料,允許其他應用訪問和修改資料;
 * 其他應用使用ContentResolve根據Uri進行訪問操作指定的資料。
 */
public class MyContentProvider extends ContentProvider {

    public static final String AUTOHORITY = "com.example.myinterview.myprovider";

    public static final int Person_DIR = 0;
    public static final int Person_ITEM = 1;
    public static final int User_DIR = 2;
    public static final int User_ITEM = 3;

    private Context mContext;
    private SQLiteHelper dbHelper = null;


    private static final UriMatcher mMatcher;

    static {
        mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        mMatcher.addURI(AUTOHORITY, "person", Person_DIR);
        mMatcher.addURI(AUTOHORITY, "person/#", Person_ITEM);
        mMatcher.addURI(AUTOHORITY, "user", User_DIR);
        mMatcher.addURI(AUTOHORITY, "user#", User_ITEM);
    }

    public MyContentProvider() {
    }

    @Override
    public boolean onCreate() {
        mContext = getContext();
        dbHelper = new SQLiteHelper(mContext, 1);
        return true;
    }


    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        //根據URI匹配 URI_CODE,從而匹配ContentProvider中相應的表名
        String table = getTableName(uri);
        //向該表新增資料
        long newId = db.insert(table, null, values);
        Uri uriReturn = Uri.parse("content://" + AUTOHORITY + "/" + table + "/" + newId);

        //當該URI的ContentProvider資料發生變化時,通知外界,即訪問該ContentProvider資料的訪問者
        mContext.getContentResolver().notifyChange(uri, null);

        //返回一個用於表示該條新紀錄的URI
        return uriReturn;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deleteRows = 0;
        switch (mMatcher.match(uri)) {
            case Person_DIR:
                deleteRows = db.delete("person", selection, selectionArgs);
                break;
            case Person_ITEM:
                String personId = uri.getPathSegments().get(1);
                deleteRows = db.delete("person", "id = ?", new String[]{personId});
                break;
            case User_DIR:
                deleteRows = db.delete("user", selection, selectionArgs);
                break;
            case User_ITEM:
                String userId = uri.getPathSegments().get(1);
                deleteRows = db.delete("user", "id = ?", new String[]{userId});
                break;
        }

        //當該URI的ContentProvider資料發生變化時,通知外界,即訪問該ContentProvider資料的訪問者
        mContext.getContentResolver().notifyChange(uri, null);

        //返回被刪除的行數
        return deleteRows;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deleteRows = 0;
        switch (mMatcher.match(uri)) {
            case Person_DIR:
                deleteRows = db.update("person", values, selection, selectionArgs);
                break;
            case Person_ITEM:
                String personId = uri.getPathSegments().get(1);
                deleteRows = db.update("person", values, "id = ?", new String[]{personId});
                break;
            case User_DIR:
                deleteRows = db.update("user", values, selection, selectionArgs);
                break;
            case User_ITEM:
                String userId = uri.getPathSegments().get(1);
                deleteRows = db.update("user", values, "id = ?", new String[]{userId});
                break;
        }
        
        //當該URI的ContentProvider資料發生變化時,通知外界,即訪問該ContentProvider資料的訪問者
        mContext.getContentResolver().notifyChange(uri, null);

        return deleteRows;
    }

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

        SQLiteDatabase db = dbHelper.getReadableDatabase();

        Cursor cursor = null;
        switch (mMatcher.match(uri)) {
            case Person_DIR:
                cursor = db.query("person", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case Person_ITEM:
                String personId = uri.getPathSegments().get(1);
                cursor = db.query("person", projection, "id = ?", new String[]{personId}, null, null, sortOrder);
                break;
            case User_DIR:
                cursor = db.query("user", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case User_ITEM:
                String userId = uri.getPathSegments().get(1);
                cursor = db.query("user", projection, "id = ?", new String[]{userId}, null, null, sortOrder);
                break;
        }
        return cursor;
    }

    @Override
    public String getType(Uri uri) {
        String mMIME = null;
        switch (mMatcher.match(uri)) {
            case Person_DIR:
                mMIME = "vnd.android.cursor.dir/vnd.com.example.myinterview.myprovider.person";
                break;
            case Person_ITEM:
                mMIME = "vnd.android.cursor.item/vnd.com.example.myinterview.myprovider.person";
                break;
            case User_DIR:
                mMIME = "vnd.android.cursor.dir/vnd.com.example.myinterview.myprovider.user";
                break;
            case User_ITEM:
                mMIME = "vnd.android.cursor.item/vnd.com.example.myinterview.myprovider.user";
                break;
        }
        return mMIME;
    }

    private String getTableName(Uri uri) {
        String tableName = null;
        switch (mMatcher.match(uri)) {
            case Person_DIR:
            case Person_ITEM:
                tableName = SQLiteHelper.PERSON_TABLE_NAME;
                break;
            case User_DIR:
            case User_ITEM:
                tableName = SQLiteHelper.USER_TABLE_NAME;
                break;
        }
        return tableName;
    }
}

  在AndroidManifest.xml中進行註冊

<provider
    android:name=".myprovider.MyContentProvider"
    android:authorities="com.example.myinterview.myprovider"
    android:enabled="true"
    android:exported="true" />

  如果想要外部程序訪問到此ContentProvider,必須設定 exported為 true

 3.自定義ContentObserver類

/**
 * 內容觀察者   觀察所監聽Uri 引起ContentProvider中的資料變化
 */
public class MyContentObsever extends ContentObserver {

    private Context context;
    private Handler handler;


    public MyContentObsever(Context context, Handler handler) {
        super(handler);
        this.context = context;
        this.handler = handler;
    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);

        Toast.makeText(context, "database has changed!", Toast.LENGTH_LONG).show();
		//傳送handler訊息,監聽到資料庫變化後,更新UI
        Message msg = new Message();
        msg.obj = "database has changed";
        handler.sendMessage(msg);
    }

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange, uri);
		//如果需要用到uri可以使用此方法。
    }
}

 4.MainActivity

    操作資料庫的insert、delete、update等activity以及ListAdapter等檔案複用 https://blog.csdn.net/Gods_magic/article/details/84706386

public class CPMainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final int INSERT_REQUESTCODE = 1;
    private static final int INSERT_RESULTCODE = 11;
    private static final int DELETE_REQUESTCODE = 2;
    private static final int DELETE_RESULTCODE = 22;
    private static final int UPDATE_REQUESTCODE = 3;
    private static final int UPDATE_RESULTCODE = 33;

    private ListAdapter listAdapter;
    public static final String AUTOHORITY = "com.example.myinterview.myprovider";

    MyContentObsever myContentObsever;
    TextView text;

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

        Button insertPersonBtn = (Button) findViewById(R.id.person_insert);
        Button deletePersonBtn = (Button) findViewById(R.id.person_delete);
        Button updatePersonBtn = (Button) findViewById(R.id.person_update);
        Button queryPersonBtn = (Button) findViewById(R.id.person_query);
        text = (TextView) findViewById(R.id.change_text);
        insertPersonBtn.setOnClickListener(this);
        deletePersonBtn.setOnClickListener(this);
        updatePersonBtn.setOnClickListener(this);
        queryPersonBtn.setOnClickListener(this);


        listAdapter = new ListAdapter(new ArrayList<Person>(), this);
        ListView listView = (ListView) findViewById(R.id.list_data2);
        listView.setAdapter(listAdapter);

        //註冊ContentObsever 傳入Context 和 Handler
        Uri uri = Uri.parse("content://com.example.myinterview.myprovider/person");
        myContentObsever = new MyContentObsever(this, handler);
        getContentResolver().registerContentObserver(uri, true, myContentObsever);

    }

    //主執行緒 Handler 更新UI
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            text.setText(msg.obj.toString());
        }
    };

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        //取消註冊 ContentObserver
        getContentResolver().unregisterContentObserver(myContentObsever);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.person_insert:
                //case R.id.user_insert:
                insert();
                break;
            case R.id.person_delete:
                // case R.id.user_delete:
                delete();
                break;
            case R.id.person_update:
                //case R.id.user_update:
                update();
                break;
            case R.id.person_query:
                //case R.id.user_query:
                query();
                break;
            default:
                break;
        }
    }

    private void insert() {
        Intent intent = new Intent(this, InsertDialog.class);
        startActivityForResult(intent, INSERT_REQUESTCODE);
    }

    private void delete() {
        Intent intent = new Intent(this, DeleteDialog.class);
        startActivityForResult(intent, DELETE_REQUESTCODE);
    }

    private void update() {
        Intent intent = new Intent(this, UpdateDialog.class);
        startActivityForResult(intent, UPDATE_REQUESTCODE);
    }

    private void query() {
        Uri uri = Uri.parse("content://" + AUTOHORITY + "/person");
        ContentResolver resolver = getContentResolver();
        Cursor cursor = resolver.query(uri, null, null, null, null);
        List<Person> list = new ArrayList<>();
        if (cursor != null && cursor.getCount() != 0) {
            cursor.moveToFirst();
            do {
                int id = cursor.getInt(cursor.getColumnIndex("id"));
                String name = cursor.getString(cursor.getColumnIndex("name"));
                int age = cursor.getInt(cursor.getColumnIndex("age"));
                String sex = cursor.getString(cursor.getColumnIndex("sex"));

                Person person = new Person(id, name, age, sex);
                list.add(person);
            } while (cursor.moveToNext());
            cursor.close();
        }

        loadData(list);
    }

    private void loadData(List<Person> list) {
        listAdapter.setList(list);
        listAdapter.notifyDataSetChanged();
    }

    private Cursor queryPersonById(Uri uri) {
        ContentResolver resolver = getContentResolver();
        return resolver.query(uri, null, null, null, null);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case INSERT_REQUESTCODE:
                if (resultCode == INSERT_RESULTCODE) {
                    if (data != null) {
                        ContentResolver resolver = getContentResolver();
                        Person person = (Person) data.getSerializableExtra("person");
                        Uri uri = Uri.parse("content://" + AUTOHORITY + "/person");
                        ContentValues values = new ContentValues();
                        values.put("name", person.getName());
                        values.put("age", person.getAge());
                        values.put("sex", person.getSex());
                        resolver.insert(uri, values);

                        Toast.makeText(this, "Insert Success!", Toast.LENGTH_SHORT).show();
                        query();
                    } else {
                        Toast.makeText(this, "Insert Cancel!", Toast.LENGTH_SHORT).show();
                    }
                }
                break;
            case DELETE_REQUESTCODE:
                if (resultCode == DELETE_RESULTCODE) {
                    if (data != null) {
                        int id = data.getIntExtra("id", -1);
                        if (id != -1) {
                            ContentResolver resolver = getContentResolver();
                            Uri uri = Uri.parse("content://" + AUTOHORITY + "/person/" + id);
                            if (queryPersonById(uri).getCount() != 0) {
                                resolver.delete(uri, null, null);
                                Toast.makeText(this, "Delete Success!", Toast.LENGTH_SHORT).show();
                                query();
                            } else {
                                Toast.makeText(this, "Delete Fail! id " + id + " not exist", Toast.LENGTH_SHORT).show();
                            }
                        } else {
                            Toast.makeText(this, "Delete Fail!", Toast.LENGTH_SHORT).show();

                        }
                    } else {
                        Toast.makeText(this, "Delete Cancel!", Toast.LENGTH_SHORT).show();
                    }
                }
                break;
            case UPDATE_REQUESTCODE:
                if (resultCode == UPDATE_RESULTCODE) {
                    if (data != null) {
                        Person person = (Person) data.getSerializableExtra("person");
                        int id = person.getId();
                        ContentResolver resolver = getContentResolver();
                        Uri uri = Uri.parse("content://" + AUTOHORITY + "/person/" + id);
                        if (queryPersonById(uri).getCount() != 0) {
                            ContentValues values = new ContentValues();
                            values.put("name", person.getName());
                            values.put("age", person.getAge());
                            values.put("sex", person.getSex());
                            resolver.update(uri, values, null, null);
                            Toast.makeText(this, "Update Success!", Toast.LENGTH_SHORT).show();
                            query();
                        } else {
                            Toast.makeText(this, "Update Fail! id " + id + " not exist", Toast.LENGTH_SHORT).show();
                        }
                    } else {
                        Toast.makeText(this, "Update Cancel!", Toast.LENGTH_SHORT).show();
                    }
                }
                break;
            default:
                break;
        }

    }
}

大部分理論知識參考: https://blog.csdn.net/carson_ho/article/details/76101093