做Android開發(fā)已經(jīng)一兩年了建钥,回首這一兩年的歲月藤韵,雖然學(xué)習(xí)到很多技能點,但是大部分技能用過之后熊经,不久就被遺忘了泽艘,為了防止自己再去踩前面踩過的坑,我決定從今天起镐依,把自己用到的技能點做一個總結(jié)匹涮,日后能夠提高自己的工作效率,同時槐壳,希望能夠幫助到更多的開發(fā)者然低。后面會不斷的完善。
okhttp,retrofit,rxjava,mvp,插件化,熱修復(fù)(近兩年熱點技術(shù)點)
zero雳攘、開發(fā)環(huán)境
1带兜、Android開發(fā)工具集合:http://www.androiddevtools.cn/
小技巧:logt以當(dāng)前的類名做為值自動生成一個TAG常量。
一吨灭、Activity
1刚照、在活動中使用Menu;
第一步:在res目錄下創(chuàng)建一個menu文件夾喧兄;右擊menu文件夾創(chuàng)建一個文件(例如起名為“main”)无畔;
第二步:在main文件中完成下面代碼:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="Add"/>
<item
android:id="@+id/remove_item"
android:title="Remove"/>
</menu>
第三步:重現(xiàn)回到Activity,重寫onCreateOptionsMenu()方法和onOptionsItemSelected()方法吠冤,例如下面的代碼:
@Override
public boolean onCreateOptionsMenu(Menu menu){
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_item:
Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this, "You clicked Remove", Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}
2檩互、Intent在活動之間穿梭;
(1)顯示Intent:
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
(2)隱式Intent:
<activity
android:name=".SecondActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.example.activitytest.MY_CATEGORY" />
</intent-filter>
</activity>
Intent intent = new Intent("com.example.activitytest.ACTION_START");
//intent.addCategory("com.example.activitytest.MY_CATEGORY");
startActivity(intent);
只有<action>和<category>中的內(nèi)容同時能夠匹配上Intent中指定的action和category時咨演,這個活動才能響應(yīng)該Intent闸昨;每個Intent中只能指定一個action,卻能指定多個category薄风;
更多隱式Intent的用法:
(3)如下調(diào)用系統(tǒng)瀏覽器打開一個網(wǎng)頁饵较,
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.baidu.com/"));
startActivity(intent);
(4)撥打電話
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
(5)Intent向下一個活動傳遞數(shù)據(jù)(put和get),具體支持哪些數(shù)據(jù)類型遭赂,自己查看Android API文檔循诉;
(6)返回數(shù)據(jù)給上一個活動
FirstActivity:
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivityForResult(intent,1);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnedData = data.getStringExtra("data_return");
Log.d("FirstActivity", returnedData);
}
break;
default:
}
}
SecondActivity:
Intent intent = new Intent();
intent.putExtra("data_return","Hello FristActivity");
setResult(RESULT_OK,intent);
finish();
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity");
setResult(RESULT_OK, intent);
finish();
}
3、Activity的生命周期撇他;
4茄猫、Activity的啟動模式;
(1)standard模式(任務(wù)棧困肩,先進(jìn)后出)
(2)singleTop(頂端復(fù)用模式)
(3)singleTask(單實例模式)
(4)singleInstance(singleTask的增強版划纽,會單獨開啟一個任務(wù)棧)
5、動態(tài)更新應(yīng)用Icon
二锌畸、UI開發(fā)
Android 基礎(chǔ):常用布局 介紹 & 使用
1勇劣、常用控件的使用
所有的Android控件都具有android:visibility屬性,可選值有3種:visible、invisible和gone潭枣。
visible表示控件是可見的,這個值是默認(rèn)值比默,不指定android:visibility時,控件都是可見的。
invisible表示控件不可見,但是它仍然占據(jù)著原來的位置和大小,可以理解為控件變成透明狀態(tài)了盆犁。
gone則表示控件不僅不可見命咐,而且不再占用任何屏幕空間。
也可以在代碼中設(shè)置,例如:imageView.setVisibility(View.VISIBLE);
(1)TextView
android:gravity屬性來指定文字(相對于控件本身)的對齊方式,可選值有top谐岁、bottom醋奠、left瓮下、right、center等钝域。
android:layout_gravity屬性,指控件相對于父控件的對齊方式;
想要掌握更多TextView的屬性讽坏,可以自己去查閱API文檔;
(2)Button
android:textAllCaps="false",不將Button中的所有英文字母自動轉(zhuǎn)成大寫;
(3)EditText
android:hint屬性例证,EditText文本為空時顯示的提示文本;
android:maxLines="2"屬性指定EditText最大行數(shù)為兩行,這樣當(dāng)輸入的內(nèi)容超過兩行時,文本就會向下滾動,而EditText則不會再繼續(xù)拉伸路呜。
(4)ImageView
可以使用android:src屬性給ImageView指定一張圖片。也可以在代碼中通過
imageView.setImageResource(R.mipmap.ic_launcher)動態(tài)地更改ImageView中的圖片织咧。
(5)ProgressBar
style="?android:attr/progressBarStyleHorizontal"(水平進(jìn)度條屬性)
android:max="100"(給進(jìn)度條設(shè)置一個最大值)
更新進(jìn)度:
int progress = this.progressBar.getProgress();
progress = progress+10;
progressBar.setProgress(progress);
(6)AlertDialog(對話框)
小例子:
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("This is Dialog");
dialog.setMessage("Something important.");
dialog.setCancelable(false);
dialog.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.show();
(7)ProgressDialog
類似于AlertDialog,可以顯示進(jìn)度條胀葱,表示當(dāng)前操作比較耗時,讓用戶耐心等待笙蒙。
小例子:
ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setTitle("This is ProgressDialog");
progressDialog.setMessage("Loading...");
progressDialog.setCancelable(true);
progressDialog.show();
2抵屿、6種基本布局
第一種,LinearLayout線性布局捅位,注意方向
第二種轧葛,RelativeLayout相對布局,相對于父布局進(jìn)行定位艇搀,或者相對于其他控件進(jìn)行定位;
第三種尿扯,F(xiàn)rameLayout幀布局
LinearLayout支持layout_weight屬性來實現(xiàn)按比例指定控件大小的功能,其他兩個布局都不支持焰雕。
第四種衷笋,百分比布局
使用百分比布局
第一步,在build.gradle文件中添加依賴:
compile 'com.android.support:percent:24.2.1'
第二步矩屁,使用PercentFrameLayout或者PercentRelativeLayout兩種布局(注意要定義一個app的命名空間);
第三步辟宗,在控件中使用app:layout_widthPercent、app:layout_heightPercent等屬性吝秕;
第五種泊脐,AbsoluteLayout(很少使用);
第六種郭膛,TableLayout(很少使用)晨抡;
3氛悬、自定義控件
第一步则剃,引入布局;
第二步如捅,創(chuàng)建自定義控件(初始化棍现,onMeasure,onLayout镜遣,onDraw)己肮;
4士袄、ListVIew
知識點一,使用Android提供的適配器谎僻;
知識點二娄柳,定制ListView的界面(圖文混排),自己寫Adapter艘绍;
知識點三赤拒,ViewHolder的使用和convertView的復(fù)用,提高運行效率诱鞠;
知識點四挎挖,ListView的點擊事件;
5航夺、RecyclerView
知識點一蕉朵,RecyclerView的基本用法:
第一步,build.gradle中添加依賴阳掐;
第二步始衅,寫一個adapter繼承RecyclerView.Adapter(重寫 onCreateViewHolder,onBindViewHolder和getItemCount方法)
第三步缭保,在代碼中使用RecyclerView觅闽;(注意:不要忘了LayoutManager)
知識點二,實現(xiàn)橫向滾動和瀑布流布局
實現(xiàn)橫向布局:
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
實現(xiàn)瀑布流布局:
GridLayoutManager gridLayoutManager = new GridLayoutManager(this);
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
知識點三涮俄,RecyclerView點擊事件(即可以實現(xiàn)Item的點擊事件蛉拙,又可以實現(xiàn)item內(nèi)部控件的點擊事件)
6、制作Nine-Patch圖片
第一步彻亲,在Android sdk目錄中有個tools文件夾孕锄,在文件夾中找到draw9patch.bat文件(必須先將JDK的bin目錄配置到環(huán)境變量中)打開;
第二步苞尝,繪制(使用鼠標(biāo)在圖片的邊緣拖動)畸肆,擦除(按住Shirft鍵,拖動鼠標(biāo)就可以進(jìn)行擦除)
三宙址、Fragment
Fragment結(jié)合ViewPager之懶加載
Fragment懶加載和ViewPager的坑
1轴脐、Fragment的使用方式
(1)Fragment的簡單使用方法
第一步,創(chuàng)建Fragment和該Fragment對應(yīng)的布局;(LeftFragment和RightFragment)
第二步抡砂,將創(chuàng)建好的Fragment添加進(jìn)Activity中大咱,具體如下(activity布局代碼)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/left_fragment"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/right_fragment"
android:name="com.example.fragmenttest.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" />
</LinearLayout>
(2)動態(tài)添加Fragment
第一步,創(chuàng)建待添加的碎片實例注益;
例如:
replaceFragment(new RightFragment());
replaceFragment(new AnotherRightFragment());
第二步碴巾,獲取FragmentManager;
FragmentManager fragmentManager = getSupportFragmentManager();
第三步丑搔,開啟一個事務(wù)厦瓢;
FragmentTransaction transaction = fragmentManager.beginTransaction();
第四步提揍,向容器內(nèi)添加或替換碎片;
transaction.replace(R.id.right_layout, fragment);
第五步煮仇,提交事務(wù)
transaction.commit();
(3)在Fragment中模擬返回棧
上面的事務(wù)提交之前劳跃,調(diào)用
transaction.addToBackStack(null);
將RightFragment和AnotherRightFragment添加到活動中,然后按下Back鍵浙垫,程序并沒有退出售碳,而是回到了RightFragment界面,繼續(xù)按下Back鍵绞呈,RightFragment界面消失贸人,再次按下Back鍵,程序才會退出佃声。
(4)Fragment和活動之間通信
在活動中調(diào)用Fragment碎片的方法:
RightFragment rightFragment = (RightFragment)getFragmentManager();
在Fragment碎片中調(diào)用活動的方法:
MainActivity activity = (Activity)getActivity();
在AnotherRightFragment碎片中調(diào)用另一個RightFragment碎片的方法:
MainActivity activity = (Activity)getActivity();
RightFragment rightFragment = (RightFragment)getFragmentManager();
2艺智、Fragment的生命周期
3、動態(tài)加載布局
在res目錄下新建layout-large文件夾圾亏,手機(jī)上加載單頁模式十拣,平板上加載雙頁模式
單頁模式:顯示layout/activity_main,只包含一個Fragment碎片的布局志鹃;
雙頁模式:顯示layout-large/activity_main夭问,包含了兩個碎片的布局;
最小寬度限定符:在res目錄下新建layout-sw600dp文件夾曹铃,然后在這個文件夾下新建activity_main.xml布局缰趋。當(dāng)設(shè)備屏幕寬度小于600dp時,加載layout/activity_main布局陕见,當(dāng)設(shè)備屏幕寬度大于600dp時秘血,會加載layout-sw600dp/activity_main布局。
四评甜、廣播機(jī)制(Broadcast Receiver)
1灰粮、廣播機(jī)制簡介
(1)標(biāo)準(zhǔn)廣播
異步、無序忍坷、效率高粘舟,但是無法被截斷(終止);
(2)有序廣播
同步、有序(優(yōu)先級高的廣播接收器先收到廣播消息)佩研、效率低柑肴,但是可以被截斷,截斷后韧骗,后面的廣播接收器就無法收到廣播消息了嘉抒。
2、接收系統(tǒng)廣播
(1)動態(tài)注冊監(jiān)聽網(wǎng)絡(luò)變化
第一步袍暴,寫一個類繼承BroadcastReceiver些侍,然后實現(xiàn)onReceive方法授瘦;
class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connectionManager = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isAvailable()) {
Toast.makeText(context, "network is available",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "network is unavailable",
Toast.LENGTH_SHORT).show();
}
}
}
第二步逗宜,注冊廣播接收器
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
NetworkChangeReceiver networkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver, intentFilter);
第三步碍粥,取消注冊
unregisterReceiver(networkChangeReceiver);
最后月杉,不要忘了聲明權(quán)限(為了更好的保證用戶設(shè)備的安全和隱式蚤霞,Android6.0系統(tǒng)中引入了更加嚴(yán)格的運行時權(quán)限)
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
(2)靜態(tài)注冊實現(xiàn)開機(jī)啟動
第一步嘹悼,創(chuàng)建BootCompleteReceiver繼承BroadcastReceiver
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();
}
}
第二步昔馋,在AndroidMannifest清單文件中注冊該廣播
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
第三步五芝,聲明權(quán)限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
這樣開機(jī)就能收到一條Boot Complete的Toast趁猴;
3刊咳、發(fā)送自定義廣播
(1)發(fā)送標(biāo)準(zhǔn)廣播
第一步,新建一個MyBroadcastReceiver廣播接收器儡司,代碼如下:
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
}
}
第二步娱挨,修改注冊該廣播接收器,代碼如下:
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
第三步捕犬,發(fā)送廣播跷坝,代碼如下:
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
sendBroadcast(intent);
(2)發(fā)送有序廣播
第一步,接著上面的項目碉碉,新建一個新的項目并且新建AnotherBroadcastReceiver柴钻,代碼如下:
public class AnotherBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in AnotherBroadcastReceiver",
Toast.LENGTH_SHORT).show();
}
}
第二步,AndroidMannifest清單文件中注冊該廣播垢粮,代碼如下:
<receiver
android:name=".AnotherBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST" />
</intent-filter>
</receiver>
第三步贴届,重新回到上個項目,重新發(fā)送廣播蜡吧,代碼如下:
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
sendBroadcast(intent);
這樣能夠看到兩個Toast(received in MyBroadcastReceiver和received in AnotherBroadcastReceiver)
發(fā)送有序廣播
第一步粱腻,發(fā)送有序廣播,
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
sendOrderedBroadcast(intent,null);
第二步斩跌,將AnotherBroadcastReceiver的AndroidMannifest清單文件中將優(yōu)先級設(shè)置成100绍些;
android:priority="100"
第三步,在MyBroadcastReceiver截斷廣播
abortBroadcast();
這樣AnotherBroadcastReceiver就收不到廣播了耀鸦。
4柬批、使用本地廣播
前面這些廣播屬于系統(tǒng)全局廣播,可以被其他應(yīng)用程序接收到袖订,也可以接收來自其他任何應(yīng)用程序的廣播氮帐。這樣很容易引起安全性的問題,攜帶關(guān)健型數(shù)據(jù)的廣播可能被其他的應(yīng)用程序截獲洛姑,其他的程序可以不停的向我們的廣播接收器里發(fā)送各種垃圾廣播上沐。因此Android引入一套本地廣播機(jī)制。
第一步楞艾,獲取到LocalBroadcastManager實例参咙,
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this); // 獲取實例
第二步龄广,注冊廣播接收器,代碼如下:
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter); // 注冊本地廣播監(jiān)聽器
class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
}
}
第三步蕴侧,發(fā)送本地廣播择同,代碼如下:
Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
localBroadcastManager.sendBroadcast(intent); // 發(fā)送本地廣播
第四步,注銷本地廣播净宵,代碼如下:
localBroadcastManager.unregisterReceiver(localReceiver);
廣播的最佳實踐之強制下線功能敲才。
五、數(shù)據(jù)存儲
1择葡、文件存儲
(1)將數(shù)據(jù)存儲到文件中
Context類中提供了一個openFileOutput(String name, int mode)方法紧武,
Open a private file associated with this Context's application package for writing.
可以用于將數(shù)據(jù)存儲到指定的文件中。方法中第一個參數(shù)是文件名敏储,這里指定的文件名不可以包含路徑阻星,所有的文件都是默認(rèn)存儲到/data/data/<packagename>/files目錄下的。第二個參數(shù)是文件的操作模式虹曙,主要有兩種模式可選迫横,MODE_PRIVATE是默認(rèn)的操作模式,表示當(dāng)指定同樣文件名的時候酝碳,所寫入的內(nèi)容將會覆蓋原文件中的內(nèi)容矾踱;MODE_APPEND則表示如果該文件已存在,就往文件中追加內(nèi)容疏哗,不存在就創(chuàng)建新文件呛讲。
小示例:
String inputText = edit.getText().toString();
FileOutputStream out = null;
BufferedWriter writer = null;
try {
out = openFileOutput("data", Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(inputText);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
(2)從文件中讀取數(shù)據(jù)
Context類中還提供了一個openFileInput(String name)方法,
Open a private file associated with this Context's application package for reading.
參數(shù)功能類似于openFileOutput(String name, int mode)方法返奉,
小示例:
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try {
in = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(in));
String line = "";
while ((line = reader.readLine()) != null) {
content.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return content.toString();
2贝搁、SharedPreferences存儲
SharedPreferences是使用鍵值對的方式來存儲數(shù)據(jù)的,根據(jù)鍵來存取數(shù)據(jù)芽偏。
(1)將數(shù)據(jù)存儲到SharedPreferences中
Android中提供了3種方法用于得到SharedPreferences對象雷逆。
第一種,Context類中的getSharedPreferences(String name, int mode)方法污尉,
Retrieve and hold the contents of the preferences file 'name', returning a SharedPreferences through which you can retrieve and modify its values.
該方法中第一個參數(shù)用于指定SharedPreferences文件的名稱膀哲,如果指定的文件不存在則會創(chuàng)建一個,SharedPreferences文件都是存在/data/data/<packagename>/shared_prefs/目錄下的被碗。第二個參數(shù)用于指定操作模式某宪,目前只有MODE_PRIVATE這一種模式可選。
第二種锐朴,Activity類中的getPreferences(int mode)方法兴喂,
Retrieve a SharedPreferences object for accessing preferences that are private to this activity.
此方法只接收一個操作模式參數(shù),使用這個方法時會自動將當(dāng)前活動的類名作為SharedPreferences的文件名。
第三種衣迷,PreferenceManager類中的getDefaultSharedPreferences(Context context)方法畏鼓,
Gets a SharedPreferences instance that points to the default file that is used by the preference framework in the given context.
自動使用當(dāng)前應(yīng)用程序的包名作為前綴來命名SharedPreferences文件。
向SharedPreferences文件中存儲數(shù)據(jù)
第一步蘑险,調(diào)用SharedPreferences對象edit()方法來獲取一個SharedPreferences.Editor對象滴肿。
第二步岳悟,向SharedPreferences.Editor對象中添加數(shù)據(jù)佃迄,putBoolean(),putString()等方法贵少。
第三步呵俏,調(diào)用apply()方法將添加的數(shù)據(jù)提交,從而完成數(shù)據(jù)存儲操作滔灶。
小例子:
SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
editor.putString("name", "Tom");
editor.putInt("age", 28);
editor.putBoolean("married", false);
editor.apply();
(2)從SharedPreferences中讀取數(shù)據(jù)
小例子:
SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
String name = pref.getString("name", "");
int age = pref.getInt("age", 0);
boolean married = pref.getBoolean("married", false);
3普碎、SQLite數(shù)據(jù)庫存儲
(1)創(chuàng)建數(shù)據(jù)庫
第一步,寫一個類繼承SQLiteOpenHelper類录平,并實現(xiàn)onCreate()和onUpgrade()方法麻车,然后分別在這兩個方法中去實現(xiàn)創(chuàng)建、升級數(shù)據(jù)庫的邏輯斗这。(在onCreate()方法中執(zhí)行建表語句)
第二步动猬,創(chuàng)建繼承SQLiteOpenHelper類的對象,執(zhí)行g(shù)etReadableDatabase()
Create and/or open a database.或getWritableDatabase()方法表箭,
Create and/or open a database that will be used for reading and writing.
這兩個方法都可以創(chuàng)建或打開一個現(xiàn)有的數(shù)據(jù)庫(如果數(shù)據(jù)庫已存在則直接打開赁咙,否則創(chuàng)建一個新的數(shù)據(jù)庫),并返回一個可對數(shù)據(jù)庫進(jìn)行讀寫操作的對象免钻。不同的是彼水,當(dāng)數(shù)據(jù)庫不可寫入的時候(如磁盤空間已滿),getReadableDatabase()方法返回的對象將以只讀的方式去打開數(shù)據(jù)庫极舔,而getWritableDatabase()方法則將出現(xiàn)異常凤覆。
這樣在/data/data/<package name>/databases/目錄下就會創(chuàng)建一個數(shù)據(jù)庫文件。
小示例:
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
MainActivity.class
public class MainActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
Button createDatabase = (Button) findViewById(R.id.create_database);
createDatabase.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
}
}
使用adb shell來對數(shù)據(jù)庫和表的創(chuàng)建情況進(jìn)行檢查拆魏。
第一步盯桦,在系統(tǒng)變量里找到Path并點擊編輯,將platform-tools目錄配置進(jìn)去稽揭。(如果使用的是Linux或Mac系統(tǒng)俺附,可以在home路徑下編輯.bash_文件,將platform-tools目錄配置進(jìn)去即可溪掀。)
第二步事镣,打開命令行,輸入adb shell;
第三步,使用cd命令進(jìn)入到/data/data/<package name>/databases/目錄下璃哟,并使用ls命令查看改目錄里的文件(BookStore.db-journal則是為了讓數(shù)據(jù)庫能夠支持事務(wù)而產(chǎn)生的臨時日志文件)氛琢;
第三步,鍵入sqlite3 + 數(shù)據(jù)庫名 打開數(shù)據(jù)庫随闪;
第四步阳似,鍵入.table命令打開數(shù)據(jù)庫;(.help命令列出所有的命令清單進(jìn)行查看)铐伴。
第五步撮奏,可以通過.schema命令來查看它們的建表語句。之后鍵入.exit或.quit命令可以退出數(shù)據(jù)庫的編輯当宴,再鍵入exit命令就可以退出設(shè)備控制臺了畜吊。
(2)升級數(shù)據(jù)庫
在上面創(chuàng)建數(shù)據(jù)庫的代碼上進(jìn)行如下更改:
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);//添加
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists Book");
db.execSQL("drop table if exists Category");
onCreate(db);
}
MainActivity中的代碼:
public class MainActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);//版本號改為2
Button createDatabase = (Button) findViewById(R.id.create_database);
createDatabase.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
}
}
(3)添加數(shù)據(jù)
小示例:
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
// 開始組裝第一條數(shù)據(jù)
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
values.put("pages", 454);
values.put("price", 16.96);
db.insert("Book", null, values); // 插入第一條數(shù)據(jù),該方法第二個參數(shù)用于在未指定添加數(shù)據(jù)的情況下給某些可為空的列自動賦值NULL户矢,一般用不到玲献,直接傳入null即可。
values.clear();
// 開始組裝第二條數(shù)據(jù)
values.put("name", "The Lost Symbol");
values.put("author", "Dan Brown");
values.put("pages", 510);
values.put("price", 19.95);
db.insert("Book", null, values); // 插入第二條數(shù)據(jù)
(4)更新數(shù)據(jù)
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?", new String[] { "The Da Vinci Code" });//第三梯浪、第四個參數(shù)用于約束更新某一行或某幾行中的數(shù)據(jù)捌年,不指定的話默認(rèn)就是更新所有行。
(5)刪除數(shù)據(jù)
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });//第二挂洛、第三個參數(shù)又是用于約束刪除某一行或某幾行的數(shù)據(jù)礼预,不指定的話默認(rèn)就是刪除所有行。
(6)查詢數(shù)據(jù)
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 查詢Book表中所有的數(shù)據(jù)
Cursor cursor = db.query("Book", null, null, null, null, null, null);
if (cursor.moveToFirst()) {
do {
// 遍歷Cursor對象抹锄,取出數(shù)據(jù)并打印
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double price = cursor.getDouble(cursor.getColumnIndex("price"));
Log.d("MainActivity", "book name is " + name);
Log.d("MainActivity", "book author is " + author);
Log.d("MainActivity", "book pages is " + pages);
Log.d("MainActivity", "book price is " + price);
} while (cursor.moveToNext());
}
cursor.close();
(7)使用SQL操作數(shù)據(jù)庫
4逆瑞、使用LitePal操作數(shù)據(jù)庫
github上關(guān)于LitePal的使用
或者看第二行代碼中的介紹
六、跨程序共享數(shù)據(jù)(內(nèi)容提供者Content Provider)
1伙单、運行時權(quán)限
(1)Android權(quán)限機(jī)制詳解
比如一款相機(jī)應(yīng)用在運行時申請了地理位置定位權(quán)限获高,就算我拒絕了這個權(quán)限,但是我應(yīng)該仍然可以使用這個應(yīng)用的其他功能吻育,不像之前那樣不授權(quán)就無法安裝念秧。只有涉及到用戶隱私的情況下才需要申請運行時權(quán)限,下面是所有需要申請運行時權(quán)限的權(quán)限(危險權(quán)限)
(2)在程序運行時申請權(quán)限
Android6.0及以上系統(tǒng)在使用危險權(quán)限時都必須進(jìn)行運行時權(quán)限處理布疼。
小示例:
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},1);
}else {
call();
}
private void call() {
try {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
} catch (SecurityException e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case 1:
if (grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
call();
}else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
2摊趾、訪問其他程序中的數(shù)據(jù)
(1)ContentResolver的基本用法
URI:
content://com.example.app.provider/table1
協(xié)議 | authority | path
authority用于區(qū)分不同應(yīng)用程序,一般用包名 ;path則是用于對同一應(yīng)用程序中不同的表做區(qū)分的游两。
查詢一條數(shù)據(jù):
添加一條數(shù)據(jù):
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
更新一條數(shù)據(jù):
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[]{"text","1"});
刪除(一條)數(shù)據(jù):
getContentResolver().delete(uri, "column2 = ?", new String[]{"1"});
(2)讀取系統(tǒng)聯(lián)系人
小示例:
public class MainActivity extends AppCompatActivity {
ArrayAdapter<String> adapter;
List<String> contactsList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView contactsView = (ListView) findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(this, android.R.layout. simple_list_item_1, contactsList);
contactsView.setAdapter(adapter);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.READ_CONTACTS }, 1);
} else {
readContacts();
}
}
private void readContacts() {
Cursor cursor = null;
try {
// 查詢聯(lián)系人數(shù)據(jù)
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
// 獲取聯(lián)系人姓名
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
// 獲取聯(lián)系人手機(jī)號
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName + "\n" + number);
}
adapter.notifyDataSetChanged();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
}
最后砾层,不要忘了在AndroidManifest中申明權(quán)限
<uses-permission android:name="android.permission.READ_CONTACTS" />
3、創(chuàng)建自己的內(nèi)容提供器
(1)創(chuàng)建內(nèi)容提供器的步驟
第一步贱案,新建一個類MyProvider繼承ContentProvider并實現(xiàn)6個抽象方法(onCreate肛炮、query、insert、update侨糟、delete和getType方法)
image.png
(2)實現(xiàn)跨程序數(shù)據(jù)共享
小示例:
提供數(shù)據(jù):
public class DatabaseProvider extends ContentProvider {
public static final int BOOK_DIR = 0;
public static final int BOOK_ITEM = 1;
public static final int CATEGORY_DIR = 2;
public static final int CATEGORY_ITEM = 3;
public static final String AUTHORITY = "com.example.databasetest.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper dbHelper;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
}
@Override
public boolean onCreate() {
dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// 查詢數(shù)據(jù)
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
cursor = db.query("Book", projection, "id = ?", new String[] { bookId }, null, null, sortOrder);
break;
case CATEGORY_DIR:
cursor = db.query("Category", projection, selection, selectionArgs, null, null, sortOrder);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
cursor = db.query("Category", projection, "id = ?", new String[] { categoryId }, null, null, sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// 添加數(shù)據(jù)
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri uriReturn = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("Book", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
break;
case CATEGORY_DIR:
case CATEGORY_ITEM:
long newCategoryId = db.insert("Category", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId);
break;
default:
break;
}
return uriReturn;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// 更新數(shù)據(jù)
SQLiteDatabase db = dbHelper.getWritableDatabase();
int updatedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
updatedRows = db.update("Book", values, selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
updatedRows = db.update("Book", values, "id = ?", new String[] { bookId });
break;
case CATEGORY_DIR:
updatedRows = db.update("Category", values, selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
updatedRows = db.update("Category", values, "id = ?", new String[] { categoryId });
break;
default:
break;
}
return updatedRows;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 刪除數(shù)據(jù)
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deletedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
deletedRows = db.delete("Book", selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deletedRows = db.delete("Book", "id = ?", new String[] { bookId });
break;
case CATEGORY_DIR:
deletedRows = db.delete("Category", selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deletedRows = db.delete("Category", "id = ?", new String[] { categoryId });
break;
default:
break;
}
return deletedRows;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.category";
}
return null;
}
}
public class MainActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
Button createDatabase = (Button) findViewById(R.id.create_database);
createDatabase.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
// 開始組裝第一條數(shù)據(jù)
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
values.put("pages", 454);
values.put("price", 16.96);
db.insert("Book", null, values); // 插入第一條數(shù)據(jù)
values.clear();
// 開始組裝第二條數(shù)據(jù)
values.put("name", "The Lost Symbol");
values.put("author", "Dan Brown");
values.put("pages", 510);
values.put("price", 19.95);
db.insert("Book", null, values); // 插入第二條數(shù)據(jù)
}
});
Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?", new String[] { "The Da Vinci Code" });
}
});
Button deleteButton = (Button) findViewById(R.id.delete_data);
deleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });
}
});
Button queryButton = (Button) findViewById(R.id.query_data);
queryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 查詢Book表中所有的數(shù)據(jù)
Cursor cursor = db.query("Book", null, null, null, null, null, null);
if (cursor.moveToFirst()) {
do {
// 遍歷Cursor對象碍扔,取出數(shù)據(jù)并打印
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double price = cursor.getDouble(cursor.getColumnIndex("price"));
Log.d("MainActivity", "book name is " + name);
Log.d("MainActivity", "book author is " + author);
Log.d("MainActivity", "book pages is " + pages);
Log.d("MainActivity", "book price is " + price);
} while (cursor.moveToNext());
}
cursor.close();
}
});
}
}
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
// Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists Book");
db.execSQL("drop table if exists Category");
onCreate(db);
}
}
注冊
第二個應(yīng)用操作數(shù)據(jù):
public class MainActivity extends AppCompatActivity {
private String newId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 添加數(shù)據(jù)
Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
ContentValues values = new ContentValues();
values.put("name", "A Clash of Kings");
values.put("author", "George Martin");
values.put("pages", 1040);
values.put("price", 55.55);
Uri newUri = getContentResolver().insert(uri, values);
newId = newUri.getPathSegments().get(1);
}
});
Button queryData = (Button) findViewById(R.id.query_data);
queryData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 查詢數(shù)據(jù)
Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor. getColumnIndex("name"));
String author = cursor.getString(cursor. getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex ("pages"));
double price = cursor.getDouble(cursor. getColumnIndex("price"));
Log.d("MainActivity", "book name is " + name);
Log.d("MainActivity", "book author is " + author);
Log.d("MainActivity", "book pages is " + pages);
Log.d("MainActivity", "book price is " + price);
}
cursor.close();
}
}
});
Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 更新數(shù)據(jù)
Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
ContentValues values = new ContentValues();
values.put("name", "A Storm of Swords");
values.put("pages", 1216);
values.put("price", 24.05);
getContentResolver().update(uri, values, null, null);
}
});
Button deleteData = (Button) findViewById(R.id.delete_data);
deleteData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 刪除數(shù)據(jù)
Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
getContentResolver().delete(uri, null, null);
}
});
}
}