安卓基礎第九天(四大組件之ContentProvider,操作系統短信與聯繫人)

ContentProvider

ContentProvider 簡介

  • 內容提供者是應用程序之間共享數據的接口
  • 使用ContentProvider 共享數據的好處是統一了數據訪問方式
  • 內容提供者中數據更改可被監聽

ContentProvider使用

  • 定義類繼承ContentProvider,根據需要重寫內部方法(增刪改查)
  • 在清單文件的節點下進行配置,標籤中需要指定name 和authorities 屬性
    name 爲類名,包名從程序Package 開始,以“.”開始
    authorities:是訪問Provider 時的路徑,要唯一
    exported:true, 意思是是否對外發布,系統默認爲false,要設置爲true。
創建一個ContentProvider

示例 代碼

public class PersonContentProvider extends ContentProvider {
    // 用於存放並匹配個Uri 標識信息,一般在靜態代碼塊中對其信息進行初始化操作
    private static UriMatcher matcher;
    // 聲明一個用於操作數據庫對象
    private PersonOpenHelper openHelper;
    // 主機名信息:對應清單文件的authorities 屬性
    private static final String AUTHORITY = "com.itheima.person";
    // 數據庫表名
    private static final String TABLE_PERSON_NAME = "person";
    // Uri 匹配成功的返回碼
    private static final int PERSON_INSERT_CODE = 1000;
    private static final int PERSON_DELETE_CODE = 10001;
    private static final int PERSON_UPDATE_CODE = 10002;
    private static final int PERSON_QUERYALL_CODE = 10003;
    private static final int PERSON_QUERYONE_CODE = 10004;
    // 靜態代碼塊,用於初始化UriMatcher
    static {
        // NO_MATCH:沒有Uri 匹配的時候返回的狀態碼(-1)
        matcher = new UriMatcher(UriMatcher.NO_MATCH);
        // 添加一個分機號:
        // 對person
        // 表進行添加操作,如果Uri=content://com.itheima.person/person/insert,則返回PERSON_INSERT_CODE
        matcher.addURI(AUTHORITY, "person/insert", PERSON_INSERT_CODE);
        // 對person 表進行刪除操作,如果Uri=content: // com.itheima.person/person/delete,
        //則返回PERSON_DELETE_CODE
        matcher.addURI(AUTHORITY, "person/delete", PERSON_DELETE_CODE);
        // 對person 表進行修改操作,如果Uri=
        content: // com.itheima.person/person/update,則返回PERSON_UPDATE_CODE
        matcher.addURI(AUTHORITY, "person/update", PERSON_UPDATE_CODE);
        // 對person
        // 表進行查詢所有操作,如果Uri=content://com.itheima.person/person,則返回PERSON_QUERYALL_CODE
        matcher.addURI(AUTHORITY, "person", PERSON_QUERYALL_CODE);
        // 對person 表進行查詢單個操作,如果Uri=
        content: // com.itheima.person/person/#,(#:代表數字)則返回PERSON_QUERYONE_CODE
        matcher.addURI(AUTHORITY, "person/#", PERSON_QUERYONE_CODE);
    }

    @Override
    public boolean onCreate() {
        // 內容提供者中,獲取contenxt,是通過getContext,與測試類一樣,不能再成員變量,構造函數中調用,但是可以再onCreate
        // 方法中獲取。
        openHelper = new PersonOpenHelper(getContext());
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        // 用匹配器去匹配uri,如果匹配成功則返回匹配器中對應的狀態碼
        int matchCode = matcher.match(uri);
        SQLiteDatabase db = openHelper.getReadableDatabase();
        switch (matchCode) {
        case PERSON_QUERYALL_CODE:
            return db.query(TABLE_PERSON_NAME, projection, selection,
                    selectionArgs, null, null, sortOrder);
        case PERSON_QUERYONE_CODE:
            // 使用ContentUris 工具類解析出uri 中的id
            long parseId = ContentUris.parseId(uri);
            return db.query(TABLE_PERSON_NAME, projection, "id=?",
                    new String[] { parseId + "" }, null, null, sortOrder);
        default:
            throw new IllegalArgumentException("Uri 匹配失敗:" + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = openHelper.getWritableDatabase();
        // 新插入對象的id
        long id = db.insert(TABLE_PERSON_NAME, null, values);
        db.close();
        // 使用ContentUris 工具類將id 追加到uri 中,返回給客戶
        return ContentUris.withAppendedId(uri, id);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = openHelper.getWritableDatabase();
        // 返回刪除的個數
        int count = db.delete(TABLE_PERSON_NAME, selection, selectionArgs);
        // 關閉數據庫
        db.close();
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        SQLiteDatabase db = openHelper.getWritableDatabase();
        // 返回更新的個數
        int count = db.update(TABLE_PERSON_NAME, values, selection,
                selectionArgs);
        // 更新數據庫
        db.close();
        return count;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }
}
訪問ContentProvider
  • 外部程序只需知道內容提供者的Uri 路徑信息,通過內容解析器ContentResolver 即可調用內容提供者。
  • 如果內容提供者所在應用程序,沒有啓動,其他應用程序訪問調用它時,就會啓動它。
    啓動時,會創建這個內容提供者。

測試query(單個)方法

    public void queryOne() {
        ContentResolver resolver = getContext().getContentResolver();
        Uri uri = Uri.parse("content://com.itheima.person/person/2");
        Cursor cursor = resolver.query(uri, new String[] { "name", "age",
                "phone", "address" }, null, null, null);
        while (cursor.moveToNext()) {
            String name = cursor.getString(0);
            int age = cursor.getInt(1);
            String phone = cursor.getString(2);
            String address = cursor.getString(3);
            System.out.println(name + "/" + age + "/" + phone + "/" + address);
        }
        cursor.close();
    }

測試query(多個)方法

    public void queryAll() {
        ContentResolver resolver = getContext().getContentResolver();
        Uri uri = Uri.parse("content://com.itheima.person/person");
        Cursor cursor = resolver.query(uri, new String[] { "name", "age",
                "phone", "address" }, null, null, null);
        while (cursor.moveToNext()) {
            String name = cursor.getString(0);
            int age = cursor.getInt(1);
            String phone = cursor.getString(2);
            String address = cursor.getString(3);
            System.out.println(name + "/" + age + "/" + phone + "/" + address);
        }
        cursor.close();
    }

URI

schema 主機名authority path ID
content:// com.itheima.provider/ person/ 10

schema:用來說明一個ContentProvider 控制這些數據。”content://”
authority:它定義了是哪個ContentProvider 提供這些數據,provider>節點中的authorites 屬性
path:路徑,URI 下的某一個Item。
ID:通常定義Uri 時使用”#”號佔位符代替, 使用時替換成對應的數字
content://com.itheima.provider/person/#:#表示數據id(#代表任意數字)
content://com.itheima.provider/person/*:*來匹配任意文本。

示例 短信的備份與恢復

需要的權限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
備份短信
    public void backupSms(View view) {
        // 如果當前正在備份,則返回。
        if (backupFlag) {
            Toast.makeText(this, "當前正在備份中。。。請稍後再操作", 0).show();
            return;
        }
        // 開闢一個新的線程來處理業務,因爲短信備份是耗時操作,如果不放在子線程中操作可能會導致ANR 異常
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 將標記位設置爲true
                backupFlag = true;
                // 子線程中創建一個Looper 對象
                Looper.prepare();
                // 獲取ContentResolver 對象
                ContentResolver resolver = getContentResolver();
                // 編輯短信訪問的uri
                Uri uri = Uri.parse("content://sms");
                // 執行查詢語句
                Cursor cursor = resolver.query(uri, new String[] { "address",
                        "date", "body", "type" }, null, null, null);
                List<Sms> list = new ArrayList<Sms>();
                // 獲取短信的總共條數
                int count = cursor.getCount();
                // 設置進度條的最大值
                pb.setMax(count);
                int i = 0;
                while (cursor.moveToNext()) {
                    Sms sms = new Sms();
                    String address = cursor.getString(0);
                    String date = cursor.getString(1);
                    String body = cursor.getString(2);
                    String type = cursor.getString(3);
                    sms.setAddress(address);
                    sms.setBody(body);
                    sms.setDate(date);
                    sms.setType(type);
                    list.add(sms);
                    // 更新進度條
                    pb.setProgress(++i);
                    // 這裏是爲了方便演示備份的效果加上線程等待
                    SystemClock.sleep(50);
                }
                try {
                    // 通過自定義的Sms2XmlUtil 工具類將短信保存到xml 中
                    Sms2XmlUtil.sms2Xml(list);
                } catch (Exception e) {
                    e.printStackTrace();
                    Toast.makeText(MainActivity.this,
                            "備份短信失敗。" + e.getLocalizedMessage(), 1).show();
                }
                // 關閉遊標
                cursor.close();
                Toast.makeText(MainActivity.this, "短信備份成功!本次備份了" + i + "條短信。",
                        1).show();
                // 循環消息
                Looper.loop();
                // 將標記設置爲flase
                backupFlag = false;
            }
        }).start();
    }
恢復短信

代碼缺陷:在插入數據庫之前先判斷該短信是否存在,如果存在則不插入。

    public void reverseSms(View view) {
        if (reverseFlag) {
            Toast.makeText(this, "當前短信正在備份中,請稍後再操作。", 0).show();
            return;
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                ContentResolver resolver = getContentResolver();
                Uri url = Uri.parse("content://sms");
                List<Sms> list = null;
                try {
                    list = Sms2XmlUtil.xml2Sms();
                    pb.setMax(list.size());
                    pb.setProgress(0);
                } catch (Exception e) {
                    e.printStackTrace();
                    Toast.makeText(MainActivity.this,
                            "短信恢復失敗!" + e.getLocalizedMessage(), 1).show();
                    return;
                }
                int i = 0;
                for (Sms s : list) {
                    ContentValues values = new ContentValues();
                    values.put(Sms2XmlUtil.SMS_TAG_ADDRESS, s.getAddress());
                    values.put(Sms2XmlUtil.SMS_TAG_BODY, s.getBody());
                    values.put(Sms2XmlUtil.SMS_TAG_DATE, s.getDate());
                    values.put(Sms2XmlUtil.SMS_TAG_TYPE, s.getType());
                    Uri uri = resolver.insert(url, values);
                    System.out.println("插入的id:" + ContentUris.parseId(uri));
                    pb.setProgress(++i);
                    SystemClock.sleep(50);
                }
                Toast.makeText(MainActivity.this, "短信恢復成功!本次恢復了" + i + "條短信。",
                        1).show();
                reverseFlag = false;
                Looper.loop();
            }
        }).start();
    }

示例 操作系統聯繫人

查看數據庫其中raw_contacts 表存放的是聯繫人條數信息,
data 表中存放的是raw_contacts 中的每一條id 對應的具體信息,
不同類型的信息由mimetype_id 來標識。

  • raw_contacts 表:其中保存了聯繫人id 字段爲:_id
  • data 表:和raw_contacts 是多對一的關係,保存了聯繫人的各項數據
  • mimetypes 表:數據類型

查詢聯繫人信息思路:先查詢raw_contacts 得到每個聯繫人的id,在使用id 從data表中查詢對應數據,根據mimetype 分類數據
tips:
- 系統內部提供了根據電話號碼獲取data 表數據的功能, 路徑爲:data/phones/filter/*
- 用電話號碼替換“*”部分就可以查到所需數據,獲取“display_name”可以獲取到
聯繫人顯示名

需要的權限

<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>

獲取聯繫人代碼

public class MainActivity extends Activity {

    private ListView lv;
    private List<Contact> list;
    private MyAdapter adapter;
    private Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            Toast.makeText(MainActivity.this, "數據獲取成功。", 1).show();
            // 更新數據
            adapter.notifyDataSetChanged();
        };
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lv = (ListView) findViewById(R.id.lv);
        list = new ArrayList<Contact>();
        adapter = new MyAdapter();
        lv.setAdapter(adapter);
    }

    /**
     * 查詢所有的系統聯繫人,這裏的業務放在子線程中操作
     */
    public void queryContacts(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                ContentResolver contentResolver = getContentResolver();
                Uri uri = Uri
                        .parse("content://com.android.contacts/raw_contacts");
                Cursor cursor = contentResolver.query(uri,
                        new String[] { "contact_id" }, null, null, null);
                list.clear();
                int i = 0;
                while (cursor.moveToNext()) {
                    i++;
                    // 先從raw_contacts 表中獲取contact_id
                    String contact_id = cursor.getString(0);
                    if (TextUtils.isEmpty(contact_id)) {
                        continue;
                    }
                    // 根據contact_id 從data 表中查詢具體的數據
                    Cursor cursor2 = contentResolver.query(
                            Uri.parse("content://com.android.contacts/data"),
                            new String[] { "data1", "mimetype" },
                            "contact_id=?", new String[] { contact_id }, null);
                    Contact contact = new Contact();
                    while (cursor2.moveToNext()) {
                        String data = cursor2.getString(0);
                        String mimetype = cursor2.getString(1);
                        if ("vnd.android.cursor.item/name".equals(mimetype)) {
                            contact.setName(data);
                        } else if ("vnd.android.cursor.item/phone_v2"
                                .equals(mimetype)) {
                            contact.setPhone(data);
                        } else if ("vnd.android.cursor.item/email_v2"
                                .equals(mimetype)) {
                            contact.setEmail(data);
                        } else {
                            contact.setOther(data);
                        }
                    }
                    list.add(contact);
                    cursor2.close();
                }
                cursor.close();
                // 發送一個空消息,更新ListView
                handler.sendEmptyMessage(RESULT_OK);
            }
        }).start();
    }

    class MyAdapter extends BaseAdapter {
        @Override
        public int getCount() {
            return list.size();

        }

        @Override
        public Object getItem(int position) {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        /**
         * 這裏面通過ViewHolder 類將其他子屬性值綁定在View 上面,這裏對 ListView 作了進一步的優化處理
         */
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view;
            ViewHolder holder;
            if (convertView != null) {
                view = convertView;
                holder = (ViewHolder) view.getTag();
            } else {
                view = View
                        .inflate(MainActivity.this, R.layout.list_item, null);
                holder = new ViewHolder();
                holder.tv_name = (TextView) view.findViewById(R.id.tv_name);
                holder.tv_phone = (TextView) view.findViewById(R.id.tv_phone);
                holder.tv_email = (TextView) view.findViewById(R.id.tv_email);
                view.setTag(holder);
            }
            Contact contact = list.get(position);
            holder.tv_name.setText(contact.getName());
            holder.tv_email.setText(contact.getEmail());
            holder.tv_phone.setText(contact.getPhone());
            return view;
        }
    }

    class ViewHolder {
        TextView tv_name;
        TextView tv_email;
        TextView tv_phone;
    }

}

插入聯繫人

    /**
     * 往系統聯繫人表中插入一條數據,這裏爲了方便演示,我們直接插入一 條固定的數據
     */
    public void insertContacts(View view) {
        // 創建一個自定義的Contact 類,將要網系統聯繫人表中插入的字段封裝起來
        Contact contact = new Contact();
        contact.setEmail("[email protected]");
        contact.setName("王二麻子" + new Random().nextInt(1000));
        contact.setPhone("9999999" + new Random().nextInt(100));
        contact.setOther("北京市中關村軟件園");
        // 獲取ContentResolver 對象
        ContentResolver resolver = getContentResolver();
        // 操作raw_contacts 表的uri
        Uri raw_uri = Uri.parse("content://com.android.contacts/raw_contacts");
        // 操作data 表的uri
        Uri data_uri = Uri.parse("content://com.android.contacts/data");
        // 在插入數據之前先查詢出當前最大的id
        Cursor cursor = resolver.query(raw_uri, new String[] { "contact_id" },
                null, null, "contact_id desc limit 1");
        int id = 1;
        if (cursor != null) {
            boolean moveToFirst = cursor.moveToFirst();
            if (moveToFirst) {
                id = cursor.getInt(0);
            }
        }
        cursor.close();
        // 要插入數據的contact_id 值
        int newId = id + 1;
        // 給raw_contact 表中添加一天記錄
        ContentValues values = new ContentValues();
        values.put("contact_id", newId);
        resolver.insert(raw_uri, values);
        // 在data 表中添加數據
        // 添加name
        values = new ContentValues();
        values.put("raw_contact_id", newId);
        values.put("mimetype", "vnd.android.cursor.item/name");
        values.put("data1", contact.getName());
        resolver.insert(data_uri, values);
        // 添加phone
        values = new ContentValues();
        values.put("raw_contact_id", newId);
        values.put("mimetype", "vnd.android.cursor.item/phone_v2");
        values.put("data1", contact.getPhone());
        resolver.insert(data_uri, values);
        // 添加地址
        values = new ContentValues();
        values.put("raw_contact_id", newId);
        values.put("mimetype", "vnd.android.cursor.item/postal-address_v2");
        values.put("data1", contact.getOther());
        resolver.insert(data_uri, values);
        // 添加email
        values = new ContentValues();
        values.put("raw_contact_id", newId);
        values.put("mimetype", "vnd.android.cursor.item/email_v2");
        values.put("data1", contact.getEmail());
        resolver.insert(data_uri, values);
        Toast.makeText(this, "成功插入聯繫人" + contact, 0).show();
    }

ContentObserver 內容觀察者

  • 類似於數據庫技術中的觸發器(Trigger)
  • 當ContentObserver所觀察的Uri 發生變化時,便會觸發它
  • ContentObserver 分爲:“表“ContentObserver、“行”ContentObserver,與的Uri MIME Type相關
  • Uri Type可以分爲:返回多條數據的Uri、返回單條數據的Uri。

示例 使用ContentObserver短信監聽

發出的短信有這樣幾個過程:草稿箱->發件箱->已發送。所以只需查詢發件箱中的信息即可,處於正在發送狀態的短信放在發送箱中。

權限
<uses-permission android:name="android.permission.READ_SMS"/>

創建一個內容觀察者

public class SmsContentObserver extends ContentObserver {

    // 聲明一個Content 對象,在構造函數中對其進行實例化
    private Context context;
    private Handler handler;

    // 聲明構造函數
    public SmsContentObserver(Context context, Handler handler) {
        super(handler);
        this.context = context;
        this.handler = handler;
    }

    // 覆寫父類onChange 方法
    @Override
    public void onChange(boolean selfChange) {
        // 通過查看發件箱中的短信即可觀察到新短信的發送
        Uri uri = Uri.parse("content://sms/outbox");
        // 獲取Context 對象的ContentResolver 對象
        ContentResolver resolver = context.getContentResolver();
        String[] projection = { "address", "body", "date" };
        // 獲取發件箱中的短信
        Cursor cursor = resolver.query(uri, projection, null, null, null);
        if (cursor != null && cursor.moveToNext()) {
            String address = cursor.getString(0);
            String body = cursor.getString(1);
            Long date = cursor.getLong(2);
            // 調用DateFormat 的方法將long 類型的時間轉化爲系統相關時間
            String dateStr = DateFormat.getTimeFormat(context).format(date);
            // 打印觀察到正在發送的短信
            System.out.println("address=" + address + " body=" + body + "date="
                    + dateStr);
        }
        cursor.close();
    }
}

在MainActivity 類中註冊SmsContentObserver

public class MainActivity extends Activity {

    public class MainActivity extends Activity {
        private SmsContentObserver observer;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // 實例化一個自定義的SMSContentObserver 對象
            observer = new SmsContentObserver(this, null);
            // 操作系統短信的uri
            Uri uri = Uri.parse("content://sms");
            /**
             * 獲取ContentResolver,然後註冊ContentObserver
             */
            getContentResolver().registerContentObserver(uri, true, observer);
        }
    }

}

自定義 ContentObserver

通過ContentObserver 實現了對系統發送短信的監聽
這是因爲系統短信數據數據庫中插入新的短信的時候通過ContentResolver 調用了notifyChange方法,
正是該方法對外發出了消息,這樣我們的ContentObserver 才監聽到了短信的發送

示例 發送消息

    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = openHelper.getWritableDatabase();
        // 新插入對象的id
        long id = db.insert(TABLE_PERSON_NAME, null, values);
        db.close();
        // 聲明一個uri,通過這個uri ContentObserver 可以進行監聽
        Uri notifyUri = Uri.parse("com.itheima.person");
        /*
         * 方法中第一個參數指定一個被監聽的uri 第二個參數是指定ContentObserver,如果不爲null 則代表只有指定
         * 的ContentObserver 可以接收到消息 爲null 則所有的ContentObserver 都有機會接收到該消息
         */
        getContext().getContentResolver().notifyChange(notifyUri, null);
        // 使用ContentUris 工具類將id 追加到uri 中,返回給客戶
        return ContentUris.withAppendedId(uri, id);
    }

監聽
contentResolver.registerContentObserver(uri,notifyForDescendents,contentObserver);
參數:
uri、監聽哪個uri 的操作
notifyForDescendents 是否觀察uri 的子集,如果是false,不觀察他的子孫,就只能完全匹配
contentObserver,內容觀察者

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章