【Android開發日記】之入門篇(九)——Android四大組件之ContentProvider


數據源組件ContentProvider與其他組件不同,數據源組件並不包括特定的功能邏輯。它只是負責為應用提供數據訪問的接口。Android內置的許多數據都是使用ContentProvider形式,供開發者調用的(如視頻,音頻,圖片,通訊錄等)。如果把第三方應用比作一個黑盒子的話,ContentProvider就像是從里面延伸出來的管道,從這個管道,應用可以把一些數據共享出來,我們也可以往里面輸送數據。但是里面怎么處理數據我們看不到,也管不着。並且這個管道是有規范標准的,不是它規定的數據你塞不進這個管道。

 

一、ContentProvider的特征

  1. 我們為什么使用ContentProvider?像上幾篇寫的博客中就有好幾種方法可以跨應用來讀取數據,但ContentProvider的特點不僅僅如此。首先它作為Android中四大組件之一,(我們都知道組件的信息會被android統一管理),提供數據的跨進程無縫隙訪問,並會在進程中提供本地緩存。跨進程調用是需要時間和資源消耗的,而通過緩存可以有效的提高效率。再則ContentProvider規定了數據訪問結構,嚴謹不容易發生錯誤。然后,應用調用接口進行操作時,是一個同步的過程,也就是說,所有對數據源組件對象中的數據操作都是在消息隊列中串行執行的,我們開發者就不需要考慮復雜的並發情形。最后,數據源組件中數據存儲的方式沒有任何的限制,可以通過數據庫、文件等任意方式實現。
  2. 通過什么方式找到想要的ContentProvider?它是通過URI進行定位。URI,就是全局統一定位標志,通過一個結構化的字符串,唯一標識數據源的地址信息,而每個數據源組件都有一個唯一的URI標識。

    ContentProvider的scheme已經由Android所規定, scheme為:content://
    主機名(或叫Authority)用於唯一標識這個ContentProvider,外部調用者可以根據這個標識來找到它。
    路徑(path)可以用來表示我們要操作的數據,路徑的構建應根據業務而定,如下:
    要操作person表中id為10的記錄,可以構建這樣的路徑:/person/10
    要操作person表中id為10的記錄的name字段, person/10/name
    要操作person表中的所有記錄,可以構建這樣的路徑:/person
    要操作xxx表中的記錄,可以構建這樣的路徑:/xxx
    當然要操作的數據不一定來自數據庫,也可以是文件、xml或網絡等其他存儲方式,如下:
    要操作xml文件中person節點下的name節點,可以構建這樣的路徑:/person/name
    如果要把一個字符串轉換成Uri,可以使用Uri類中的parse()方法,如下:
    Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person")

二、ContentProvider的實例

我們還是通過一個實例來了解它吧。利用ContentProvider來對第三方的數據庫進行操作。

  1. 首先我們建一個DBHelper的類繼承SQLiteOpenHelper
    package com.example.database;
    
    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    import android.database.sqlite.SQLiteDatabase.CursorFactory;
    
    
    public class DBHelper extends SQLiteOpenHelper{
        private static final int VERSION=1;
        /** 
         * 在SQLiteOpenHelper的子類當中,必須有該構造函數 
         * @param context   上下文對象 
         * @param name      數據庫名稱 
         * @param factory 
         * @param version   當前數據庫的版本,值必須是整數並且是遞增的狀態 
         */
        public DBHelper(Context context,String name,CursorFactory factory,int version){
            super(context,name,factory,version);
        }
        public DBHelper(Context context, String name, int version){  
            this(context,name,null,version);  
        }  
      
        public DBHelper(Context context, String name){  
            this(context,name,VERSION);  
        }
        @Override
        public void onCreate(SQLiteDatabase db) {
             // 數據庫首次構造時,會調用該函數,可以在這里構造表、索引,等等 
            System.out.println("create a database");  
            //execSQL用於執行SQL語句  
            db.execSQL("create table notebook(_id integer primary key autoincrement,title varchar(20),content text,time long)");
            
        }
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            // 如果給定的當前數據庫版本高於已有數據庫版本,調用該函數
            System.out.println("upgrade a database");
        }  
    
    }

    這一步沒什么好解釋的,不懂的可以看一看我寫的上一篇關於數據庫操作的博文。


  2. 接下來我們就要新建一個MyProvider的類繼承ContentProvider
    package com.example.database;
    
    import android.content.ContentProvider;
    import android.content.ContentUris;
    import android.content.ContentValues;
    import android.content.UriMatcher;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    import android.net.Uri;
    
    public class MyProvider extends ContentProvider {
    
        private DBHelper dh = null;// 數據庫管理對象
        private SQLiteDatabase db;//獲取其中的數據庫
        //UriMatcher:Creates the root node of the URI tree.
        //按照官方解釋,UriMatcher是一顆Uri的樹,然后利用addURI()方法往里面添加枝干,通過match()函數來查找枝干。
        private static final UriMatcher MATCHER = new UriMatcher(
                UriMatcher.NO_MATCH);
        //設定匹配碼
        private static final int NOTEBOOK = 1;
        private static final int NOTEBOOKS = 2;
        static {
            //添加枝干,並給它們加上唯一的匹配碼,以方便查找
            //如果match()方法匹配content://com.example.database/notebook路徑,返回匹配碼為1
            MATCHER.addURI("com.example.database", "notebook", NOTEBOOKS);
            //如果match()方法匹配content://com.example.database/notebook/#路徑,返回匹配碼為2
            //其中#號為通配符。
            MATCHER.addURI("com.example.database", "notebook/#", NOTEBOOK);
        }
    
        @Override
        public boolean onCreate() {
            // 創建ContentProvider對象時會調用這個函數
            dh = new DBHelper(this.getContext(),"note.db");// 數據庫操作對象
            db = dh.getReadableDatabase();
            return false;
        }
    
        /** 
         * 查詢,返回Cursor
         **/
        @Override
        public Cursor query(Uri uri, String[] projection, String selection,
                String[] selectionArgs, String sortOrder) {
            //通過match函數,獲取匹配碼
            switch (MATCHER.match(uri)) {
            case NOTEBOOKS:
                //返回數據庫操作的結果
                return db.query("notebook", projection, selection, selectionArgs,
                        null, null, sortOrder);
            case NOTEBOOK:
                //因為添加 了id,所以要把id加到where條件中
                long id = ContentUris.parseId(uri);
                String where = "_id=" + id;
                if (selection != null && !"".equals(selection)) {
                    where = selection + " and " + where;
                }
                return db.query("notebook", projection, where, selectionArgs, null,
                        null, sortOrder);
            default:
                throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
            }
        }
    
        //獲取Uri的類型
        @Override
        public String getType(Uri uri) {
            // TODO Auto-generated method stub
            switch (MATCHER.match(uri)) {  
            case NOTEBOOKS:  
                return "com.example.Database.all/notebook";  
      
            case NOTEBOOK:  
                return "com.example.Database.item/notebook";  
      
            default:  
                throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());  
            }
        }
    
        //插入數據
        @Override
        public Uri insert(Uri uri, ContentValues values) {
            // TODO Auto-generated method stub
            switch (MATCHER.match(uri)) {  
            case NOTEBOOKS:  
                //調用數據庫的插入功能
                // 特別說一下第二個參數是當title字段為空時,將自動插入一個NULL。  
                long rowid = db.insert("notebook", "title", values);  
                Uri insertUri = ContentUris.withAppendedId(uri, rowid);// 得到代表新增記錄的Uri  
                this.getContext().getContentResolver().notifyChange(uri, null);  
                return insertUri;  
      
            default:  
                throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());  
            }
        }
    
        //刪除數據
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            // TODO Auto-generated method stub
            int count;
            switch (MATCHER.match(uri)) {  
            case NOTEBOOKS:  
                count = db.delete("notebook", selection, selectionArgs);  
                return count;  
      
            case NOTEBOOK:  
                long id = ContentUris.parseId(uri);  
                String where = "_id=" + id;  
                if (selection != null && !"".equals(selection)) {  
                    where = selection + " and " + where;  
                }  
                count = db.delete("notebook", where, selectionArgs);  
                return count;  
      
            default:  
                throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());  
            }
        }
    
        //更新數據
        @Override
        public int update(Uri uri, ContentValues values, String selection,
                String[] selectionArgs) {
            // TODO Auto-generated method stub
            int count = 0;  
            switch (MATCHER.match(uri)) {  
            case NOTEBOOKS:  
                count = db.update("notebook", values, selection, selectionArgs);  
                return count;  
            case NOTEBOOK:  
                long id = ContentUris.parseId(uri);  
                String where = "_id=" + id;  
                if (selection != null && !"".equals(selection)) {  
                    where = selection + " and " + where;  
                }  
                count = db.update("notebook", values, where, selectionArgs);  
                return count;  
            default:  
                throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());  
            } 
        }
    
    }

    因為Uri代表了要操作的數據,所以我們經常需要解析Uri,並從Uri中獲取數據。Android系統提供了兩個用於操作Uri的工具類,分別為UriMatcher和ContentUris 。掌握它們的使用,會便於我們的開發工作。
    看上去這個類很像我上次寫的DBManager類吧。其實這可以算是一個很簡單的數據操作類,關鍵地方就在於它放在了ContentProvider這個“容器”上,讓第三方應用也能訪問到己方的數據。所以想要吃透這個組件,只要透徹理解什么是Uri,怎么操作Uri就八九不離十了。

  3. 最后,不要忘記在配置文件中為ContentProvider注冊,因為這也是一個組件,所以無法避免了~
    <provider android:name=".MyProvider" android:authorities="com.example.database" />

    前面的是你的類名,后面則是關鍵地方,它是要寫在Uri中的,所以不要弄錯了。
    到此,一個可以供其他應用訪問的工程就建好了
    接下來我們來寫個測試工程來檢驗效果吧

三、調用ContentProvider

在使用其他應用為你提供的ContentProvider時,你必須要知道的有兩點:(1)它的authorities值,在我這里的是“com.example.database”;(2)數據文件的結構,比如我這里要使用的是數據庫中的booknote表,它里面有着(_id,title,content,time)這些字段。只有知道了這些你才能操作ContentProvider

  1. 好的,我們先新建一個工程,設置一下布局文件,效果如下

    <LinearLayout 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:orientation="vertical" >
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="90dp"
            android:orientation="horizontal" >
    
            <LinearLayout
                android:layout_width="120dp"
                android:layout_height="match_parent"
                android:orientation="vertical" >
    
                <TextView
                    android:id="@+id/textView1"
                    android:layout_width="match_parent"
                    android:layout_height="45dp"
                    android:text="Title:"
                    android:textAlignment="center"
                    android:textSize="25sp" />
    
                <TextView
                    android:id="@+id/textView2"
                    android:layout_width="match_parent"
                    android:layout_height="45dp"
                    android:text="Content:"
                    android:textAlignment="center"
                    android:textSize="25sp" />
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="200dp"
                android:layout_height="match_parent"
                android:orientation="vertical" >
    
                <EditText
                    android:id="@+id/editText1"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textSize="25sp" >
    
                    <requestFocus />
                </EditText>
    
                <EditText
                    android:id="@+id/editText2"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textSize="25sp" />
            </LinearLayout>
        </LinearLayout>
    
        <ListView
            android:id="@+id/listView1"
            android:layout_width="match_parent"
            android:layout_height="196dp" >
        </ListView>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="90dp"
            android:layout_weight="0.20" >
    
            <Button
                android:id="@+id/button1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="獲取全部信息" />
    
            <Button
                android:id="@+id/button2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="插入一條信息" />
    
            <Button
                android:id="@+id/button3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="刪除一條信息" />
    
            <Button
                android:id="@+id/button4"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="更新一條信息" />
    
        </LinearLayout>
    
    </LinearLayout>
    activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >
        <TextView  
              android:layout_width="80dip"  
              android:layout_height="wrap_content"  
              android:text="_id"  
              android:id="@+id/id"  
              />  
         <TextView  
              android:layout_width="80dip"  
              android:layout_height="wrap_content"  
              android:text="title"  
              android:id="@+id/title"  
              />  
            
         <TextView  
              android:layout_width="100dip"  
              android:layout_height="wrap_content"  
               android:text="content"  
              android:id="@+id/content"  
              />     
                
         <TextView  
              android:layout_width="fill_parent"  
              android:layout_height="wrap_content"  
               android:text="time"  
              android:id="@+id/time"  
              /> 
    
    </LinearLayout>
    item.xml

     

  2. 接下來在MainActivity添加代碼
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.AdapterView;
    import android.widget.AdapterView.OnItemClickListener;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.ListView;
    import android.widget.SimpleCursorAdapter;
    import android.widget.Toast;
    import android.app.Activity;
    import android.content.ContentResolver;
    import android.content.ContentValues;
    import android.database.Cursor;
    import android.net.Uri;
    import android.os.Bundle;
    
    public class MainActivity extends Activity implements OnClickListener {
    
        private ListView listView;
        private SimpleCursorAdapter adapter;
        private Button button_query, button_insert, button_delete, button_update;
        private EditText editText_title, editText_content;
        private int CurItem;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            editText_title = (EditText) this.findViewById(R.id.editText1);
            editText_content = (EditText) this.findViewById(R.id.editText2);
            
            listView = (ListView) this.findViewById(R.id.listView1);
            listView.setOnItemClickListener(new OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view,
                        int position, long id) {
                    ListView lView = (ListView) parent;
                    Cursor data = (Cursor) lView.getItemAtPosition(position);
                    int _id = data.getInt(data.getColumnIndex("_id"));
                    Toast.makeText(MainActivity.this, _id + "", Toast.LENGTH_SHORT)
                            .show();
                    CurItem = _id;
                    editText_title.setText(data.getString(data.getColumnIndex("title")));
                    editText_content.setText(data.getString(data.getColumnIndex("content")));
                }
            });
            button_query = (Button) this.findViewById(R.id.button1);
            button_query.setOnClickListener(this);
            button_insert = (Button) this.findViewById(R.id.button2);
            button_insert.setOnClickListener(this);
            button_delete = (Button) this.findViewById(R.id.button3);
            button_delete.setOnClickListener(this);
            button_update = (Button) this.findViewById(R.id.button4);
            button_update.setOnClickListener(this);
    
        }
    
        @Override
        public void onClick(View v) {
            //ContentResolver它是ContentProvider提供的一個接口,它能夠調用ContentProvider里面的所有方法。
            ContentResolver contentResolver;
            // TODO Auto-generated method stub
            switch (v.getId()) {
            case R.id.button1:
                contentResolver = getContentResolver();
                //Uri.parse()能將字符串轉換成Uri格式。
                Uri selectUri = Uri.parse("content://com.example.database/notebook");
                Cursor cursor = contentResolver.query(selectUri, null, null, null,
                        null);
                adapter = new SimpleCursorAdapter(this, R.layout.item, cursor,
                        new String[] { "_id", "title", "content", "time" },
                        new int[] { R.id.id, R.id.title, R.id.content, R.id.time },
                        1);
                listView.setAdapter(adapter);
                break;
            case R.id.button2:
                contentResolver = getContentResolver();
                Uri insertUri = Uri
                        .parse("content://com.example.database/notebook");
                ContentValues values = new ContentValues();
                values.put("title", editText_title.getText().toString());
                values.put("content", editText_content.getText().toString());
                values.put("time", System.currentTimeMillis());
                Uri uri = contentResolver.insert(insertUri, values);
                Toast.makeText(this, uri.toString() + "添加完成", Toast.LENGTH_SHORT)
                        .show();
                break;
            case R.id.button3:
                contentResolver = getContentResolver();
                Uri deleteUri = Uri
                        .parse("content://com.example.database/notebook/"+CurItem);
                int d = contentResolver.delete(deleteUri, null,null);
                Toast.makeText(this, CurItem+"刪除完成", Toast.LENGTH_SHORT)
                        .show();
                break;
            case R.id.button4:
                contentResolver = getContentResolver();
                Uri updateUri = Uri
                        .parse("content://com.example.database/notebook/"+CurItem);
                ContentValues updatevalues = new ContentValues();
                updatevalues.put("title", editText_title.getText().toString());
                updatevalues.put("content", editText_content.getText().toString());
                updatevalues.put("time", System.currentTimeMillis());
                int u = contentResolver.update(updateUri, updatevalues,null,null);
                Toast.makeText(this, CurItem+"更新完成", Toast.LENGTH_SHORT)
                        .show();
                break;
            }
        }
    }

    兩個應用之間的流程圖大概就是這樣了(手挫,不要嫌棄~)

  3. 最后,將兩個應用安裝好,打開實踐一下。那么我們看看運行結果吧

    正常運行。那么今天就到此結束,收工了~

四、結束語

理論上來說,數據源組件並沒有所謂的生命周期,因為數據源組件的狀態並不作為判定進程優先級的依據。所以系統回收進程資源時,並不會將數據源組件的銷毀事件告訴開發者。但構造ContentProvider組件時還是會調用onCreate()函數。所以,不要在數據源組件中部署延遲寫入等寫優化策略,當被系統默默回收時,一些未持久化的數據會丟失。一旦數據源組件被構造出來,就會保持長期運行的狀態至其所在的進程被系統回收。所以,也不要在數據源組件中緩存過多的數據,以免占用內存空間。

 

到此,Android的四大組件已經介紹完畢,他們各有各的的特色和用法,同時也是我們開發時候不可缺少的工具。希望通過這些介紹能讓大家體會到Android設計的巧妙和特征,學會正確的使用Android應用的框架。那么接下來可能會帶來的是Android的Intent機制,盡請期待~

 

參考文章:(1)ContentProvider和Uri詳解  http://www.cnblogs.com/linjiqin/archive/2011/05/28/2061396.html

               (2) ContentProvider淺析   http://bbs.51cto.com/thread-1024108-1.html

               (3) 從頭學Android之ContentProvider   http://blog.csdn.net/worker90/article/details/7016430

下載:demo下載

========================================

作者:cpacm

出處:http://www.cpacm.net/2015/03/22/Android開發日記(七)——Android四大組件之ContentProvider/)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM