1 android studio
https://developer.android.google.cn/studio/
安装
创建Empty项目
选择开发语言和支持的android版本
创建模拟器
2 android工程结构
app模块
(1)mainfests,下面只有一个xml文件,AndroidMainfest.xml,它是App的运行配置文件
(2)java目录,下面有3个包,其中第一个是存放当前模块的java代码,后面两个包存放测试代码
(3)res,存放当前模块的资源文件:
drawable目录存放图形描述文件与图片文件
layout存放App页面的布局文件
mipmap存放app的启动图标
values目录存放一些常量定义文件,例如字符串常量string.xml、像素常量dimens.xml、颜色常量color.xml、样式风格常量style.xml
Gradle Script下面主要是工程的编译配置文件,主要有
(1)build.gradle,该文件分为项目级和模块级两种,用于描述App工程的编译规则
(2)proguard-rules.pro,改文件用于描述java代码的混淆规则
(3)gradle.properties,该文件用于配置编译工程的命令行参数,一般无需改动
(4)settings.gradle,该文件配置了需要编译哪些模块。初始内容为include ':app',表示只编译app模块
(5)local.properties,项目的本地配置文件,它在工程编译时自动生成,用于描述开发者电脑的环境配置,包括SDK的本地路径、NDK的本地路径等
创建新的App页面:
第一步,layout目录下创建xml文件
activity_main2.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text2"/>
</LinearLayout>
第二步,创建xml文件对应的java代码
public class MainActivity2 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
第三步,在AndroidManifest.xml中注册页面配置
<activity android:name=".MainActivity2"/>
3 简单控件
3.1 文本控件
我们创建一个新的module:chapter03
创建一个layout,activity_text_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
创建TextViewActivity.java
public class TextViewActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_text_view);
TextView tv_hello = findViewById(R.id.tv_hello);
tv_hello.setText(R.string.hello);
}
}
string.xml中定义字符串常量
<string name="hello">你好,世界</string>
4 布局
4.1 线性布局LinearLayout
线性布局内部的各视图有两种排列方式:
orientation=horizontal和vertical
线性布局的权重,指的是线性布局的下级视图各自拥有多达比例的宽高,权重属性名叫layout_weight,但该属性不在LinearLayout节点设置,而在线性布局的直接下级视图设置,表示该下级视图占据的宽高比例。layout_width=0dp时,layout_weight表示水平方向的宽高比例。layout_height=0dp时,layout_weight表示垂直方向的宽高比例
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="横排第一个"
android:textSize="17sp"
android:textColor="#000000"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="横排第二个"
android:textSize="17sp"
android:textColor="#000000"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:text="竖排第一个"
android:textSize="17sp"
android:textColor="#000000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:text="竖排第二个"
android:textSize="17sp"
android:textColor="#000000"/>
</LinearLayout>
</LinearLayout>
4.2 相对布局RelativeLayout
相对布局的下级视图位置由其他视图决定。用于确定下级视图位置的参照物分两种:与该视图自身平级的视图,该视图的上级视图。如果不设置下级视图的参照物,那么下级视图默认显示在RelativeLayout内部的左上角
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="150dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="#ffffff"
android:text="我在中间"
android:textSize="11sp"
android:textColor="#000000"/>
</RelativeLayout>
4.3 网格布局GridLayout
网格布局默认从左向右,从上向下排列。columnCount指定网格列数,rowCount指定网格行数
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnCount="2"
android:rowCount="2">
<TextView
android:layout_width="0dp"
android:layout_columnWeight="1"
android:layout_height="60dp"
android:background="#ffcccc"
android:gravity="center"
android:text="浅红色"
android:textColor="#000000"
android:textSize="17sp"/>
<TextView
android:layout_width="0dp"
android:layout_columnWeight="1"
android:layout_height="60dp"
android:background="#ffaa00"
android:gravity="center"
android:text="橙色"
android:textColor="#000000"
android:textSize="17sp"/>
<TextView
android:layout_width="0dp"
android:layout_columnWeight="1"
android:layout_height="60dp"
android:background="#00ff00"
android:gravity="center"
android:text="绿色"
android:textColor="#000000"
android:textSize="17sp"/>
<TextView
android:layout_width="0dp"
android:layout_columnWeight="1"
android:layout_height="60dp"
android:background="#660066"
android:gravity="center"
android:text="深紫色"
android:textColor="#000000"
android:textSize="17sp"/>
</GridLayout>
4.4 滚动视图ScrollView
ScrollView:垂直滚动
HorizontalScrollView:水平滚动
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="200dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<View
android:layout_width="300dp"
android:layout_height="match_parent"
android:background="#aaffff"/>
<View
android:layout_width="300dp"
android:layout_height="match_parent"
android:background="#ff0000"/>
</LinearLayout>
</HorizontalScrollView>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="#aaffff"/>
<View
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="#ff0000"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
4.5 按钮控件Button
Button由TextView派生而来。新增了两个属性textAllCaps,它指定了是否将英文字母转为大写,默认时true。onClick用于接管用户的点击事件。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="下面的按钮英文默认大写"
android:textColor="@color/black"
android:textSize="17sp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World"
android:textColor="@color/black"
android:textSize="17sp"
android:onClick="doClick"/>
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="这里查看按钮点击结果"
android:textColor="@color/black"
android:textSize="17sp"/>
</LinearLayout>
public class ButtonStyleActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_button_style);
textView = findViewById(R.id.tv_result);
}
public void doClick(View view)
{
String desc = String.format("%s 您点击了按钮:%s", DataUtil.getNowTime(), ((Button)view).getText());
textView.setText(desc);
}
}
上面例子中我们在xml文件中使用了onClick指定java代码,不建议这样,这样使布局文件和后端代码高耦合,而应该使用setOnClickListener方式添加监听器
点击监听器,通过setOnClickListener方法设置。按钮被按下少于500ms时会触发点击事件
长按监听器,通过setOnLongClickListener方法设置。按钮内按下超过500ms时触发
activity_button_click.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<Button
android:id="@+id/btn_click_single"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="单独按钮点击事件"
android:textColor="@color/black"
android:textSize="17sp"/>
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="这里查看按钮点击结果"
android:textColor="@color/black"
android:textSize="17sp"/>
<Button
android:id="@+id/btn_click_public"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="公共按钮点击事件"
android:textColor="@color/black"
android:textSize="17sp"/>
</LinearLayout>
ButtonClickActivity.java
public class ButtonClickActivity extends AppCompatActivity implements View.OnClickListener {
private TextView tv_result;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_button_click);
tv_result = findViewById(R.id.tv_result);
Button btn_click_single = findViewById(R.id.btn_click_single);
btn_click_single.setOnClickListener(new MyOnClickListener(tv_result));
Button btn_click_public = findViewById(R.id.btn_click_public);
btn_click_public.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if(view.getId() == R.id.btn_click_public) {
String desc = String.format("%s 您点击了按钮:%s", DataUtil.getNowTime(), ((Button)view).getText());
tv_result.setText(desc);
}
}
static class MyOnClickListener implements View.OnClickListener {
private final TextView textView;
public MyOnClickListener(TextView textView) {
this.textView = textView;
}
@Override
public void onClick(View view) {
String desc = String.format("%s 您点击了按钮:%s", DataUtil.getNowTime(), ((Button)view).getText());
textView.setText(desc);
}
}
}
按钮的长按示例
activity_button_long_click.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_long_click"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按钮长按事件"
android:textColor="@color/black"
android:textSize="17sp"/>
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="这里查看按钮点击结果"
android:textColor="@color/black"
android:textSize="17sp"/>
</LinearLayout>
ButtonLongClickActivity.java
public class ButtonLongClickActivity extends AppCompatActivity {
private TextView tv_result;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_button_long_click);
tv_result = findViewById(R.id.tv_result);
Button btn_long_click = findViewById(R.id.btn_long_click);
btn_long_click.setOnLongClickListener(new View.OnLongClickListener(){
@Override
public boolean onLongClick(View view) {
String desc = String.format("%s 您点击了按钮:%s", DataUtil.getNowTime(), ((Button)view).getText());
tv_result.setText(desc);
return true;
}
});
}
}
图形定制按钮
我们可以指定按钮的图形定制,比如按钮的背景,以及按下时按钮的背景
drawable/btn_nine_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/anniu" android:state_pressed="true"/>
<item android:drawable="@drawable/anniu2"/>
</selector>
activity_drawable_state.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="默认样式的按钮"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_nine_selector"
android:text="定制样式的按钮"
android:padding="5dp"/>
</LinearLayout>
4.6 图像视图ImageView
图像视图展示的图片通常在res/drawable目录。设置显式图片的方式有两种:在xml中用android:src设置和在java代码中调用setImageResource设置
activity_image_scale.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_scale"
android:layout_width="match_parent"
android:layout_height="220dp"
android:layout_marginTop="5dp"
android:src="@drawable/flower"
android:scaleType="fitCenter"/>
</LinearLayout>
ImageScaleActivity.java
public class ImageScaleActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_scale);
ImageView iv_scale = findViewById(R.id.iv_scale);
iv_scale.setImageResource(R.drawable.flower);
}
}
我们使用android:scaleType="fitCenter"调节图片的fit方式。
4.7 图像按钮ImageButton
ImageButton和Button的区别
Button即可显式文本也可显示图片,ImageButton只能显式图片
ImageButton上的图像可按比例缩放,而Button通过背景设置的图像会拉伸变形
Button只能靠背景显式一张图片,而ImageButton可分别在前景和背景显式图片,从而实现两个图片叠加的效果
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageButton
android:layout_width="match_parent"
android:layout_height="80dp"
android:src="@drawable/flower"
android:scaleType="fitCenter"/>
</LinearLayout>
4.8 Shape
drawable/shape_rect_gold.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 指定了形状内部填充的颜色 -->
<solid android:color="#ffdd66"/>
<!-- 指定了形状轮廓的粗细和颜色 -->
<stroke android:width="1dp" android:color="#aaaaaa"/>
<!-- 指定了形状四个圆角的半径 -->
<corners android:radius="10dp"/>
</shape>
drawable/shape_oval_rose.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<!-- 指定了形状内部填充的颜色 -->
<solid android:color="#ff66aa"/>
<!-- 指定了形状轮廓的粗细和颜色 -->
<stroke android:width="1dp" android:color="#aaaaaa"/>
</shape>
layout/activity_drawable_shape.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/v_content"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="10dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_rect"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="圆角矩形背景"/>
<Button
android:id="@+id/btn_oval"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="椭圆背景"/>
</LinearLayout>
</LinearLayout>
DrawableShapeActivity.java
public class DrawableShapeActivity extends AppCompatActivity implements View.OnClickListener {
private View v_content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drawable_shape);
v_content = findViewById(R.id.v_content);
findViewById(R.id.btn_rect).setOnClickListener(this);
findViewById(R.id.btn_oval).setOnClickListener(this);
v_content.setBackgroundResource(R.drawable.shape_rect_gold);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_rect:
v_content.setBackgroundResource(R.drawable.shape_rect_gold);
break;
case R.id.btn_oval:
v_content.setBackgroundResource(R.drawable.shape_oval_rose);
break;
}
}
}
4.9 其他控件后续补充
5 Activity
5.1 Activity的启动和结束
从当前页面跳到新页面:startActivity(原页面.this, 目标页面.class)
从当前页面回到上一个页面,相当于关闭当前页面:finish()
activity_act_start.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@+id/btn_act_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳到下一个页面"/>
</LinearLayout>
activity_act_finish.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_back"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="5dp"
android:src="@drawable/back"/>
<Button
android:id="@+id/btn_finish"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="完成"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按返回键或点击完成按钮均可返回上一个Activity"/>
</LinearLayout>
ActStartActivity.java
public class ActStartActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_start);
findViewById(R.id.btn_act_next).setOnClickListener(this);
}
@Override
public void onClick(View v) {
startActivity(new Intent(this, ActFinishActivity.class));
}
}
ActFinishActivity.java
public class ActFinishActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_finish);
findViewById(R.id.iv_back).setOnClickListener(this);
findViewById(R.id.btn_finish).setOnClickListener(this);
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.iv_back || v.getId() == R.id.btn_finish) {
finish();
}
}
}
修改清单文件AndroidManifest.xml
<activity
android:name=".ActFinishActivity"
android:exported="false" />
<activity
android:name=".ActStartActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
5.2 Activity生命周期
onCreate 表示Activity正在被创建,这也是Activity的生命周期的第一个方法。
onRestart 表示Activity正在重新启动,此生命周期只有在onPause与onStop都执行过才会被调用
onStart 表示Activity正在被启动,即将开始,此时Activity已经可见但是还没有出现在前台,还无法交互
onResume 表示Activity已经可见并出现在前台可以与用户进行交互
onPause 表示Activity正在停止
onStop 表示Activity停止并不可见
onDestroy 表示Activity即将被销毁,这是Activity的最后一个回调
我们还以上一节中的ActStartActivity为例说明
public class ActStartActivity extends AppCompatActivity implements View.OnClickListener {
private static String TAG = "song";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG,"ActStartActivity onCreate");
setContentView(R.layout.activity_act_start);
findViewById(R.id.btn_act_next).setOnClickListener(this);
}
@Override
public void onClick(View v) {
startActivity(new Intent(this, ActFinishActivity.class));
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG,"ActStartActivity onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG,"ActStartActivity onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG,"ActStartActivity onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG,"ActStartActivity onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG,"ActStartActivity onDestroy");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG,"ActStartActivity onRestart");
}
}
5.3 Activity启动模式
任务栈
任务栈是Activity实例的容器。通常当一个Android应用程序启动时,系统会创建一个任务栈,此后这个应用程序所启动的Activity都将在这个任务栈中被管理。
standard模式
该模式是Activity启动的默认启动方式,每启动一个Activity就会在栈顶创建一个新的实例
singleTop模式(栈顶复用模式)
启动Activity时,首先判断栈顶是否已经存在该Activity的实例,如果存在则不创建,复用栈顶的实例。
singleTask模式(栈内复用模式)
当启动一个Activity实例时,首先判断栈中是否已经存在该Activity实例,如果存在,则不创建,并且把这个Activity实例上面的所有Activity实例全部弹出栈。此后当我们点击返回按钮时,点击一次就退出页面了。
singleInstance模式
该种模式的Activity启动时,当要启动的Activity实例在栈中不存在,系统先创建一个新的任务栈,然后压入Activity;当要启动的Activity实例已存在,系统把该Activity所在的任务栈移到前台,从而使Activity展示。
例如创建三个Activity A、B、C,将B的启动模式设置成singleInstance,点击A跳转到B,点击B跳转到C,这时候点击返回按钮,你会发现会从C直接返回到A而不是B。因为B和A、C不是一个任务栈,B是单独、独立的一个任务栈。
示例:两个页面,A可以跳转到B,B也可以跳转到A,经过几轮跳转之后,点击返回B返回A,A再返回退出应用。
activity_jump_first.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_jump_second"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="跳到第二个页面"/>
</LinearLayout>
activity_jump_second.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_jump_first"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="跳到第一个页面"/>
</LinearLayout>
JumpFirstActivity.java
public class JumpFirstActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jump_first);
findViewById(R.id.btn_jump_second).setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(this,JumpSecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);//栈中存在待跳转的活动实例时,则重新创建该活动的实例,并清除原实例上方的所有实例
startActivity(intent);
}
}
JumpSecondActivity.java
public class JumpSecondActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jump_second);
findViewById(R.id.btn_jump_first).setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(this,JumpFirstActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);//栈中存在待跳转的活动实例时,则重新创建该活动的实例,并清除原实例上方的所有实例
startActivity(intent);
}
}
AndroidManifest.xml
<activity
android:name=".JumpSecondActivity"
android:exported="false" />
<activity
android:name=".JumpFirstActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
实例2:一个登陆页面,当登陆后,跳转到登陆成功页面,当在登陆成功页面返回时,不返回登陆页面,而是直接退出app
activity_login_input.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这里是登陆页面,省略了用户名密码输入框"/>
<Button
android:id="@+id/btn_jump_success"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="跳转到登陆成功页面"/>
</LinearLayout>
activity_login_success.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这里是登陆页面,当返回时不必返回登陆页面,直接退出app,请按返回键查看效果"/>
</LinearLayout>
LoginInputActivity.java
public class LoginInputActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_input);
findViewById(R.id.btn_jump_success).setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(this,LoginSuccessActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);//设置启动标志:跳转到新页面时,栈中原来实例被清空,同时开辟新的任务栈
startActivity(intent);
}
}
LoginSuccessActivity.java
public class LoginSuccessActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_success);
}
}
5.4 Intent
显式Intent
直接指定源Activity和目的Activity。
Intent intent = new Intent(this,JumpSecondActivity.class);
隐式Intent
没有明确指定要跳转的目标Activity,只给出一个动作字符串让系统自动匹配。比如调用打电话功能,我们不知道具体的Activity对应的类是什么,这时我们需要使用隐式Intent
activity_action_uri.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="点击以下按钮将向号码12345发起请求"/>
<Button
android:id="@+id/btn_dial"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="跳到拨号页面"/>
<Button
android:id="@+id/btn_sms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="跳到短信页面"/>
<Button
android:id="@+id/btn_my"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="跳到我的页面"/>
</LinearLayout>
ActionUriActivity.java
public class ActionUriActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_action_uri);
findViewById(R.id.btn_dial).setOnClickListener(this);
findViewById(R.id.btn_sms).setOnClickListener(this);
findViewById(R.id.btn_my).setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent();
String phoneNo = "12345";
switch (v.getId()) {
case R.id.btn_dial:
intent.setAction(Intent.ACTION_DIAL);//设置意图动作
Uri uri = Uri.parse("tel:"+phoneNo);
intent.setData(uri);
startActivity(intent);
break;
case R.id.btn_sms:
intent.setAction(Intent.ACTION_SENDTO);//设置意图动作
Uri uri2 = Uri.parse("smsto:"+phoneNo);
intent.setData(uri2);
startActivity(intent);
break;
case R.id.btn_my:
intent.setAction("android.intent.action.song");//设置意图动作
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
break;
}
}
}
然后,我们修改在module3中之前写过的一个app的配置文件AndroidManifest.xml(添加绿色显示的内容,表示允许其他应用打开该应用)
<activity
android:name=".ImageButtonActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.song" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
5.5 Activity间传送数据
Intent使用Bundle对象存放待传递的数据信息
向下一个Activity传送数据
activity_act_send.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="今天天气真不错"/>
<Button
android:id="@+id/btn_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="发送以上文字"/>
</LinearLayout>
activity_act_receive.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_receive"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
ActSendActivity.java
public class ActSendActivity extends AppCompatActivity implements View.OnClickListener{
private TextView tv_send;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_send);
tv_send = findViewById(R.id.tv_send);
findViewById(R.id.btn_send).setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(this, ActReceiveActivity.class);
Bundle bundle = new Bundle();
bundle.putString("request_time", DateUtil.getNowTime());
bundle.putString("request_data", tv_send.getText().toString());
intent.putExtras(bundle);
startActivity(intent);
}
}
ActReceiveActivity.java
public class ActReceiveActivity extends AppCompatActivity {
private TextView tv_receive;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_receive);
tv_receive = findViewById(R.id.tv_receive);
//从上一个页面传来得意图中获取快递包裹
Bundle bundle = getIntent().getExtras();
String request_time = bundle.getString("request_time");
String request_content = bundle.getString("request_data");
String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s", request_time, request_content);
tv_receive.setText(desc);
}
}
AndroidMainfest.xml
<activity
android:name=".ActReceiveActivity"
android:exported="false" />
<activity
android:name=".ActSendActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
向上一个Activity传送数据
上一个页面打包好请求数据,调用startActivityForResult方法执行跳转动作
下一个页面接收并解析请求数据,进行相应处理
下一个页面在返回上一个页面时,打包应答数据并调用setResult方法返回数据包裹
上一个页面重写方法onActivityResult,解析得到下一个页面的返回数据
activity_act_request.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_request"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_request"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="传送请求数据"/>
<TextView
android:id="@+id/tv_response"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
activity_act_response.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_request"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn_response"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="返回应答数据"/>
<TextView
android:id="@+id/tv_response"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
ActRequestActivity.java
public class ActRequestActivity extends AppCompatActivity implements View.OnClickListener {
private static final String mRequest = "你睡了吗,来我家睡吧";
private ActivityResultLauncher<Intent> register;
private TextView tv_response;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_request);
TextView tv_request = findViewById(R.id.tv_request);
tv_response = findViewById(R.id.tv_response);
tv_request.setText("待发送的消息为:"+mRequest);
findViewById(R.id.btn_request).setOnClickListener(this);
register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if(null != result) {
Intent intent = result.getData();
if(null != intent && result.getResultCode() == Activity.RESULT_OK) {
Bundle bundle = intent.getExtras();
String response_time = bundle.getString("response_time");
String response_data = bundle.getString("response_data");
String desc = String.format("收到请求消息:\n请求时间为:%s\n请求内容为:%s", response_time, response_data);
tv_response.setText(desc);
}
}
});
}
@Override
public void onClick(View v) {
Intent intent = new Intent(this, ActResponseActivity.class);
Bundle bundle = new Bundle();
bundle.putString("request_time", DateUtil.getNowTime());
bundle.putString("request_data", mRequest);
intent.putExtras(bundle);
register.launch(intent);
}
}
ActResponseActivity.java
public class ActResponseActivity extends AppCompatActivity implements View.OnClickListener {
private static final String mResponse = "我还没睡";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_response);
TextView tv_request = findViewById(R.id.tv_request);
//从上一页传来的数据
Bundle bundle = getIntent().getExtras();
String request_time = bundle.getString("request_time");
String request_content = bundle.getString("request_data");
String desc = String.format("收到请求消息:\n请求时间为:%s\n请求内容为:%s", request_time, request_content);
tv_request.setText(desc);
findViewById(R.id.btn_response).setOnClickListener(this);
TextView tv_response = findViewById(R.id.tv_response);
tv_response.setText("待返回的消息为:"+mResponse);
}
@Override
public void onClick(View v) {
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("response_time", DateUtil.getNowTime());
bundle.putString("response_data", mResponse);
intent.putExtras(bundle);
//携带意图返回上一个页面,RESULT_OK表示处理成功
setResult(Activity.RESULT_OK, intent);
finish();
}
}
5.6 从string.xml获取配置
activity_read_string.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_resource"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
ReadStringActivity.java
public class ReadStringActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_read_string);
TextView tv_resource = findViewById(R.id.tv_resource);
//从string.xml中获取配置信息
String value = getString(R.string.weather_str);
tv_resource.setText(value);
}
}
string.xml
<resources>
<string name="app_name">chapter04</string>
<string name="weather_str">晴天</string>
</resources>
5.7 从元数据中获取配置信息
元数据是指AndroidManifest.xml的<meta-data>标签中的配置。
代码中获取元数据的步骤:
调用getPackageManager方法获取当前应用的包管理器
调用包管理器的getActivityInfo方法获得当前活动的信息对象
活动信息对象的metaData时Bundle包裹类型,调用包裹对象的getString即可获得指定名称的参数值
AndroidManifest.xml
<activity
android:name=".MetaDataActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="weather" android:value="晴天"/>
</activity>
activity_meta_data.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_meta"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
MetaDataActivity.java
public class MetaDataActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_meta_data);
TextView tv_meta = findViewById(R.id.tv_meta);
PackageManager pm = getPackageManager();
try {
ActivityInfo info = pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
Bundle bundle = info.metaData;
String weather = bundle.getString("weather");
tv_meta.setText(weather);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
6 数据存储
1 SharePreferences
SharePreferences是Android的一个轻量级存储工具,采用的存储结构是key-value键值对的形式。
activity_share_write.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存到共享参数"/>
</LinearLayout>
ShareWriteActivity.java
public class ShareWriteActivity extends AppCompatActivity implements View.OnClickListener {
private EditText et_name;
private SharedPreferences preferences;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share_write);
et_name = findViewById(R.id.et_name);
findViewById(R.id.btn_save).setOnClickListener(this);
preferences = getSharedPreferences("config", Context.MODE_PRIVATE);
reload();
}
private void reload() {
String name = preferences.getString("name", null);
if(null != name)
{
et_name.setText(name);
}
}
@Override
public void onClick(View v) {
String name = et_name.getText().toString();
SharedPreferences.Editor editor = preferences.edit();
editor.putString("name", name);
editor.commit();
}
}
2 SQLite
2.1 使用SQLiteDatabase创建和删除数据库
activity_database.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_database_create"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="创建数据库"/>
<Button
android:id="@+id/btn_database_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="删除数据库"/>
</LinearLayout>
<TextView
android:id="@+id/tv_database"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
DatabaseActivity.java
public class DatabaseActivity extends AppCompatActivity implements View.OnClickListener {
private TextView tv_database;
private String mDatabaseName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_database);
tv_database = findViewById(R.id.tv_database);
findViewById(R.id.btn_database_create).setOnClickListener(this);
findViewById(R.id.btn_database_delete).setOnClickListener(this);
mDatabaseName = getFilesDir()+"test.db";
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_database_create:
SQLiteDatabase db = openOrCreateDatabase(mDatabaseName, Context.MODE_PRIVATE, null);
tv_database.setText("数据库创建成功");
break;
case R.id.btn_database_delete:
boolean result = deleteDatabase(mDatabaseName);
if(result) {
tv_database.setText("数据库删除成功");
}
else {
tv_database.setText("数据库删除失败");
}
break;
}
}
}
2.2 使用SQLiteOpenHelper操作数据库
activity_sqlite_helper.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="姓名"/>
<EditText
android:id="@+id/et_name"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="年龄"/>
<EditText
android:id="@+id/et_age"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_save"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="保存"/>
<Button
android:id="@+id/btn_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="删除"/>
<Button
android:id="@+id/btn_update"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="更新"/>
<Button
android:id="@+id/btn_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="查询"/>
</LinearLayout>
</LinearLayout>
SQLiteHelperActivity.java
public class SQLiteHelperActivity extends AppCompatActivity implements View.OnClickListener {
private EditText et_name;
private EditText et_age;
private UserDbHelper mHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite_helper);
et_name = findViewById(R.id.et_name);
et_age = findViewById(R.id.et_age);
findViewById(R.id.btn_save).setOnClickListener(this);
findViewById(R.id.btn_delete).setOnClickListener(this);
findViewById(R.id.btn_update).setOnClickListener(this);
findViewById(R.id.btn_query).setOnClickListener(this);
}
@Override
protected void onStart() {
super.onStart();
mHelper = UserDbHelper.getInstance(this);
mHelper.openWriteLink();
mHelper.openReadLink();
}
@Override
protected void onStop() {
super.onStop();
mHelper.closeLink();
}
@Override
public void onClick(View v) {
String name = et_name.getText().toString();
String age = et_age.getText().toString();
User user = null;
switch (v.getId()) {
case R.id.btn_save:
user = new User(1,name,Integer.parseInt(age),1,1,false);
if (mHelper.insert(user) > 0) {
System.out.println("insert success");
Toast.makeText(this,"添加成功",Toast.LENGTH_SHORT).show();
}
break;
case R.id.btn_delete:
System.out.println(name);
if (mHelper.deleteByName(name) > 0) {
System.out.println("delete success");
Toast.makeText(this,"delete成功",Toast.LENGTH_SHORT).show();
}
break;
case R.id.btn_update:
user = new User(1,name,Integer.parseInt(age),1,1,false);
if (mHelper.update(user) > 0) {
System.out.println("update success");
Toast.makeText(this,"update成功",Toast.LENGTH_SHORT).show();
}
break;
case R.id.btn_query:
List<User> list = mHelper.queryAll();
for(User u : list) {
Log.d("song", u.toString());
}
break;
}
}
}
数据库操作类
public class UserDbHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "user.db";
public static final String TABLE_NAME = "user_info";
private static final int DB_VERSION = 1;
public static UserDbHelper mHelper = null;
private SQLiteDatabase mRDB = null;
private SQLiteDatabase mWDB = null;
private UserDbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public static UserDbHelper getInstance(Context context) {
if(null == mHelper) {
mHelper = new UserDbHelper(context);
}
return mHelper;
}
public SQLiteDatabase openReadLink() {
if(null == mRDB || !mRDB.isOpen()) {
mRDB = mHelper.getReadableDatabase();
}
return mRDB;
}
public SQLiteDatabase openWriteLink() {
if(null == mWDB || !mWDB.isOpen()) {
mWDB = mHelper.getWritableDatabase();
}
return mWDB;
}
public void closeLink() {
if(null != mRDB && mRDB.isOpen()) {
mRDB.close();
mRDB = null;
}
if(null != mWDB && mWDB.isOpen()) {
mWDB.close();
mWDB = null;
}
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "CREATE TABLE IF NOT EXISTS "+TABLE_NAME+"(_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,name VARCHAR NOT NULL,age INTEGER NOT NULL,height LONG NOT NULL,weight FLOAT NOT NULL,married INTEGER NOT NULL);";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public long insert(User user) {
ContentValues values = new ContentValues();
values.put("name", user.name);
values.put("age", user.age);
values.put("height", user.height);
values.put("weight", user.weight);
values.put("married", user.married);
return mWDB.insert(TABLE_NAME,null,values);
}
public long deleteByName(String name) {
return mWDB.delete(TABLE_NAME,"name=?",new String[]{name});
}
public long update(User user) {
ContentValues values = new ContentValues();
values.put("name", user.name);
values.put("age", user.age);
values.put("height", user.height);
values.put("weight", user.weight);
values.put("married", user.married);
return mWDB.update(TABLE_NAME,values,"name=?",new String[] {user.name});
}
public List<User> queryAll() {
List<User> list = new ArrayList<>();
Cursor cursor = mRDB.query(TABLE_NAME,null, null,null,null,null,null);
while (cursor.moveToNext()) {
User user = new User();
user.id = cursor.getInt(0);
user.name = cursor.getString(1);
user.age = cursor.getInt(2);
user.height = cursor.getLong(3);
user.weight = cursor.getFloat(4);
user.married = (cursor.getInt(5) == 0) ? false :true;
list.add(user);
}
return list;
}
}
实体类
public class User {
public int id;
public String name;
public int age;
public long height;
public float weight;
public boolean married;
public User() {
}
public User(int id, String name, int age, long height, float weight, boolean married) {
this.id = id;
this.name = name;
this.age = age;
this.height = height;
this.weight = weight;
this.married = married;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", height=" + height +
", weight=" + weight +
", married=" + married +
'}';
}
}
我么可以在App Inspection查看数据库
2.3 数据库版本升级
指的是数据库的版本会有变化,如果代码中版本和数据库版本不一致,则会执行SQLiteOpenHelper的onUpdate方法。
2.4 Room框架
Room是谷歌推出的数据库处理框架,基于SQLite,通过注解技术极大地简化了数据库操作。
activity_room_write.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="书籍名称"/>
<EditText
android:id="@+id/et_name"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="书籍作者"/>
<EditText
android:id="@+id/et_author"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_save"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="保存"/>
<Button
android:id="@+id/btn_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="删除"/>
<Button
android:id="@+id/btn_update"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="更新"/>
<Button
android:id="@+id/btn_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="查询"/>
</LinearLayout>
</LinearLayout>
RoomWriteActivity.java
public class RoomWriteActivity extends AppCompatActivity implements View.OnClickListener {
private EditText et_author;
private EditText et_name;
private BookDao bookDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_room_write);
et_name = findViewById(R.id.et_name);
et_author = findViewById(R.id.et_author);
findViewById(R.id.btn_save).setOnClickListener(this);
findViewById(R.id.btn_delete).setOnClickListener(this);
findViewById(R.id.btn_update).setOnClickListener(this);
findViewById(R.id.btn_query).setOnClickListener(this);
bookDao = MyApplication.getInstance().getBookDatabase().bookDao();
}
@Override
public void onClick(View v) {
String name = et_name.getText().toString();
String author = et_author.getText().toString();
switch (v.getId()) {
case R.id.btn_save:
BookInfo b1 = new BookInfo();
b1.setName(name);
b1.setAuthor(author);
bookDao.insert(b1);
break;
case R.id.btn_query:
List<BookInfo> list = bookDao.queryAll();
for (BookInfo bookInfo : list) {
Log.d("song", bookInfo.toString());
}
break;
}
}
}
BookInfo.java
@Entity
public class BookInfo {
@PrimaryKey(autoGenerate = true)
private int id;
private String name;
private String author;
//getter and setters
@Override
public String toString() {
return "BookInfo{" + "id=" + id +", name='" + name + ", author='" + author + "}";
}
}
BookDao.java
@Dao
public interface BookDao {
@Insert
void insert(BookInfo... book);
@Delete
void delete(BookInfo... book);
@Update
int update(BookInfo... book);
@Query("SELECT * FROM bookinfo")
List<BookInfo> queryAll();
}
BookDatabase.java
@Database(entities = {BookInfo.class}, version = 1, exportSchema = true)
public abstract class BookDatabase extends RoomDatabase {
public abstract BookDao bookDao();
}
build.gradle
android {
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":"$projectDir/schemas".toString()]//指定数据库schema导出的位置
}
}
}
}
dependencies {
implementation 'androidx.room:room-runtime:2.4.2'
annotationProcessor 'androidx.room:room-compiler:2.4.2'
}
3 存储空间
app的存储空间有4种:内部私有、内部公共、外部私有、外部公共;内部私有和外部私有存储空间在应用卸载时删除。
activity_file_write.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="姓名"/>
<EditText
android:id="@+id/et_name"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="年龄"/>
<EditText
android:id="@+id/et_age"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存"/>
<Button
android:id="@+id/btn_read"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="读取"/>
<TextView
android:id="@+id/tv_txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
FileWriteActivity.java
public class FileWriteActivity extends AppCompatActivity implements View.OnClickListener {
private EditText et_name;
private EditText et_age;
private TextView tv_txt;
private String path;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file_write);
et_name = findViewById(R.id.et_name);
et_age = findViewById(R.id.et_age);
tv_txt = findViewById(R.id.tv_txt);
findViewById(R.id.btn_save).setOnClickListener(this);
findViewById(R.id.btn_read).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_save:
String name = et_name.getText().toString();
String age = et_age.getText().toString();
String directory = null;
//外部存储的私有空间
directory = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();
//外部存储的公共空间
directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
//内部存储的私有空间
directory = getFilesDir().toString();
path = directory + File.separatorChar + System.currentTimeMillis() + ".txt";
FileUtil.saveText(path,"姓名:"+name+",年龄:"+age);
System.out.println("保存成功");
break;
case R.id.btn_read:
tv_txt.setText(FileUtil.readText(path));
break;
}
}
}
FileUtil.java
public class FileUtil {
//把文本写到指定路径下的文件中
public static void saveText(String path, String txt) {
BufferedWriter os = null;
try {
os = new BufferedWriter(new FileWriter(path));
os.write(txt);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//从指定路径下的文件中读取文件中的内容
public static String readText(String path) {
BufferedReader is = null;
String str = "";
try {
is = new BufferedReader(new FileReader(path));
String line = null;
while((line = is.readLine()) != null) {
str = str+line;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return str;
}
}
AndroidManifest.xml
该文件中需要增加如下内容
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:requestLegacyExternalStorage="true"
</application>
4 存储卡读写图片文件
activity_image_write.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存"/>
<Button
android:id="@+id/btn_read"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="读取"/>
<ImageView
android:id="@+id/iv_content"
android:layout_width="match_parent"
android:layout_height="400dp"
android:scaleType="center"/>
</LinearLayout>
ImageWriteActivity.java
public class ImageWriteActivity extends AppCompatActivity implements View.OnClickListener {
private ImageView iv_content;
private String path;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_write);
iv_content = findViewById(R.id.iv_content);
findViewById(R.id.btn_save).setOnClickListener(this);
findViewById(R.id.btn_read).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_save:
//获取当前App私有下载目录
path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString()+ File.pathSeparatorChar+System.currentTimeMillis()+".jpeg";
//从指定的资源文件中获取位图对象
Bitmap b1 = BitmapFactory.decodeResource(getResources(), R.drawable.flower);
//把位图对象保存为图片文件
FileUtil.saveImage(path, b1);
break;
case R.id.btn_read:
//方式1
// Bitmap b2 = FileUtil.readImage(path);
// iv_content.setImageBitmap(b2);
//方式2
// Bitmap b2 = BitmapFactory.decodeFile(path);
// iv_content.setImageBitmap(b2);
//方式3
iv_content.setImageURI(Uri.parse(path));
break;
}
}
}
FileUtil.java
public static Bitmap readImage(String path) {
Bitmap bitmap = null;
FileInputStream fis = null;
try {
fis = new FileInputStream(path);
bitmap = BitmapFactory.decodeStream(fis);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if(fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
7 Application
Application是Android的一大组件,在App运行过程中有且仅有一个Application对象贯穿整个生命周期
MyApplication.java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Log.d("song", "onCreate");
}
@Override
public void onTerminate() {
super.onTerminate();
Log.d("song", "onTerminate");
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d("song", "onConfigurationChanged");
}
}
AndroidManifest.xml
如果我们不创建MyApplication,应用程序有一个默认的Application。如果我们想使用自己定义的MyApplication,则需要在这个xml中指定。如下
<application
android:name=".MyApplication"
使用Application存储信息
activity_app_write.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存到Application"/>
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
AppWriteActivity.java
public class AppWriteActivity extends AppCompatActivity implements View.OnClickListener {
private EditText et_name;
private MyApplication app;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_app_write);
et_name = findViewById(R.id.et_name);
app = MyApplication.getInstance();
findViewById(R.id.btn_save).setOnClickListener(this);
}
@Override
public void onClick(View v) {
String name = et_name.getText().toString();
app.infoMap.put("name",name);
TextView tv_content = findViewById(R.id.tv_content);
tv_content.setText(app.infoMap.get("name"));
}
}
MyApplication.java
public class MyApplication extends Application {
private static MyApplication myApp;
public Map<String,String> infoMap = new HashMap<>();
public static MyApplication getInstance() {
return myApp;
}
@Override
public void onCreate() {
super.onCreate();
myApp = this;
Log.d("song", "onCreate");
}
@Override
public void onTerminate() {
super.onTerminate();
Log.d("song", "onTerminate");
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d("song", "onConfigurationChanged");
}
}
8 ContentProvider
ContentProvider为App存取内部数据提供统一的外部接口,让不同的应用之间得以共享数据。
8.1 ContentProvider的基本使用
下面实例我们创建两个module:chapter07-server和chapter07-client,server为内容提供者,client通过这个Provider进行数据的插入、查询和删除。
chapter07-server
先创建一个provider
UserInfoProvider.java
public class UserInfoProvider extends ContentProvider {
private UserDbHelper dbHelper;
@Override
public boolean onCreate() {
Log.d("song","UserInfoProvider onCreate");
dbHelper = UserDbHelper.getInstance(getContext());
return true;
}
//content://com.szj.chapter07_server.provider.UserInfoProvider/user
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.d("song","UserInfoProvider insert");
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.insert("user_info", null, values);
return uri;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Log.d("song","UserInfoProvider query");
SQLiteDatabase db = dbHelper.getReadableDatabase();
return db.query("user_info", projection,selection,selectionArgs,null,null,null);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = db.delete("user_info",selection,selectionArgs);
db.close();
return count;
}
@Override
public String getType(Uri uri) {
// TODO: Implement this to handle requests for the MIME type of the data
// at the given URI.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO: Implement this to handle requests to update one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
}
UserDbHelper.java
public class UserDbHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "user.db";
public static final String TABLE_NAME = "user_info";
private static final int DB_VERSION = 1;
public static UserDbHelper mHelper = null;
private UserDbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public static UserDbHelper getInstance(Context context) {
if(null == mHelper) {
mHelper = new UserDbHelper(context);
}
return mHelper;
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "CREATE TABLE IF NOT EXISTS "+TABLE_NAME+"(_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,name VARCHAR NOT NULL,age INTEGER NOT NULL,height LONG NOT NULL,weight FLOAT NOT NULL,married INTEGER NOT NULL);";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
AndroidMainfest.xml
修改如下标红加粗的部分,为Provider的全限定名。这个表示了该Provider的唯一标识。
<provider
android:name=".provider.UserInfoProvider"
android:authorities="com.szj.chapter07_server.provider.UserInfoProvider"
android:enabled="true"
android:exported="true"></provider>
chapter7-client
activity-content-write.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="姓名"/>
<EditText
android:id="@+id/et_name"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="年龄"/>
<EditText
android:id="@+id/et_age"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_save"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="保存"/>
<Button
android:id="@+id/btn_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="删除"/>
<Button
android:id="@+id/btn_update"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="更新"/>
<Button
android:id="@+id/btn_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="查询"/>
</LinearLayout>
</LinearLayout>
ContentWriteActivity.java
public class ContentWriteActivity extends AppCompatActivity implements View.OnClickListener {
private EditText et_name;
private EditText et_age;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content_write);
et_name = findViewById(R.id.et_name);
et_age = findViewById(R.id.et_age);
findViewById(R.id.btn_save).setOnClickListener(this);
findViewById(R.id.btn_delete).setOnClickListener(this);
findViewById(R.id.btn_update).setOnClickListener(this);
findViewById(R.id.btn_query).setOnClickListener(this);
}
@Override
public void onClick(View v) {
String name = et_name.getText().toString();
String age = et_age.getText().toString();
Uri uri = Uri.parse("content://com.szj.chapter07_server.provider.UserInfoProvider/user");
switch (v.getId()) {
case R.id.btn_save:
ContentValues values = new ContentValues();
values.put("name", name);
values.put("age", Integer.parseInt(age));
values.put("height", 1);
values.put("weight", 1);
values.put("married", 1);
getContentResolver().insert(uri, values);
break;
case R.id.btn_delete:
int count = getContentResolver().delete(uri,"name=?",new String[]{"songzhenjing"});
if(count > 0) {
Log.d("song","delete success");
}
break;
case R.id.btn_query:
Cursor cursor = getContentResolver().query(uri, null, null, null, null, null);
if(cursor != null) {
while (cursor.moveToNext()) {
String name1 = cursor.getString(1);
Log.d("song", name1);
}
cursor.close();
}
break;
}
}
}
AndroidManifest.xml
<!-- 出于安全考虑,android11要求应用事先说明需要访问的其他软件包 -->
<queries>
<package android:name="com.szj.chapter07_server"/>
</queries>
先启动server,再启动client。然后我们先新增记录,再查询记录再删除记录,可以观察到,可以成功通过Provider实现两个App之间的通信。
8.2 动态申请权限
动态申请权限的步骤:
检查App是否开启了指定权限---调用ContextCompat的checkSelfPermission方法
请求系统弹窗,以便用户选择是否开启权限---调用ActivityCompat的requestPermissions方法,即可命令系统自动弹出权限申请窗口
判断用户的权限选择结果---重写活动页面的权限请求回调方法onRequestPermissionsResult,在该方法内部处理用户的权限选择结果。
8.2.1 Lazy模式
activity_permission_lazy.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="读写通讯录"/>
<Button
android:id="@+id/btn_sms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="收发短信"/>
</LinearLayout>
PermissionLazyActivity.java
public class PermissionLazyActivity extends AppCompatActivity implements View.OnClickListener {
public static final String[] PERMISSIONS_CONTACTS = new String[] {
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS
};
public static final String[] PERMISSIONS_SMS = new String[] {
Manifest.permission.SEND_SMS,
Manifest.permission.READ_SMS
};
public static final int REQUEST_CODE_CONTACTS = 1;
public static final int REQUEST_CODE_SMS = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission_lazy);
findViewById(R.id.btn_contact).setOnClickListener(this);
findViewById(R.id.btn_sms).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_contact:
PermissionUtil.checkPermission(this,PERMISSIONS_CONTACTS,REQUEST_CODE_CONTACTS);
break;
case R.id.btn_sms:
PermissionUtil.checkPermission(this,PERMISSIONS_SMS,REQUEST_CODE_SMS);
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CODE_CONTACTS:
if(PermissionUtil.checkGrant(grantResults)) {
Log.d("song","通讯录权限获取成功");
} else {
Log.d("song","通讯录权限获取失败");
jumpToSettings();
}
break;
case REQUEST_CODE_SMS:
if(PermissionUtil.checkGrant(grantResults)) {
Log.d("song","收发短信权限获取成功");
} else {
Log.d("song","收发短信权限获取失败");
jumpToSettings();
}
break;
}
}
//跳转到应用设置页面
private void jumpToSettings() {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package",getPackageName(),null));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
8.2.2 hungry模式
该模式授权是在应用第一次安装的时候进行授权。且在代码里设置了当App安装时未授权,当点击按钮时将会再次询问授权。
activity_permission_hungry.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="读写通讯录"/>
<Button
android:id="@+id/btn_sms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="收发短信"/>
</LinearLayout>
PermissionHungryActivity.java
public class PermissionHungryActivity extends AppCompatActivity implements View.OnClickListener {
public static final String[] PERMISSIONS = new String[] {
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.SEND_SMS,
Manifest.permission.READ_SMS
};
public static final int REQUEST_CODE_ALL = 1;
public static final int REQUEST_CODE_CONTACTS = 2;
public static final int REQUEST_CODE_SMS = 3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission_lazy);
findViewById(R.id.btn_contact).setOnClickListener(this);
findViewById(R.id.btn_sms).setOnClickListener(this);
PermissionUtil.checkPermission(this,PERMISSIONS, REQUEST_CODE_ALL);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_contact:
PermissionUtil.checkPermission(this,new String[]{PERMISSIONS[0],PERMISSIONS[1]},REQUEST_CODE_CONTACTS);
break;
case R.id.btn_sms:
PermissionUtil.checkPermission(this,new String[]{PERMISSIONS[2],PERMISSIONS[3]},REQUEST_CODE_SMS);
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CODE_ALL:
if(PermissionUtil.checkGrant(grantResults)) {
Log.d("song","所有权限获取成功");
} else {
Log.d("song","所有权限获取失败");
for (int i = 0; i < grantResults.length; i++) {
if(grantResults[i] != PackageManager.PERMISSION_GRANTED) {
switch (permissions[i]) {
case Manifest.permission.READ_CONTACTS:
case Manifest.permission.WRITE_CONTACTS:
Log.d("song","通讯录权限获取失败");
jumpToSettings();
break;
case Manifest.permission.READ_SMS:
case Manifest.permission.SEND_SMS:
Log.d("song","收发短信权限获取失败");
jumpToSettings();
break;
}
}
}
}
break;
case REQUEST_CODE_CONTACTS:
if(PermissionUtil.checkGrant(grantResults)) {
Log.d("song","通讯录权限获取成功");
} else {
Log.d("song","通讯录权限获取失败");
jumpToSettings();
}
break;
case REQUEST_CODE_SMS:
if(PermissionUtil.checkGrant(grantResults)) {
Log.d("song","收发短信权限获取成功");
} else {
Log.d("song","收发短信权限获取失败");
jumpToSettings();
}
break;
}
}
//跳转到应用设置页面
private void jumpToSettings() {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package",getPackageName(),null));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
PermissionUtil.java 和上面一样
8.3 添加和查询手机通信录
activity_contact_add.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="联系人姓名:"/>
<EditText
android:id="@+id/et_contact_name"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="联系人号码:"/>
<EditText
android:id="@+id/et_contact_phone"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="联系人邮箱:"/>
<EditText
android:id="@+id/et_contact_email"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
<Button
android:id="@+id/btn_add_contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="添加联系人"/>
<Button
android:id="@+id/btn_read_contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询联系人"/>
</LinearLayout>
ContactAddActivity.java
public class ContactAddActivity extends AppCompatActivity implements View.OnClickListener {
private EditText et_contact_name;
private EditText et_contact_phone;
private EditText et_contact_email;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact_add);
et_contact_name = findViewById(R.id.et_contact_name);
et_contact_phone = findViewById(R.id.et_contact_phone);
et_contact_email = findViewById(R.id.et_contact_email);
findViewById(R.id.btn_add_contact).setOnClickListener(this);
findViewById(R.id.btn_read_contact).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_add_contact:
//方式1 使用ContentResolver多次写入,每次一个字段(因为要写多个表,可能某一个表写入失败,可能造成联系人信息缺失)
//addContacts(getContentResolver(),et_contact_name.getText().toString(),et_contact_phone.getText().toString(),et_contact_email.getText().toString());
//方式2 批处理方式
//每一次操作都是一个ContentProviderOperation,构建一个操作集合,然后一次性执行。好处是要么全部成功,要么全部失败,保证事务的一致性。
addFullContacts(getContentResolver(),et_contact_name.getText().toString(),et_contact_phone.getText().toString(),et_contact_email.getText().toString());
break;
case R.id.btn_read_contact:
readPhoneContacts(getContentResolver());
break;
}
}
@SuppressLint("Range")
private void readPhoneContacts(ContentResolver resolver) {
//先查询raw_contacts表,再根据raw_contacts_id去查询data表
Cursor cursor = resolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ContactsContract.RawContacts._ID}, null,null);
while (cursor.moveToNext()) {
int rawContactsId = cursor.getInt(0);
Uri uri = Uri.parse("content://com.android.contacts/contacts/"+rawContactsId+"/data");
Cursor dataCursor = resolver.query(uri,new String[] {ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.RawContacts.Data.DATA1, ContactsContract.RawContacts.Data.DATA2},
null, null, null);
while (dataCursor.moveToNext()) {
String data1 = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.RawContacts.Data.DATA1));
String mimeType = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.RawContacts.Data.MIMETYPE));
//是姓名
if(mimeType.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)) {
Log.d("song", "姓名:"+data1);
} else if(mimeType.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
Log.d("song", "邮箱:"+data1);
} else if(mimeType.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
Log.d("song", "号码:"+data1);
}
}
dataCursor.close();
}
cursor.close();
}
private void addFullContacts(ContentResolver resolver, String name, String phone, String email) {
ContentProviderOperation op_main = ContentProviderOperation
.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build();
ContentProviderOperation op_name = ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
//第0个操作的id
.withValueBackReference(ContactsContract.RawContacts.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.RawContacts.Data.DATA2, name)
.build();
ContentProviderOperation op_phone = ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
//第0个操作的id
.withValueBackReference(ContactsContract.RawContacts.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.RawContacts.Data.DATA1, phone)
.withValue(ContactsContract.RawContacts.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
.build();
ContentProviderOperation op_email = ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
//第0个操作的id
.withValueBackReference(ContactsContract.RawContacts.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.RawContacts.Data.DATA1, email)
.withValue(ContactsContract.RawContacts.Data.DATA2, ContactsContract.CommonDataKinds.Email.TYPE_WORK)
.build();
//声明一个内容操作器的列表,并将上面4个操作器添加到该列表中
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
operations.add(op_main);
operations.add(op_name);
operations.add(op_phone);
operations.add(op_email);
try {
resolver.applyBatch(ContactsContract.AUTHORITY, operations);
} catch (OperationApplicationException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
//往手机通信录添加一个联系人信息
private void addContacts(ContentResolver resolver, String name, String phone, String email) {
ContentValues values = new ContentValues();
//往raw_contacts添加联系人记录,并获取添加后的联系人编号
Uri uri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
long rawContactId = ContentUris.parseId(uri);
ContentValues nameValues = new ContentValues();
//关联联系人编号
nameValues.put(ContactsContract.RawContacts.Data.RAW_CONTACT_ID,rawContactId);
//姓名的数据类型
nameValues.put(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
//联系人姓名
nameValues.put(ContactsContract.RawContacts.Data.DATA2, name);
//添加联系人的姓名记录
resolver.insert(ContactsContract.Data.CONTENT_URI,nameValues);
ContentValues phoneValues = new ContentValues();
phoneValues.put(ContactsContract.RawContacts.Data.RAW_CONTACT_ID,rawContactId);
phoneValues.put(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
phoneValues.put(ContactsContract.RawContacts.Data.DATA1, phone);
//联系类型。1表示家庭,2表示工作
phoneValues.put(ContactsContract.RawContacts.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
resolver.insert(ContactsContract.Data.CONTENT_URI,phoneValues);
ContentValues emailValues = new ContentValues();
emailValues.put(ContactsContract.RawContacts.Data.RAW_CONTACT_ID,rawContactId);
emailValues.put(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
emailValues.put(ContactsContract.RawContacts.Data.DATA1, email);
//联系类型。1表示家庭,2表示工作
emailValues.put(ContactsContract.RawContacts.Data.DATA2, ContactsContract.CommonDataKinds.Email.TYPE_WORK);
resolver.insert(ContactsContract.Data.CONTENT_URI,emailValues);
}
}
下图是执行添加动作之后的结果
下图是执行查询后的打印信息
2022-07-09 12:38:44.991 11595-11595/com.szj.chapter07_client D/song: 姓名:zhangsan
2022-07-09 12:38:44.991 11595-11595/com.szj.chapter07_client D/song: 号码:18866668888
2022-07-09 12:38:44.991 11595-11595/com.szj.chapter07_client D/song: 邮箱:1886688@qq.com
8.4 监听短信内容
MonitorSmsActivity.java
public class MonitorSmsActivity extends AppCompatActivity {
private SmsGetObserver mObserver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_monitor_sms);
//给指定Uri注册内容观察器,一旦数据发生变化,就触发观察器的onChange方法
Uri uri = Uri.parse("content://sms");
mObserver = new SmsGetObserver(this);
getContentResolver().registerContentObserver(uri, true, mObserver);
}
@Override
protected void onDestroy() {
super.onDestroy();
getContentResolver().unregisterContentObserver(mObserver);
}
private static class SmsGetObserver extends ContentObserver {
private final Context mContext;
public SmsGetObserver(Context context) {
super(new Handler(Looper.getMainLooper()));
this.mContext = context;
}
@SuppressLint("Range")
@Override
public void onChange(boolean selfChange, @Nullable Uri uri) {
super.onChange(selfChange, uri);
//onChage会多次调用,收到一条短信会调用两次onChange
//mUri===content://sms/raw/20
//mUri===content://sms/inbox/20
//android7.0以上系统,点击标记为已读,也会调用一次
//mUri===content://sms
//收到一条短信都是uri后面都会有确定的一个数字,对应数据库的_id,比如上面的20
if(uri == null) {
return;
}
if(uri.toString().contains("content://sms/raw") || uri.toString().equals("content://sms")) {
return;
}
Cursor cursor = mContext.getContentResolver().query(uri,new String[] {"address","body","date"}, null, null,"date DESC");
while (cursor.moveToNext()) {
//短信的发送号码
String sender = cursor.getString(cursor.getColumnIndex("address"));
//短信内容
String content = cursor.getString(cursor.getColumnIndex("body"));
Log.d("song",String.format("sender:%s,content:%s",sender,content));
}
cursor.close();
}
}
}
然后我们可以模拟收到一条短信,操作如下,打印出的监控短信内容如下