編輯:關於Android編程
本博文是《第一行代碼 Android》的讀書筆記/摘錄。
一、Content Provider簡介
內容提供器(Content Provider)主要用於在不同的應用程序之間實現數據共享的功能,它提供了一套完整的機制,允許一個程序訪問另一個程序中的數據,同時還能保證被訪數據的安全性。目前,使用內容提供器是Android 實現跨程序共享數據的標准方式。不同於文件存儲和SharedPreferences 存儲中的兩種全局可讀寫操作模式,內容提供器可以選擇只對哪一部分數據進行共享,從而保證我們程序中的隱私數據不會有洩漏的風險。
內容提供器的用法一般有兩種,一種是使用現有的內容提供器來讀取和操作相應程序中的數據,另一種是創建自己的內容提供器給我們程序的數據提供外部訪問接口。
二、訪問其他程序中的數據
當一個應用程序通過內容提供器對其數據提供了外部訪問接口,任何其他的應用程序就都可以對這部分數據進行訪問。Android 系統中自帶的電話簿、短信、媒體庫等程序都提供了類似的訪問接口,這就使得第三方應用程序可以充分地利用這部分數據來實現更好的功能。
(一)ContentResolver 的基本用法
對於每一個應用程序來說,如果想要訪問內容提供器中共享的數據,就一定要借助ContentResolve 類,可以通過Context 中的getContentResolver()方法獲取到該類的實例。
public abstract ContentResolver getContentResolver();
ContentResolver 中提供了一系列的方法用於對數據進行CRUD 操作,其中insert()方法用於添加數據,update()方法用於更新數據,delete()方法用於刪除數據,query()方法用於查詢數據。
//插入 public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values); //更新 public final int update(@NonNull Uri uri, @Nullable ContentValues values,@Nullable String where, @Nullable String[] selectionArgs); //刪除 public final int delete(@NonNull Uri url, @Nullable String where,@Nullable String[] selectionArgs); //查詢 public final @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,@Nullable String selection, @Nullable String[] selectionArgs,@Nullable String sortOrder)
有沒有似曾相識的感覺?沒錯,SQLiteDatabase 中也是使用的這幾個方法來進行CRUD操作的,只不過它們在方法參數上稍微有一些區別。
不同於SQLiteDatabase,ContentResolver 中的增刪改查方法都是不接收表名參數的,而是使用一個Uri 參數代替,這個參數被稱為內容URI。
內容URI 給內容提供器中的數據建立了唯一標識符,它主要由兩部分組成,權限(authority)和路徑(path)。
權限是用於對不同的應用程序做區分的,一般為了避免沖突,都會采用程序包名的方式來進行命名。比如某個程序的包名是com.example.app,那麼該程序對應的權限就可以命名為com.example.app.provider。
路徑則是用於對同一應用程序中不同的表做區分的,通常都會添加到權限的後面。比如某個程序的數據庫裡存在兩張表,table1 和table2,這時就可以將路徑分別命名為/table1和/table2,然後把權限和路徑進行組合,內容URI 就變成com.example.app.provider/table1和com.example.app.provider/table2。
不過,目前還很難辨認出這兩個字符串就是兩個內容URI,我們還需要在字符串的頭部加上協議聲明。因此,內容URI 最標准的格式寫法如下:
content://com.example.app.provider/table1 content://com.example.app.provider/table2
有沒有發現,內容URI 可以非常清楚地表達出我們想要訪問哪個程序中哪張表裡的數據。也正是因此,ContentResolver 中的增刪改查方法才都接收Uri 對象作為參數,因為使用表名的話系統將無法得知我們期望訪問的是哪個應用程序裡的表。
在得到了內容URI 字符串之後,我們還需要將它解析成Uri 對象才可以作為參數傳入。解析的方法也相當簡單,代碼如下所示:
Uri uri = Uri.parse("content://com.example.app.provider/table1")
只需要調用Uri.parse()方法,就可以將內容URI 字符串解析成Uri 對象了。
現在我們就可以使用這個Uri 對象來查詢table1 表中的數據了,代碼如下所示:
Cursor cursor = getContentResolver().query( uri, projection, selection, selectionArgs, sortOrder);
這些參數和SQLiteDatabase 中query()方法裡的參數很像,但總體來說要簡單一些,畢竟這是在訪問其他程序中的數據,沒必要構建過於復雜的查詢語句。下表對使用到的這部分參數進行了詳細的解釋。
查詢完成後返回的仍然是一個CursZ喎?/kf/ware/vc/" target="_blank" class="keylink">vciC21M/zo6zV4sqxztLDx77Nv8nS1L2ryv2+3bTTQ3Vyc29yILbUz/PW0NbwuPa2wcihs/bAtMHLoaO2wcihtcTLvMK3yNTIu8rHzai5/dLGtq/TzrHqtcTOu9bDwLSx6cD6Q3Vyc29yILXEy/nT0NDQo6zIu7rz1NnIobP2w7/Su9DQ1tDP4NOmwdC1xMr9vt2jrLT6wuvI58/Cy/nKvqO6PC9jb2RlPjwvY29kZT48L2NvZGU+PC9wPg0KPHByZSBjbGFzcz0="brush:java;"> if (cursor != null) { while (cursor.moveToNext()) { String column1 = cursor.getString(cursor.getColumnIndex("column1")); int column2 = cursor.getInt(cursor.getColumnIndex("column2")); } cursor.close(); }
掌握了最難的查詢操作,剩下的增加、修改、刪除操作就更不在話下了。我們先來看看如何向table1 表中添加一條數據,代碼如下所示:
ContentValues values = new ContentValues(); values.put("column1", "text"); values.put("column2", 1); getContentResolver().insert(uri, values);
可以看到,仍然是將待添加的數據組裝到ContentValues 中,然後調用ContentResolver的insert()方法,將Uri 和ContentValues 作為參數傳入即可。
現在如果我們想要更新這條新添加的數據, 把column1 的值清空, 可以借助ContentResolver 的update()方法實現,代碼如下所示:
ContentValues values = new ContentValues(); values.put("column1", ""); getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[] {"text", "1"});
注意上述代碼使用了selection 和selectionArgs 參數來對想要更新的數據進行約束,以防止所有的行都會受影響。
最後,可以調用ContentResolver 的delete()方法將這條數據刪除掉,代碼如下所示:
getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });
那麼接下來,我們就利用目前所學的知識,看一看如何讀取
系統電話簿中的聯系人信息。
(二)讀取系統聯系人
首先,打開模擬器,手動添加幾個聯系人以便稍後讀取:
現在新建一個ContactsTest 項目,首先還是來編寫一下布局文件,這裡我們希望讀取出來的聯系人信息能夠在ListView 中顯示,因此,修改activity_main.xml 中的代碼,如下所示:
簡單起見,RelativeLayout裡就只放置了一個ListView。接著修改MainActivity 中的代碼,如下所示:
package wz.com.contactstest; import android.Manifest; import android.app.Activity; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.Build; import android.os.Bundle; import android.provider.ContactsContract; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends Activity { private static final int REQUEST_CODE_ASK_CALL_PHONE = 123; private ListView listView; private ListcontactsList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.contacts_ListView); } @Override protected void onResume() { super.onResume(); //動態權限管理 if (Build.VERSION.SDK_INT >= 23) { int checkCallPhonePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS); if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE_ASK_CALL_PHONE); return; } } else { readContacts(); ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, contactsList); listView.setAdapter(adapter); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case REQUEST_CODE_ASK_CALL_PHONE: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { readContacts(); ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, contactsList); listView.setAdapter(adapter); } else { // Permission Denied Toast.makeText(this, "READ_CONTACTS Denied", Toast.LENGTH_SHORT).show(); } break; default: super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } /** * 從通訊錄中讀取聯系人數據 */ private void readContacts() { Cursor cursor = null; try { cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null); while (cursor.moveToNext()) { String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); String phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); contactsList.add("姓名:" + name + " 電話:" + phoneNumber); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } } }
下面重點看下readContacts()方法,可以看到,這裡使用了ContentResolver 的query()方法來查詢系統的聯系人數據。不過傳入的Uri 參數怎麼有些奇怪啊, 為什麼沒有調用Uri.parse() 方法去解析一個內容URI 字符串呢?
這是因為ContactsContract.CommonDataKinds.Phone類已經幫我們做好了封裝,提供了一個CONTENT_URI常量,而這個常量就是使用Uri.parse()方法解析出來的結果。接著我們對Cursor 對象進行遍歷, 將聯系人姓名和手機號這些數據逐個取出, 聯系人姓名這一列對應的常量是ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,聯系人手機號這一列對應的常量是ContactsContract.CommonDataKinds.Phone.NUMBER。兩個數據都取出之後,將它們進行拼接,並且中間加上換行符,然後將拼接後的數據添加到ListView 裡。最後千萬不要忘記將Cursor 對象關閉掉。
需要注意的是,Android6.0及以上版本多了動態權限管理,需要處理一下。
讀取系統聯系人也是需要聲明權限的,因此修改AndroidManifest.xml 中的代碼,如下所示:
運行,點擊允許:
剛剛添加的兩個聯系人的數據都成功讀取出來了!說明跨程序訪問數據的功能確實是實現了。
未完待續。。。
手機裡設置鬧鐘需要選擇時間,那個選擇時間的控件就是滾動選擇器,前幾天用手機刷了MIUI,發現自帶的那個時間選擇器效果挺好看的,於是就自己仿寫了一個,權當練手。先來看效果:
前言:動態加載在應用開發中有著很重要的地位,當我們項目越來越大,我們可以通過插件化來減少應用的內存,然後動態加載那些插件。還有一個方面,如果我們的應用頻繁的更新,頻繁的發
由於Worker線程不能修改UI,所以當在Worker線程中接收到消息之後,需要通知主線程來更新UI。下面是一個下例子: 一 布局 二 代碼&
一、表格布局 TableLayout表格布局TableLayout以行列的形式管理子元素,每一行是一個TableRow布局對象,當然也可以是普通的View對象,Table