android教程
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(); } } }
然后我们可以模拟收到一条短信,操作如下,打印出的监控短信内容如下