Android移动开发
一、Android入门基础
1.安卓通信技术
第一代通信技术(1G):是指最初的模拟、仅限语音的蜂窝电话标准,只能进行通话。
第二代通信技术(2G):是指第2代移动通信技术,代表为GSM,以数字语音传输技术为核心。增加接收数据功能,传输速度9.6k/s。
第三代通信技术(3G):是指将无线通信与国际互联网等多媒体通信结合的新一代移动通信系统。3G通信网在室内、室外和行车的环境中能够分别支持至少2M/s、384K/s以及144K/s的传输速度。
第四代通信技术(4G):又称IMT-Advanced技术,它包括了TD-LTE 和 FDD-LTE。4G通信网最高甚至可以达到100M/s的传输速度。
第五代通信技术(5G):传输速度可达20Gbps。
2.安卓体系结构
1、应用程序层
是一个核心应用程序的集合,所有==安装在手机上的应用程序==都属于这一层。
2、应用程序框架层
主要提供了构建应用程序时用到的各种API 。
3、核心类库
-
系统库主要通过C/C++库来为Android系统提供主要的特性支持。
-
Android运行时库主要提供一些核心库,还包括了Dalvik虚拟机。
4、Linux内核
为Android设备的各种硬件提供了底层的驱动。
3.Dalvik虚拟机
Dalvik是Google公司设计的,用于在Android平台上运行的虚拟机,其指令集基于寄存器架构,执行其特有的dex文件来完成对象生命周期管理、堆栈管理、线程管理、安全异常管理、垃圾回收等重要功能。每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,每一个独立的Dalvik虚拟机实例都是一个独立的进程空间,进程间可以相互通信,其代码在虚拟机的解释下得以执行,Dalvik虚拟机编译文件的过程如下图所示。
4.安卓程序结构
4.1 AndriodManifest.xml:
**(1)**整个程序的配置文件,在该文件中可以配置程序需要的权限和注册程序中用到的四大组件。
**(2)**在该文件中去掉默认标题栏:
(3) 横竖屏切换
(4)配置activity
(5)权限申请
(6)广播注册
(7)服务注册
5.资源管理与使用
res文件夹:
5.1 图片资源
-
应用图标资源:存放在以mipmap开头的文件夹中
-
界面中使用图标资源:存放在以drawable开头的文件夹中
密度范围值 | mipmap文件夹 | drawable文件夹 |
---|---|---|
120~160dpi | mipmap_mdpi | mipmap_mdpi |
160~240dpi | mipmap_hdpi | drawable_hdpi |
240~320dpi | mipmap_xdpi | drawable_xdpi |
320~480dpi | mipmap_xxdpi | drawable_xxdpi |
480~640dpi | mipmap_xxxdpi | drawable_xxxdpi |
表1 屏幕密度匹配规则
5.1.1 Java调用图片资源
1.在Acitvity的方法中通过getResources().getDrawable()方法调用。
getResources().getDrawable(R.mipmap.ic_launcher);
getResources().getDrawable(R.drawable.icon);
2.XML布局文件调用图片资源
@mipmap/ic_launcher
@drawable/icon
5.2 主题和样式资源
5.2.1主题
主题:包含一种或多种格式化属性的集合,在程序中调用主题资源可改变窗体的样式。
1.主题资源定义在res/values/styles.xml文件中。
<style></style>标签定义主题,name指定主题名,parent指定系统父主题,<item></item>标签设置主题样式。
根元素<resources></resources>中可以包含多个<style></style>,每个<style></style>可以包含多个<item></item>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
2.调用styles.xml中定义的主题
(1)在AndroidManifest.xml中设置主题
<application
......
android:theme ="@style/AppTheme">
</application>
(2)在Java代码中设置主题
setTheme(R.style.AppTheme);
5.2.2 样式
样式:设置View的宽度、高度和背景颜色等信息。
1.样式存放在res/values/styles.xml中
通过<style>标签中的name属性设置样式名称,通过<item>标签设置控件样式。
2.在布局文件的View控件中通过style属性调用textViewStyle样式:
<TextView
......
style="@style/textViewStyle"/>
5.3 布局资源
布局资源:通常用于搭建程序中的各个界面。
1.当创建一个Android程序时,默认会在**res/layout文件夹中生成一个布局资源文件activity_main.xml**,也可在res/layout文件夹中创建新的布局资源文件。
2.调用布局资源文件
(1)通过Java代码调用布局资源文件
//在onCreat()方法中通过调用setContenView()方法来载入Activity对应布局资源文件:
setContentView(R.layout.activity_Main);
(2)在XML布局文件中调用布局资源文件
在XML布局文件中通过标签调用activity_main.xml布局资源文件:
<include layout="@layout/activity_main">
5.4 字符串资源
字符串:用于显示界面上的文本信息。
在res/values/strings.xml文件定义字符串:
<resources>
<string name="app_name">字符串</string>
</resources>
1.string.xml文件中只能有一个根元素,根元素中可以包含多个标签
2.调用字符串资源
(1)通过Java代码调用字符串资源
//在Activity的onCreat()方法中调用getResources().getString()加载app_name字符串资源:
getResources().getString(R.string.app_name);
(2)在XML布局文件中调用字符串资源
@string/app_name
5.5 颜色资源
颜色:用于显示View控件的不同色彩效果。
1.颜色资源通常定义在res/values/colors.xml文件中。
<resources>
<color name="colorPrimary">#3F51B5</color>
</resources>
<color></color>标签用于定义颜色资源,name属性用于指定颜色资源的名称,两个标签中间设置的是颜色值。
2.调用颜色资源
(1)通过Java代码调用颜色资源
在Activity的onCreat()方法中调用getResources().getColor()加载colorPrimary颜色资源:
getResources().getColor(R.color.colorPrimary);
(2)在XML布局文件中调用颜色资源
@color/colorPrimary
3.定义颜色值
RGB: 使用一位十六进制数值表示红绿蓝
ARGB: 使用一位十六进制数值表示透明度、红绿蓝
RRGGBB: 使用二位十六进制数值表示红绿蓝
AARRGGBB: 使用二位十六进制数值表示透明度、红绿蓝
颜色的小写字母可以换成大写
5.6 尺寸资源
Android界面中View的宽高和View之间的间距值是通过尺寸资源设置的。
1.尺寸资源通常定义在res/values/dimens.xml文件中。
在Android Studio3.2版本中没有默认创建dimen.xml文件,需要手动创建。
<dimen></dimen>标签用于定义尺寸资源,name属性指定尺寸资源名称,标签中间设置尺寸大小。
<resources>
<dimen name="activity_horizontal_margin">16dp</dimen>
</resources>
2.调用尺寸资源
(1)通过Java代码调用尺寸资源
getResources().getDimension(R.dimen.activity_horizontal_margin);
(2)通过XML布局文件中调用尺寸资源
@dimen/activity_horizontal_margin
3.Android支持的尺寸单位
- px 像素:每个px对应屏幕上的一个点
- dp 设备独立像素:dp与dip意义相同,是与屏幕密度无关的尺寸单位。每英寸160点的显示屏上,1dip=1px。dp按屏幕分辨率比例放大或缩小。
- sp 比例像素:主要处理字体大小,sp与dp比较相似,能够跟随用户系统字体大小变化而变化。
- in 英寸:屏幕对角线的长度单位
- pt 磅:屏幕唔理察高度单位,1pt=1/72in
- mm 毫米:屏幕物理长度单位
6.程序调试
6.1 单元测试
单元测试是指在Android程序开发过程中对最小的功能模块进行测试,单元测试包括Android单元测试和Junit单元测试。
1.Android Studio 3.2版本在创建项目时,会默认在app/src/androidTest和app/src/test文件夹中创建Android单元测试类ExampleInstrumentedTest和Junit单元测试类ExampleUnitTest。
(1) Android单元测试类ExampleInstrumentedTest
-
使用@RunWith(AndroidJUnit4.class)注解ExampleInstrumentedTest类
-
@Test注解类中的方法
(2)Junit单元测试类ExampleUnitTest
- @Test注解类中的方法
2.Android Studio 3.2版本在创建项目时,会自动在build.gradle文件中添加单元测试的支持库,如果在进行单元测试时,程序中的build.gradle文件中没有添加单元测试的支持库,则需要手动进行添加。
6.2 Logcat的使用
LogCat是Android中的命令行工具,用于获取程序从启动到关闭的日志信息。
1.Log类所输出的日志内容分为六个级别
级别 | 显示信息 | Logcat类中的静态方法 |
---|---|---|
Verbose | 全部信息 | Log.v() |
Debug | 调试信息 | Log.d() |
Info | 一般信息 | Log.i() |
Warning | 警告信息 | Log.w() |
Error | 错误信息 | Log.e() |
Assert | 断言失败后的错误消息 | Log.wtf() |
二、Andriod常见布局
Ch 2 Android常见界面布局
2.1 View视图
ViewGroup 和 View 控件的包含关系
------Android应用的每个界面的根元素必须有且只有一个ViewGroup容器。
------viewGroup作为容器盛装界面中的控件,可包含View也可包含viewGroup
2.2 界面布局编写方式
2.2.1 在XML文件中编写布局
Android可以使用XML布局文件控制界面布局,从而有效地将界面中布局的代码和Java代码分离,使程序的结构更加清晰。
2.2.2 在Java代码中编写布局
在Android中所有布局和空间的对象都可以通过==new关键字创建出来==,将创建的View控件添加到ViewGroup布局中,从而实现View控件在布局界面中显示。
不管使用哪种方式编写布局,它们控制Android用户界面行为的本质是完全一样的。
2.3 常见界面布局
2.3.1 布局的通用属性
Android系统提供的五种常用布局直接或间接继承自ViewGroup,因此也常用布局支持在ViewGroup中定义的属性。
1.android:id
(1)用于设置当前布局的唯一标识。通过"@+id/属性名称"定义。
(2)在R.java文件中会自动生成对应的int值。
(3)在Java代码中通过为findViewById()方法传入该int值来获取该布局对象。
2.android:layout_width
用于设置布局宽度
(1)fill_parent: 该布局宽度与父容器宽度相同。
(2)match_parent:与fill_parent相同,Android2.2开始推荐使用。
(3)wrap_content:该布局宽度恰好能包裹内容。
3.android:layout_heigh
用于设置布局高度
(1)fill_parent: 该布局宽度与父容器高度相同。
(2)match_parent:与fill_parent相同,Android2.2开始推荐使用。
(3)wrap_content:该布局高度恰好能包裹内容。
4.android:background
用于设置布局背景。其值可以引用图片资源也可以引用颜色资源
5.android:layout_margin
用于设置当前布局与屏幕边界、周围布局或控件的距离。
(1)android:layout_marginTop
(2)android:layout_marginBottom
(3)android:layout_marginLeft
(4)android:layout_marginRight
6.android:padding
用于设置当前布局内控件与该布局的距离。
(1)android:paddingTop
(2)android:paddingBottom
(3)android:paddingLeft
(4)android:paddingRight
Android系统提供的五种常用布局==必须设置android:layout_width和android:layout_height。==
2.3.2 RelativeLayout相对布局
- RelativeLayout通过相对定位的方式指定子控件的位置。
定义格式:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
属性 = "属性值"
......>
</RelativeLayout>
Relativelayout中子控件属性
属性名称 | 功能描述 |
---|---|
android:layout_centerInParent | 设置当前控件位于父布局的中央位置 |
android:layout_centerVertical | 设置当前控件位于父布局的垂直居中位置 |
android:layout_centerHorizontal | 设置当前控件位于父布局的水平居中位置 |
android:layout_above | 设置当前控件位于某控件上方 |
android:layout_below | 设置当前控件位于某控件下方 |
android:layout_toLeftOf | 设置当前控件位于某控件左侧 |
android:layout_toRightOf | 设置当前控件位于某控件右侧 |
android:layout_alignParentTop | 设置当前控件是否与父控件顶端对齐 |
android:layout_alignParentLeft | 设置当前控件是否与父控件左端对齐 |
android:layout_alignParentRight | 设置当前控件是否与父控件右端对齐 |
android:layout_alignParentBottom | 设置当前控件是否与父控件底端对齐 |
android:layout_alignTop | 设置当前控件的上边界与某控件的上边界对齐 |
android:layout_alignBottom | 设置当前控件的下边界与某控件的下边界对齐 |
android:layout_alignLeft | 设置当前控件的左边界与某控件的左边界对齐 |
android:layout_alignRight | 设置当前控件的右边界与某控件的右边界对齐 |
- 在Relativelayout布局中定义的控件默认与父布局左上角对齐。
布局和控件的宽高
为了让Android程序拥有更好的屏幕适配能力,在设置控件和布局宽高时最好使用match_parent或wrap_content。
2.3.3 LinearLayout线性布局
LinearLayout通常指定布局内的子空间水平或竖直排列。
定义格式:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
属性 = "属性值"
......>
</LinearLayout>
1.属性说明
(1)android:orientation
用于设置LinearLayout布局中控件的排列顺序,可选vertical和horizontal。
(2)android:weight
通过设置权重使布局内的控件按照权重比显示大小,在进行屏幕适配时起关键作用。
2.注意点
LinearLayout布局中的android:layout_width不可设为warp_content,因为LinearLayout优先级比Botton高,如果设置warp_content,则Button控件的android:layout_weight会失去作用。设置了Button控件的android:layout_weight属性时,Button控件的android:layout_width一般设置为0dp才会有权重占比效果。
2.3.4 TableLayout表格布局
-
TableLayout采用行、列的形式来管理控件。通过在TableLayout布局中添加TableRow布局或控件来控制表格行数,在TableRow布局中添加控件来控制表格列数。
-
XML布局文件中定义表格布局基本语法格式:
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
属性 = "属性值">
<TableRow>
UI控件
</TableRow>
UI控件
......
</TableLayout>
-
TableLayout继承自LinearLayout,因此完全支持LinearLayout所支持的属性。
TableLayout布局常用属性
属性名称 | 功能描述 | 例子(列从0开始计数) |
---|---|---|
android:stretchColumns | 设置可被拉伸的列 | =“0” 表示第1列可被拉伸 |
android:shrinkColumns | 设置可被收缩的列 | =“1,2” 表示2,3列可被收缩 |
android:collapseColumns | 设置可被隐藏的列 | =“0” 表示第1列可被隐藏 |
TableLayout布局中控件常用属性
属性名称 | 功能描述 |
---|---|
android:layout_column | 设置该控件显示的位置,="1"表示在第二个位置显示 |
android:layout_span | 设置该控件占据几行,默认为1行 |
###2.3.5 FrameLayout帧布局
FrameLayout用于在屏幕上创建一块空白区域,添加到该区域中的每个子控件占一帧。后加入的控件会叠加在上一个控件上层。默认情况下,帧布局中的所有控件会与左上角对齐。
XML布局文件中定义FrameLayout基本语法格式:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
属性 = "属性值">
</FrameLayout>
FrameLayout属性
属性名称 | 功能描述 |
---|---|
android:foreground | 设置帧布局容器的前景图像(始终在所有子控件之上) |
android:foregroundGravity | 设置前景图像显示的位置 |
2.4 计算器编码(编码)
计算器排版
C | <— | + | - |
---|---|---|---|
7 | 8 | 9 | × |
4 | 5 | 6 | / |
1 | 2 | 3 | = |
三、Android常见界面控件
3.1 简单控件的使用
3.1.1 TextView
- TextView用于显示==文本信息==。
属性名称 | 功能描述 |
---|---|
android:layout_width | 控件宽度 |
android:layout_height | 控件高度 |
android:id | 控件唯一标识 |
android:background | 设置TextView控件背景 |
android:layout_margin | 设置控件与屏幕边界或周围控件、布局的距离 |
android:padding | 设置TextView控件与该控件中内容的距离 |
android:text | 设置文本内容 |
android:textColor | 设置文字显示的颜色 |
android:textSize | 设置文字大小,推荐单位为sp |
android:gravity | 设置文本内容的位置 |
android:maxLength | 设置文本最大长度 |
android:lines | 设置文本行数 |
android:maxLines | 设置文本的最大行数 |
android:ellipsize | 设置当文本超出TextView规定范围的显示方式。属性值可选为“start”、“middle”、“end”,分别表示文本超出范围时,从文本开始、中间或者末尾显示省略号 |
android:drawableTop | 在文本的顶部显示图像,该图像资源可以放在res/drawable相应分辨率目录下,通过"@drawable/文件名"调用。类似属性android:drawableBottom、android:drawableLeft、android:drawableRight |
android:lineSpacingExtra | 设置文本的行间距 |
android:textStyle | 设置文本样式“bold”、“italic”、“normal” |
- 控件每个XML属性都对应一个Java方法
3.1.2 Button
Button表示按钮,它继承自TextView控件,可显示文本和图片,允许通过点击来实现操作。
- 在MainActivity.java设置**Button点击事件的三种方式**
(1)在布局文件(xml文件)中指定Onclick属性的值
(2)使用匿名内部类
(3)使用Activity实现OnClickListener接口
- 在activity_main.xml定义Button1、2、3属性
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按钮1"/>
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="Button2Click"
android:text="按钮2"/>
<Button
android:id="@+id/button3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:text="按钮3"/>
</LinearLayout>
package com.milk.demo2app;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
Button button1;
Button button2;
Button button3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1=(Button)findViewById(R.id.button1);
button2=(Button)findViewById(R.id.button2);
button3=(Button)findViewById(R.id.button3);
//设置Button1点击事件
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
button1.setText("已点击button1");
}
});
//方式3:设置Button3点击事件,使用Activity实现View.OnClickListener接口
button3.setOnClickListener(this);
}
//设置Button2点击事件
public void Button2Click(View view){
button2.setText("已点击Button2Click");
}
//设置Button3点击事件
@Override
public void onClick(View view) {
switch(view.getId()){
case R.id.button3:
button3.setText("已点击Button3");
break;
}
}
}
3.1.3 EditText
EditText表示编辑框,是TextView的子类,除支持TextView控件的属性,EditText还支持一些其他的常用属性。
EditText控件常用属性
属性名称 | 功能描述 |
---|---|
android:hint | 控件中内容为空时显示的提示文本信息 |
android:textColorHint | 控件中内容为空时显示的提示文本信息的颜色 |
android:password | 输入文本框中的内容显示为"." |
android:phoneNumber | 设置输入文本框中的内容只能是数字 |
android:scrollHorizontally | 设置文本信息超出EditText的宽度情况下是否出现横拉条 |
android:editable | 设置是否可编辑 |
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="姓名:"
android:textSize="23sp"
android:textColor="#bbccff"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入姓名"
android:textColor="#bbccff"
android:textColorHint="#66ccff"
android:textSize="23sp"
android:maxLines="2"
android:textStyle="italic"/>
</LinearLayout>
(1)删掉EditText底线:android:background=“@null”
(2)设置EditText高度行数:android: MaxLines
(3)设置EditText右侧竖直滚轴:android:scrollbars=“vertical”
3.1.4 ImageView
ImageView继承自View,可以加载各种图片资源
(1)andriod:background=“@drawable/bg”:自动调整将图片显示的和屏幕一样大
(2)andriod:src=“@drawable/icon”:原样显示图片,不会自动调整让他跟屏幕一样大
3.1.5 RadioButton
- RadioButton表示单选按钮,它是Button的子类。每个单选按钮由android:checked属性指定”**选中“(True)**和“未选中”(False)两种状态
- RadioButton与RadioGroup配合使用,实现RadioButton的单选功能。RadioGroup是单选组合框,可容纳多个RadioButton,但RadioButton中不会出现多个RadioButton同时选中。
- XML文件中RadioGroup和RadioButton配合使用语法格式:
<RadioGroup
android:属性名称="属性值"
......>
<RadioButton
android:属性名称="属性值"
....../>
......
<RadioGroup/>
- 使用**setOnCheckedChangeListener()方法为RadioGroup设置监听布局内控件状态是否改变的事件**,通过事件返回的**onCheckedChanged()方法获取被点击的控件ID**
(1) 使用RadioGroup、RadioButton和TextView,用TextView显示被选中RadioButton的内容
(2) RadioButton控件没有设置android:checked属性的值,默认为false
xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto">
<RadioGroup
android:id="@+id/radiogroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/radiobutton1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="男" />
<RadioButton
android:id="@+id/radiobutton2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="女" />
</RadioGroup>
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:text="无"
android:layout_height="wrap_content"></TextView>
</LinearLayout>
java文件
package com.milk.teseapp2;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.RadioGroup;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
RadioGroup radioGroup;
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
radioGroup=(RadioGroup)findViewById(R.id.radiogroup);
textView=(TextView)findViewById(R.id.textview);
//使用setOnCheckedChangeListener()方法为RadioGroup设置监听布局内控件状态是否改变的事件
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup radioGroup, int i) {
if(i==R.id.radiobutton1){
textView.setText("男");
}
if(i==R.id.radiobutton2){
textView.setText("女");
}
}
});
}
}
3.1.6 CheckBox
CheckBox表示复选框,是Button子类,用于实现多选功能。每个复选框由android:checked属性指定**”选中“(True)**和“未选中”(False)
3.1.7 Toast
Toast是Android系统提供的轻量级信息提醒机制,用于向用户提示即时消息。
- 使用Toast显示提示信息的示例代码:
Toast.makeText(Context,text,Time).show();
Toast.makeText(Activity_Sign.this, "请填写账号", Toast.LENGTH_SHORT).show();
(1) Context:表示应用程序环境的信息,即当前组件的上下文环境。如果在Activity中使用Toast提示信息,那么该参数设置为”当前Activity.this”
(2) Text:表示提示的字符串信息
(3)Time:表示显示信息的时长,包括Toast.LENGTH_LONG和Toast.LENGTH_SHORT,分别表示显示较短时间和较长时间
- 默认情况下,Toast类的消息会显示在屏幕下方
3.2 ListWiew的使用(编码)
3.2.1 ListView控件的简单使用
ListView常用属性
3.2.2 常用数据适配器(Adapter)
- BaseAdapter
BaseAdapter是一个抽象类
BaseAdapter的四个抽象方法
- SimpleAdapter
SimpleAdapter继承自BaseAdapter,实现了BaseAdapter的四个抽象方法。
SimpleAdapter构造方法:
public SimpleAdapter(Context, List<? extends Map<String, ?>>data, int resource, String[] from, int[] to);
//context:上下文对象
//data:数据集合,data每一项对应ListView控件中条目的数据
//resource:Item布局的资源id
//from:Map集合中的key值
//to:Item布局中对应的硬件
- ArrayAdapter
ArrayAdapter是BaseAdapter的子类,通常用于适配TextView控件。
//ArrayAdapter构造方法:
public ArrayAdapter(Context context, int resoure);
public ArrayAdapter(Context context, int resource, int textViewResourceId);
public ArrayAdapter(Context context, int resource, int textViewResourceId,T[] objects);
public ArrayAdapter(Context context, int resource, List<T> objects);
public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objexts);
//context:Context上下文对象
//resource:Item布局的资源id
//textViewResourceId:Item布局中相应TextView的id
//T[] object:需要适配的List类型的数据
//Listobject:需要适配的List类型的数据
-
可以通过ListView对象的setAdapter()方法添加适配器。
示例代码如下:
ListView lv=(ListView)findViewById(R.id.lv); MyBaseAdapter mAdapter=new MyBaseAdapter(); lv.setAdapter(mAdapter);
MainActivity.java
package com.milk.listview; import android.annotation.SuppressLint; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; public class MainActivity extends Activity { private ListView mListView; private final String[] titles={"桌子","苹果","蛋糕","毛衣","猕猴桃","围巾"};//定义项目标题 private final String[] prices ={"1800","10/g","300","350","10/kg","280"};//定义项目价格 private final int[] icon={R.drawable.book,R.drawable.book,R.drawable.book, R.drawable.book,R.drawable.book,R.drawable.book};//定义项目图标,没有素材,全部用一个图片代替 protected void onCreate(Bundle saveInstanceState){ super.onCreate(saveInstanceState); //如果先findViewById()再setContentView()程序会闪退 setContentView(R.layout.activity_main); mListView=(ListView)findViewById(R.id.lv); MyBaseAdapter mAdapter=new MyBaseAdapter(); mListView.setAdapter(mAdapter);//程序在添加适配器处闪退 } class MyBaseAdapter extends BaseAdapter { @Override public int getCount() {//获得item总数 return titles.length; } @Override public Object getItem(int i) {//获得ListView Item条目总数 return titles[i]; } @Override public long getItemId(int i) {//返回Item的Id return i; } @Override public View getView(int position, View convertView, ViewGroup viewGroup) {//得到Item的视图 //在此处加载list_item.xml布局文件 @SuppressLint("ViewHolder") View view =View.inflate(MainActivity.this,R.layout.list_item,null); //inflate用于找主布局之外需要用程序修改view组件的布局文件,因此要注意下面的findViewById要通过view调用 TextView title=(TextView)view.findViewById(R.id.title); TextView price=(TextView)view.findViewById(R.id.price); ImageView iv=(ImageView) view.findViewById(R.id.iv); title.setText(titles[position]); price.setText(prices[position]); iv.setBackgroundResource(icon[position]); return view; } } }
list_item.xml(一个列表条目的布局文件)
<?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="match_parent">
<ImageView
android:id="@+id/iv"
android:layout_width="120dp"
android:layout_height="90dp"
android:layout_centerVertical="true">
</ImageView>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/iv"
android:layout_centerVertical="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/title"
android:text="桌子"
android:textSize="20sp">
</TextView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_price"
android:layout_below="@id/title"
android:textSize="20sp"
android:layout_marginTop="10dp"
android:text="价格">
</TextView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/price"
android:layout_below="@id/title"
android:layout_toRightOf="@id/tv_price"
android:text="1000"
android:textSize="20sp"
android:layout_marginTop="10dp">
</TextView>
</RelativeLayout>
</RelativeLayout>
activity_main.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="45dp"
android:text="购物平台"
android:textSize="18sp"
android:gravity="center">
</TextView>
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
- 优化ListView控件
3.3 RecyclerView的使用
在Android5.0之后谷歌提供了用于在有限的窗口范围内显示大量数据的控件RecyclerView。与ListView控件相似,RecyclerView控件同样是以列表的形式展示数据,并且数据是通过适配器加载的。但RecyclerView功能更加强大。
-
展示效果
RecyclerView控件可以通过LayoutManager类实现横向或竖向的列表效果、瀑布效果和GridView效果,而ListView控件只能实现竖直的列表效果。 -
适配器
RecyclerView控件使用的是RecyclerView.Adapter适配器,该适配器将BaseAdapter中的getView()方法拆分为==onCreateViewHolder()方法和onBindViewHolder()==方法,强制使用ViewHolder类,使代码编写规范化,避免了初学者写的代码性能不佳。 -
复用效果
RecyclerView控件复用Item对象的工作由该控件自己实现,而ListView控件复用Item对象的工作需要开发者通过convertView的setTag()方法和getTag()方法进行操作。 -
动画效果
RecyclerView控件可以通过setItemAnimator()方法为Item添加动画效果,而ListView控件不可以通过该方法为Item添加动画效果。
3.4自定义View
最简单的自定义View是创建继承自View类或其子类的类,并重写该类构造方法。
public class Customview extends View{
public Customview(Context context){//使用该构造方法创建一个该类对象
super(context);
}
public Customview(Context context, AttributeSet attrs){//使用该构造方法在布局文件中引用自定义的控件
super(context, attrs);
}
}
自定义View常用三个方法
(3)onMeasure()
该方法用于测量尺寸,可以设置控件本身或者子控件的宽高
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
//第一个参数表示获取父容器指定该控件的宽度
//第二个参数表示获取父容器指定该控件的高度
//参数不仅包含父容器指定的属性值,还包括父容器指定的测量模式。
测量模式:
EXACTLY:当自定义控件的宽高值设置为具体值时使用。
AT_MOST:当自定义控件宽高值为wrap_content时使用。
UNSPECIFIED:当父容器没有指定自定义控件宽高时使用。
虽然这两个参数是父容器指定该控件的宽高,但是该控件还需要通过setMeasuredDimension(int,int)方法设置具体的宽高。
(2)onDraw()
该方法用于绘制图像。
onDraw(Canvas canvas)
//参数canvas表示画布。使用Paint类可以在Canvas类中绘制图像。
(3)onLayout()
该方法用于指定布局中子控件的位置,该方法通常在自定义ViewGroup中重写。
onLayout(boolean changed, int left, int top, int right, int bottom)
//第一个参数表示自定义View的大小和位置是否发生变化
//剩下四个参数分别表示子控件与父容器左边、顶部、右边、底部的距离。
四、程序活动单元Activity
(1)Android四大组件:Activity,Service,ContentProvider,BroadcastReceiver
(2)Activity负责与用户交互的组件,用Activity来显示界面和处理界面上一些控件的事件。
4.1 Activity的生命周期
4.1.1 生命周期状态(简答)
Activity的生命周期指Activity从创建到销毁的整个过程。
-
启动状态
一般情况下,当Activity启动后便会进入运行状态 -
运行状态
Activity在此状态时处于界面最前端,它是可见、有焦点的,可以与用户进行交互。
当Activity处于运行状态时,Android会尽可能地保持这种状态。如果出现内存不足的情况,Android也会先销毁栈底的Activity来确保当前Activity正常运行。 -
暂停状态
Activity对用户来说依然可见,但无法获取焦点,用户对它操作没有响应。(例如Activity上覆盖了一个透明或非全屏的界面) -
停止状态
当Activity完全不可见时处于停止状态。 -
销毁状态
当Activity处于销毁状态时,将被清理出内存Activity生命周期的启动状态和销毁状态是过渡状态,Activity不会在这两种状态停留
4.1.2 生命周期方法
-
onCreat():Activity创建时调用
-
onStart():Activity即将可见时调用
-
onResume():Activity获取焦点时调用
-
onPause():Activity被其他Activity覆盖或屏幕锁屏时调用
-
onStop():Activity对用户不可见时调用
-
onDestroy():Activity销毁时调用
-
onRestart():Activity从停止状态到再次启动时调用
(1)如果程序中只有一个Activity,则程序无法进行从停止状态到再次启动状态的操作。
(2)当手机横竖屏切换时,会根据AndroidManifest.xml文件中Activity的configChanges属性值的不同而调用相应的生命周期方法。
(3)在进行横竖屏切换时,首先会调用onDestory()方法销毁Activity,之后调用onCreate()方法重建Activity。(4)如果不希望在横竖屏切换时Activity被销毁重建,可以通过configChanges属性进行设置
<activity android:name".ManActivity"
android:configChanges="orientation|KeyboardHidden">
(5) 如果希望某一个界面一直处于竖屏或者横屏状态,可以在清单文件中通过设置Activity的screenOrientation属性完成。
竖屏: android:screenOrientation="portrait"
横屏:android:screenOrientation="landscape"
4.2 Activity的创建、配置、开启和关闭
4.2.1 创建Activity
[右击包名]->[new]->[Activity]->[Empty Activity]->创建ActivityExample
4.2.2 配置Activity
创建一个SecondAcitvity类继承Activity,当在MainActivity的onCreat()方法中启动SecondActivity时,会抛出异常信息
package com.milk.learnproject_activity;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//启动SecondActivity
Intent intent= new Intent(this,SecondActivity.class);
startActivity(intent);
}
}
(1) 每一个创建的Activity都必须在清单文件AndroidManifest.xml中配置才能生效。
配置SecondAcivity示例代码:
<activity
android:name="com.milk.learnproject_activity.SecondActivity"/>
(2) 如果Activity所在的包与AndroidManifest.xml文件的标签中通过package属性指定的包名一致,则==android:name属性==的值可以直接设置为".Activity名称"
<activity
android:name=".SecondActivity"
android:exported="true"
</activity>
<!--android:exported=“true”-->
<!--这句代码的意思就是该activity允许外部应用调用。-->
(3) 编译器有新建Activity时自动修改AndroidManifest.xml配置的功能
4.2.3 开启和关闭Activity
- 启动Activity:可以通过**startActivity()**方法开启创建的Activity
public void startActivity(Intend intent)
//参数Intent为Android应用中各组件之间通信的桥梁,一个Activity通过Intent表达自己的“意图”。
//在MainActivity的onCreate()方法中启动SecondActivity实例代码
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
- 关闭Activity:调用Activity的finish()方法关闭当前的Activity
public void finish()
//finish()方法既没有参数,也没有返回值,只需要在Activity的相应事件中调用该方法即可。
4.3 Intent与IntentFilter
如果用户需要从==一个Activity切换到另一个Activity==,则必须使用Intent来进行切换。Intent用于相同或者不同应用程序组件间的绑定。
4.3.1 Intent(意图)
(1) Intent是程序中各组件间进行交互的一种重要方式,它不仅可以指定当前组件要执行的动作,还可以在不同组件之间进行数据传递。
(2)Intent分为两种类型:显式Intent,隐式Intend
-
显式Intent:直接指定目标组件
eg:显式指定要跳转的目标Activity
//第一个参数this表示当前的Activity
//第二个参数SecondActivity.class表示要跳转到的目标Activity
Intent intent = new Intent(this,SecondActivity.class);
startActivity(intent);
-
隐式Intent:不会明确指出需要激活的目标组件,它被广泛的应用在不同应用程序之间,进行消息传递。
Android会用IntentFilter来匹配相应组件,匹配的属性主要包括3个——action,data,category
-
action:表示Intent对象要完成的动作
-
data:表示Intent对象中传递的数据
-
category:表示为action添加的额外信息
Intent intent=new Intent(); //设置action动作,该动作要和清单文件中设置的一样 intent.setAction("com.milk.learnproject_activity_secondapp.SE_APP_SE_ACT"); startActivity(intent);
在使用隐式Intent开启Activity时,系统会**默认为该Intent添加"Android.intent.category.DEFAULT"的category,因此为了被开启的Activity能够接收隐式Intent,必须在AndroidManifest.xml文件的Activity标签下的中为被开启的Activity指定catrgory为**“android:intent.category.DEFAULT”**。
- 使用App1打开App2的SecondActivity:
SecondActivity清单文件中的配置代码
<activity
android:name=".SecondActivity"
android:exported="true" >
<intent-filter>
<action android:name="com.milk.learnproject_activity_secondapp.SE_APP_SE_ACT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
App1打开SecondActivity的Button点击事件:
button1 = (Button) findViewById(R.id.Button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent=new Intent();
//设置action动作,该动作要和SecondActivity清单文件中设置的一样
intent.setAction("com.milk.learnproject_activity_secondapp.SE_APP_SE_ACT");
startActivity(intent);
}
});
4.3.2 IntentFilter(过滤器)
当发送一个隐式Intent后,Android系统会将它与程序中每一个组件的过滤器进行匹配,匹配属性有action、data、category,需要三个属性都匹配成功才能唤起相应组件。
- action属性匹配规则:action属性用来指定Intent对象的动作
<intent-filter>
<action android:name="android.intent.action.EDIT"/>
······
<intent-filter>
(1)标签中间可以罗列多个action属性,但是当使用隐式Intent激活组件时,只要Intent携带的action与其中一个标签中action的声明相同,action属性就匹配成功。
(2)在清单文件中为Activity添加标签时,必须添加action属性,否则隐式Intent无法开启该Activity。
- data属性匹配规则:
data属性用来指定数据的URI或者数据MIME类型,它的值通常与Intent的action属性有关联。
<intent-filter>
<data android:mimeType="Video/mpeg" android:scheme="http..."/>
···
</intent-filter>
(1)标签中间可以罗列多个data属性,每个data属性可以指定数据的MIME类型和URI。其中MIME类型可以表示image/ipeg、video/*等媒体类型。
(2)隐式Intent携带的data与其中一个标签中data的声明相同,data属性就匹配成功。
- category属性匹配规则
category属性用于为action添加额外信息,一个interFilter可以不声明category属性,也可以声明多个category属性。
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
······
</intent-filter>
(1) 隐式Intent中声明的category必须全部能够与某一个IntentFilter中的category匹配才算匹配成功。IntentFilter中罗列的category属性数量必须大于或者等于隐式Intent携带的category属性数量时,category属性才能匹配成功。如果一个隐式Intent没有设置category属性,那么它可以与任何一个IntentFilter的category匹配。
4.4 Activity之间的跳转
4.4.1 在Activity之间数据传递
-
使用Intent的==putExtra()方法传递数据
(1)通过putExtra()方法将传递的数据存储在Intent对象后,如果想获取该数据,可以通过getXxxExtra()==方法来实现。 -
使用==Bundle类==传递数据
Bundle类与Map接口比较类似,都是通过键值对的形式来保存数据。(1)首先将数据保存到bundle对象中
(2)然后调用**putExtras()方法将bundle对象封装到Intent对象中**
Intent intent = new Intent(); intent.setClass(this,SecondActivity.class); Bundle bundle = new Bundle();//创建Bundle对象 bundle.putString("account","Admin");//封装用户名信息 bundle.putString("password","123456");//封装密码信息 intent.putExtras(bundle);//将Bundle对象封装到Intent对象 startActivity(intent);
-
SecondActivity获取传递数据:
Bundle bundle = getIntent().getExtras();//获取Bundle对象
String account = bundle.getString("account");
String password = bundle.getString("Password");
4.4.2 Activity之间的数据回转
-
startActivityForResult()方法
startActivityForResult()方法用于开启一个Activity,当开启的Activity销毁时,希望从中返回数据。
startActivityForResult(Intent intent, int requestCode)
//intent:意图对象
//requestCode:请求码,用于标识请求来源。
-
setResult()方法
用于携带数据进行回传
setResult(int resultCode, Intent intent)
//resultCode:返回码,用于标识返回的数据来自哪个Activity。
//intent:用于携带数据并回传到上个界面。
-
onActivityResult()
用于**接收回传的数据,**并根据传递的参数requestCode、resultCode来识别数据来源
onActivityResult(int requestCode, int resultCode, Intent data)
//requestCode:请求码
//resultCode:返回码
//data:回传的数据
- 示例代码
在MainActivity中点击button1控件跳转到SecondActivity示例代码:
button1.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view){
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivityForResult(intent,1);
}
});
//在SecondActivity中点击button2控件返回数据到MainActivity的示例代码:
button2.setOnClickListener(new View.OnClickListener){
@Override
public void onClick(View view){
Intent intent = new Intent();
intent.putExtra("data","Hello MainActivity");
//setResult()方法只负责返回数据,没有跳转功能,需要调用finish()方法关闭SecondActivity
setResult(2,intent);
finish();
}
});
(1) setResult()方法==只负责返回数据,没有跳转功能,需要调用finish()方法关闭SecondActivity。==
(2) 在MainActivity中调用startActivityForResult()方法启动SecondActivity,在SecondActivity被销毁后程序会回调MainActivity中的OnActivityResult()方法来接收回传的数据,因此需要在MainActivity中重写onActivityResult()方法。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
//如果activity中重写了onActivityResult,那么activity中的onActivityResult一定要加上
//super.onActivityResult(requestCode, resultCode, data)。
super.onActivityResult(requestCode,resultCode,data);
if(requestCode == 1 && resultCode == 2){
String acquiredData = data.getStringExtra("data");
Toast.makeText(MainActivity.this,acquiredData,Toast.LENGTH_SHORT).show();
}
}
(3)如果activity中==重写了onActivityResult,那么activity中的onActivityResult一定要加上super.onActivityResult(requestCode, resultCode, data)。==
4.5 Activity的任务栈和启动模式
4.5.1 Android中的任务栈
(1)Android的任务栈是一种用来存放Activity实例的容器。任务栈最大的特点就是先进后出。主要有压栈和出栈两个操作。
(2)用户操作的Activity永远都是栈顶的Activity。
4.5.2 Activity的启动模式
-
standard模式
(1)standard是Activity的默认启动方式。当android:launchMode没有被指定属性时默认为standard。(2)这种方式的特点是==每启动一个Activity就会在栈顶创建一个新的实例==(闹钟程序)。
(3)当Activity已经位于栈顶时,再次启动该Activity时还需要创建一个新的实例压入任务栈,不能直接复用。
-
singleTop模式
(1)会判断要启动的Activity实例是否位于栈顶,如果位于栈顶则直接复用,否则创建新的实例(浏览器书签)。
(2)如果Activity并未处于栈顶位置,则在栈中还会压入多个不相连的Activity实例 -
singleTask模式
(1)每次启动Activity时系统首先检查栈中是否存在当前Activity实例,如果存在则直接使用,并把当前Activity上面的所有实例全部弹出栈(浏览器主页面)。(2)某个Activity在整个应用程序中只有1个实例
-
singleInstance模式
(1)Activity会启动一个新的任务栈来管理Activity实例,无论从哪个任务栈中启动该Activity,该实例在整个系统中只有一个(Android桌面)。(2)要启动的Activity实例在栈中不存在,则系统先创建一个新任务栈再压入Activity实例
(3)要启动的Activity已经存在,系统会把Activity所在任务栈转移到前台,从而显示Activity(来电界面)。
4.6 使用Fragment(碎片)
为了能够同时兼顾到手机和平板电脑的开发,自Android3.0版本开始提供了Fragment
4.6.1 Fragment简介
Fragment是一种嵌入在Activity中的UI片段,它可以用来描述Activity中的一部分布局。
一个Activity可以包含多个Fragment,一个Fragment也可以在多个Activity中使用。
4.6.2 Fragment的生命周期
Fragment的生命周期也有Activity生命周期的五种状态:启动,运行,暂停,停止,销毁
-
Fragment是嵌入到Activity中使用的,它的生命周期状态直接受其所属的Activity的生命周期状态影响。
(1)当在Activity中创建Fragment时,Fragment处于启动状态,
(2)当Activity被暂停时,其中的所有Fragment也被暂停,
(3)当Activity被销毁时,其中的所有Fragment也被销毁,
(4)当一个Activity处于运行状态时,可以单独地对每一个Fragment进行操作:添加时Fragment处于启动状态,删除时Fragment处于销毁状态。 -
Fragment生命周期相比Activity额外方法:
-
onAttach():Fragment和Activity建立关联时调用
-
onCreateView():Fragment创建视图(加载布局)时创建
-
onActivityCreate():Fragment相关联的Activity已经创建时调用
-
onDestroyView(): Fragment关联的视图被移除时调用
-
onDetach(): Fragment和Activity解除关联时调用
4.6.3 创建Fragment
创建Fragment时必须创建一个类继承自Fragement。
public class NewListFragment extends Fragment{
@Override
public View on CreareView(LayoutInflater, inflater, ViewGroup container, Bundle savedInstanceState){
View v = inflater.inflate(R.layout.fragment,container, false);
return v;
}
}
4.6.4 在Activity中添加Fragment
Fragment创建完成后并不能单独使用,还需要将Fragment添加到Activity中。
- 在布局文件中添加Fragment
需要在Activity引用的布局文件中使用标签,必须指定android:name属性,属性值为Fragment的全路径名称。
<fragment
android:name="cn.itcast.NewsListFragment"
android:id="@+id/newslist"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- 在Activity中动态加载Fragment
(1)创建一个Fragment实例 (2)获取FragmentManager的实例 (3)开启FragmentTransaction (4)向Activity的布局容器中添加Fragment (5)通过commit()提交事务 |
---|
public class MainActivity extends Activity{
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
NewsListFragment fragment = new NewsListFragment(); //创建一个Fragment实例
FragmentManage fm = getFragementManager(); //获取FragmentManager的实例
FragmentTransaction beginTransaction = fm.beginTransaction(); //开启FragmentTransaction
beginTransaction.replace(R.id.ll,fragment); //向Activity的布局容器中添加Fragment
beginTransaction.commit(); //通过commit()提交事务
}
}
(1)
五、数据存储
5.1 数据存储方式
Android平台提供的数据存储方式:
- 文件存储:提供了openFileInput()和openFileOutput()方法读取文件,读取方式与java中I/O程序是完全一样的
- SharedPrefeences:用来存储一些简单的配置信息的一种机制,它采用了XML格式将数据存储到设备中。通常存储一些应用程序的各种配置信息。
- SQLite数据库:SQLite是Android自带的一个轻量级的数据库,支持基本SQL语法,一般使用它作为复杂数据的存储引擎。
- ContentProvider:Android四大组件之一,用于应用程序之间的数据交换,可以将自己的数据共享给其他应用程序使用。
- 网络储存:需要与Android网络数据包打交道,通过网络提供的存储空间来存储/获取数据信息。
5.2 文件存储
文件存储是Android中最基本的一种数据存储方式。
5.2.1 将数据存入文件中
- 内部存储
(1)将应用程序的数据以文件的形式存储到应用中(默认位于==data/data/目录下)。
(2)存储的文件会被其所在的应用程序私有化,其他应用程序想要操作本应用程序中的文件,则需要设置权限。
(3)创建的应用程序被卸载时,其内部存储文件也随之被删除。
(4)内部存储使用的是Context提供的openFileOutput()方法和openFileInput()方法,返回进行读写操作的FileOutputStream对象和FileInputStream对象。==
FileOutputStream fos = openFileOutput(String name, int mode);
FileInputStream fis = openFileInput(String name);
//name表示文件名
//mode表示文件的操作模式
//MODE_PRIVATE:该文件只能被当前程序读写
//MODE_APPEND:该文件的内容可以增加
//MODE_WORLD_READABLE:该文件的内容可以被其他程序读
//MODE_WORLD_WRITEABLE:该文件的内容可以被其他程序写
Android默认情况下任何应用创建的文件都是私有的,其他程序无法访问。 |
---|
- 如果希望文件能够被其他程序进行读写操作则需要同时指定该文件的MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE权限。
private void WriteInsideFile(){
//打印表示该函数开始执行
Toast.makeText(this,"WriteInsideFile",Toast.LENGTH_SHORT).show();
String fileName="data.txt";
String content="HelloWorld";
FileOutputStream fos=null;
try{
fos=openFileOutput(fileName,MODE_PRIVATE);
fos.write(content.getBytes());
//打印写入文件路径
Toast.makeText(this,fos.getFD().toString(),Toast.LENGTH_SHORT).show();
}catch(Exception e){
e.printStackTrace();
}finally {
try {
if (fos != null) {
fos.close();
//打印表示fos.close()已执行
Toast.makeText(this,"fos.close()",Toast.LENGTH_SHORT).show();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
- 外部存储
(1)将数据以文件的形式存储到一些外部设备上,属于永久性的存储方式(文件通常位于==storage/emulated/0==目录下)。
(2)外部存储的文件可以被其他应用程序所共享,当外部存储设备连接到计算机时,这些文件可以被浏览、修改和删除 (3)使用外部设备之前必须使用Environment.getExternalStorageState()方法确认外部设备是否可用。
private void WriteExtFile(){
//打印表示该函数开始执行
Toast.makeText(this,"WriteExtFile",Toast.LENGTH_SHORT).show();
String state = Environment.getExternalStorageState();//获取设备状态
if(state.equals(Environment.MEDIA_MOUNTED)){//判断设备是否可用
//File SDPath = Environment.getExternalStorageDirectory();//获得sd卡根目录,该方法在安卓29后被废弃,且实践可得该方法无法被成功执行(设备版本android29)
File SDPath = getExternalFilesDir(null);//获得sd卡目录
Toast.makeText(this,SDPath.getAbsolutePath(),Toast.LENGTH_SHORT).show();//显示设备状态
File file = new File(SDPath, "data.txt");
String data ="HelloWorld";
FileOutputStream fos=null;
try{
fos = new FileOutputStream(file);
fos.write(data.getBytes());
//打印写入文件绝对路径
Toast.makeText(this,file.getAbsolutePath(),Toast.LENGTH_SHORT).show();
} catch(Exception e){
e.printStackTrace();
}finally {
try{
if(fos!=null){
fos.close();
//打印表示fos.close()已执行
Toast.makeText(this,"fos.close()",Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//显示设备状态
Toast.makeText(this,state,Toast.LENGTH_SHORT).show();
}
5.2.2 从文件中读取数据
- 读取内部存储中的文件数据
private void ReadInsideFile() {
//打印表示该函数开始执行
Toast.makeText(this,"ReadInsideFile()",Toast.LENGTH_SHORT).show();
String content = "";
FileInputStream fis = null;
try {
fis = openFileInput("data.txt");
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
content = new String(buffer);
Toast.makeText(this,fis.getFD().toString(),Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
//打印表示fos.close()已执行
Toast.makeText(this,"fos.close()",Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//显示文件内容
Toast.makeText(this,content,Toast.LENGTH_SHORT).show();
}
- 读取外部存储中的文件数据
private void ReadExtFile(){
//表示该函数开始执行
Toast.makeText(this,"ReadExtFile()",Toast.LENGTH_SHORT).show();
String state = Environment.getExternalStorageState();
//打印SDCard挂载情况
Toast.makeText(this,state,Toast.LENGTH_SHORT).show();
if(state.equals(Environment.MEDIA_MOUNTED)){
//File SDPath = Environment.getExternalStorageDirectory();//废弃
File SDPath = getExternalFilesDir(null);//获得sd卡目录
File file = new File(SDPath, "data.txt");
FileInputStream fis=null;
BufferedReader br =null;
try{
fis= new FileInputStream(file);
br =new BufferedReader(new InputStreamReader(fis));
String data=br.readLine();
//打印读取内容
Toast.makeText(this,data,Toast.LENGTH_SHORT).show();
}catch(Exception e){
e.printStackTrace();
}finally {
if(br!=null){
try{
br.close();
//打印表示br.close()已执行
Toast.makeText(this,"br.close()",Toast.LENGTH_SHORT).show();
}catch(IOException e){
e.printStackTrace();
}
}
if(fis!=null) {
try {
fis.close();
//打印表示fis.close()已执行
Toast.makeText(this, "fis.close()", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
5.2.3 权限申请
当程序访问系统一些关键信息时,必须申请权限
1.静态申请权限
静态申请权限方式适用于Android SDK6.0 以下版本。该方式是在清单文件的标签中声明需要申请的权限。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.milk.myapplication">
<!-- SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 向SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 访问网络状态权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 唤醒和锁屏权限 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- 录音权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 访问wifi权限 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 修改音频参数权限 -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- 摄像头权限 -->
<uses-permission android:name="android.permission.CAMERA"/>
<application
······
</application>
</manifest>
2.动态申请权限
Android SDK6.0及以上版本改变了权限的管理方式,将权限分为正常权限和危险权限
(1)正常权限
不会直接给用户隐私权带来风险的权限
(2)危险权限
申请了该权限的应用可能涉及了用户隐私信息的数据或资源,也可能对用户存储的数据或其它应用的操作带来影响
-
位置(LOCATION)
-
日历(CALENDAR)
-
照相机(CAMERA)
-
联系人(CONTACTS)
-
存储卡(STORAGE)
-
传感器(SENSORS)
-
麦克风(MICROPHONE)
-
电话(PHONE)
-
短信(SMS)
①危险权限不仅需要在清单文件的节点中添加权限,还需要在代码中动态申请权限。
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"}, 1);
//requestPermissions(context上下文,需要申请的权限,请求码)
②该方法会在界面上弹出是否允许请求权限的对话框,当用户点击对话框的"ALLOW"按钮时,程序会执行动态申请权限的回调方法onRequestRermissionsResult(),在该方法中可以获取用户授予申请的权限的结果。
onRequestPermissionsResult(请求码,请求的权限,用户授予权限的结果) |
---|
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,int[] grantResults){
super.onRequestPermissionsResult(requestCode,permissions,grantResults);
if(requestCode==1){
for(int i=0;i<permissions.length;i++){
if(permissions[i].equals("android.permission.WRITE_EXTERNAL_STORAGE")&&
grantResults[i]== PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "" + "权限" + permissions[i] + "申请成功", Toast.LENGTH_SHORT).show();
}
else{
Toast.makeText(this,""+"权限"+permissions[i]+"申请失败",Toast.LENGTH_SHORT).show();
}
}
}
}
※似乎权限申请代码需要写在onStart()方法中
5.3 SharedPreferences存储
5.3.1 将数据存入SharedPreferences中
-
首先需要调用==getSharedPreferences(String name,int mode)==方法获取实例对象。
name:xml文件名 mode:模式 (1)MODE_PRIVATE(只能被自己的应用程序访问) (2)MODE_WORLD_READABLE(除了自己访问外还可以被其它应该程序读取) (3)MODE_WORLD_WRITEABLE(除了自己访问外还可以被其它应该程序读取和写入)
-
获取实例对象的本身只能获取数据,不能对数据进行修改和存储,需要调用SharedPreferences类的==edit()方法获取可编辑的Editor对象==,最后通过该对象putXxx()方法存储数据。
-
Editor对象以key/value形式将文件保存在==data/data//shared_prefs==文件夹下XML文件中
-
value只能是**float、int、long、boolean、String、Set**类型数据
-
操作数据完成后,一定要调用commit()方法进行数据提交,否则所有操作不生效。
SharedPreferences sp= getSharedPreferences("data",MODE_PRIVATE); //data:文件data.xml,若不存在,则创建
SharedPreferences.Editor editor =sp.edit(); //获取edit对象对数据进行修改和存储
editor.putString("name","姓名"); //putXxx()方法存储数据
editor.putInt("age",8);
editor.commit(); //提交存储的数据
5.3.2 读取与删除SharedPreferences中的数据
-
读取SharedPreferences中的数据
需要获取SharedPreferences对象,通过该对象的**getXXX()**方法根据相应key值获取到value的值。获取数据的key值与存入数据的key值数据类型要一致,否则查找不到数据
SharedPreferences sp = getSharedPreferences("data",MODE_PRIVATE);
String data = sp.getString("name","");
//getXXX()方法的第二个参数为缺省值,如果sp中不存在该key,则返回缺省值。
- 删除SharedPreferences
需要调用Editor对象的**remove(String key)方法或者clear()**方法。
editor.remove("name");//删除一条数据
editor.clear();//删除全部数据
5.4 SQLite数据库存储
5.4.1 SQLite数据库的创建
创建一个类继承SQLiteOpenHelper类,**重写onCreat()方法和onUpgrade()**方法。
- 构造方法参数:上下文对象、数据库名称、游标工厂(通常是null)、数据库版本。
- onCreat()方法是在数据库第一次创建时调用,该方法通常用于初始化表结构。
- onUpgrade()方法在数据库版本号增加时调用,如果版本号不增加,则该方法不调用。
public class UserSQLHelper extends SQLiteOpenHelper {
public UserSQLHelper(@Nullable Context context) {
//构造方法参数:上下文对象、数据库名称、游标工厂(通常是null)、数据库版本。
super(context,m_dbName+".db",null,1);
}
@Override
//onCreat()方法是在数据库第一次创建时调用,该方法通常用于初始化表结构。
public void onCreate(SQLiteDatabase db) {
db.execSQL(
"CREATE TABLE "+m_dbName+"(" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"account VARCHAR(20)," +
"password VARCHAR(20)," +
"superpower BOOLEAN)"
);
}
@Override
//onUpgrade()方法在数据库版本号增加时调用,如果版本号不增加,则该方法不调用。
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
5.4.2 SQLite数据库的基本操作
用SQLiteDatabase类对数据进行增删改查
使用完SQLiteDatabase对象后一定要调用close()方法关闭数据库连接,否则数据库连接会一直存在,不断消耗内存。
-
新增数据(insert)
insert(数据表名称,null,ContentValues对象) insert()第二个参数null:表示如果发现将要插入的行为空行时,会将这个列名的值设为null。
public void insert(String name,String sex,String phone,String number,String state) {
SQLiteDatabase db = m_helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", name);
values.put("sex", sex);
values.put("phone", phone);
values.put("number", number);
values.put("state", state);
long id = db.insert(m_dbName, null, values);
db.close();
}
-
删除数据
delete(数据表名字,删除条件,删除条件的值) 删除条件:一个字符串
删除条件的值:字符串数组 new String[]
public int delete(String deletevalue, String whereClause) {
SQLiteDatabase db = m_helper.getWritableDatabase();
int number = db.delete(m_dbName, "id=?", new String[]{id+""});
db.close();
return number;
}
-
修改数据
update(数据库表的名称,最新的数据,要修改的数据的查找条件,查找条件的参数)包含参数: 要修改的数据的查找条件:一个字符串
查找条件的参数:字符串数组 new String[]
public int update(String queryvalue, String selection,String name,String sex,String phone,String number,
String state) {
SQLiteDatabase db = m_helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", name);
values.put("sex", sex);
values.put("phone", phone);
values.put("number", number);
values.put("state", state);
int Number = db.update(m_dbName, values, selection, new String[]{queryvalue});
db.close();
return Number;
}
-
查询数据
该方法返回一个行数集合Cursor,是一个游标接口,提供了遍历查询结果的方法。使用完Cursor对象后一定要及时关闭,否则会造成内存泄露。query(表名称,查询列名,接受查询条件的子句,接收查询子句对应的条件值,分组方式,having条件,排序方式) 接受查询条件的子句:一个字符串
接收查询子句对应的条件值:字符串数组 new String[]
//queryvalue为查询匹配值,selection为数据库查询条件语句
public String[][] query(String queryvalue, String selection) {
SQLiteDatabase db = m_helper.getReadableDatabase();
Cursor cursor = db.query(m_dbName, null, selection, new String[]{queryvalue}, null, null, null);
if (cursor.getCount() == 0) {
//按理说cursor已经获得数据,可以提前关掉db,但事实表明不能在if之前调用db.close(),只能在if else中各调用一次db.close()
db.close();
cursor.close();
return null;
} else {
String[][] result = new String[cursor.getCount()][6];
for (int i = 0; cursor.moveToNext(); i++) {
result[i][0] = cursor.getString(cursor.getColumnIndex("_id"));
result[i][1] = cursor.getString(cursor.getColumnIndex("name"));
result[i][2] = cursor.getString(cursor.getColumnIndex("sex"));
result[i][3] = cursor.getString(cursor.getColumnIndex("phone"));
result[i][4] = cursor.getString(cursor.getColumnIndex("number"));
result[i][5] = cursor.getString(cursor.getColumnIndex("state"));
}
db.close();
cursor.close();
return result;
}
}
5.4.3 用SQL语句进行数据库操作
//增加一条数据
db.execSQL("insert into information(name,phone)values(?,?)",new Objext[]{name,phone});
//删除一条数据
db.execSQL("deletefrom information where _id = 1");
//修改一条数据
db.execSQL("update information set name=? where phone =?",new Object[]{name,price});
//执行查询的SQL语句
Cursor cursr =db.rawQuery("select * from information where name=?",new String[]{name});
5.4.4 SQLite数据库中的事务
数据库事务是一个数据库执行工作单元,是针对数据库的一组操作,可以由一条或多条SQL语句组成,必须满足数据库事务正确执行基本要素**(ACID)**。
ACID:原子性、一致性、隔离性、一致性
PersonSQLiteOpenHelper helper =new PersonSQLiteOpenHelper(getApplication());
//获取可读写SQLiteDataBase对象
SQLiteDataBase db = helper.getWitableDatabase();
//开始事务
db.begnTransaction();
try{
//数据库操作1
//数据库操作2
//数据库操作3
//标记数据库事务成功执行
db.setTransctionSuccessful();
}catch(Excepton e){
Log.i("事务处理失败"),e.toString());
}finlly{
//关闭事务
db.endTransaction();
//关闭数据库
db.close();
}
当执行endTranscation()时,会检查是否有事务执行成功的标记,有则提交数据,无则回滚数据,最后关闭事务 |
---|
六、内容提供者和内容观察者
6.1内容提供者概述
(1)文件存储、SharedPreference、数据库存储,这些持久化数据所保存的数据都只能在==当前应用程序中访问==。
(2)在Android中应用程序之间是相互独立的,分别运行在自己的进程中,如果需要应用程序之间共享数据,则需要用到ContentProvioder
(3)ContentProvider功能:在Android不同程序之间实现数据的共享,不仅允许一个程序访问另一个程序的数据,还可以选择只共享哪一部分的数据,保证隐私数据不被泄露
6.1.1 内容提供者ContentProvider的工作原理
(1)A程序需要使用ContentProvider暴露数据,该数据才能够被其他应用程序操作
(2)B程序通过**ContentResolver操作A程序暴露出来的数据**
(3)A程序将操作的结果返回给ContentResolver,然后ContentResolver再将操作的结果返回给B程序
6.2.2 数据模型和Uri
对于ContentResolver来说最重要的就是数据模型(Data Model)和Uri
1. 数据模型
(1) ContentResolver使用基于数据库模型的简单表格来提供需要共享的数据,每一行表示一条数据,每一列代表特点类型和含义的数据,并且每一条数据都包含一个名为_ID的字段标识每一条数据
(2) 如果要查询上述表的任何一个字段,则需要知道各个字段对应的数据类型,Cursor对象专门为这些数据类型提供了相关的方法,getInt(),getString(),getLong()等
2.Uri
(1) ContentResolver与SQLiteDatabase类似,提供一系列增删改查的方法对数据进行操作
(2) ContentResolver不同于数据库,这里的增删改查以Uri的形式对外提供数据,Uri为ContentProviderr中的数据建立了唯一的标识符,该标识主要用来区分不同的与应用程序,一般为了==避免不同的authority产生冲突==,会直接采用应用程序的包名直接进行命名
(3) Uri由三部分组成:scheme,authority,path
Uri:content://package/person
content:scheme,Android规定的标准前缀(固定不变)
package:authority,唯一标识
person:path,标识要访问的数据
6.2创建内容提供者
创建一个继承抽象类ContentProvide的类
重写其中的方法insert(),update(),delete(),query(),getType(),onCreate()
- insert(),update(),delete(),query():根据指定的Uri对数据进行增删改查操作
- getType():返回MIME类型的数据
- onCreate():创建内容提供者的时候自动调用
//创建一个新类继承抽象类
class Mycontentprovider extends ContentProvider//继承抽象类
{
@Override
public boolean onCreate() {//创建内容提供者的时候自动调用
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {//查
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {//获得MIME类型的数据
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {//增
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {//删
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {//改
return 0;
}
}
(1)创建完内容提供者后自动在AndroidManifest.xml文件中对内容提供者进行注册
<provider
android:authorities="cn.itcast.testandroid.Mycontentprovider"
android:name=".MyContentProvider"
android:enabled="true"
android:exported="true">
</provider>
- name:MyContenProvider的全称,可以用**.MyContenProvider代替**
- authorities:标识了MyContenProvider提供的数据,该值可以是一个或者多个URI authority,多个authority可以用逗号隔开
- enabled:表示MyContenProvider能否被系统实例化,如果属性时true则表示可以被系统实例化,false则不允许被系统实例化,该属性的默认值是true
- exported:表示MyContenProvider能否被其他应用程序使用,如果这个属性的值为true则表示==任何应用程序都可以通过Uri访问MyContenProvider,为false则表示用户id(程序build.gradle文件中的applicationId)相同的应用程序才能够访问到他==
6.3访问其他应用程序
6.3.1查询其他程序的数据
ContentResolver充当一个中介的角色,ContentProvider暴露数据时提供了相应的Uri,所以==要访问现有的ContentProvider时要指定Uri==,然后通过ContentResolver来对数据采取操作。
查询数据的具体步骤如下
-
通过parse()方法解析Uri
Uri的parse(字符串Uri):将字符串Uri解析成Uri类型对象
//parse(字符串Uri) Uri uri = Uri.parse("content://cn.itcast.mycontentprovider/person");
-
通过query()方法查询数据
(1)通过==getContentResolver()获取ContentResolver对象==
(2)调用该对象的query()方法查询数据
ContentResolver resolver = context.getContentResolver(); Cursor cursor = resolver.query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder);
(3)上述query传递的五个参数如下
-
uri:表示查询其他应用程序数据需要的Uri
-
projection:表示要查询的内容,该内容相当于数据库中每列的内容
-
selection:表示设置的查询条件,相当于sql语句中的where
-
selectionArgs:该参数需要配合section使用,如果section语句中有了?,则sectionArgs会替换掉?,否则参数sectionArgs传递的值是null
-
sortOrder:表示查询的数据按照什么顺序进行排序,相当于sql语句中的Order by,如果该参数传递的值是null,则数据默认是按照升序排列,如果想要数据降序排序,则该参数传递的值是字符串 " DESC",注意这里==DESC之前有一个空格==
-
-
通过while()循环语句遍历查询到的数据
举例:(与前面数据库类似)
while(cursor.moveToNext()) {
String address = cursor.getString(0); //0:第一列,且第一列的数据类型为String
long date = cursor.getLong(1);
int type = cursor.getInt(2);
}
cursor.close();
6.3.2 UriMatcher类
如果一个ContentProvider含有多个数据源(有多张表格),则可以使用UriMatcher类对Uri进行匹配
匹配步骤如下:
-
初始化UriMatcher类
构造函数UriMatcher()的参数:Uri没有匹配成功的成功码,通常为-1,或者设置为UriMatcher.NO_MATCH:表示-1的常量
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); //UriMatcher.NO_MATCH:表示-1的常量
-
注册需要的Uri
将需要用到Uri通过**==addUri( Uri的authority部分,Uri的path部分,Uri匹配成功后返回的匹配码 )==注册**到UriMatcher对象中
matcher.addURI("cn.itcast.contentprovider","people",PEOPLE);//注册需要的Uri matcher.addURI("cn.itcast.contentprovider","person/#",PEOPLE_ID);
-
与已经注册的Uri进行匹配
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);//初始化
matcher.addURI("cn.itcast.contentprovider","people",PEOPLE);//注册需要的Uri
matcher.addURI("cn.itcast.contentprovider","person/#",PEOPLE_ID);
Uri uri = Uri.parse("content://"+"cn.itcast.contentprovider"+"/people");
int match = matcher.match(uri);//匹配操作
switch (match) {
case PEOPLE:
break;
case PEOPLE_ID:
break;
}
6.4 内容观察者
ContentResolver可以查询到ContentProvider共享出来的数据,如果应用程序要实时监听ContentResolver的数据是否发生变化,则需要使用内容观察者ContentObserve
6.4.1 什么是内容观察者
内容观察者ContentObserve用于==观察指定Uri代表的数据的变化,当ContentResolver观察到指定Uri代表的数据的变化时,就会触发ContentObserver的onChange()方法==。此时在OnChange()中使用ContentResovler就可以查询到变化的数据
(1)内容观察者的工作原理
6.4.2 ContentObserve观察特定Uri数据变化的步骤
- 创建内容观察者
继承ContentObserve类,重写OnChange()方法和构造方法
class MyObserver extends ContentObserver {
//上述构造方法中的Handler参数可以是主线程中的Handle对象,也可以是别的对象中的Handle对象
public MyObserver(Handler handler) {
super(handler);
}
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
}
-
注册内容观察者
ContentResolve对象.registerContentObserver( uri ,true/false, 内容观察者)
(1)uri:内容提供者的Uri
(2)true:可以匹配Uri派生的其他Uri
false:只匹配当前提供的Uri
ContentResolver resolver = getContentResolver();//获取ContentResolver对象 Uri uri = Uri.parse("content://aaa.bbb.ccc");//获取Uri resolver.registerContentObserver(uri,true,new MyObserver(new Handler()));//注册内容观察者
-
取消注册内容观察者
一般在内容观察者的Activity的onDestroy()方法中进行
@Override protected void onDestroy() { super.onDestroy(); getContentResolver().unregisterContentObserver(new MyObserver(new Handler()));//取消 }
注意:在内容监听者监听的ContentProvider中,重写insert(),delete(),update()方法的时候,程序都会调用如下代码,提示ContentProvider共享的数据发生了变化
getContext().getContentResolver().notifyChange(uri,null);
//第二个参数代表内容观察者,参数为null则默认通知上一步注册的内容观察者
七、广播机制
在Android中,广播是一种在组件之间传递消息的机制,例如电量过低会发送一条提示广播,,如果==要接受并且过滤广播中的消息,则需要使用BroadcastReceiver(==广播接收者),广播接收者是**Android四大组件(Activity、Service、BroadcastReceiver和ContentProvider)**之一,通过广播接收者可以监听系统中的广播消息,实现不同组件之间通信
7.1 广播机制概述
Android中的广播机制用于进程和线程之间的通信,
7.1.1广播机制实现流程==具体流程==
- 广播接受者通过Binder机制在处理中心AMS中进行注册
- 广播发送者通过Binder机制向AMS发送广播
- AMS查找到符合条件的广播接收者,将广播发送到相应的消息循环队列中
- 程序执行消息循环会收到此广播,并会调广播接受者的onReceive()方法进行相关的处理
广播机制使用的场景
- 同一APP:同一组件内消息通信(单个或多个线程之间)
- 同一APP:不同组件之间(单个进程)
- 同一APP:具有多个进程的不同组件之间
- 不同APP组件之间
- Android和APP之间的通信
后三者较为常见
广播发送者只负责发送广播,至于广播有无接收者接受,或者何时接受都不需要关心,广播发送者和广播接受者的执行是异步的 |
---|
7.2 广播接收者
7.2.1 什么是广播接收者
当Android产生一个广播事件的时候,可以有很多个对应的广播接收者接受并进行处理,这些==广播接收者需要在清单文件中或者代码中注册并指定要接收的广播事件,然后再继承BroadcastReceiver的类,再重写该类的onReceive()方法==,并再该方法中对广播事件进行处理
7.2.2 创建广播接收者
-
创建一个类来继承BroadcastReceiver的类,并重写onReceive()方法
public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { ... } }
7.2.3 注册广播接收者
注册方式分为:动态注册、静态注册。
- 动态注册:即在代码中进行注册
- 静态注册:也就是在清单文件中注册,该方法在Android 8.0后不适用,接受不到广播
1.动态注册
public class MainActivity extends AppCompatActivity{
private MyReceiver myReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ContentResolver resolver = getContentResolver();
myReceiver = new MyReceiver();//实例化广播接收者
String action = "android.povider.Telephony.SMS_RECEIVED";
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(action);//实例化过滤器
registerReceiver(myReceiver,intentFilter);//注册广播,第一个参数为广播的接收者,第二个参数为过滤器
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(myReceiver);//当activity销毁的时候注销广播接收者
}
}
2.静态注册
<receiver
android:name=".MyReceiver"
android:enable="true"
android:exported="true">
</receiver>
(1)enable=“true”:广播接收者可以由系统实例化
(2)exported=“true”:可以接收当前程序之外的广播
静态注册的特点:无论应用程序是否处于运行状态,广播接收者都会对程序进行监听 |
---|
7.3 自定义广播和广播类型
7.3.1实例代码
目标:实现简单的广播发送和接受功能
具体实现:
(1)mainactivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private MyReceiver myReceiver;
private Button mbtn_send;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
ContentResolver resolver = getContentResolver();
//实例化广播接收者
myReceiver = new MyReceiver();
//要过滤的action属性值
String action = "test";
//实例化过滤器
IntentFilter intentFilter = new IntentFilter();
//设置要过滤的广播
intentFilter.addAction(action);
//注册广播
registerReceiver(myReceiver,intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
//当activity销毁的时候注销广播接收者
unregisterReceiver(myReceiver);
}
void init() {
mbtn_send = findViewById(R.id.btn_send);
mbtn_send.setOnClickListener(this);
}
@Override
public void onClick(View view) {
Intent intent = new Intent();
//定义广播事件类型:
//setAction():设置广播的action属性名称,该名称必须与动态注册的广播者的action属性名称一致
intent.setAction("test");
//发送广播:sendBroadcast()为无序广播发送方法
sendBroadcast(intent);
mbtn_send.setText("广播成功");
}
}
(2)Myreceiver.java
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals("test")) {
Toast.makeText(context, "接收到广播", Toast.LENGTH_SHORT).show();
}
}
}
7.3.2 广播的类型
Android分为两种广播类型,无序广播和有序广播
-
无序广播
完全异步执行,所有监听到这个广播的广播接收者都会接收到此广播消息,但是接受和执行的顺序是不确定的,这种广播方式的效率高,但是无法被拦截 -
有序广播
按接收者声明的优先级依次接收,发送广播的时候,只会有一个广播接收者能够接收到此消息,在当前广播接收者中逻辑执行完后,广播才会继续传递,这种广播的效率低,可以拦截
(1)优先级的设置方法
使用IntentFilter对象(过滤器)的setPriority()方法设置优先级
intentfilter.setPriority(1000);//数值越大优先级越高
若两个广播接收者的优先级一样,则先注册的广播接收者优先级更高 |
---|
(简答题)无序广播和有序广播的比较:
(1) 发送广播时,使用的方法不同。
有序广播使用sendOrderedBroadcast()发送广播,而无序广播使用sendBroadcast()方法发送广播。
(2) 广播接收者执行的顺序
a) 有序广播的接收者是顺序执行的。
有序广播按照广播接收者声明的优先级别被依次接收。当在高级别的广播接收者逻辑执行完毕之后,广播才会继续传递。当优先级 相同时,先注册的广播接受者优先执行。
b) 无序广播是完全异步执行的。
当发送无序广播时,所有监听这个广播的广播接收者都会接收到此广播消息,但接收和执行的顺序不确定。
(3)拦截广播
有序广播的接收者可拦截广播。如果优先级较高的广播接收者将广播终止,那么广播将不再向后传递。
而无序广播则不能被拦截。
(4)效率
有序广播的效率比无序广播低。
八、服务(Service)
服务是一个长期运行在后台的用户组件,没有用户界面,可以在后台执行很多任务,例如下载文件,播放音乐
8.1 服务概述
Service是Android的四大组件之一,能够在后台长时间执行操作,且没有用户界面,服务可以去其他组件进行交互,一般由Activity启动,但是它不依赖于Activity,Activity的生命周期结束时,自己的服务也不会停止
-
服务的应用场景
主要有两个,分别是后台运行和跨进程访问
- 后台运行
顾名思义,可以常驻后台运行,不用提供界面信息,只有当系统回收内存资源的时候才会被销毁 - 跨进程访问
服务被其他应用组件启动时,即使用户切换到其他应用程序,服务依然会在后台运行,服务运行在主线程中,不是子线程,它要处理的耗时操作需要开启子线程进行处理,否则会出现ANR(程序没有响应)异常
8.2 服务的创建
与创建广播类似,继承Service类,重写onBind()方法
package cn.itcast.testandroid;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {//子类必须实现的方法,可以通过返回的LBinder对象与服务通信
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");//默认抛出的异常,后续可删除
}
}
自动在清单文件中注册服务
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
(1)enable=“true”:服务可以由系统实例化
(2)exported=“true”:可以被其他应用程序中的组件调用或进行交互
8.3 服务的生命周期
服务的生命周期与启动服务方式有关
- 服务的启动方式
- startService()启动
- bindService()启动
- 生命周期的方法
-
onCreat():第一次创建服务的方法
-
onStartCommand():调用startService()方法启动服务时执行的方法
-
onBind():调用bindService()方法启动服务时执行的方法
-
onUnbind():调用unBindService()方法断开服务绑定时执行的方法
-
onDestory():销毁服务执行的方法
如果想要停止通过startService()方法启动的服务,通过==服务自身调用stopSelf()方法或者其他组件调用stopService()==方法 |
---|
如果想要停止通过bindService()方法启动的服务,调用unBindService()方法将服务进行解绑 |
---|
8.4 服务的启动方式
8.4.1 调用startService()方法启动服务
(1)这种方法启动的服务会在后台长期运行
(2)启动服务的组件和服务之间无关联
(3)即使启动服务的组件被销毁,服务依旧会运行
-
关闭服务
在MainActivity.java中关闭服务
Intent intent = new Intent(this,MyService.class); stopService(intent);
-
开启服务
在MainActivity.java中开启服务
Intent intent = new Intent(this,MyService.class);
startService(intent);
8.4.2 调用bindService()方法启动服务
此种方法启动服务的时候
(1)服务会与组件绑定,程序允许组件与服务交互
(2)组件一旦退出或者调用unbindService()方法解绑服务,服务就会被销毁
(3)多个组件可以绑定一个服务
绑定的语法如下
bindService(Intent service, ServiceConnection conn, int flags);//绑定服务,用unbindService()解绑
- service:指定要启动的服务
- conn:用于监听调用者(服务绑定的组件)与service之间的连接状态,连接成功会调用调用者的onServiceConnected()方法,断开连接的时候会调用调用者的onServiceDisconnnected()方法
- flags:组件绑定服务的时候是否自动创建Service(如果service还未创建),该参数可以设置为0,表示不自动创建Service,”BIND_AUTO_CREATE“,表示自动创建Service
8.5 服务的通信
通过bindService()方法开启服务后,服务与绑定服务的组件之间是可以进行通信的
(1)服务的通信方式:
- 本地服务通信
- 远程服务通信
8.5.1 本地服务通信与远程服务通信
-
本地服务通信
本地服务通信表示应用程序内部的通信(1)首先创建一个Service类,该类会提供一个onBind()方法,onBind()方法返回值是一个IBinder对象
(2)IBinder参数作为参数传递给ServiceConnection类中的onServiceConnected(ComponentName name,IBinder service)方法
(3)这样访问者(绑定服务的组件)就可以**通过IBinder对象与Service进行通信**
- 服务在进行通信的时候使用的是IBinder对象,在ServiceConnection类中得到IBinder对象,通过该对象可以获得服务中自定义的方法,执行具体的操作
- 远程服务通信
远程服务通信用在应用程序之间的通信,远程服务通信通过AIDL实现,是一种接口语言
ot yet implemented");//默认抛出的异常,后续可删除
}
}
**自动在清单文件中注册服务**
```xml
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
(1)enable=“true”:服务可以由系统实例化
(2)exported=“true”:可以被其他应用程序中的组件调用或进行交互