做Android开发已经一两年了,回首这一两年的岁月,虽然学习到很多技能点,但是大部分技能用过之后,不久就被遗忘了,为了防止自己再去踩前面踩过的坑,我决定从今天起,把自己用到的技能点做一个总结,日后能够提高自己的工作效率,同时,希望能够帮助到更多的开发者。后面会不断的完善。
okhttp,retrofit,rxjava,mvp,插件化,热修复(近两年热点技术点)
zero、开发环境
1、Android开发工具集合:http://www.androiddevtools.cn/
小技巧:logt以当前的类名做为值自动生成一个TAG常量。
一、Activity
1、在活动中使用Menu;
第一步:在res目录下创建一个menu文件夹;右击menu文件夹创建一个文件(例如起名为“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>
第三步:重现回到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>中的内容同时能够匹配上Intent中指定的action和category时,这个活动才能响应该Intent;每个Intent中只能指定一个action,却能指定多个category;
更多隐式Intent的用法:
(3)如下调用系统浏览器打开一个网页,
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向下一个活动传递数据(put和get),具体支持哪些数据类型,自己查看Android API文档;
(6)返回数据给上一个活动
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模式(任务栈,先进后出)
(2)singleTop(顶端复用模式)
(3)singleTask(单实例模式)
(4)singleInstance(singleTask的增强版,会单独开启一个任务栈)
5、动态更新应用Icon
二、UI开发
Android 基础:常用布局 介绍 & 使用
1、常用控件的使用
所有的Android控件都具有android:visibility属性,可选值有3种:visible、invisible和gone。
visible表示控件是可见的,这个值是默认值,不指定android:visibility时,控件都是可见的。
invisible表示控件不可见,但是它仍然占据着原来的位置和大小,可以理解为控件变成透明状态了。
gone则表示控件不仅不可见,而且不再占用任何屏幕空间。
也可以在代码中设置,例如:imageView.setVisibility(View.VISIBLE);
(1)TextView
android:gravity属性来指定文字(相对于控件本身)的对齐方式,可选值有top、bottom、left、right、center等。
android:layout_gravity属性,指控件相对于父控件的对齐方式;
想要掌握更多TextView的属性,可以自己去查阅API文档;
(2)Button
android:textAllCaps="false",不将Button中的所有英文字母自动转成大写;
(3)EditText
android:hint属性,EditText文本为空时显示的提示文本;
android:maxLines="2"属性指定EditText最大行数为两行,这样当输入的内容超过两行时,文本就会向下滚动,而EditText则不会再继续拉伸。
(4)ImageView
可以使用android:src属性给ImageView指定一张图片。也可以在代码中通过
imageView.setImageResource(R.mipmap.ic_launcher)动态地更改ImageView中的图片。
(5)ProgressBar
style="?android:attr/progressBarStyleHorizontal"(水平进度条属性)
android:max="100"(给进度条设置一个最大值)
更新进度:
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,可以显示进度条,表示当前操作比较耗时,让用户耐心等待。
小例子:
ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setTitle("This is ProgressDialog");
progressDialog.setMessage("Loading...");
progressDialog.setCancelable(true);
progressDialog.show();
2、6种基本布局
第一种,LinearLayout线性布局,注意方向
第二种,RelativeLayout相对布局,相对于父布局进行定位,或者相对于其他控件进行定位;
第三种,FrameLayout帧布局
LinearLayout支持layout_weight属性来实现按比例指定控件大小的功能,其他两个布局都不支持。
第四种,百分比布局
使用百分比布局
第一步,在build.gradle文件中添加依赖:
compile 'com.android.support:percent:24.2.1'
第二步,使用PercentFrameLayout或者PercentRelativeLayout两种布局(注意要定义一个app的命名空间);
第三步,在控件中使用app:layout_widthPercent、app:layout_heightPercent等属性;
第五种,AbsoluteLayout(很少使用);
第六种,TableLayout(很少使用);
3、自定义控件
第一步,引入布局;
第二步,创建自定义控件(初始化,onMeasure,onLayout,onDraw);
4、ListVIew
知识点一,使用Android提供的适配器;
知识点二,定制ListView的界面(图文混排),自己写Adapter;
知识点三,ViewHolder的使用和convertView的复用,提高运行效率;
知识点四,ListView的点击事件;
5、RecyclerView
知识点一,RecyclerView的基本用法:
第一步,build.gradle中添加依赖;
第二步,写一个adapter继承RecyclerView.Adapter(重写 onCreateViewHolder,onBindViewHolder和getItemCount方法)
第三步,在代码中使用RecyclerView;(注意:不要忘了LayoutManager)
知识点二,实现横向滚动和瀑布流布局
实现横向布局:
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
实现瀑布流布局:
GridLayoutManager gridLayoutManager = new GridLayoutManager(this);
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
知识点三,RecyclerView点击事件(即可以实现Item的点击事件,又可以实现item内部控件的点击事件)
6、制作Nine-Patch图片
第一步,在Android sdk目录中有个tools文件夹,在文件夹中找到draw9patch.bat文件(必须先将JDK的bin目录配置到环境变量中)打开;
第二步,绘制(使用鼠标在图片的边缘拖动),擦除(按住Shirft键,拖动鼠标就可以进行擦除)
三、Fragment
Fragment结合ViewPager之懒加载
Fragment懒加载和ViewPager的坑
1、Fragment的使用方式
(1)Fragment的简单使用方法
第一步,创建Fragment和该Fragment对应的布局;(LeftFragment和RightFragment)
第二步,将创建好的Fragment添加进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)动态添加Fragment
第一步,创建待添加的碎片实例;
例如:
replaceFragment(new RightFragment());
replaceFragment(new AnotherRightFragment());
第二步,获取FragmentManager;
FragmentManager fragmentManager = getSupportFragmentManager();
第三步,开启一个事务;
FragmentTransaction transaction = fragmentManager.beginTransaction();
第四步,向容器内添加或替换碎片;
transaction.replace(R.id.right_layout, fragment);
第五步,提交事务
transaction.commit();
(3)在Fragment中模拟返回栈
上面的事务提交之前,调用
transaction.addToBackStack(null);
将RightFragment和AnotherRightFragment添加到活动中,然后按下Back键,程序并没有退出,而是回到了RightFragment界面,继续按下Back键,RightFragment界面消失,再次按下Back键,程序才会退出。
(4)Fragment和活动之间通信
在活动中调用Fragment碎片的方法:
RightFragment rightFragment = (RightFragment)getFragmentManager();
在Fragment碎片中调用活动的方法:
MainActivity activity = (Activity)getActivity();
在AnotherRightFragment碎片中调用另一个RightFragment碎片的方法:
MainActivity activity = (Activity)getActivity();
RightFragment rightFragment = (RightFragment)getFragmentManager();
2、Fragment的生命周期
3、动态加载布局
在res目录下新建layout-large文件夹,手机上加载单页模式,平板上加载双页模式
单页模式:显示layout/activity_main,只包含一个Fragment碎片的布局;
双页模式:显示layout-large/activity_main,包含了两个碎片的布局;
最小宽度限定符:在res目录下新建layout-sw600dp文件夹,然后在这个文件夹下新建activity_main.xml布局。当设备屏幕宽度小于600dp时,加载layout/activity_main布局,当设备屏幕宽度大于600dp时,会加载layout-sw600dp/activity_main布局。
四、广播机制(Broadcast Receiver)
1、广播机制简介
(1)标准广播
异步、无序、效率高,但是无法被截断(终止);
(2)有序广播
同步、有序(优先级高的广播接收器先收到广播消息)、效率低,但是可以被截断,截断后,后面的广播接收器就无法收到广播消息了。
2、接收系统广播
(1)动态注册监听网络变化
第一步,写一个类继承BroadcastReceiver,然后实现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);
最后,不要忘了声明权限(为了更好的保证用户设备的安全和隐式,Android6.0系统中引入了更加严格的运行时权限)
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
(2)静态注册实现开机启动
第一步,创建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>
第三步,声明权限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
这样开机就能收到一条Boot Complete的Toast;
3、发送自定义广播
(1)发送标准广播
第一步,新建一个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>
第三步,发送广播,代码如下:
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
sendBroadcast(intent);
(2)发送有序广播
第一步,接着上面的项目,新建一个新的项目并且新建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>
第三步,重新回到上个项目,重新发送广播,代码如下:
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
sendBroadcast(intent);
这样能够看到两个Toast(received in MyBroadcastReceiver和received in AnotherBroadcastReceiver)
发送有序广播
第一步,发送有序广播,
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
sendOrderedBroadcast(intent,null);
第二步,将AnotherBroadcastReceiver的AndroidMannifest清单文件中将优先级设置成100;
android:priority="100"
第三步,在MyBroadcastReceiver截断广播
abortBroadcast();
这样AnotherBroadcastReceiver就收不到广播了。
4、使用本地广播
前面这些广播属于系统全局广播,可以被其他应用程序接收到,也可以接收来自其他任何应用程序的广播。这样很容易引起安全性的问题,携带关健型数据的广播可能被其他的应用程序截获,其他的程序可以不停的向我们的广播接收器里发送各种垃圾广播。因此Android引入一套本地广播机制。
第一步,获取到LocalBroadcastManager实例,
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this); // 获取实例
第二步,注册广播接收器,代码如下:
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter); // 注册本地广播监听器
class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
}
}
第三步,发送本地广播,代码如下:
Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
localBroadcastManager.sendBroadcast(intent); // 发送本地广播
第四步,注销本地广播,代码如下:
localBroadcastManager.unregisterReceiver(localReceiver);
广播的最佳实践之强制下线功能。
五、数据存储
1、文件存储
(1)将数据存储到文件中
Context类中提供了一个openFileOutput(String name, int mode)方法,
Open a private file associated with this Context's application package for writing.
可以用于将数据存储到指定的文件中。方法中第一个参数是文件名,这里指定的文件名不可以包含路径,所有的文件都是默认存储到/data/data/<packagename>/files目录下的。第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容;MODE_APPEND则表示如果该文件已存在,就往文件中追加内容,不存在就创建新文件。
小示例:
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)从文件中读取数据
Context类中还提供了一个openFileInput(String name)方法,
Open a private file associated with this Context's application package for reading.
参数功能类似于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是使用键值对的方式来存储数据的,根据键来存取数据。
(1)将数据存储到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.
该方法中第一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,SharedPreferences文件都是存在/data/data/<packagename>/shared_prefs/目录下的。第二个参数用于指定操作模式,目前只有MODE_PRIVATE这一种模式可选。
第二种,Activity类中的getPreferences(int mode)方法,
Retrieve a SharedPreferences object for accessing preferences that are private to this activity.
此方法只接收一个操作模式参数,使用这个方法时会自动将当前活动的类名作为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.
自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件。
向SharedPreferences文件中存储数据
第一步,调用SharedPreferences对象edit()方法来获取一个SharedPreferences.Editor对象。
第二步,向SharedPreferences.Editor对象中添加数据,putBoolean(),putString()等方法。
第三步,调用apply()方法将添加的数据提交,从而完成数据存储操作。
小例子:
SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
editor.putString("name", "Tom");
editor.putInt("age", 28);
editor.putBoolean("married", false);
editor.apply();
(2)从SharedPreferences中读取数据
小例子:
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数据库存储
(1)创建数据库
第一步,写一个类继承SQLiteOpenHelper类,并实现onCreate()和onUpgrade()方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。(在onCreate()方法中执行建表语句)
第二步,创建继承SQLiteOpenHelper类的对象,执行getReadableDatabase()
Create and/or open a database.或getWritableDatabase()方法,
Create and/or open a database that will be used for reading and writing.
这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将出现异常。
这样在/data/data/<package name>/databases/目录下就会创建一个数据库文件。
小示例:
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来对数据库和表的创建情况进行检查。
第一步,在系统变量里找到Path并点击编辑,将platform-tools目录配置进去。(如果使用的是Linux或Mac系统,可以在home路径下编辑.bash_文件,将platform-tools目录配置进去即可。)
第二步,打开命令行,输入adb shell;
第三步,使用cd命令进入到/data/data/<package name>/databases/目录下,并使用ls命令查看改目录里的文件(BookStore.db-journal则是为了让数据库能够支持事务而产生的临时日志文件);
第三步,键入sqlite3 + 数据库名 打开数据库;
第四步,键入.table命令打开数据库;(.help命令列出所有的命令清单进行查看)。
第五步,可以通过.schema命令来查看它们的建表语句。之后键入.exit或.quit命令可以退出数据库的编辑,再键入exit命令就可以退出设备控制台了。
(2)升级数据库
在上面创建数据库的代码上进行如下更改:
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)添加数据
小示例:
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
// 开始组装第一条数据
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); // 插入第一条数据,该方法第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般用不到,直接传入null即可。
values.clear();
// 开始组装第二条数据
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); // 插入第二条数据
(4)更新数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?", new String[] { "The Da Vinci Code" });//第三、第四个参数用于约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。
(5)删除数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });//第二、第三个参数又是用于约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。
(6)查询数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 查询Book表中所有的数据
Cursor cursor = db.query("Book", null, null, null, null, null, null);
if (cursor.moveToFirst()) {
do {
// 遍历Cursor对象,取出数据并打印
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操作数据库
4、使用LitePal操作数据库
github上关于LitePal的使用
或者看第二行代码中的介绍
六、跨程序共享数据(内容提供者Content Provider)
1、运行时权限
(1)Android权限机制详解
比如一款相机应用在运行时申请了地理位置定位权限,就算我拒绝了这个权限,但是我应该仍然可以使用这个应用的其他功能,不像之前那样不授权就无法安装。只有涉及到用户隐私的情况下才需要申请运行时权限,下面是所有需要申请运行时权限的权限(危险权限)
(2)在程序运行时申请权限
Android6.0及以上系统在使用危险权限时都必须进行运行时权限处理。
小示例:
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、访问其他程序中的数据
(1)ContentResolver的基本用法
URI:
content://com.example.app.provider/table1
协议 | authority | path
authority用于区分不同应用程序,一般用包名 ;path则是用于对同一应用程序中不同的表做区分的。
查询一条数据:
添加一条数据:
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
更新一条数据:
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[]{"text","1"});
删除(一条)数据:
getContentResolver().delete(uri, "column2 = ?", new String[]{"1"});
(2)读取系统联系人
小示例:
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 {
// 查询联系人数据
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
// 获取联系人姓名
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
// 获取联系人手机号
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中申明权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
3、创建自己的内容提供器
(1)创建内容提供器的步骤
第一步,新建一个类MyProvider继承ContentProvider并实现6个抽象方法(onCreate、query、insert、update、delete和getType方法)
(2)实现跨程序数据共享
小示例:
提供数据:
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) {
// 查询数据
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) {
// 添加数据
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) {
// 更新数据
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) {
// 删除数据
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();
// 开始组装第一条数据
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); // 插入第一条数据
values.clear();
// 开始组装第二条数据
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); // 插入第二条数据
}
});
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表中所有的数据
Cursor cursor = db.query("Book", null, null, null, null, null, null);
if (cursor.moveToFirst()) {
do {
// 遍历Cursor对象,取出数据并打印
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);
}
}
注册
第二个应用操作数据:
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) {
// 添加数据
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) {
// 查询数据
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) {
// 更新数据
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) {
// 删除数据
Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
getContentResolver().delete(uri, null, null);
}
});
}
}