詳解Android ContentProvider的基本原理和使用
一、前言
Android 的數(shù)據(jù)存儲(chǔ)方式總共有五種,分別是:Shared Preferences、網(wǎng)絡(luò)存儲(chǔ)、文件存儲(chǔ)、外儲(chǔ)存儲(chǔ)、SQLite。但一般這些存儲(chǔ)都只是在單獨(dú)的一個(gè)應(yīng)用程序之中達(dá)到一個(gè)數(shù)據(jù)的共享,有時(shí)候我們需要操作其他應(yīng)用程序的一些數(shù)據(jù),就會(huì)用到 ContentProvider。而且 Android 為常見(jiàn)的一些數(shù)據(jù)提供了默認(rèn)的 ContentProvider(包括音頻、視頻、圖片和通訊錄等)。

要實(shí)現(xiàn)與其他的 ContentProvider 通信首先要查找到對(duì)應(yīng)的 ContentProvider 進(jìn)行匹配。Android 中 ContenProvider 借助 ContentResolver 通過(guò) Uri 與其他的 ContentProvider 進(jìn)行匹配通信。
二、URI(Uniform Resource Identifier)
其它應(yīng)用可以通過(guò) ContentResolver 來(lái)訪問(wèn) ContentProvider 提供的數(shù)據(jù),而 ContentResolver 通過(guò) uri 來(lái)定位自己要訪問(wèn)的數(shù)據(jù),所以我們要先了解 uri。URI(Universal Resource Identifier)統(tǒng)一資源定位符,如果您使用過(guò)安卓的隱式啟動(dòng)就會(huì)發(fā)現(xiàn),在隱式啟動(dòng)的過(guò)程中我們也是通過(guò) uri 來(lái)定位我們需要打開(kāi)的 Activity 并且可以在 uri 中傳遞參數(shù)。
URI 為系統(tǒng)中的每一個(gè)資源賦予一個(gè)名字,比方說(shuō)通話記錄。每一個(gè) ContentProvider 都擁有一個(gè)公共的 URI,用于表示 ContentProvider 所提供的數(shù)據(jù)。URI 的格式如下:
// 規(guī)則
[scheme:][//host:port][path][?query]
// 示例
content://com.wang.provider.myprovider/tablename/id:
1.標(biāo)準(zhǔn)前綴(scheme)——content://,用來(lái)說(shuō)明一個(gè)Content Provider控制這些數(shù)據(jù);
2.URI 的標(biāo)識(shí) (host:port)—— com.wang.provider.myprovider,用于唯一標(biāo)識(shí)這個(gè) ContentProvider,外部調(diào)用者可以根據(jù)這個(gè)標(biāo)識(shí)來(lái)找到它。對(duì)于第三方應(yīng)用程序,為了保證 URI 標(biāo)識(shí)的唯一性,它必須是一個(gè)完整的、小寫(xiě)的類名。這個(gè)標(biāo)識(shí)在元素的authorities屬性中說(shuō)明,一般是定義該 ContentProvider 的包.類的名稱;
3.路徑(path)——tablename,通俗的講就是你要操作的數(shù)據(jù)庫(kù)中表的名字,或者你也可以自己定義,記得在使用的時(shí)候保持一致就可以了;
4.記錄ID(query)——id,如果URI中包含表示需要獲取的記錄的 ID,則返回該id對(duì)應(yīng)的數(shù)據(jù),如果沒(méi)有ID,就表示返回全部;
對(duì)于第三部分路徑(path)做進(jìn)一步的解釋,用來(lái)表示要操作的數(shù)據(jù),構(gòu)建時(shí)應(yīng)根據(jù)實(shí)際項(xiàng)目需求而定。如:
- 操作tablename表中id為11的記錄,構(gòu)建路徑:/tablename/11;
- 操作tablename表中id為11的記錄的name字段:tablename/11/name;
- 操作tablename表中的所有記錄:/tablename;
- 操作來(lái)自文件、xml或網(wǎng)絡(luò)等其他存儲(chǔ)方式的數(shù)據(jù),如要操作xml文件中tablename節(jié)點(diǎn)下name字段:/ tablename/name;
- 若需要將一個(gè)字符串轉(zhuǎn)換成Uri,可以使用Uri類中的parse()方法,
Uri uri = Uri.parse("content://com.wang.provider.myprovider/tablename");
再來(lái)看一個(gè)例子:
http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack
uri 的各個(gè)部分在安卓中都是可以通過(guò)代碼獲取的,下面我們就以上面這個(gè) uri 為例來(lái)說(shuō)下獲取各個(gè)部分的方法:
- getScheme():獲取 Uri 中的 scheme 字符串部分,在這里是 http
- getHost():獲取 Authority 中的 Host 字符串,即 www.baidu.com
- getPost():獲取 Authority 中的 Port 字符串,即 8080
- getPath():獲取 Uri 中 path 部分,即 wenku/jiatiao.html
- getQuery():獲取 Uri 中的 query 部分,即 id=15&name=du
三、MIME
MIME 是指定某個(gè)擴(kuò)展名的文件用一種應(yīng)用程序來(lái)打開(kāi),就像你用瀏覽器查看 PDF 格式的文件,瀏覽器會(huì)選擇合適的應(yīng)用來(lái)打開(kāi)一樣。Android 中的工作方式跟 HTTP 類似,ContentProvider 會(huì)根據(jù) URI 來(lái)返回 MIME 類型,ContentProvider 會(huì)返回一個(gè)包含兩部分的字符串。MIME 類型一般包含兩部分,如:
text/html
text/css
text/xml
application/pdf
分為類型和子類型,Android 遵循類似的約定來(lái)定義MIME類型,每個(gè)內(nèi)容類型的 Android MIME 類型有兩種形式:多條記錄(集合)和單條記錄。
集合記錄(dir):
vnd.android.cursor.dir/自定義
單條記錄(item):
vnd.android.cursor.item/自定義
vnd 表示這些類型和子類型具有非標(biāo)準(zhǔn)的、供應(yīng)商特定的形式。Android中類型已經(jīng)固定好了,不能更改,只能區(qū)別是集合還是單條具體記錄,子類型可以按照格式自己填寫(xiě)。
在使用 Intent 時(shí),會(huì)用到 MIME,根據(jù) Mimetype 打開(kāi)符合條件的活動(dòng)。
四、UriMatcher
Uri 代表要操作的數(shù)據(jù),在開(kāi)發(fā)過(guò)程中對(duì)數(shù)據(jù)進(jìn)行獲取時(shí)需要解析 Uri,Android 提供了兩個(gè)用于操作 Uri 的工具類,分別為 UriMatcher 和 ContentUris 。掌握它們的基本概念和使用方法,對(duì)一個(gè) Android 開(kāi)發(fā)者來(lái)說(shuō)是一項(xiàng)必要的技能。
UriMatcher 類用于匹配 Uri,它的使用步驟如下:
將需要匹配的Uri路徑進(jìn)行注冊(cè),代碼如下:
//常量UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼
UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//如果match()方法匹配“content://com.wang.provider.myprovider/tablename”路徑,返回匹配碼為1
sMatcher.addURI("content://com.wang.provider.myprovider", " tablename ", 1);
//如果match()方法匹配content://com.wang.provider.myprovider/tablename/11路徑,返回匹配碼為2
sMatcher.addURI("com.wang.provider.myprovider", "tablename/#", 2);
此處采用 addURI 注冊(cè)了兩個(gè)需要用到的 URI;注意,添加第二個(gè) URI 時(shí),路徑后面的 id 采用了通配符形式 “#”,表示只要前面三個(gè)部分都匹配上了就 OK。
注冊(cè)完需要匹配的 Uri 后,可以使用 sMatcher.match(Uri) 方法對(duì)輸入的 Uri 進(jìn)行匹配,如果匹配就返回對(duì)應(yīng)的匹配碼,匹配碼為調(diào)用 addURI() 方法時(shí)傳入的第三個(gè)參數(shù)。
switch (sMatcher.match(Uri.parse("content://com.zhang.provider.yourprovider/tablename/100"))) {
case 1:
//match 1, todo something
break;
case 2
//match 2, todo something
break;
default:
//match nothing, todo something
break;
}
五、ContentUris
ContentUris 類用于操作 Uri 路徑后面的 ID 部分,它有兩個(gè)比較實(shí)用的方法:withAppendedId(Uri uri, long id) 和 parseId(Uri uri)。
withAppendedId(Uri uri, long id) 用于為路徑加上 ID 部分:
Uri uri = Uri.parse("content://cn.scu.myprovider/user")
//生成后的Uri為:content://cn.scu.myprovider/user/7
Uri resultUri = ContentUris.withAppendedId(uri, 7);
parseId(Uri uri) 則從路徑中獲取 ID 部分:
Uri uri = Uri.parse("content://cn.scu.myprovider/user/7")
//獲取的結(jié)果為:7
long personid = ContentUris.parseId(uri);
ContentProvider 主要方法ContentProvider 是一個(gè)抽象類,如果我們需要開(kāi)發(fā)自己的內(nèi)容提供者我們就需要繼承這個(gè)類并復(fù)寫(xiě)其方法,需要實(shí)現(xiàn)的主要方法如下:
public boolean onCreate():在創(chuàng)建 ContentProvider 時(shí)使用public Cursor query():用于查詢指定 uri 的數(shù)據(jù)返回一個(gè) Cursorpublic Uri insert():用于向指定uri的 ContentProvider 中添加數(shù)據(jù)public int delete():用于刪除指定 uri 的數(shù)據(jù)public int update():用戶更新指定 uri 的數(shù)據(jù)public String getType():用于返回指定的 Uri 中的數(shù)據(jù) MIME 類型
數(shù)據(jù)訪問(wèn)的方法 insert,delete 和 update 可能被多個(gè)線程同時(shí)調(diào)用,此時(shí)必須是線程安全的。
如果操作的數(shù)據(jù)屬于集合類型,那么 MIME 類型字符串應(yīng)該以 vnd.android.cursor.dir/ 開(kāi)頭,
要得到所有 tablename 記錄: Uri 為 content://com.wang.provider.myprovider/tablename,那么返回的MIME類型字符串應(yīng)該為:vnd.android.cursor.dir/table。
如果要操作的數(shù)據(jù)屬于非集合類型數(shù)據(jù),那么 MIME 類型字符串應(yīng)該以 vnd.android.cursor.item/ 開(kāi)頭,
要得到 id 為 10 的 tablename 記錄,Uri 為 content://com.wang.provider.myprovider/tablename/10,那么返回的 MIME 類型字符串為:vnd.android.cursor.item/tablename 。
5.1、方法使用示例
使用 ContentResolver 對(duì) ContentProvider 中的數(shù)據(jù)進(jìn)行操作的代碼如下:
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://com.wang.provider.myprovider/tablename");
// 添加一條記錄
ContentValues values = new ContentValues();
values.put("name", "wang1");
values.put("age", 28);
resolver.insert(uri, values);
// 獲取tablename表中所有記錄
Cursor cursor = resolver.query(uri, null, null, null, "tablename data");
while(cursor.moveToNext()){
Log.i("ContentTest", "tablename_id="+ cursor.getInt(0)+ ", name="+ cursor.getString(1));
}
// 把id為1的記錄的name字段值更改新為zhang1
ContentValues updateValues = new ContentValues();
updateValues.put("name", "zhang1");
Uri updateIdUri = ContentUris.withAppendedId(uri, 2);
resolver.update(updateIdUri, updateValues, null, null);
// 刪除id為2的記錄,即字段age
Uri deleteIdUri = ContentUris.withAppendedId(uri, 2);
resolver.delete(deleteIdUri, null, null);
5.2、監(jiān)聽(tīng)數(shù)據(jù)變化
如果ContentProvider的訪問(wèn)者需要知道數(shù)據(jù)發(fā)生的變化,可以在ContentProvider發(fā)生數(shù)據(jù)變化時(shí)調(diào)用getContentResolver().notifyChange(uri, null)來(lái)通知注冊(cè)在此URI上的訪問(wèn)者。只給出類中監(jiān)聽(tīng)部分的代碼:
public class MyProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert("tablename", "tablenameid", values);
getContext().getContentResolver().notifyChange(uri, null);
}
}
而訪問(wèn)者必須使用 ContentObserver 對(duì)數(shù)據(jù)(數(shù)據(jù)采用 uri 描述)進(jìn)行監(jiān)聽(tīng),當(dāng)監(jiān)聽(tīng)到數(shù)據(jù)變化通知時(shí),系統(tǒng)就會(huì)調(diào)用 ContentObserver 的 onChange() 方法:
getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"),
true, new PersonObserver(new Handler()));
public class PersonObserver extends ContentObserver{
public PersonObserver(Handler handler) {
super(handler);
}
public void onChange(boolean selfChange) {
//to do something
}
}
六、實(shí)例說(shuō)明
數(shù)據(jù)源是 SQLite, 用 ContentResolver 操作 ContentProvider。

Constant.java(儲(chǔ)存一些常量)
public class Constant {
public static final String TABLE_NAME = "user";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_NAME = "name";
public static final String AUTOHORITY = "cn.scu.myprovider";
public static final int ITEM = 1;
public static final int ITEM_ID = 2;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/user";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/user";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY + "/user");
}
DBHelper.java (操作數(shù)據(jù)庫(kù))
public class DBHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "finch.db";
private static final int DATABASE_VERSION = 1;
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) throws SQLException {
//創(chuàng)建表格
db.execSQL("CREATE TABLE IF NOT EXISTS "+ Constant.TABLE_NAME + "("+ Constant.COLUMN_ID +" INTEGER PRIMARY KEY AUTOINCREMENT," + Constant.COLUMN_NAME +" VARCHAR NOT NULL);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) throws SQLException {
// 這里知識(shí)簡(jiǎn)單刪除并創(chuàng)建表格
// 如果需要保留原來(lái)的數(shù)據(jù),需要先備份再刪除
db.execSQL("DROP TABLE IF EXISTS "+ Constant.TABLE_NAME+";");
onCreate(db);
}
}
MyProvider.java (自定義的 ContentProvider )
public class MyProvider extends ContentProvider {
DBHelper mDbHelper = null;
SQLiteDatabase db = null;
private static final UriMatcher mMatcher;
static{
mMatcher = new UriMatcher(UriMatcher.NO_MATCH); // 注冊(cè) uri
mMatcher.addURI(Constant.AUTOHORITY,Constant.TABLE_NAME, Constant.ITEM);
mMatcher.addURI(Constant.AUTOHORITY, Constant.TABLE_NAME+"/#", Constant.ITEM_ID);
}
@Override
public String getType(Uri uri) { // 根據(jù)匹配規(guī)則返回對(duì)應(yīng)的類型
switch (mMatcher.match(uri)) {
case Constant.ITEM:
return Constant.CONTENT_TYPE;
case Constant.ITEM_ID:
return Constant.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI"+uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
long rowId;
if(mMatcher.match(uri)!=Constant.ITEM){
throw new IllegalArgumentException("Unknown URI"+uri);
}
rowId = db.insert(Constant.TABLE_NAME,null,values);
if(rowId>0){
Uri noteUri=ContentUris.withAppendedId(Constant.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
mDbHelper = new DBHelper(getContext());
db = mDbHelper.getReadableDatabase();
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
Cursor c = null;
switch (mMatcher.match(uri)) {
case Constant.ITEM:
c = db.query(Constant.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
break;
case Constant.ITEM_ID:
c = db.query(Constant.TABLE_NAME, projection,Constant.COLUMN_ID + "="+uri.getLastPathSegment(), selectionArgs, null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Unknown URI"+uri);
}
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
}
MainActivity.java(ContentResolver操作)
public class MainActivity extends Activity {
private ContentResolver mContentResolver = null;
private Cursor cursor = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.tv);
mContentResolver = getContentResolver();
tv.setText("添加初始數(shù)據(jù) ");
for (int i = 0; i < 10; i++) {
ContentValues values = new ContentValues();
values.put(Constant.COLUMN_NAME, "fanrunqi"+i);
mContentResolver.insert(Constant.CONTENT_URI, values);
}
tv.setText("查詢數(shù)據(jù) ");
cursor = mContentResolver.query(Constant.CONTENT_URI, new String[]{Constant.COLUMN_ID,Constant.COLUMN_NAME}, null, null, null);
if (cursor.moveToFirst()) {
String s = cursor.getString(cursor.getColumnIndex(Constant.COLUMN_NAME));
tv.setText("第一個(gè)數(shù)據(jù): "+s);
}
}
}
最后在manifest申明 :
<provider android:name="MyProvider" android:authorities="cn.scu.myprovider" />
七、總結(jié)
- 如何通過(guò) ContentProvider 查詢數(shù)據(jù)? 通過(guò) ContentResolver 進(jìn)行uri匹配
- 如何實(shí)現(xiàn)自己的ContentProvider? 繼承 ContentProvider,實(shí)現(xiàn)對(duì)應(yīng)的方法。在 manifest 中聲明
7.1、額外補(bǔ)充:隱式 Intent 中 <data> 標(biāo)簽
該部分內(nèi)容與ContentProvider 沒(méi)關(guān)系,只是這里講到了 URI,就順便此處在插入另外一個(gè)知識(shí)點(diǎn):Intent 中 <data> 標(biāo)簽。看不懂的可以直接略過(guò),看下一步分的內(nèi)容,此處內(nèi)容與 activity 相關(guān)。
Data 的匹配規(guī)則:如果過(guò)濾規(guī)則 intent-filter 中定義了 data,那么 Intent 中必須也要攜帶可匹配的 data
data 的語(yǔ)法如下所示:
<data android:scheme=“string”
android:host=“string”
android:port=“string”
android:path=“string”
android:pathPattern=“string”
android:pathPrefix=“string”
android:mimeType=“string”>
data 由兩部分組成:mimeType 和 URI。mimeType 可以為空,URI 一定不會(huì)為空,因?yàn)橛心J(rèn)值。mimeType 指媒體類型,比如 image/jpeg,video/* 等,可表示圖片,視頻等不同的媒體格式
示例1
data 的匹配:
<intent-filter>
<action android:name="com.action.demo1"></action>
<category android:name="android.intent.category.DEFAULT" />
<data
android:scheme="x7"
android:host="www.360.com"
/>
</intent-filter>
清單文件 intent-filter 定義的 data 中,只有 URI, 沒(méi)有 mimeType 類型,匹配如下
intent.setData(Uri.parse("x7://www.360.com"))
示例2
<intent-filter>
<action android:name="com.action.demo1"></action>
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
清單文件 intent-filter 定義的 data 中,沒(méi)有定義 URI,只有 mimeType 類型,但是 URI 卻有默認(rèn)值,URI 中的 scheme 默認(rèn)為 content 或者 file,host 一定不能為空,隨便給個(gè)字符串a(chǎn)bc 都可以,匹配如下
intent.setDataAndType(Uri.parse("content://abc"),"image/png");
注意:
content://abc 換成 file://abc 在 7.0 以上的版本(即把 targetSdkVersion 指定成 24 及之上并且在 API>=24 的設(shè)備上運(yùn)行)會(huì)出現(xiàn) FileUriExposedException 異常,google 提供了FileProvider 解決這個(gè)異常,使用它可以生成 content://Uri 來(lái)替代 file://Uri.
示例3
<intent-filter>
<action android:name="com.action.demo1"></action>
<category android:name="android.intent.category.DEFAULT" />
<data
android:mimeType="image/*"
android:scheme="x7"
android:host="www.360.com"
/>
<data
android:mimeType="video/*"
android:scheme="x7"
android:host="www.360.com"
/>
</intent-filter>
清單文件 intent-filter 定義的 data 中,URI 和 mimeType 都有, 匹配如下:
intent.setDataAndType(Uri.parse("x7://www.360.com"),"image/png");
// 或者
intent.setDataAndType(Uri.parse("x7://www.360.com"),"video/mpeg");
Inent 中攜帶的 data 標(biāo)簽對(duì)應(yīng)的數(shù)據(jù),在某一組 intent-filter 中可以找到,即匹配成功。data 標(biāo)簽數(shù)據(jù)在 intent-filter 中也可以有多組隱示啟動(dòng),防止匹配失敗的可以提前檢測(cè)。
判斷的方法如下:
PackageManager mPackageManager = getPackageManager(); //返回匹配成功中最佳匹配的一個(gè)act信息,intent 需要按照前面的把 action, data 等都設(shè)置好 ResolveInfo info = mPackageManager.resolveActivity(intent,PackageManager.MATCH_DEFAULT_ONLY); //返回所有匹配成功的act信息,是一個(gè)集合 List<ResolveInfo> infoList = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
只要上述 2 個(gè)方法的返回值不為 null,那么 startActivity 一定可以成功
以上就是詳解Android ContentProvider的基本原理和使用的詳細(xì)內(nèi)容,更多關(guān)于Android ContentProvider的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android?Studio實(shí)現(xiàn)簡(jiǎn)單計(jì)算器開(kāi)發(fā)
這篇文章主要為大家詳細(xì)介紹了Android?Studio實(shí)現(xiàn)簡(jiǎn)單計(jì)算器開(kāi)發(fā),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Android studio 連接手機(jī)調(diào)試操作步驟
這篇文章主要介紹了Android studio 連接手機(jī)調(diào)試操作步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05
Android使用Intent傳大數(shù)據(jù)簡(jiǎn)單實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Android使用Intent傳大數(shù)據(jù)簡(jiǎn)單實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
非常實(shí)用的小功能 Android應(yīng)用版本的更新實(shí)例
這篇文章主要為大家詳細(xì)介紹了一個(gè)非常實(shí)用的小功能,Android應(yīng)用版本的更新實(shí)例,感興趣的小伙伴們可以參考一下2016-08-08
Android編程實(shí)現(xiàn)壓縮圖片并加載顯示的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)壓縮圖片并加載顯示的方法,涉及Android開(kāi)發(fā)中圖片的運(yùn)算、壓縮處理操作及界面布局顯示壓縮圖片等相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-10-10
Android開(kāi)發(fā)實(shí)現(xiàn)TextView超鏈接5種方式源碼實(shí)例
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)TextView超鏈接5種方式源碼實(shí)例,需要的朋友可以參考下2020-03-03
Android Webview上的ssl warning的處理方式詳解及實(shí)例
這篇文章主要介紹了Android Webview上的ssl warning的處理方式詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02
Android使用vitamio插件實(shí)現(xiàn)視頻播放器
這篇文章主要為大家詳細(xì)介紹了Android使用vitamio實(shí)現(xiàn)視頻播放器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04
Android中外接鍵盤(pán)的檢測(cè)的實(shí)現(xiàn)
這篇文章主要介紹了Android中外接鍵盤(pán)的檢測(cè)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11

