Android移动应用基础教程
- 0x01基础入门
- 开发环境
- 程序结构
- 四大组件
- 课堂答题
- 课堂测试1
- 本章练习
- 0x02布局设计
- 常用布局类型
- 定义和引用样式
- 关联控件
- 本章练习
- 0x03Activity程序活动单元
- 创建Activity
- 启动Activity
- Activity生命周期
- Activity间传数据
- 本章练习
- 0x04本地数据存储
- 文件存储方式
- SharedPerferences
- 实战演练——保存QQ帐号和密码
- SQLite数据库
- 本章练习
- 课程信息作业2
- 课堂测试2
- 0x05 广播接收者
- 创建广播接收者
- 发送广播消息
- 本章练习
- 0x06服务Service
- 创建服务
- Service生命周期
- Service本地通信
- 课堂练习
- 本章练习
按照课程要求整理的笔记,如有错误感谢指正🏇
0x01基础入门
开发环境
- IDE-Android Studio
- JDK
- AVD-模拟器
Android SDK Manager:Android软件开发工具包管理器
AVD Manager:Android虚拟驱动管理器
项目移植文件用import不要用open
程序结构
- app ~存放程序代码和资源
- AndroidManifest.xml :配置文件
- java :代码文件
- res :资源文件
- 图片资源 drawble | mipmap (文件名不能有大写)
- Gradle Scripts
- build.gradle :gradle构建脚本
- 创建一个Android项目,它的包名自动生成:域名倒过来+应用名称
- 红色波浪线,意味着错误
- 常见错误-改一下依赖关系中的support和编译版本可以解决R文件报红的问题
四大组件
- Acitivity:负责用户交互
- Service:执行持续性的、耗时且无需用户界面交互操作
- BroadcastReceiver:接收来自系统和应用程序的广播
- Content Provider:共享的持久数据存储机制
课堂答题
- Android项目的res文件夹存放的是各种资源.
- Toast和Log中,可以输出日志信息的是Log.
- 属性值“@color/…"的省略号部分应当是预定义在color文件中的颜色名称.
- 要改变APP图标,应当修改AndroidManifest.xml文件.
- 在AVD上运行android项目,目前所用的退出方法是按虚拟设备上的回退键.
课堂测试1
- 移动开发技术中,Android是属于原生开发类别。
- Android体系结构中,负责硬件驱动的是Linux内核层。
- Android项目打包后,得到APK文件。
- 创建新样式TextStyle后,要在某个控件中引用它,则在控件标签内加入一条属性:style="@style/TextStyle"。
- 如果一个线性布局的属性orientation值为“horizontal”,那么它里面的元素将水平横向排列。
- 如果一组单选按钮可以选中多项,则可能的原因是没有放入同一个RadioGroup控件容器。
- 使用getText()方法获取控件的文本。
- 为Button对象btn关联布局控件的方法是findViewById。
- activity接收Intent对象时,应调用getIntent()方法。
- 当一个activity从不可见变得可见时,将调用回调方法onStart()。这是因为只有一个Activity,无法进行从停止状态到再次启动状态的操作,当程序有多个Activity进行切换时就可以看到onReStart()方法的执行。
- 安装和配置Android Studio,必须安装它自带的模拟器Android Virtual Device。(×)
- 用Java语言编写的Android项目,不可以运行在Java虚拟机上。(√)
- Android项目配置在AndroidManifest.xml中完成。(√)
- 可供用户输入信息的控件只有EditText。(×)
- 每一个新建的activity都对应配置文件中的一个
<activity>
标签,并且都配有<intent-filter>
子标签。(×) - 用setAction方法配置的意图,是隐式意图。(√)
- 方法startActivity()启动activity时,只能启动同一个app项目中的activity。(×)
- 当一个activity失去焦点时,系统将调用onStop()方法。(×)onPause()
- 键值对
本章练习
- Dalvik中的Dx工具会把部分class文件转换成dex文件。
- 如果希望在XML布局文件中调用颜色资源,可以使用@color调用。
- Android程序入口的Activity是在AndroidManifest.xml文件中注册的。
- Android中查看应用程序日志的工具是LogCat。
- Dalvik是Google公司设计的用于Android平台的虚拟机。(√)
- Android应用程序的主要语言是Java。(√)
- Android系统采用分层架构,分别是应用程序层、应用程序框架层、核心类库和Linux内核。(√)
- 第三代移动通信技术(3G)包括TD-LTE和FDD-LTE两种制式。(×)第四代
- Android程序中,Log.e()用于输出警告级别的日志信息。(×)错误级别
- 每个Dalvik虚拟机实例都是一个独立的进程空间,并且每个进程之间不可以通信。(×)可以通信
- Dalvik虚拟机是基于寄存器的架构。
- Android项目中的主题和样式资源,通常放在res/values目录。
- 下列关于AndroidManifest.xml文件的说法中,错误的是
A. 它是整个程序的配置文件
B. 可以在该文件中配置程序所需的权限
C. 可以在该文件中注册程序用到的组件
D. 该文件可以设置UI布局 - Dalvik虚拟机属于Android系统架构中的核心类库层。
- Android中短信、联系人管理,浏览器等属于Android系统架构中的应用程序层。
- 简述Android系统架构包含的层次以及各层的特点。
Android系统架构从高到低分为四层,依次是应用程序层(Applications)、应用程序框架层(Application Framework)、核心类库(Libraries)和Linux内核(Linux Kernel)。
应用程序层:一个核心应用程序的集合,安装在书籍中的应用程序都属于这一层。
应用程序架构层:主要提供了构建应用程序时用到的各种API。例如活动管理器(Activity Manager)。
核心类库:主要包含了系统库和Android运行环境。
Linux内核:为Android设备的各种硬件提供了底层的驱动,如:显示驱动。
0x02布局设计
常用布局类型
布局(layout)是Android项目中用于呈现用户界面资源,布局文件存放在项目包的res/layout文件夹中,采用xml编码格式,其扩展名为 xml;
在活动activity中,通过调用setContentView方法设置该活动所呈现的布局文件。
定义和引用样式
- 为了统一Android项目外观风格,可以为项目创建并引用样式Style
- 在res/values/styles文件中新建一个
<style>...</style>
节点,并将其名称属性赋一个样式名称; - 在
<style>...</style>
节点内部,增加若干<item>...</item>
节点,用于规定某几个样式属性及其对应值; - 在布局文件的控件节点内,插入一条属性:style="@style/样式名"
RadioGroup是单选组合框,可以容纳多个RadioButton
方向布局orientantion
-水平 | 垂直
布局比重weight
颜色样式在res/values/colors.xml
res/values/style.xml
关联控件
在布局文件中,给这个控件分配一个id;
在Java代码中通过为findViewById()方法传入该int值来获取该布局对象;
访问控件的属性值,或者调用控件的方法。
- 声明变量member
private Button btn1; - 利用id将对象指向控件
btn1=findViewById(R.id.button1) - 设置监听
逐个设置:btn1.setOnclickListener(new View.OnClickListener)匿名内部类
继承接口:重写onClick方法-implement View.OnClickListener
- Toast类,即时提示信息,面向用户
Toast.makeText(this,"请先完成评论",Toast.LENGTH_LONG).show()
三个参数-上下文,提示信息,显示时长
自学-调整显示位置
void
setGravity(int gravity, int xOffset, int yOffset)
Set the location at which the notification should appear on the screen.
本章练习
- Android的常见布局都直接或者间接的继承自ViewGroup类。
- Android中的TableLayout继承自LinearLayout。
- 表格布局TableLayout通过TableRow布局来控制表格的行数。
- RelativeLayout布局通过相对定位的方式制定子控件的位置。
- 在R.java文件中,android:id属性会自动生成对应的int类型的值。
- ViewGroup是盛放界面控件的容器。(√)
- 如果在帧布局FrameLayout中放入三个所有属性都相同的按钮,那么能够在屏幕上显示的是第一个被添加的按钮。(×)
- Android中的布局文件通常放在res/layout文件夹中。(√)
- TableLayout继承自LinearLayout,因此它完全支持LinearLayout所支持的属性。(√)
- LinearLayout布局中的android:layout——weight属性用于设置布局内控件所占的权重。(√)
- 用于设置线性布局方向的是orientation。
- 下列选项中,不属于Android布局的是()。
A. FrameLayout B.LinearLayout C.Button - 帧布局FrameLayout是将其中的组件放在自己的左上角。
- 对于XML布局文件,android:layout_width属性的值不可以是()。
A. match_parent B.fill_parent C.wrap_content D.match_content - RelativeLayout表示相对布局,其中控件的位置都是相对位置。
- Android的控件样式,每一个XML属性都对应一个Java方法。(√)
- 当指定RadioButton按钮的android:checked属性为true时,表示未选中状态。(×)true是选中
- AlertDialog对话框能够直接通过new关键字创建对象。(×)
由于AlertDialog类的构造方法被声明成protected方法,因此不能直接使用new 关键字来创建AlertDialog类的对象实例。需要使用AlertDialog中定义的一个内嵌类,即Builder类:
AlertDialog ad=new AlertDialog.Builder(this).setTitle(“title”).create();
ad.setMesssage(“信息”);
ad.show();
- Toast是Android系统提供的轻量级信息提醒机制,用于向用户提示即时消息。(√)
- ListView列表中的数据是通过Adapter加载的。(√)
- 在XML布局中定义了一个Button,决定Button按钮上显示文字的属性是android:text。
- android:textSize="18sp"用于设置TextView中的文字显示的大小。
- 使用EditText控件时,当文本内容为空时,如果想做一些提示,那么可以使用的属性是android:hint。
- 为了让一个ImageView显示一张图片,可以通过设置的属性是android:src。
- 当数据超出能显示范围时,ListView自动具有可滚动的特性。
- CheckBox被选择的监听器事件通常使用setOnCheckedChangeListener。
- 当使用EditText控件时,能够使文本框设置为多行显示的属性是android:lines。
- 下列股阿奴与AlertDialog的描述,错误的是()。
A. 使用new关键字创建AlertDialog的实例
B. 对话框的现实需要调用show()方法
C. setPositionButton()方法是用来设置确定按钮的
D. setNegativeButton()方法是用来设置取消按钮的
0x03Activity程序活动单元
创建Activity
过滤器
启动Activity
- 启动一个Activity,通常有两种方法:
startActivity( intent )
startActivityForResult( intent, requestCode)
两种方法都需要先设置意图对象intent,用于指定被启动的activity;
但后者不仅要启动这个activity,还要请求对方返回指定的结果,因此参数比前者多一个requestCode参数。 - Intent对象的设置可以是显式设置(指定类名setClass),也可以是隐式设置(指定动作名setAction)
- 关闭当前Activity的方法是
finish()
- 显式意图-跳转
case R.id.btn1:
Intent intent = new Intent();
//explicit Intent
intent.setClass(MainActivity.this,Main2Activity.class);
stratActivity(intent)
break;
- 隐式意图
隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity,IntentFilter中的过滤信息有action、category、data
//AndroidManifest.xml
<activity android:name=".Main2Activity">
<intent-filter>
<action android:name="SHOW_RESULT"/>
//目标Activity的category必须写为android.intent.category.DEFAULT
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
//MainActivity.java
intent.setAction("SHOW_RESULT");
stratActivity(intent)
- 传参
• //put review into intent
intent.putExtra(“review”,review_content);
Activity生命周期
- Activity的生命周期是指创建到销毁的整个过程,这个过程大致可以分为五种状态,分别是启动状态、运行状态、暂停状态、停止状态和销毁状态。
Activity间传数据
- 用Intent在Activity间完成数据单向传递
- putExtra()方法
- getIntent()方法
- get ***Extra()方法
- 意图中数据以键值对的形式读写
//发送方
Intent intent= new Intent( MainActivity.this, SecondActivity.class);
intent.putExtra("name", "Andrea"); //"name" 是键,"Andrea"是值
startActivity(intent);
//接收方:
Intent intent= getIntent(); //获取意图
String name= intent.getStringExtra("name"); //读name键中存储的值
本章练习
- Activity的启动模式包括standard、singleTop、singleTask和singleInstance。
- 启动一个新的Activity并且获取这个Activity的返回数据,需要重写startActivityForResult()方法。
- 发送隐式Intent后,Android系统会使用IntentFilter匹配相应的组件。
- 在清单文件中为Activity添加标签时,必须添加的属性名为
<category android:name="android.intent.category.DEFAULT"/>
,否则隐式Intent无法开启该Activity。 - Activity的finish()方法用于关闭当前的Activity。
- 如果Activity不设置启动模式,则默认为standard。(√)
- Fragment与Activity的生命周期方法是一致的。(√)
- 如果想要关闭当前的Activity,可以调用Activity提供的finish()方法。(√)
- 标签中间只能包含一个action属性。(×)
- 默认情况下,Activity的启动方式是standard。(√)
- 下列选项中,不属于Android四大组件的是(C)
A. Service
B. Activity
C. Handler
D. ContentProvider - 下列关于Android中Activity管理方式的描述中,正确的是(B)
A. Android以堆的形式管理Activity
B. Android以栈的形式管理Activity
C. Android以书的形式管理Activity
D. Android以链表的形式管理Activity - 下列选项中,(B)不是Activity生命周期方法。
A. onCreate()
B. startActivity()
C. onStart()
D. onResume() - 下列方法中,(A)是启动Activity的方法。
A. startActivity
B. goToActivity()
C. startActivityResult()
D. 以上都是 - 下列关于Intent的描述中,正确的是(B)
A. Intent不能够是实现应用程序间的数据共享
B. Intent可以实现界面的切换,还可以在不同组件间直接进行数据传递
C. 使用显示Intent可以不指定要跳转的目标组件
D. 隐式Intent不会明确指出需要激活的目标组件,所以无法实现组件之间的数据跳转 - 简述Activity的生命周期的方法及什么时候被调用。
Activity一共有7个方法,这些方法和调用的时机具体如下:
1、onCreate():Activity创建时调用,通常做一些初始化设置。
2、onStart():Activity即将可见时调用。
3、onResume():Activity获取焦点时调用。
4、onPause():当前Activity被其他Activity覆盖或屏幕锁屏时调用。
5、onStop():Activity对用户不可见时调用。
6、onDestroy():Activity销毁时调用。
7、onRestart():Activity从停止状态到再次启动时调用。
- 简述Activity的四种启动模式及其特点。
答:Activity的四种启动模式分别为standard、singleTop、singleTask和singleInstance,这些模式的特点具体如下:
1、standard:每启动一个Activity就会在栈顶创建一个新的实例。
2、singleTop:当被启动的Activity位于栈顶时,复用该Activity实例。
3、singleTask:当被启动的Activity在栈中存在实例时,会直接复用此Activity实例,并把当前Activity上面的所有实例弹出栈。
4、singleInstance:会启动一个新的任务栈来管理该Activity。
- 简述Activity、Intent、IntentFilter的作用。
(1) Activity是一个负责与用户交互的组件,每个Android应用中都会用Activity来显示界面以及处理界面上一些控件的事件。
(2) Intent被称为意图,是程序中各组件间进行交互的一种重要方式,它不仅可以指定当前组件要执行的动作,还可以在不同组件之间进行数据传递。
(3) IntentFilter为过滤器,当发送一个隐式Intent后,Android系统会将它与程序中的每一个组件的过滤器进行匹配,匹配属性有 action、data、category,需要这三个属性都匹配成功才能唤起相应的组件。
0x04本地数据存储
文件存储方式
- 文件存储是将数据组织成二进制字节流文件,存放在移动设备指定的文件路径上。
- 要读取文件字节数据,需要一个FileInputStream对象。
- 要存储数据到文件中,需要一个FileOutputStream对象。
- 文件写入、写出时,我们用try{…}catch{…}结构捕获和处理FileNotFoundException,IOException异常。
- 向文件写出数据时,文件打开模式可以设为MODE_PRIVATE,MODE_APPEND。
- 用文件存储方式进行本地数据读写,优点是什么?缺点又是什么?
优点:通过i/o流的形式把数据进行读和写,非常直接,效率高;
缺点:顺序访问,无法对文件的某些数据进行单独读取,更新、维护数据存在一定的难度。
SharedPerferences
SharedPreferences是Android中的一个数据读写类,以键值对的形式完成数据的读和写 (常用于储存用户名、密码等具有“唯一性”的少量数据),这些数据被保存为一个本地的xml文件。
- 读写之前都要调用getSharedPerferences()方法打开指定的xml文件
- SharedPreferences写数据分三步骤
- 获取一个Editor对象
- 调用Editor对象的写方法
- putInt(),putString(),putBoolean等添加键值对方法
- remove(),clear()等删除键值对方法
- 提交写的数据commit()或apply()
- SharedPerences读数据只需要一步——调用它的相关方法
- getInt(),getString(),getBoolean()等
实战演练——保存QQ帐号和密码
//MainActivity.java
package com.example.wenjiancunchu;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_login;
private EditText et_account, et_password;
private CheckBox ck;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_login = findViewById(R.id.btn_login);
et_account = findViewById(R.id.edt_user);
et_password = findViewById(R.id.edt_pwd);
ck = findViewById(R.id.checkBox);
//设置按钮的点击监听事件
btn_login.setOnClickListener(this);
//通过工具类SPSaveQQ中的getUserInfo()方法获取账号和密码信息
Map<String, String> userInfo = SPSaveQQ.getUerInfo(this);
if (userInfo != null) {
//将获取的账号显示到界面上
et_account.setText(userInfo.get("account"));
//将获取的密码显示到界面上
et_password.setText(userInfo.get("password"));
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login:
//当点击“登录”按钮时,获取界面上输入的账号和密码
String account = et_account.getText().toString().trim();
String password = et_password.getText().toString();
//检验输入的账号和密码是否为空
if (TextUtils.isEmpty(account)) {
Toast.makeText(this, "请输入QQ帐号", Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(password)) {
Toast.makeText(this, "请输入密码", Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(this, "登陆成功", Toast.LENGTH_SHORT).show();
//保存用户信息
boolean isSaveSucessed = SPSaveQQ.saveUserInfo(this, account, password);
if (isSaveSucessed) {
Toast.makeText(this, "保存成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "保存失败", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
//SPSaveQQ.java
package com.example.wenjiancunchu;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.HashMap;
import java.util.Map;
public class SPSaveQQ {
//保存账号密码到data.xml文件中
public static boolean saveUserInfo(Context context, String account, String password){
SharedPreferences sp =context.getSharedPreferences("data",Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putString("user",account);
edit.putString("pwd",password);
edit.commit();
return true;
}
//从data.xml文件中获取存储的账号密码
public static Map<String,String> getUerInfo(Context context){
SharedPreferences sp=context.getSharedPreferences("data",Context.MODE_PRIVATE);
String account = sp.getString("user",null);
String password=sp.getString("pwd",null);
Map<String,String> userMap =new HashMap<String,String>();
userMap.put("account",account);
userMap.put("password",password);
return userMap;
}
}
运行之后对应的xml文件:
存进去了哦
SQLite数据库
[SQLite Expert Personal可视化工具](http://www.sqliteexpert.com/download.html)
创建一个类继承SQLiteOpenHelper类,在该类中重写onCreate()方法和onUpgrade()方法即可,实例代码:
public class CourseHelper extends SQLiteOpenHelper {
//重写该类的构造方法
public CourseHelper(Context context){
//通过super()调用父类SQLiteOpenHelper的构造方法
//传入四个参数: 上下文对象,数据库名称,游标工厂(通常是null),数据库版本
super(context,"course",null,1);
}
//数据库被首次创建时
@Override
public void onCreate(SQLiteDatabase db) {
//初始化数据库的表结构,执行一条建表的SQL语句
String sql = "CREATE TABLE course_info(number VARCHAR(8),name VARCHAR(30),credit REAL)";
db.execSQL(sql);
}
//当数据库的版本号增加时调用,如果版本号不在能加,则该本方法不调用,但是必须重写
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
在oncreate()重写初始化:打开或创建数据库course.db
//打开或创建数据库course.db
helper=new CourseHelper(this);
Log.i("DB","database has been created and opened");
数据库创建成功,可以在设备文件管理器查看
部分代码:
case R.id.btn_add://增
//准备数据
ContentValues values = new ContentValues();
values.put("number",edt_number.getText().toString().trim());
values.put("name",edt_name.getText().toString().trim());
values.put("credit",edt_credit.getText().toString().trim());
//用db方法实现插入
db=helper.getWritableDatabase();
long position =db.insert(table,null,values);
Log.i("DB","新数据被插入在第"+position+"行");
db.close();
可见增是成功的,同理可做其他SQLite数据库的基本操作
需要注意的是,使用完SQLiteDatabase对象后一定要用close()方法关闭数据库连接,否则数据库连接就会一直存在,不断消耗内存,当系统内存不足时将获取不到SQLiteDatabase对象,并且会报出数据库未关闭异常。
删除
case R.id.btn_del://删
db=helper.getWritableDatabase();
int deleted=db.delete(table,"credit=?",new String[]{edt_credit.getText().toString().trim()});
Log.i("DB","本次共删除"+deleted+"条记录");
db.close();
break;
可视化查看太麻烦了就没看了,但是库中符合3学分就是两条数据,所以应当是对的
- 查询数据
在进行数据查询时使用的是query()方法,返回一个行数集合Cursor,Cursor是一个游标接口,提供了遍历查询结果的方法。
同样值得注意的是,对象使用后及时关闭,否则会造成内存泄露。
本章练习
一、 判断题
- SQLite是Android自带的一个轻量级的数据库,支持基本SQL语法。(√)
- Android中的文件存储方式,分为内部存储方式和外部存储方式。(√)
- 使用openFileOutput()方式打开应用程序的输出流时,只需指定文件名。(×)FileOutputStream fos=openFileOutput(String name,int mode)
- 当Android SDK版本低于23时,应用程序想要操作SD卡数据,必须在清单文件中添加权限。(√)
- SQLiteDatabase类的update()方法用于删除数据库表中的数据。(×)其中的update()类修改数据库表中的数据
- SQLite数据库的事务操作满足原子性、一致性、隔离性和持续性。(√)
二、选择题
7. 下列关于SharedPerferences存取文件的描述中,错误的是(C)
A. 属于移动存储解决方式
B. SharedPerences处理的就是key-value对
C. 读取xml的路径是/sdcard/shared_prefs
SharedPrefernences中的Editor编辑器是通过key/value的形式将数据保存在data/data/<packagename>/shared_prefs
文件下XML文件中
D. 文本的保存格式是xml
8. 下列选项中,不属于getSharedPreferences方法的文件操作模式参数是
A. Context.MODE_PRIVATE
B. Context.MODE_PUBLIC
Context.MODE_APPEND
C. Context.MODE_WOELD_READABLE
D. Context.MODE_WORLD_WRITEABLE
9. edit()方法是sharedPreference获取其编辑器的方法。
10. Android对数据库的表进行查询操作时,会使用SQLiteDatabase类中的query()方法。
11. 下列关于SQLite数据库的描述中,错误的是(C)
A. SqliteOpenHelper类有创建数据库和更新数据库版本的功能
B. SqliteDatabase类是用来操作数据库的
C. 每次强调SqliteDatabase的getWriteableDatabase方法时,都会执行SqliteOpenHelper的onCreate()方法
D. 每当数据库版本发生变化时,会调用SqliteOpenHelper的onUpgrade()方法更新数据库
12. 初始化SharedPreferences的代码中,正确的是SharedPreferences sp = getSharedPreferences();
13. 简述数据库事务的4个基本要素
事务的操作比较严格,他必须满足ACID。
ACID是指数据库事务正确执行的四个基本要素的缩写,这些要素包括原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
- 原子性:表示事务是一个不可再分割的工作单位,事务中的操作要么全部成功,要么全部失败回滚。
一致性:表示事务开始之前和结束之后,数据库的完整性没有被破坏。也就是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。- 隔离性:表示并发的事务是相互隔离的,也就是一个事务内部的操作都必须封锁起来,不会被其他事务影响到。
- 持久性:表示事务一旦提交后,该事务对数据做的更改便持久保存在数据库中,并不会被回滚,即使出现了断电等事故,也不会影响数据库中的数据。
- 简述Android数据存储的方式
Android平台提供的五种数据存储方式,分别为文件存储、SharedPreferences、SQLite数据库、ContentProvider和网络存储。
- 文件存储:Android提供了openFileInput()和openFileOutput()方法来读取设备上的文件,其读取方式与Java中I/O程序是完全一样的。
- SharedPreferences:这是Android提供的用来存储一些简单的配置信息的一种机制,他采用了XML格式将数据存储到设备中。通常情况下,我们使用SharedPreferences存储一些应用程序的各种配置信息,如用户名、密码等。
- SQLite数据库:SQLite是Android自带的一个轻量级的数据库,他运算速度快,占用资源少,还支持基本SQL语法,一般使用他作为复杂数据的存储引擎,可以存储用户信息等。
- ContentProvider:Android四大组件之一,主要用于应用程序之间的数据交换,他可以将自己的数据共享给其他应用程序使用。
- 网络存储:需要与Android网络数据包打交道,将数据存储到服务器上,通过网络提供的存储空间来存储/获取数据信息。
- 使用SQLite数据库的事务操作,编写一段模拟银行转账的逻辑代码。
PersonSQLiteOpenHelper helper = new PersonSQLiteOpenHelper (getApplication());
//获取一个可读写的SQLiteDataBase对象
SQLiteDatabase db = helper.getWritableDatabase();
// 开始数据库的事务
db.beginTransaction();
try {
//执行转出操作
db.execSQL("update person set account = account-1000 where name =?",
new Object[] { "张三" });
//执行转入操作
db.execSQL("update information set account = account +1000 where name =?",
new Object[] { "王五" });
//标记数据库事务执行成功
db.setTransactionSuccessful();
}catch (Exception e) {
Log.i("事务处理失败", e.toString());
} finally {
db.endTransaction(); //关闭事务
db.close(); //关闭数据库
}
课程信息作业2
//MainActivity.java
package com.example.myhelper;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private CourseHelper helper;
private static final String table="course_info";
private SQLiteDatabase db;
private TextView info;
private EditText edt_number,edt_name,edt_credit,edt_kind;
private Button btn_add,btn_del,btn_mod,btn_que;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//打开或创建数据库course.db
helper=new CourseHelper(this);
Log.i("DB","database has been created and opened");
//关联控件
info =findViewById(R.id.txv_info);
edt_number=findViewById(R.id.edt_number);
edt_name=findViewById(R.id.edt_name);
edt_credit=findViewById(R.id.edt_credit);
edt_kind = findViewById(R.id.edt_kind);
btn_del=findViewById(R.id.btn_del);
btn_mod=findViewById(R.id.btn_mod);
btn_que=findViewById(R.id.btn_que);
btn_add=findViewById(R.id.btn_add);
//设置单击监听
btn_add.setOnClickListener(this);
btn_del.setOnClickListener(this);
btn_mod.setOnClickListener(this);
btn_que.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_add://增
//准备数据
ContentValues values = new ContentValues();
values.put("number",edt_number.getText().toString().trim());
values.put("name",edt_name.getText().toString().trim());
values.put("credit",edt_credit.getText().toString().trim());
values.put("kind",edt_kind.getText().toString().trim());
//用db方法实现插入
db=helper.getWritableDatabase();
long position =db.insert(table,null,values);
info.setText("新数据被插入在第"+position+"行");
// Log.i("DB","新数据被插入在第"+position+"行");
db.close();
break;
case R.id.btn_del://删
db=helper.getWritableDatabase();
int deleted=db.delete(table,"number=?",new String[]{edt_number.getText().toString().trim()});
info.setText("本次共删除"+deleted+"条记录");
// Log.i("DB","本次共删除"+deleted+"条记录");
db.close();
break;
case R.id.btn_mod://改
db=helper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put("name",edt_name.getText().toString().trim());
//用户输入一门课程的 “课程编号”,并输入“课程名称”
int updated = db.update(table,contentValues,"number=?",new String[]{edt_number.getText().toString().trim()});
info.setText("本次共更新"+updated+"条记录");
// Log.i("DB","本次共更新"+updated+"条记录");
db.close();
break;
case R.id.btn_que://查
String text="";
db=helper.getReadableDatabase();
Cursor cursor=db.query(table,null,"kind=?",new String[]{edt_kind.getText().toString().trim()},null,null,null,null);
long count=cursor.getCount();
text+="一共"+count+"行记录"+"\n";
if (count>0){
cursor.moveToFirst();
do {
String number=cursor.getString(cursor.getColumnIndex("number"));
String name=cursor.getString(cursor.getColumnIndex("name"));
String credit=cursor.getString(cursor.getColumnIndex("credit"));
String kind=cursor.getString(cursor.getColumnIndex("kind"));
text+=cursor.getPosition()+":"+number+" "+name+" "+credit+" "+kind+"\n";
info.setText(text);
}while (cursor.moveToNext());
}
cursor.close();
db.close();
break;
}
}
}
//CourseHelper.java
package com.example.myhelper;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class CourseHelper extends SQLiteOpenHelper {
//重写该类的构造方法
public CourseHelper(Context context){
//通过super()调用父类SQLiteOpenHelper的构造方法
//传入四个参数: 上下文对象,数据库名称,游标工厂(通常是null),数据库版本
super(context,"course",null,1);
}
//数据库被首次创建时
@Override
public void onCreate(SQLiteDatabase db) {
//初始化数据库的表结构,执行一条建表的SQL语句
String sql = "CREATE TABLE course_info(number VARCHAR(8), name VARCHAR(30), credit REAL, kind VARCHAR(30))";
db.execSQL(sql);
}
//当数据库的版本号增加时调用,如果版本号不在能加,则该本方法不调用
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
浏览器给我关了,这里都没保存,原来的都不来重写了😢
- 可以用ListView控件对SQLite数据库查询所得的数据在布局上逐条展示,需要设置Adapter建立数据源。
相比Toast、TextView来说,ListView更加整洁美观,适合要展示的数据比较多的情况。另外两种比较适合展示量比较少的数据,但比较快捷方便。其中,Toast显示数据只能保留一段时间。
- 新建布局文件
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private String names[]={"apple","kiwi","melon","lulul"};
private Double prices[]={6.5,12.98,3.5,1.1};
private Integer amounts[]={100,200,300,400};
private boolean onsales[]={true,true,false,true};
private ListView listView_fruit;
private FruitAdapter fruitAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//关联控件
listView_fruit=findViewById(R.id.listView_fruit);
//?by using an adapter,get views for each fruit item
fruitAdapter=new FruitAdapter();
listView_fruit.setAdapter(fruitAdapter);
}
public class FruitAdapter extends BaseAdapter{
//显式构造方法
public FruitAdapter(){
super();
}
@Override
public int getCount() {
return names.length;
}
@Override
public Object getItem(int position) {
return names[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = View.inflate(MainActivity.this,R.layout.item_layout,null);
TextView tv =view.findViewById(R.id.textView_name);
tv.setText(names[position]);
tv=view.findViewById(R.id.textView_price);
tv.setText("$"+prices[position]);
tv=view.findViewById(R.id.textView_amount);
tv.setText(amounts[position]+"pieces");
RadioButton rb=view.findViewById(R.id.radioButton_discount);
rb.setChecked(onsales[position]);
return view;
}
}
}
课堂测试2
- 要存储数据到文件中,需要一个FileInputStream对象。
- 为读写“student.xml”中的学生信息数据,获取SharedPreferences对象的方法是getSharedPreferences()。
- SharedPreference写键值对数据时,还需要一个Editor对象。
- SQLite是一个轻量级数据库,是Google专门为Android系统开发的。(×)
- 在SQLiteOpenHelper子类的构造方法中,指定数据库的文件名和版本号。
- SQLiteOpenHelper子类的onCreate()方法完成数据表的创建。
- 假设已有一个SQLiteDatabase对象db,则下列代码行____能实现将表student_info中学号stuID为“20201872001”的学生姓名更改为“张三”。
ContentValues values = new ContnetValues();
values.put("name","张三");
int position= db.update(student_info,contentValues,"stuID=?",new String[]{"20201872001"});
- 为了设定ListView的单行布局,在res/layout中另外创建一个布局资源文件,以下说法正确的是
A. 在ListView控件中引用单行布局
B. 在活动的onCreate()中调用setItemLayout()方法为ListView设置单行布局
C. 在适配器Adapter的getView()方法中用inflate()方法为ListView设置单行布局 - ListView控件所对应的适配器子类,方法getCount()完成数据项的计数。
- 在广播接收者子类在AndroidManifest.xml文件中对应一个receiver标签。
- 动态注册广播接收者组件,通过设置IntentFilter的priority属性可以指定待接收/过滤的广播。
- 广播接受者的onReceive()方法,在Android系统或某APP发出指定action名称的广播时
- 无序广播和有序广播的区别在于接收者不可以拦截无序广播,可以拦截有序广播
- 在同一个生命周期内,一个广播接收者只能接受一种广播。(×)
- 服务Service可以长时间运行在后台。
- 用startService()方法启动服务,需要Intent对象。
- 用bindService()方法启动服务,需要一个接口对象ServiceConnection。
- startService()方法启动服务,将依次触发该服务的回调方法:onCreate,onStartCommand()
- bindService()方法启动服务,将依次触发该服务的回调方法:onCreate(),onBinid()
- 两种服务启动方式的参数
Intent service = new Intent(this, MyService.class);
startService(service);//开启服务
stopService(intent);//关闭服务
-------------------------------------
Intent service = new Intent(this, MyService.class);
bindService(service, connection, BIND_AUTO_CREATE);//绑定服务
unbindService(connection);//解绑服务
0x05 广播接收者
创建广播接收者
- 新建子类
public class BatteryLowReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// TODO: This method is called when the BroadcastReceiver is receiving
// an Intent broadcast.
// throw new UnsupportedOperationException("Not yet implemented");
Toast.makeText(context,"电池电量低于15%",Toast.LENGTH_LONG).show();
}
}
2.1 静态注册
2.2 动态注册
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
BatteryLowReceiver receiver;
IntentFilter intentFilter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//动态注册广播接收者
receiver = new BatteryLowReceiver();
intentFilter = new IntentFilter("android.intent.action.BATTERY_LOW");
registerReceiver(receiver,intentFilter);
}
@Override
protected void onDestroy() {
//动态注销广播接收者
unregisterReceiver(receiver);
super.onDestroy();
}
发送广播消息
sendBroadcast()-- 发送无序广播,不会被拦截;
sendOrderedBroadcast() -- 发送有序广播,优先级高的接收者可对其拦截。
两个方法都需用Intent对象封装广播消息:
调用setAction()方法为Intent对象设置action,用于标识这是一条什么广播
调用putExtra()方法向Intent对象存入数据,即广播内容。
- 设置优先级intentFilter.setPriority()
- 拦截广播abortBroadcast();
【注意】只有有序广播才有拦截效果。
MainActivity.java:
package com.example.helo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Receiver1 receiver1;
private Receiver2 receiver2;
private Button bt1,bt2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt1=findViewById(R.id.btn1);
bt2=findViewById(R.id.btn2);
bt1.setOnClickListener(this);
bt2.setOnClickListener(this);
//注册两个广播接收者
IntentFilter intentFilter=new IntentFilter("ORDERED BROADCAST");
intentFilter.setPriority(10);
receiver1 = new Receiver1();
registerReceiver(receiver1,intentFilter);
intentFilter.setPriority(1000);
receiver2 = new Receiver2();
registerReceiver(receiver2,intentFilter);
}
@Override
protected void onDestroy() {
//注销两个广播接收者
unregisterReceiver(receiver1);
unregisterReceiver(receiver2);
super.onDestroy();
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn1://发送一条无序广播
Intent intent =new Intent();
intent.setAction("UNORDERED BROADCAST");
intent.putExtra("content","这是一条无序广播");
sendBroadcast(intent);
break;
case R.id.btn2:
intent =new Intent();
intent.setAction("ORDERED BROADCAST");
intent.putExtra("content","这是一条有序广播");
sendOrderedBroadcast(intent, null);
break;
}
}
}
本章练习
- BroadcastReceiver用来监听来自系统或者应用程序的广播。
- 广播接收者的注册方式,分别是动态注册和静态注册。
- Broadcast表示广播,它是一种运用在应用程序之间传递消息的机制。(√)
- 在清单文件注册广播接收者时,可在标签中使用priority属性设置优先级别,属性值越大优先级越高。(√)
- 有序广播的广播效率比无序广播更高。(×)
- 动态注册的广播接收者的生命周期依赖于注册广播的组件。(√)
- Android中广播接收者必须在清单文件里面注册。(×)
- 关于广播类型的说法,错误的是(B C)
A. Android中的广播类型分有序广播和无序广播
B.无序广播是按照一定的优先级进行接收
C. 无序广播可以被拦截,可以被修改数据
D. 有序广播按照一定的优先级进行发送 - 广播作为Android组件间的通信方式,使用的场景有(ABCD)
A. 在同一个APP内部的同一组件内进行消息通信
B. 不同APP的组件之间进行消息通信
C. 在同一APP内部的不同组件之间进行消息通信(单个进程)
D. 在同一个APP具有多个进程的不通组件之间进行消息通信 - 简述广播机制的实现过程
答:Android中的广播使用了观察者模式,即基于消息的发布/订阅事件的模式。广播发送者和接收者分别处于观察者模式中的消息发布和订阅两端。广播机制的实现过程具体如下:
1.广播接收者通过Binder机制在AMS(Activity Manager Service)中进行注册。
2.广播发送者通过Binder机制向AMS发送广播。
3.AMS查找符合相应条件(IntentFilter/Permission)的广播接收者,将广播发送到相应的消息循环队列中。
4.执行消息循环时获取到发送的广播,然后回调广播接收者中的onReceive()方法并在该方法中进行相关处理。
- 简述有序广播和无序广播的区别
(1) 发送广播时,使用的方法不同。有序广播使用sendOrderedBroadcast()发送广播,而无序广播使用sendBroadcast()方法发送广播。
(2) 广播接收者执行的顺序
a)有序广播的接收者是顺序执行的。
有序广播按照广播接收者声明的优先级别被依次接收。当在高级别的广播接收者逻辑执行完毕之后,广播才会继续传递。当优先级相同时,先注册的广播接受者优先执行。
b)无序广播是完全异步执行的。
当发送无序广播时,所有监听这个广播的广播接收者都会接收到此广播消息,但接收和执行的顺序不确定。
(2)拦截广播
有序广播的接收者可拦截广播。如果优先级较高的广播接收者将广播终止,那么广播将不再向后传递。而无序广播则不能被拦截。
(3)效率
有序广播的效率比无序广播低。
0x06服务Service
服务运行于后台,无需用户界面,通常可用于完成网络请求、数据刷新等耗时操作或者地理定位、收发短信等特别功能。
创建服务
服务运行于后台,无需用户界面,通常可用于完成网络请求、数据刷新等耗时操作或者地理定位、收发短信等特别功能。
创建Myservice:
服务分两种:
- 系统服务SystemService,如地理位置信息、电话管理器、震动器等。调用这类系统服务,通常需要先请求使用权限uses-permission,在获得用户授权的情况下获取服务接口getSystemService(),这样才能调用其中的各类方法。
- 自定义服务,继承自Service类,需要重写服务的回调方法onBind(). Service组件在配置文件中对应 的标签是
<service>
。 调用startService()或bindService()方法,可以启动自定义服务。
Service生命周期
启动/停止服务的方法不同,导致服务的生命周期不一样。
startService() : onCreate()-- onStartCommand()
stopService(): onDestroy()
bindService() : onCreate()-- onBind()
unbindService(): onUnbind()-- OnDestroy()
Service本地通信
Service本地通信:同一个App项目包中的Service组件向绑定的其他组件提供数据读写接口。由此可见,要实现Service通信,前提是用bindService()方法启动Service。
通信的关键是onBind()方法中返回的Bind对象,这个对象将被传递到对方的onServiceConnected()中,从而使对方获得读写Service内部数据的接口。
课堂练习
MainActivity.java
package com.example.myservice;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private final static String TAG = "TRY_SERVICE";
private Button btn_start, btn_end, btn_num;
private Intent service;
private MyService.MyBinder myBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder = (MyService.MyBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_start:
service = new Intent(this, MyService.class);
bindService(service, connection, BIND_AUTO_CREATE);
Log.i(TAG, "MainActivity has bound MyService");
break;
case R.id.btn_num:
double ans = myBinder.getCount();
Log.i(TAG,"count="+ans);
break;
case R.id.btn_end:
unbindService(connection);
Log.i(TAG, "MainActivity has unbound MyService");
break;
}
}
//控件绑定
private void init() {
btn_start = findViewById(R.id.btn_start);
btn_end = findViewById(R.id.btn_end);
btn_num = findViewById(R.id.btn_num);
btn_start.setOnClickListener(this);
btn_end.setOnClickListener(this);
btn_num.setOnClickListener(this);
}
}
MyService.java
package com.example.myservice;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class MyService extends Service {
private final static String TAG = "TRY_SERVICE";
private double count;
private boolean flag;
private MyBinder binder;
public class MyBinder extends Binder {
public double getCount() {
return count;
}
}
//构造方法
public MyService() {
count = 0;
flag = true;
binder = new MyBinder();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
// throw new UnsupportedOperationException("Not yet implemented");
Log.i(TAG, "MyService--onBind()");
return binder;
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "MyService--onCreate()");
new Thread(new Runnable() {
@Override
public void run() {
while (flag) {
try {
//每隔500让计数值增加0.5
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count += 0.5;
}
}
}).start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "MyService—— onStartCommand()");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "MyService—— onUnbind()");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
flag = false;
super.onDestroy();
Log.i(TAG, "MyService—— onDestroy");
}
}
运行截图:
本章练习
- 如果想要停止bindService()方法启动的服务,需要调用unBindService()方法。
- Android系统的服务的通信方式分为本地服务和远程服务。
- 远程服务通过AIDL实现服务的通信。
- Service服务是运行在子线程中的。(×)
- 不管使用哪种方式启动Service,它的生命周期都是一样的。(×)
- 使用服务的通信方式进行通信时,必须保证服务是以绑定的方式开启的,否则无法通信。(×)
- 一个组件只能绑定一个服务。(×)
- 远程服务和本地服务都运行在同一个进程中。(×)
- 如果通过bindService方式开启服务,那么服务的生命周期是
A. onCreate()->onStart()->onBind()->onDestory()
B. onCreate()->onBind()->onUnBind()->onDestory()
C.onCreate()->onBind()->onUnBind()->onDestory()
D. onCreate()->onStart()->onBind()->onUnBind()->onDestory() - 下列关于Service服务的描述中,错误的是(D)
A. Service是没有用户可见的界面,不能与用户交互
B. Service可以通过Context.startService()来启动
C. Service可以通过Context.bindService()来启动
D.Service无须在清单文件中进行配置 - 下列股阿奴与Service的方法描述,错误的是(D)
A. onCreate()表示第一次创建服务时执行的方法
B. 调用startService()方法启动服务执行的方法是onStartCommand()
C. 调用bindService()方法启动服务时执行的方法是onBind()
D. 调用startService()方法断开服务绑定时执行的方法是onUnbind() - 简述Service的两种启动方式的区别
Service的启动方式分别可以调用startService()、bindService()方法,这两个启动方式的区别如下所示:
(1)生命周期
startService():使用该方法开启Service时,执行的生命周期方法依次为onCreate()、onStartCommand()、onDestroy()。
bindService():使用该方法开启Service时,执行的生命周期方法依次为:onCreate()、onBind()、onUnbind()、onDestroy()。
(2)停止服务的方法
startService():调用stopSelf()、stopService()方法停止服务。
bindService():调用unbindService()方法停止服务。
(3)组件的关联
startService():当一个组件通过此方法开启服务时,服务与开启该组件没有关联,即使开启服务的组件被销毁,服务依旧运行。
bindService():当一个组件通过此方法开启服务时,服务会与该组件绑定,组件一旦被销毁,该服务也会被销毁。
- 简述Service的生命周期
使用不同的方式启动服务,其生命周期会不同。开启服务的方法分别为startService()、bindService(),当通过startService()方法启动服务时,执行的生命周期方法依次为onCreate()、onStartCommand()、onDestroy()。当通过bindService()方法启动服务时,执行的生命周期方法依次为onCreate()、onBind()、onUnbind()、onDestroy()。