編輯:關於Android編程
創建一個項目,在項目中利用SQLiteOpenHelper創建一個名稱為account的數據庫,並在數據庫中創建一張名為info的表。
public class MyOpenHelper extends SQLiteOpenHelper {
public MyOpenHelper(Context context) {
super(context, "account.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table info (_id integer primary key autoincrement,name varchar(20),money varchar(20))");
db.execSQL("insert into info('name','money') values ('張三','2000')");
db.execSQL("insert into info ('name','money') values ('李四','5000')");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
}
}
在MainActivity中需要調用以下代碼才能創建數據庫:
private MyOpenHelper myOpenHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myOpenHelper = new MyOpenHelper(this);
myOpenHelper.getReadableDatabase();
}
運行程序,我們利用DDMS中的FileExplorer查看我們的數據庫文件:

從上圖可以看到創建了一個account.db的數據庫,查看文件權限可以看到,對其他用戶沒有權限。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPlNRTGl0ZURhdGFiYXNl09DSu7j2vrLMrLXEt723qKOsv8nS1NaxvdO809TYxLO49sK3vrbPwrXEyv2+3b/izsS8/qGjztLDx9TZtLS9qNK7uPbP7sS/uaSzzKOs1NrV4rj2uaSzzNbQ08PV4rj2vrLMrLe9t6i3w87KYWNjb3VudC5kYqGjPC9wPg0KPHByZSBjbGFzcz0="brush:java;"> @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SQLiteDatabase db = SQLiteDatabase.openDatabase("/data/data/com.itheima.transation/databases/account.db", null, SQLiteDatabase.OPEN_READWRITE); Cursor cursor = db.query("info", null, null, null, null, null, null); if (cursor!=null && cursor.getCount()>0){ while (cursor.moveToNext()) { String name=cursor.getString(cursor.getColumnIndex("name")); String money=cursor.getString(cursor.getColumnIndex("money")); System.out.println("name"+name + "money"+money); } } }
運行結果:


以上結果說明我們使用SQLiteDatabase.openDatabase()方法打開數據庫需要有權限才能夠訪問。我們改變account.db的訪問權限,使其他用戶也能訪問該文件:

再次運行程序,訪問account.db數據庫,這時候就能夠訪問到數據庫中的數據了。查看日志輸出如下:

這種方式雖然能夠訪問到其他應用程序的數據庫,但是這種方式需要手動改變其他應用程序數據庫的訪問權限,並且這是一種非常不安全的操作,如果改變應用數據庫的訪問權限,其他程序很容易修改數據庫的內容。那麼如何才能訪問其他應用程序的數據庫呢?Google給我們提供了Android中另一個組件ContentProvider內容提供者來解決這個問題。
ContentProvider(內容提供者)是Android中的四大組件之一,在一般的開發中,可能使用的比較少。
ContentProvider為不同的軟件之間數據共享,提供統了一套接口。也就是說,如果我們想讓其他的應用使用我們自己程序內的數據,就可以使用ContentProvider定義一組對外開放的接口,從而使得其他的應用可以使用咱們應用的文件、數據庫內存儲的信息。
當然,自己開發的應用需要給其他應用共享信息的需求可能比較少見,但是在Android系統中,很多系統自帶應用,比如聯系人信息,圖片庫,音頻庫等應用,為了對其他應用暴露數據,所以就使用了ContentProvider機制。所以,學習ContentProvider的基本使用,在遇到獲取聯系人信息,圖片庫,音頻庫等需求的時候,才能更好的開發。
內容提供者定義了一組對外開放的接口,使其他應用可以訪問自己的應用的數據庫內容,下圖是外部應用訪問系統聯系人應用數據庫的原理圖:

從上圖可以看出,普通外部應用不可以直接訪問私有的數據庫,只能通過內容提供者訪問私有數據庫,內容提供者處於應用內部,在內容提供者中定義了一些訪問路徑匹配等操作,當外部應用通過路徑訪問私有數據庫時,內容提供者根據路徑匹配出具體的操作,將私有數據返回給外部應用。
(a)創建一個類繼承ContentProvider,實現其中的方法
public class AccountProvider extends ContentProvider {
//當內容提供者創建的時候調用
@Override
public boolean onCreate() {
return true;
}
//用於查詢數據庫數據,返回值是Cursor即結果的數據集
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
}
//返回MIME類型的字符串,如果返回null,說明沒有數據類型
@Override
public String getType(Uri uri) {
return null;
}
//用於向數據庫插入記錄
@Override
public Uri insert(Uri uri, ContentValues values) {
}
//用於刪除數據庫記錄
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
}
//用於更新數據庫記錄
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
}
}
(b)清單文件中配置內容提供者
(c)定義路徑匹配規則
//定義當路徑匹配成功後對指定的組件返回的返回碼
private static final int QUERYSUCESS = 1;
private static final int ADDSUCESS = 2;
private static final int DELSUCESS = 3;
private static final int UPDATESUCESS = 4;
//創建Uri的匹配對象
static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
//增加匹配規則,參數1為主機名,參數2為路徑,參數3為當路徑匹配成功後對指定的組件返回的返回碼,必須是正數。該主機名必須和清單文件中配置的主機名一致。
matcher.addURI("com.itheima.account.provider", "query", QUERYSUCESS);
matcher.addURI("com.itheima.account.provider", "add", ADDSUCESS);
matcher.addURI("com.itheima.account.provider", "delete", DELSUCESS);
matcher.addURI("com.itheima.account.provider", "update", UPDATESUCESS);
}
注意:主機名必須和清單文件中配置的主機名一致。
Uri的組成規則:協議名://主機名或authority/路徑/ID。Uri的組成可以參考下圖:

(d)創建SQLiteOpenHelper
public class MyOpenHelper extends SQLiteOpenHelper {
public MyOpenHelper(Context context) {
super(context, "account.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db){
db.execSQL("create table info (_id integer primary key autoincrement,name varchar(20),money varchar(20))");
db.execSQL("insert into info ('name','money') values ('張三','2000')");
db.execSQL("insert into info ('name','money') values ('李四','5000')");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
}
}
獲取數據庫,在ContentProvider中實現覆蓋的增刪改查方法:
@Override
public boolean onCreate() {
//在onCreate()方法中創建SQLiteOpenHelper對象
helper = new MyOpenHelper(getContext());
return true;
}
//內容提供者的查詢方法
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[]
selectionArgs, String sortOrder) {
//匹配路徑,返回值是一個int類型的返回碼,如果匹配不成功,返回-1
int code = matcher.match(uri);
if (code == QUERYSUCESS) {
SQLiteDatabase db = helper.getReadableDatabase();
//調用SQLiteDatabase對象的query()查詢數據
Cursor cursor = db.query("info", projection, selection, selectionArgs, null, null, sortOrder);
return cursor;
} else {
throw new IllegalArgumentException("路徑不匹配 請檢查");
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
int code = matcher.match(uri);
if (code == ADDSUCESS) {
SQLiteDatabase db = helper.getReadableDatabase();
long insert = db.insert("info", null, values);
return Uri.parse("com.itheima.account.provider" + insert);
} else {
throw new IllegalArgumentException("路徑不匹配 請檢查");
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int code = matcher.match(uri);
if (code == DELSUCESS) {
SQLiteDatabase db = helper.getReadableDatabase();
int delete = db.delete("info", selection, selectionArgs);
return delete;
} else {
throw new IllegalArgumentException("路徑不匹配 請檢查");
}
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[]
selectionArgs) {
int code = matcher.match(uri);
if (code == UPDATESUCESS) {
SQLiteDatabase db = helper.getReadableDatabase();
int update = db.update("info", values, selection, selectionArgs);
return update;
} else {
throw new IllegalArgumentException("路徑不匹配 請檢查");
}
}
內容提供者定義好了,那麼如何在其他應用中訪問內容提供者提供的數據呢?這時候需要用到ContentResolver來訪問。
創建另外一個項目,在項目中利用ContentResolver來訪問其他應用的內容提供者。
界面布局:
在MainActivity中通過Context獲取ContentResolver來訪問私有數據庫:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
public void add(View v){
//定義訪問路徑
Uri uri = Uri.parse("content://com.itheima.account.provider/add");
//創建一系列ContentResolver能夠處理的數據,其值為key-value的結構,key必須和數據庫字段一致
ContentValues values = new ContentValues();
values.put("name", "zhaoqi");
values.put("money", "1000");
//通過Context對象獲取ContentResolver對象,調用ContentResolver對象的insert()方法插入數據,參數1為訪問uri,參數2為插入的數據
Uri insert = getContentResolver().insert(uri, values);
System.out.println("uri--"+insert.toString());
}
public void del(View v){
Uri uri = Uri.parse("content://com.itheima.account.provider/delete");
int delete = getContentResolver().delete(uri, "name=?", new String[]{"zhaoqi"});
System.out.println("delete=="+delete);
}
public void update(View v){
Uri uri = Uri.parse("content://com.itheima.account.provider/update");
ContentValues values = new ContentValues();
values.put("money", "20000");
int update = getContentResolver().update(uri, values, "name=?", new String[]{"zhaoqi"});
System.out.println("update--"+update);
}
public void find(View v){
Uri uri = Uri.parse("content://com.itheima.account.provider/query");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor!=null && cursor.getCount()>0){
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("name"));
String money = cursor.getString(cursor.getColumnIndex("money"));
System.out.println("name"+name + "money"+money);
}
}
}
}
運行後,發現其他應用就可以通過內容提供者來訪問私有數據庫了。
注意:匹配路徑後面是可以攜帶數字的,即Uri中的ID,Android中有一個方便的api來獲取這個值:
int id = (int) ContentUris.parseId(uri);
本案例通過內容提供者獲取系統短信數據庫中的短信數據,將這些短信數據通過XML序列化到文件中。
系統短信數據的位置存放在系統短信應用包名下的databases下,如下圖:

導出數據庫,利用SQLiteStudio工具查看數據中的數據:

要備份短信數據,需要備份短信數據庫中sms表中的短信的address、date、sms三個字段的數據。此外當通過ContentResolver來查詢的時候需要提供訪問的Uri,那麼這個Uri如何獲取呢?可以查看系統短信的源碼來得到訪問Uri,因為短信源碼中肯定定義了供其他應用訪問的ContentProvider。
首先我們找到源碼目錄,在清單文件中查找:

從上圖可以得知短信Provider的主機名。
接著找到源碼中SmsProvider這個類,查看Uri後面的path:

從上圖可以得知訪問短信數據庫的Uri的權限是sms,查看匹配規則,第一個匹配規則的路徑為null,代表查詢所有數據,這樣我們就知道查詢短信數據庫的Uri為content://sms/。
接下來我們通過ContentResolver來查詢短信數據庫並將數據序列化到文件中:
public void click(View v){
try {
//創建一個xml序列化對象
XmlSerializer serializer = Xml.newSerializer();
//備份文件存儲位置
File file = new File(Environment.getExternalStorageDirectory().getPath(),"smsback.xml");
FileOutputStream fos = new FileOutputStream(file);
serializer.setOutput(fos, "utf-8");
serializer.startDocument("utf-8", true);
serializer.startTag(null, "smss");
Uri uri = Uri.parse("content://sms/");
//通過ContentResolver獲取數據庫中的內容
Cursor cursor = getContentResolver().query(uri, new String[]{"address","date","body"}, null, null, null);
while(cursor.moveToNext()){
String address = cursor.getString(0);
String date = cursor.getString(1);
String body = cursor.getString(2);
serializer.startTag(null,"sms");
serializer.startTag(null, "address");
serializer.text(address);
serializer.endTag(null, "address");
serializer.startTag(null, "date");
serializer.text(date);
serializer.endTag(null, "date");
serializer.startTag(null, "body");
serializer.text(body);
serializer.endTag(null, "body");
serializer.endTag(null, "sms");
}
cursor.close();
serializer.endTag(null, "smss");
serializer.endDocument();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
運行結果:


導出到電腦查看:

利用ContentResolver向數據庫中插入一條短信。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
Uri uri = Uri.parse("content://sms/");
ContentValues values = new ContentValues();
values.put("address", "95553");
values.put("body", "您的余額為0.00元.....");
values.put("date", System.currentTimeMillis());
getContentResolver().insert(uri, values);
}
}
加入權限:
運行結果:


首先我們查看手機系統聯系人和聯系人數據庫。


將contact2.db導出到電腦,使用SQLiteStudio查看數據庫,查看data表:

通過data表,可以發現data1字段存儲的是聯系人的信息,那麼如何區這些信息是哪個聯系人的呢?通過觀察可以發現raw_contact_id表示的就是屬於某個聯系人,然後查看raw_contacts表:

通過查看raw_contacts表,發現contact_id就與剛才data表中的raw_contact_id是對應的。接下來需要知道data表中data1字段的信息是姓名呢,還是電話或者其他的類型,這時候就需要通過minetype_id來區分信息的類型,如下圖:

接著,到mimetypes表中查找對應的mimetype_id是什麼類型,如下圖:

從上圖的mimetypes表可以看出,id為5表示手機號碼,6表示姓名,1表示郵箱。
接下來我們就通過這幾張表查詢出聯系人信息:
(a)raw_contacts表:可以得到所有聯系人的id
contact_id:聯系人id
(b)data表:聯系人的具體信息,一個信息占一行
data1:信息的具體內容
raw_contact_id:聯系人id,描述信息屬於哪個聯系人
mimetype_id:描述信息是屬於什麼類型
(c)mimetypes表:通過mimetype_id到該表查看具體類型
查詢聯系人的步驟:
1. 通過raw_contacts獲取聯系人id,表查詢一共有多少個聯系人;
2. 通過聯系人id獲取data表中的data1字段和mimetypes字段;
3. 通過mimetypes表查詢類型。
得到了如何查詢到聯系人後,接下來使用ContentResolver來查詢聯系人信息,那麼問題來了,如何知道訪問的Uri呢?這又需要通過查看聯系人源碼才能得到Uri。查看ContactsProvider源碼清單文件如下圖:

在Activity通過raw_contacts表查詢所有的contact_id即一共多少聯系人:
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Cursor cursor = getContentResolver().query(uri, new String[]{"contact_id"}, null, null, null);
while (cursor.moveToNext()) {
String contact_id = cursor.getString(0);
System.out.println("contact_id====="+ contact_id);
}
運行結果:

運行出錯,查看日志提示需要在清單文件中加入權限:
再次運行程序,運行結果獲取到了聯系人id:

通過data表查詢data1、mimetype_id字段:
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Uri datauri = Uri.parse("content://com.android.contacts/data");
Cursor cursor = getContentResolver().query(uri, new String[]{"contact_id"}, null, null, null);
while (cursor.moveToNext()) {
String contact_id = cursor.getString(0);
System.out.println("contact_id====="+ contact_id);
Cursor dataCursor = getContentResolver().query(datauri, new String[] {"data1","mimetype_id"}, "raw_contact_id=?", new String[]{contact_id}, null);
while (dataCursor.moveToNext()) {
String data1 = dataCursor.getString(0);
String mimetype_id = dataCursor.getString(1);
System.out.println("data1 = "+data1+"-----mimetype_id = "+mimetype_id);
}
}
運行結果報錯,提示無效的列mimetype_id:

為什麼會報無效的列mimetype_id呢?這是因為系統的ContentProvider在做查詢的時候不是直接查詢的mimetype_id這個字段,而是查詢view_data這個視圖,這個視圖將data表和mimetypes表聯系起來,所以查詢的字段應該是mimetypes表中的mimetype字段,如下圖:


我們將查詢的字段改成mimetype,再次查詢,運行結果如下:

拿到聯系人信息後,判斷mimetype的類型:
if ("vnd.android.cursor.item/email_v2".equals(mimetype)) {
System.out.println("郵件data:"+data1);
}else if("vnd.android.cursor.item/name".equals(mimetype)){
System.out.println("姓名data:"+data1);
}else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){
System.out.println("電話號碼data:"+data1);
}
運行結果如下:

將數據封裝成對象,定義聯系人實體bean:
public class Contact {
private String id;
private String name;
private String phone;
private String email;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Contact [id=" + id + ", name=" + name + ", phone=" + phone + ", email=" + email + "]";
}
}
查詢聯系人數據並且將數據封裝,並且創建聯系人對象集合用來存儲聯系人:
//創建保存聯系人的集合 ListcontactLists = new ArrayList (); Uri uri = Uri.parse("content://com.android.contacts/raw_contacts"); Uri datauri = Uri.parse("content://com.android.contacts/data"); Cursor cursor = getContentResolver().query(uri, new String[]{"contact_id"}, null, null, null); while(cursor.moveToNext()){ String contact_id = cursor.getString(0); //一定要判斷contact_id是否為空,因為刪除聯系人後,聯系人的數據還會存在數據庫中,但是contact_id為null if (contact_id != null) { //創建聯系人對象 Contact contact = new Contact(); //設置聯系人id contact.setId(contact_id); Cursor dataCursor = getContentResolver().query(datauri, new String[]{"data1","mimetype"}, "raw_contact_id=?", new String[]{contact_id}, null); while(dataCursor.moveToNext()){ String data1 = dataCursor.getString(0); String mimetype = dataCursor.getString(1); if ("vnd.android.cursor.item/email_v2".equals(mimetype)) { //設置聯系人email contact.setEmail(data1); }else if("vnd.android.cursor.item/name".equals(mimetype)){ //設置聯系人的姓名 contact.setName(data1); }else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){ //設置聯系人電話 contact.setPhone(data1); } } //將聯系人添加到集合中 contactLists.add(contact); } }
注意:第9行,判斷contact_id是否為空,因為刪除聯系人後,聯系人的數據還會存在數據庫中,但是contact_id為null;
谷歌為何這樣設計呢,是由於,在國外,聯系人的數據都會上傳到服務器,如果刪除聯系人,那麼聯系人的數據data1字段都需要刪除,那麼當同步的時候,就非常的麻煩。
輸入姓名,電話,郵箱,點擊“還原”按鈕,將聯系人數據插入到數據庫中。
界面布局:
MainActivity中實現插入聯系人,插入數據之前先查詢raw_contacts表,查詢一共多少聯系人數據,確定新的聯系人的id(查詢值+1),然後向raw_contacts表中插入新的聯系人id,最後向data表中插入數據(raw_contact_id,mimetype_id,data1等)。
public class MainActivity extends Activity {
private EditText et_name;
private EditText et_phone;
private EditText et_email;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_name = (EditText) findViewById(R.id.et_name);
et_phone = (EditText) findViewById(R.id.et_phone);
et_email = (EditText) findViewById(R.id.et_email);
}
public void click(View v) {
String email = et_email.getText().toString().trim();
String name = et_name.getText().toString().trim();
String phone = et_phone.getText().toString().trim();
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Uri datauri = Uri.parse("content://com.android.contacts/data");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
int count = cursor.getCount();
int contact_id = count + 1;
ContentValues values = new ContentValues();
values.put("contact_id", contact_id);
getContentResolver().insert(uri, values);
ContentValues nameValues = new ContentValues();
nameValues.put("raw_contact_id", contact_id);
nameValues.put("mimetype", "vnd.android.cursor.item/name");
nameValues.put("data1", name);
getContentResolver().insert(datauri, nameValues);
ContentValues emailValues = new ContentValues();
emailValues.put("raw_contact_id", contact_id);
emailValues.put("mimetype", "vnd.android.cursor.item/email_v2");
emailValues.put("data1", email);
getContentResolver().insert(datauri, emailValues);
ContentValues phoneValues = new ContentValues();
phoneValues.put("raw_contact_id", contact_id);
phoneValues.put("mimetype", "vnd.android.cursor.item/phone_v2");
phoneValues.put("data1", phone);
getContentResolver().insert(datauri, phoneValues);
}
}
利用ContentResolver可以獲取內容提供者提供的其他應用是私有數據庫信息,但是如果有這樣的需求,當其他應用的私有數據庫發生改變時,我們的應用能夠收到數據變化的通知,這裡就用到了ContentObserver內容觀察者來實現。
接下來,利用內容觀察者實現短信數據庫變化:
public class MainActivity extends Activity {
private Uri uri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
uri = Uri.parse("content://sms/");
//調用getContentResolver().registerContentObserver()方法注冊內容觀察者,參數1表示需要觀察的Uri,參數2表當為false表示觀察精確的Uri,true表示指定Uri或者指定Uri下的所有Uri都能匹配
getContentResolver().registerContentObserver(uri, true, new MyObserver(new Handler()));
}
//定義內容觀察者,該類繼承ContentObserver
private class MyObserver extends ContentObserver{
public MyObserver(Handler handler) {
super(handler);
}
//當內容發生變化的時候調用
@Override
public void onChange(boolean selfChange) {
Cursor cursor = getContentResolver().query(uri, new String[]{"address","body"}, null, null, "date desc");
cursor.moveToFirst();
String address = cursor.getString(0);
String body = cursor.getString(1);
System.out.println("body:"+body+"address:"+address);
super.onChange(selfChange);
}
}
}
模擬器中模擬接收一條短信:

查看日志打印,獲取到了最新的短信:

注意,在自定義的內容提供者中,我們需要在改變數據庫數據後通知內容觀察者數據發生改變:
ContentResolver cr = getContext().getContentResolver(); //發出通知,所有注冊在這個uri上的內容觀察者都可以收到通知 cr.notifyChange(uri, null);
Android基礎入門教程——10.14 Android GPS初涉
1.定位相關的一些API1)LocationManager官方API文檔:LocationManager這玩意是系統服務來的,不能直接new,需要
AndroidStudio-Eat-Guide—— 3.設置相關
Github地址:https://github.com/coder-pig/AndroidStudio-Eat-Guide1.必須記住的快捷鍵:Ctrl+Shift+A
Android中實現Webview頂部帶進度條的方法
寫這篇文章,做份備忘,簡單滴展示一個帶進度條的Webview示例,進度條位於Webview上面.示例圖如下:主Activity代碼:復制代碼 代碼如下:package c
Android系統開發(3)——Makefile的編寫
Makefile是什麼?makefile的作用:1、工程文件組織,編譯成復雜的程序2、安裝及卸載我們的程序Makefile使用示例在/home/username/make
【Android 仿微信通訊錄 導航分組列表-上】使用ItemDecoration為RecyclerView打造帶懸停頭部的分組列表
一 概述本文是Android導航分組列表系列上,因時間和篇幅原因分上下,