Android笔记总结了教材1到9章节的知识,可用于Android的学习或者期末复习,笔记字体有点小,建议放大阅读,欢迎读者提出建议或批评指正!!
1. Android入门
1.1 【开发基本介绍】
Android是一种基于Linux内核的自由及开放源代码的移动操作系统,由Google公司和开放手机联盟领导及开发。它最初由安迪·鲁宾开发,并于2005年被Google收购注资。Android操作系统最初主要支持手机,随后逐渐扩展到平板电脑、电视、数码相机、游戏机、智能手表等其他领域。
总的来说,Android系统以其开放性、丰富的应用生态、强大的多任务处理能力以及灵活的开发工具等特点,赢得了全球用户的广泛认可和喜爱,成为了移动设备领域的主流操作系统之一。
1.2 【Android系统架构】
-
应用程序层:这一层是用户直接交互的界面,包含了各种核心应用程序,如联系人、短信、浏览器等。这些应用程序可以是系统自带的,也可以是从Google Play等应用商店下载的。
-
应用程序框架层:这一层为应用程序的开发提供了大量的API(应用程序接口),使得开发者能够高效地构建各种应用。
-
核心类库:这一层包含了一些C/C++库,为Android系统提供核心功能支持,同时包含了Android运行环境(ART),负责Android应用程序的执行。
-
Linux内核:Android系统基于Linux内核,这一层为Android设备提供了底层的硬件驱动和系统服务。
1.3 【Android项目结构】
- src:存放项目用到的各种Activity,可以有多个不同的包
- res:存放Android项目的各种资源文件,如布局layout文件,value目录下的文件
- libs:存放Android项目开发的第三方JAR包
- build:Android Studio自动生成的各种资源文件,R.java文件也放在该目录下
1.4 【AndroidMainfest.xml清单文件详解】
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- android:icon="@mipmap/ic_launcher" 应用程序的图标-->
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.RadionButtonAndCheckBox"
tools:targetApi="31">
<!-- 自定义的Activity-->
<activity
android:name=".SecondActivity"
android:exported="false" />
<!--应用程序的Activity-->
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<!--指定该Activity为程序的入口-->
<action android:name="android.intent.action.MAIN" />
<!--指定启动应用时运行该Activity-->
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
1.5【安卓四大组件】
-
Activity(活动)
-
Service(服务)
-
BroadcastReceiver(广播接收器)
-
ContentProvider(内容提供者)
2. Android应用界面
2.1【View概述】
- Android应用界面由View和ViewGroup对象构建,ViewGroup是View的子类,是View的扩展,可以容纳多个View
- View是AndroidUI的基本构建块,所有UI组件都是View的子类,它包含的XML属性和方法是所有组件都可以使用的
- ViewGroup是View的扩展可以包含多个View,作为容器使用
2.2【布局管理器】
基本的宽高单位
px: 像素单位,同样100px,不同分辨率的手机显示的控件大小不一样;
pt: 磅数单位,一般作为字体单位使用,与 px相似,不同分辨率的手机显 示的字体大小不一样;
dp: 密度无关像素单位,无关屏幕分辨率,都能显示相同的大小,一般作 为宽高单位;
sp: 推荐的字体单位,代表可伸缩像素,与dp一样的设计理念,设置字体 大小使用
布局类型
-
线性布局:线性布局允许其子元素按照水平或垂直方向排列。
-
相对布局:相对布局允许子元素相对于彼此或父元素进行定位。
-
表格布局:表格布局将子元素按照行列的方式排列,类似于HTML中的表格。
-
网格布局:网格布局将界面划分为行和列的网格,允许子元素在网格中的特定位置进行定位。
-
扁平化布局:Android的扁平化布局是近年来在Android应用界面设计中越来越受欢迎的一种风格。扁平化布局强调简洁、直观和去除复杂的视觉效果,使得界面更加清晰、现代和易于理解。
-
绝对布局:绝对布局允许开发者通过指定子元素的精确坐标(X,Y)来进行布局。这种方式提供了最大的灵活性,但也带来了最大的复杂性。
-
帧布局:帧布局是最简单的布局方式,它将所有的子元素放置在屏幕的左上角,并且后面的元素会覆盖前面的元素。
2.3【常用的Android UI控件】
TextView
-
直接继承了View,还是EditText和Button组件的父类
-
作用:在界面上显示文字
gravity:字体的位置
textColor:字体颜色
textSize:字体大小
<TextView android:id="@+id/textview1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="这是一个TextView组件" android:textColor="@color/red" android:textSize="16sp" />
EditText
-
作用:接收用户输入
-
hint:提示用户输入的内容
-
inputType:设置输入的类型
<EditText android:id="@+id/EditText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:hint="请输入密码" android:inputType="number" android:textSize="16sp" android:textColor="@color/red"/>
Button
-
继承了TextView组件
-
作用:单击按钮,添加onClick事件
-
background:设置按钮背景色
-
textColor:文本颜色
<Button android:id="@+id/btnGo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="第一个aty,现在实现跳转" android:textSize="18sp" android:textColor="@color/blue" android:background="@color/red" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
ImageView
-
图像组件继承View组件,它还派生了ImageButton、ZoomButton等组件
-
作用:显示图片,ImageView和ImageButton都能添加单击事件
<ImageView android:id="@+id/imageview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_launcher" tools:layout_editor_absoluteX="33dp" tools:layout_editor_absoluteY="96dp" /> <ImageButton android:id="@+id/iamgebtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_launcher"
RadioButton
-
与HTML的单选按钮一样,搭配RadioGroup使用
-
代码:checked属性是否选中
<RadioGroup android:layout_width="wrap_content" android:layout_height="wrap_content"> <RadioButton android:id="@+id/radiobtn1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="女"/> </RadioGroup>
CheckBox
-
与HTML的复选框一样
-
代码:
<CheckBox android:id="@+id/checkbox1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="读书"/> <CheckBox android:id="@+id/checkbox2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="看电影"/>
ProgressBar
-
即进度条组件,派生了两个常用的组件:seekBar、RatingBar
-
常用属性:
android:max:设置进度条的最大值
android:progress:设置进度条已完成值
style:设置ProgressBar的风格
<!--水平进度条--> <ProgressBar style="@style/Widget.AppCompat.ProgressBar.Horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:max="100" android:progress="50" /> <!--环形进度条--> <ProgressBar style="@style/TextAppearance.AppCompat.Inverse" android:layout_width="wrap_content" android:layout_height="wrap_content" android:max="100" android:progress="70" />
SeekBar
-
它继承了ProgressBar,因此ProgressBar的xml属性和方法也适用于SeekBar
<SeekBar android:id="@+id/seekbar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/gray"/>
2.4【AdapterView及其子类】
AdapterView具有以下特征:
- AdapterView继承ViewGroup,本身是一个抽象基类,它的本质是容器
- AdapterView包括多个“列表项”,以合适的方法显示出来
- 它的列表项由Adapter提供,调用AdapterView的setAdapter(Adapter)方法设置Adapter即可
- 它的子类有ListView(列表视图)、Spinner(下拉列表)、GridView
2.4.1【ListView列表视图】
-
ListView是一个垂直方向显示列表项的列表视图组件,用于生成列表视图,ListActivity直接继承自Activity,只包含一个组件即ListView
-
生成列表视图有两种方式:
直接使用ListView
写一个类继承ListActivity的Activity,再使用ListView
ListView的细节:
- ListView可以添加事件处理,可以应用于导航到新的Activity
- ListView底层已经使用了setAdapter(…) 只是代码不用写了
- 数据超出屏幕范围,ListView自动具有滚动的特性
ListView常用的xml属性
xml属性 | 说明 |
---|---|
android:divide | 设置分割条样式 |
android:entries | 指定一个数组资源 |
android:dividerHeight | 设置分割条高度 |
android:scrollbars=“vertical”|“horizontal” | 数据超出范围时设置滚动条 |
-
代码
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:layout_width="wrap_content" android:layout_height="wrap_content" android:divider="@color/blue" android:entries="@array/teacher_name" android:dividerHeight="1dp"/> </LinearLayout> strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="teacher_name"> <item>张三</item> <item>李四</item> <item>王五</item> <item>赵六</item> </string-array> </resources>
2.4.2【Spinner下拉列表】
-
显示一个下拉的菜单,然后选择一个
-
主要的xml属性:
entries:指定遍历某个数组资源
spinnerMode:dialog (页面中弹出) | dropdown(下拉列表的下方弹出)
-
代码:
<Spinner android:layout_width="wrap_content" android:layout_height="wrap_content" android:entries="@array/teacher_name" android:spinnerMode="dropdown"/>
Spinner还可以使用Adapter为其添加数据:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test_spinner); String[] spinnerArray = {"选项1", "选项2", "选项3"}; Spinner spinner = findViewById(R.id.spinner); ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_activated_1, spinnerArray); spinner.setAdapter(adapter); }
2.4.3【Adapter适配器接口】
-
Adapter接口派生了ListAdapter、SpinnerAdaper两个子接口
-
常用的适配器实现类:
ArrayAdapter:功能简单,只能展示一行文字,可以设置下拉框
SimpleAdapter:可以在列表项中同时展示文字和图片
BaseAdapter:适应性更强,可以在别的代码文件中编写操作代码,全能
ArrayAdapter数组适配器
示例:
ArrayAdapter adapter = new ArrayAdapter<>(参数1,参数2,参数3)
- this:这整个安卓程序接口
- 第二个参数是资源ID,代表一个ListView设置列表项的样式,可自定义
- 第三个参数是列表项的来源
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //加载指定的布局文件 setContentView(R.layout.listview_layout); ListView lv = findViewById(R.id.listview1); String[] arr = {"唱","跳","rap","篮球"}; /** * this是这整个安卓程序的接口 * 第二个参数是资源ID,代表一个TextView设置列表项的样式,可自定义 * 第三个参数是列表项的来源 */ ArrayAdapter<String> adapter = new ArrayAdapter<>( this, android.R.layout.simple_expandable_list_item_1,arr ); lv.setAdapter(adapter);
<ListView android:id="@+id/listview1" android:layout_width="match_parent" android:layout_height="wrap_content" />
SimpleAdapter简单适配器
该适配器可以展示文字和图片
- new SimpleAdapter()的后4个参数
- list:指定一个集合对象
- R.layout.list_item_layout:指定界面布局的id
- new String[]:决定提取哪些内容显示在ListView的每一行
- new int[]{R.id.name,R.id.icon,R.id.dexc}):决定显示哪些组件
- MainActivity.java
public class MainActivity extends AppCompatActivity {
//定义名字数组
private String[] name={"张三","王五","赵六"};
//定义描述任务数组
private String[] desc={"唱歌","跳舞","打球"};
//定义头像数组
private int[] icon=new int[]
{R.mipmap.ic_launcher,R.mipmap.ic_launcher,R.mipmap.ic_launcher};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView)findViewById(R.id.listview1);
//创建一个list集合,list集合的元素是MAP
List<Map<String,Object>> list=new ArrayList<Map<String,Object>>();
for(int i=0;i<name.length;i++){
Map<String, Object> listitem=new HashMap<String, Object>();
listitem.put("icon",icon[i]);
listitem.put("name",name[i]);
listitem.put("desc",desc[i]);
list.add(listitem);
}
//创建一个SimpleAdapter
SimpleAdapter adapter = new SimpleAdapter(this,list,R.layout.list_item_layout,
new String[]{"name","icon","desc"},new int[]{R.id.name,R.id.icon,R.id.dexc});
listView.setAdapter(adapter);
}
}
- list_item_layout.xml
<ImageView
android:id="@+id/icon"
android:layout_width="60dp"
android:layout_height="wrap_content"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:textSize="16sp"
/>
<TextView
android:id="@+id/desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
/>
</LinearLayout>
- activity_main.xml
<ListView
android:id="@+id/listview1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#C4C4C4"
android:dividerHeight="1dp">
</ListView>
BaseAdapter基本适配器
从BaseAdapter派生的数据适配器主要实现下面5种方法。
- 构造方法:指定适配器需要处理的数据集合。
- getCount:获取列表项的个数。
- getItem:获取列表项的数据。
- getItemId:获取列表项的编号。
- getView:获取每项的展示视图,并对每项的内部控件进行业务处理。
2.4.4【Dialog对话框的使用】
本次介绍AlertDialog 弹出对话框的使用
创建AlertDialog对话框的步骤如下:
- 创建AlertDialog.Builder对象
- setTitle:设置对话框的标题文本
- setMessage:设置对话框的内容文本
- setPositiveButton:设置肯定按钮的信息、setNegativeButton设置否定按钮的信息
- 调用AlertDialog.Builder对象的create( )方法创建AlertDialog对象,再show()方法显示出来
- 其他:setIcon:设置对话框的标题图标
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_layout);
AlertDialog.Builder builder
= new AlertDialog.Builder(this);
//设置标题
builder.setTitle("温馨提示");
//设置对话框的消息内容
builder.setMessage("是否退出");
//设置确定按钮
builder.setPositiveButton("确定", (dialog, which) -> {
finish();
});
//设置取消按钮
builder.setNegativeButton("取消",(dialig,which)->{
dialig.cancel();
});
//创建AlertDialog实例
AlertDialog alertDialog = builder.create();
//结合按钮使用
Button btn = findViewById(R.id.button1);
btn.setOnClickListener(v -> {
alertDialog.show();//显示对话框
});
}
2.4.5【Toast的使用】
-
作用:短暂的显示一下消息
-
掌握两个方法:
- makeText():设置提示用户的文字,三个参数(上下文环境,显示的文字,显示时间的长短)
- show():显示Toast
public class MainActivity extends AppCompatActivity { @SuppressLint("MissingInflatedId") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.dialog_layout); Button btn = findViewById(R.id.button1); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this,"你好啊~~~",Toast.LENGTH_SHORT).show(); } }); } }
3. Activity
- 掌握Activity生命周期的作用,能够正确使用每个方法
- 掌握Activity的创建、配置、启动和关闭的方式,能够完成创建、配置、启动和关闭Activity
- 掌握Intent和lntentFilter的用法,能够灵活使用lntent与lntentFilter
3.1【Activity基础】
Activity是一个负责与用户交互的组件,每个Android应用中都会用Activity来显示界面以及处理界面上一些控件的事件。每个APP中可以包含多个Activity,每个Activity负责管理一个用户界面。
Activity生命周期
- 基本介绍:Activity的生命周期指的是Activity从创建到销毁的整个过程,这个过程大致可以分为五种状态,分别是启动状态、运行状态、暂停状态、停止状态和销毁状态
-
回调方法(7个)
- onCreate():Activity创建时调用,通常做一些初始化设置。
- onStart():Activity即将可见时调用。
- onResume():Activity获取焦点时调用。
- onPause():当前Activity被其他Activity覆盖或屏幕锁屏时(失去焦点时)调用。
- onStop:Activity对用户不可见时调用。
- onRestart():Activity从停止状态到再次启动时调用。
- onDestroy():Activity销毁时调用。
package com.example.radionbuttonandcheckbox; //import..... public class MainActivity extends AppCompatActivity { private Button btn; private TextView tv; @SuppressLint("MissingInflatedId") protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //加载指定的布局文件 setContentView(R.layout.activity_main); btn = findViewById(R.id.button1); btn.setOnClickListener(v -> { //SecondActivity Intent intent = new Intent(this, SecondActivity.class); startActivity(intent); }); } ==== 测试生命周期函数==== //onStart @Override protected void onStart() { super.onStart(); Log.i("MainActivity","onStart()"); } ........... }
-
注意事项
-
状态之间的转换不是线性的
比如当在onPause()中,系统不保证接下来一定是onStop(),如果用户迅速回到这个Activity,系统会调用onResume()。
-
系统不一定按顺序执行每个回调
在异常情况下,如内存不足,系统可能会在调用onStop()之前直接调用onDestroy()
-
-
横竖屏切换
-
当Activity对应的界面进行横竖屏切换时,程序首先会销毁Activity,然后再调用onCreate()方法重建Activity,如:用户竖屏填写信息,切换为横屏时,信息被清除。
-
如果不希望界面进行横竖屏切换时Activity被销毁重建,在Android9.0或以下的版本可以通过configChanges属性进行设置,示例代码如下:
<!--横竖屏切换时不销毁Activity--> <!--portrait在Activity中添加竖屏状态--> <!--landscape在Activity中添加横屏状态--> <activity android:name=".SecondActivity" android:exported="false" android:screenOrientation="landscape" />
Log日志的使用
5种方法打印日志信息:
两个参数:tag,msg
Log.v():打印繁琐且没什么用的信息
Log.d():打印调试信息
Log.i():打印主要的信息,且是你想看到的,帮助用户分析行为数据
Log.w():打印警告信息,若打印出来,说明程序有潜在的风险
Log.e():打印程序的错误信息
 { super.onCreate(savedInstanceState, persistentState); setContentView(R.layout.activity_main); } }
Activity的启动和关闭
- 启动:startActivity(intent);
- 关闭:通常使用finish()方法关闭
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_layout);
Intent intent = new Intent(MainActivity.this, MainActivity2.class);
startActivity(intent);//启动Activity
Button button = findViewById(R.id.button1);
button.setOnClickListener(v -> {
finish();//关闭Activity
}
);
}
3.2【Intent的使用】
Intent介绍
-
Intent是相同或者不同应用程序组件间通信的中介。在Android程序中,Activity、Service和BroadcastReceiver这三种核心组件都需要使用Intent进行操作,例如,如果用户需要从一个Activity切换到另一个Activity,则必须使用lntent来进行切换。本节将针对lntent的相关知识进行详细讲解。
-
Intent被称为意图,用于不同组件间的通信,分为显示Intent和隐式Intent
显式Intent
两种方式:
- 显式lntent可以直接通过类的名称指定目标Activity
- 还可以指定目标组件的包名、全路径(完整包名+类名)指定开启的组件
方式一:
this指当前Activity,MainActivity2为要启动的Activity
Intent intent = new Intent(this, MainActivity2.class);
startActivity(intent);//启动Activity
方式二:
使用setClassName(包名,全类名)
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
Intent intent = new Intent();
intent.setClassName("com.example.radionbuttonandcheckbox.testIntent",
"com.example.radionbuttonandcheckbox.testIntent.TestActivity");
startActivity(intent);
}
}
隐式Intent
- 程序没有明确指定需要启动的Activity,Android系统会根据AndroidManifest.xml文件设置的动作(action)、类别(category)、数据(Uri和数据类型)来启动合适的组件
- action、category、data三个属性匹配成功后才可以唤起相应的组件
<!-- 隐式Intent-->
<activity
android:name=".SecondActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<data android:scheme="http"/>
</intent-filter>
</activity>
//SecondActivity
//隐式Intent
Intent intent = new Intent();
intent.setAction("android.intent.action.MAIN");
startActivity(intent);
Android系统中常用的action常量
- android.intent.action.MAIN:Android程序的入口。
- android.intent.action.VIEW:显示指定数据。
- android.intent.action.EDIT:编辑指定数据。
- android.intent.action.DIAL:显示拨号面板。
- android.intent.action.CALL:直接呼叫指定的号码。
- android.intent.action.ANSWER:接听来电。
- android.intent.action.SEND:向其他程序发送数据,例如彩信或邮件等。
- android.intent.action.SENDTO:向他人发送短信。
- android.intent.action.SEARCH:执行搜索。
- android.intent.action.GET_CONTENT:让用户选择数据,并返回所选数据。
IntentFilter匹配规则
当发送一个隐式lntent后,Android系统会将它与程序中的每一个组件的过滤器进行匹配,匹配属性有action、data和category,需需要这3个属性都匹配成功才能唤起相应的组件
-
action属性匹配规则
action属性用来指定lntent对象的动作
**注意:**在清单文件中为Activity添加标签时,必须添加action属性,否则隐式Intent无法开启该Activity。
-
data属性匹配规则
data属性用来指定数据的URI或者数据MIME类型
<data android:scheme="http..." android:mimeType="vedio/mpeg"/>
-
category属性匹配规则
category属性用于为action添加额外信息,一个lntentFilter可以不声明category属性,也可以声明多个category属性。
<intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category .BROWSABLE" /> </intent-filter>
3.3【Activity之间的数据传递】
-
一个Android程序通常会包含多个Activity,这些Activity之间可以互相跳转并传递数据
-
Android提供的Intent可以在界面跳转时传递数据。使用Intent传递数据有两种方式。
使用lntent的putExtra()方法传递数据
传递数据
//MainActivity
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("name","琳琳");
intent.putExtra("age",19);
intent.putExtra("gender","女");
intent.putExtra("isMan",true);
startActivity(intent);
获取数据
//SecondAcitvity
Intent intent = this.getIntent();
String name = intent.getStringExtra("name");
int age = intent.getIntExtra("age",18);
boolean isMan = intent.getBooleanExtra("isMan",false);
/**
标准:
*/
Intent intent = this.getIntent();
if (intent != null) {
String[] userinfo = intent.getStringArrayExtra("userInfo");
if (userinfo != null && userinfo.length == 2) {
String username = userinfo[0];
String password = userinfo[1];
// 使用用户名和密码
String showInTv = "用户名:"+username+"\n"+"密码:"+password;
tv.setText(showInTv);
}
}
使用Bundle类传递数据
- 类似Java的Map集合
传递数据
//MainActivity
Intent intent1 = new Intent(this,SecondActivity.class);
Bundle bundle = new Bundle();
bundle.putString("school","斯坦福大学");
bundle.putInt("sno",12345);
intent1.putExtras(bundle);//数据封装到Bundle对象中
获取数据
//SecondAcitvity
//通过bundle对象获取数据
Bundle bundle = getIntent().getExtras();
String sch = bundle.getString("school");
int sno = bundle.getInt("sno");
putExtra()和putExtras()
putExtra
- 使用putExtra()时,如果有多个数据要传递,需要对每个数据单独调用一次putExtra(),使用putExtra()允许一次添加一个额外的数据(键值对)
putExtras
- 使用putExtras()时,可以先将所有数据放入一个Bundle,然后一次性全部添加到IntentputExtras()允许一次添加一个Bundle对象,这个对象可以包含多个键值对。putExtras()更适用于需要一次性传递大量的数据
使用Serializable序列化接口传递对象
步骤:
-
假如写个Person类,必须实现序列化接口
-
MainActivity:
Person person = new Person(name,gender,age); Intent intent = new Intent(this,B.class); intent.putExtra("personObject",person); startActivity(intent);
-
SerializableActivity:
Person person=(Person)getIntent().getSerializableExtar("personObject"); 然后就操作这个对象获取信息了
3.4【Activity之间的数据回传】
【介绍】
- 当我们从MainActivity界面跳转到SecondActivity界面时,可以在SecondActivity界面上进行一些操作。当关闭SecondActivity界面时,想要从该界面返回一些数据到MainActivity界面,Android系统为我们提供了一些方法用于Activity之间数据的回传。
旧的数据回传方式
- Android 10版本的数据回传方式:
Activity数据回传流程图
-
Activity之间进行数据回传时包含3个方法:
-
startActivityForResult()
-
setResult()
-
onActivityResult()
-
startActivityForResult()
-
作用:用于开启一个Activity,当开启的Activity销毁时,会从销毁的Activity中返回数据。
-
代码:
第一个参数为Intent对象,第二个参数为requestCode请求码
startActivityForResult(intent, 1);
setResult()
-
作用:用于携带数据进行回传
-
代码:
第一个参数:结果码resultCode
setResult(1,intent);
onActivityResult()
-
作用:用于接收回传的数据
-
代码:
//从第二个Activity获取数据 @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); tv = findViewById(R.id.textView); if (requestCode == 1 && resultCode == 1) { String studentName = data.getStringExtra("studentName"); tv.setText(studentName); } }
- 小案例:
步骤:
- 创建MainActivity;
- 创建SecondActivity;
- 在MainActivity中点击按钮跳转到SecondActivity;
- 在SecondActivity中点击按钮将数据返回到MainActivity;
//MainActivity
private Button btn;
private TextView tv;
//OnCreate()方法
btn = findViewById(R.id.button);
btn.setOnClickListener(v -> {
//Android 10版本以下的api
Intent intent = new Intent(this, SecondActivity.class);
//请求返回结果
startActivityForResult(intent, 1);
});
@Override
protected void onActivityResult(int requestCode,
int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
tv = findViewById(R.id.textView);
if (requestCode == 1 && resultCode == 1) {
String studentName = data.getStringExtra("studentName");
tv.setText(studentName);
}
}
//SecondActivity
btn = findViewById(R.id.button2);
//点击按钮数据回传
btn.setOnClickListener(v -> {
Intent intent = new Intent();
intent.putExtra("studentName","张三");
//将结果返回给第一个Activity
setResult(1,intent);
finish();//一定要关闭
});
新版本数据回传方式
- 推荐使用ActivityResultContracts和ActivityResultLauncher。这种方法比旧版本更为简洁和模块化。
- 使用Android10或以上版本这种新方式,不需要再处理请求码,这使得代码更加简洁和易于维护减少了Activity间耦合,使得代码结构更清晰。
步骤:
- 在Activity中接收回传的数据
- 使用launch()方法启动目标Activity
- 在被启动的Activity中设置返回结果
3.5【 Android中的任务栈】
- Android系统采用任务栈的方式来管理Activity实例
- 任务栈:一种用来存放Activity实例的容器
- 特点:“先进后出”
- 操作:压栈和出栈
- 一个应用程序有一个任务栈
- 每启动一个Activity都会将其加入栈中,放在栈顶位置
- 用户操作的界面永远都是栈顶的Activity
- 每启动一个新的Activity都会创建新的实例,并覆盖在原Activity之上。
- 当单击返回按钮,最上面的Activity会被销毁,下面的Activity会重新显示。
3.6【Activity的启动模式】
- 在开发中,还可以为每个Activity指定恰当的启动模式,来复用 Activity实例,优化 App 的效率和资源占用。
- 掌握Activity的启动模式,能够归纳Activity的4种启动模式的作用
1. Activity启动模式有四种:
standard模式
- standard模式是Activity的默认启动方式,每启动一个Activity就会在栈顶创建一个新的实例。
singleTop模式
- singleTop模式会判断要启动的Activity实例是否位于栈顶,如果位于栈顶则直接复用,否则创建新的实例。
singleTask模式
- singleTask模式下每次启动该Activity时,系统首先会检查栈中是否存在当前Activity实例,如果存在则直接使用,并把当前Activity之上的所有实例全部出栈。
singleInstance模式
- singlelnstance模式会启动一个新的任务栈来管理Activity实例,无论从哪个任务栈中启动该Activity,该实例在整个系统中只有一个。
开启使用singlelnstance模式的Activity的两种情况:
- 要开启的Activity实例在栈中不存在,则系统会先创建一个新的任务栈,然后再压入Activity实例
- 要启动的Activity已存在,系统会把Activity所在的任务栈转移到前台,从而使Activity显示。
4. Android事件处理
- 掌握基于回调机制的事件处理方法,能够处理按下、弹起、触摸等事件
- 掌握基于监听接口机制的事件处理方法,能够处理点击、长按等事件
- 掌握Handler消息机制原理,能够使用Handler进行线程间通信
Android事件处理分为两种:
- 键盘事件
- 触摸事件
4.1【基于回调机制的事件处理】
- 当用户与UI控件发生某个事件(如按下事件、滑动事件、双击事件)时,程序会调用控件自己特定的方法处理该事件,这个处理过程就是基于回调机制的事件处理。
方法 | 说明 |
---|---|
boolean onKeyDown() | 按下键盘时触发 |
boolean onKeyUp() | 松开键盘时触发 |
boolean onTouchEvent() | 触摸控件时触发 |
boolean onFocusChanged() | 焦点发生改变时触发 |
注意:对于基于回调机制的事件传播而言,某控件上所发生的事件:
- 不仅触发该控件的回调方法
- 如果事件传播到Activity中,也会触发该控件所在的Activity的回调方法。
键盘事件
onKeyDown()方法
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode==KeyEvent.KEYCODE_BACK){
Toast.makeText(this,"你点击了后退键",Toast.LENGTH_LONG).show();
return true;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
Toast.makeText(this,"你点击了音量键+",Toast.LENGTH_LONG).show();
return true;
}
return super.onKeyDown(keyCode, event);
}
触摸事件
onTouchEvent()
- 方法处理的事件分为三种:
- 屏幕被按下;
- 屏幕弹起;
- 在屏幕中滑动;
-
示例:
/** * 触摸事件 * * @param event The touch screen event being processed. * @return */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Toast.makeText(MainActivity.this, "按下了按钮", Toast.LENGTH_SHORT).show(); break; case MotionEvent.ACTION_UP: Toast.makeText(MainActivity.this, "按钮被弹起", Toast.LENGTH_SHORT).show(); break; case MotionEvent.ACTION_MOVE: Toast.makeText(MainActivity.this, "在按钮上进行移动", Toast.LENGTH_SHORT).show(); break; } return super.onTouchEvent(event); }
onFocusChanged()
-
onFocusChanged()方法是焦点改变的回调方法,只能在View中重写。当某个控件重写了该方法后,焦点发生变化时,会自动调用该方法来处理焦点改变的事件,其定义方式如下:
public class MyButton extends androidx.appcompat.widget.AppCompatButton { public MyButton(Context context) { super(context); } @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(focused, direction, previouslyFocusedRect); } }
三个参数:
- focused:触发该事件的View是否获得了焦点;
- direction:焦点移动的方向;
- previouslyFocusedRect:在触发事件的View坐标系中,前一个获得焦点的矩形区域
自定义Button
/**
* 自定义的 Button
*/
public class MyButton extends androidx.appcompat.widget.AppCompatButton {
public MyButton(Context context) {
super(context);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("测试btn","我是Button,你触碰了我:"+event.getAction());
Toast.makeText(getContext(),"我是Button,你触碰了我"+event.getAction(),Toast.LENGTH_SHORT).show();
return true;
}
}
//布局文件中使用Button
<com.example.radionbuttonandcheckbox.MyButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试一下onTouchEvent"/>
4.2【基于监听接口的事件处理】
-
为Android界面组件绑定特定的事件监听器:
如:setOnClickListener等等
-
基于监听接口机制的事件处理是一种“面向对象”的事件处理,在事件监听的处理模型中主要涉及三个对象,这三个对象的具体介绍如下:
EventSource
- 事件发生的场所
Event
- 封装了界面组件发生的特定事情
EventListener
-
负责监听事件源所发生的事件
-
流程图
监听器接口
- 在基于监听的事件处理模型中,事件监听器必须实现事件监听器接口font>,Android系统为不同的界面组件提供了不同的监听器接口,这些接口通常以内部类的形式存在。
4.3【Handleri消息机制】
【引入】:
-
当应用程序启动时,Android首先会开启一个Ul线程(主线程),UI线程负责管理UI界面中的控件,并进行事件分发
-
在Android中,更新Ui界面只能在主线程中完成,其他线程是无法直接对主线程进行操作的。
-
为了解决以上问题,Android中提供了一种异步回调机制Handler,由Handler来负责与子线程进行通信。
【介绍】:
Handler是一种异步回调机制主要负责与子线程进行通信,主要包括4个关键对象:
- Message:线程之间传递的消息
- Handler:发送消息和处理消息
- MessageQueen:存放发送消息
- Looper:loop()方法进入循环
4.4【AnsyncTask异步任务】
- AnsyncTask异步任务可以简化操作、更轻量级
使用AnsyncTask的步骤如下:
-
创建AsyncTask的子类,并指定参数类型;
-
实现AsyncTask的方法:
dolnBackground(Params…):后台线程将要完成的功能
onPostExecute(Resultresult):一般负责更新UI线程等操作
-
调用AsyncTask子类的实例的execute(Params…params)方法执行耗时操作。
5.Fragment基础
5.1【Fragment简介】
-
Fragment(碎片)是Android3.0后引入的一个新的API。
它是一种可以嵌入在活动中的Ul片段,可以将其看成一个小型Activity,它又被称作Activity片段。
-
一个Activity中可以包含多个Fragment
-
一个Fragment也可以在多个Activity中使用
-
手机或平板竖屏:Fragment1需要嵌入到Activity1中,Fragment2需要嵌入到Activity2中;
-
平板横屏:两个Fragment可同时嵌入到Activity1中
5.2【Fragment生命周期】
- onAttach():Fragment和Activity建立关联时调用
- onCreateView():Fragment加载布局时调用
- onActivityCreated():Fragment关联的Activity创建完成时调用
- onDestoryView():Fragment关联的视图被移除时调用
- onDetach():Fragment和Activity解除关联时调用
Fragment生命周期状态是直接受其所属Activity的生命周期状态影响:
5.3【创建Fragment】
- 第一个参数:表示Fragment对应的布局资源ID
- 第二个参数:表示存放Fragment视图的父视图
- 第三个参数:表示是否将生成的视图添加一个父视图
public class LeftFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_left,container,false);
}
}
5.4【在Activity中添加Fragment】
两种方式:
-
在布局文件中静态添加Fragment:
在Activity布局文件中:
使用fragment标签
必须指定android:name属性,其属性值为Fragment的全路径名称
<fragment android:id="@+id/left_fragment" android:name="com.example.radionbuttonandcheckbox.LeftFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> //weight表示在Activity中的权重,即占多少份
-
在Activity中动态加载Fragment:
步骤:
- 创建指定Fragment的实例对象
- 获取FragmentManager的实例
- 开启FragmentTransaction事务
- 向Activity的布局容器中添加Fragment
- 通过commit0方法提交事务
注意:
getSupportFragmentManager() 是新版本的用法,getFragmentManager()是旧版本的用法。
public class MainActivity extends AppCompatActivity { Button button; @SuppressLint("MissingInflatedId") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button=(Button)findViewById(R.id.button); button.setOnClickListener(v -> { //创建fragment实例 SecondFragment secondFragment = new SecondFragment(); //获取fragment示例 FragmentManager fm = getSupportFragmentManager(); //开启事务 FragmentTransaction transaction = fm.beginTransaction(); //将right_layout对应的fragment替换成secondFragment transaction.replace(R.id.right_layout,secondFragment); //将此事务添加到返回栈,以便用户可以按返回键返回到上一个Fragment transaction.addToBackStack(null); //提交事务 transaction.commit(); }); } }
6. Android数据存储
6.1【数据存储方式】
- 多种数据存储方式
6.2【文件存储】
- 文件存储是Android中最基本的一种数据存储方式通过I/O流的形式把数据直接存储到文件中,适合存储少量数据。
- 分为内部存储和外部存储
6.2.1【文件的内部存储】
1. 写入文件
- 使用
Context
的openFileOutput
方法来创建或打开一个文件时,这个文件默认会被创建在应用的内部存储中的私有目录下。不要直接指定目录,因为这个方法是自动处理的。
openFileOutput
- 用于在私有目录下创建或打开一个文件
两个参数:
name(String):指的是文件名,可以不用加扩展名,因为可以识别
mode(int):文件的操作模式,默认Private
mode的取值:
MODE_PRIVATE(默认取值,该文件只能被当前程序读写)
MODE_APPEND(该文件的内容可以追加)
MODE_WORLD_WRITEABLE:该文件的内容可以被其他程序读取
MODE_WORLD_READABLE:该文件的内容可以被其他程序写入
/**
* 写入文件
*/
button1.setOnClickListener(v -> {
//文件名称
String fileName="data.txt";
//这里写入一个字符串
String writeContent = "helloWorld";
try {
fos = openFileOutput(fileName,MODE_PRIVATE);
fos.write(writeContent.getBytes());
//刷新流,确保文件被完整写入
fos.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
assert fos != null;
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
2. 读取文件
openFileInput
- 用于打开并且读取应用程序的私有文件目录下已存在的文件
/**
* 读取文件
*/
button2.setOnClickListener(v -> {
String readContent = "";
FileInputStream fis = null;
try {
fis = openFileInput("data.txt");
//available()方法用于返回可以从此输入流中读取(或跳过)的字节数的估计值
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
readContent = new String(buffer);
//显示读取的数据
tv.setText(readContent);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
assert fis!=null;
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
6.2.2【文件的外部存储】
写入文件
getExternalFilesDir().getAbsolutePath();
- 这个方法返回的是应用私有的外部存储目录的路径。这个目录是专门为当前应用保留的,其他应用通常无法直接访问这个目录。
- 用途:这个目录通常用于存储应用生成的文件,这些文件是应用特有的,不需要与其他应用共享。
Environment.getExternalStorageDirectory().getPath();
- 这个方法返回的是外部存储的根目录路径,也就是传统意义上的SD卡路径(尽管现在很多设备并没有物理的SD卡)。这个目录是公共的,所有应用都可以访问(如果有相应的权限)。
- 用途:这个目录通常用于存储需要与其他应用或用户共享的文件,例如照片、视频、音乐等。
button1.setOnClickListener(v -> {
//获取外部设备状态
String state = Environment.getExternalStorageState();
//判断设备是否可用
if (state.equals(Environment.MEDIA_MOUNTED)){
String SDPath;
//如果当前Android版本的高于29(Android 10)
if(Build.VERSION.SDK_INT>29){
SDPath = getExternalFilesDir(null).getAbsolutePath();
}else {
SDPath = Environment.getExternalStorageDirectory().getPath();
}
File file = new File(SDPath, "data.txt");
String data = "Hello World 外部存储";
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
fos.write(data.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
});
读取文件
button2.setOnClickListener(v -> {
//获取外部存储路径
File SDPath = Environment.getExternalStorageDirectory();
//找到或创建该路径下名为“data.txt”的文件
File file = new File(SDPath, "data.txt");
FileInputStream fis;
BufferedReader br;
try {
//创建文件输入流,准备读取这个文件
fis = new FileInputStream(file);
//创建字符输入缓冲流对象
br = new BufferedReader(new InputStreamReader(fis));
//按行读取数据
String data = br.readLine();
tv.setText(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
6.2.3【缓冲字符输入输出流】
在Java的IO(输入/输出)体系中,字符流和字节流是两种基本的流类型,它们分别用于处理字符数据和字节数据。
字符流
字符流用于处理字符数据。在Java中,字符流以
Reader
和Writer
为基类。这些流按照16位Unicode字符进行操作,它们更适合处理文本文件。
- Reader: 所有字符输入流的超类。
- Writer: 所有字符输出流的超类。
常见的字符流子类包括:
FileReader
和FileWriter
:用于文件操作。BufferedReader
和BufferedWriter
:带有缓冲区的字符流,用于提高读写效率。InputStreamReader
和OutputStreamWriter
:桥接类,用于将字节流转换为字符流,或将字符流转换为字节流。字节流
字节流用于处理字节数据。在Java中,字节流以
InputStream
和OutputStream
为基类。这些流按照字节进行操作,它们更适合处理二进制文件。
- InputStream: 所有字节输入流的超类。
- OutputStream: 所有字节输出流的超类。
常见的字节流子类包括:
FileInputStream
和FileOutputStream
:用于文件操作。BufferedInputStream
和BufferedOutputStream
:带有缓冲区的字节流,用于提高读写效率。ObjectInputStream
和ObjectOutputStream
:用于序列化和反序列化对象。桥接流
InputStreamReader
和OutputStreamWriter
是特殊的桥接流,它们允许你将字节流转换为字符流,或将字符流转换为字节流。这在需要处理文本文件但文件是以字节形式存储时非常有用。总结
- 字符流:以
Reader
和Writer
为基类,用于处理字符数据,更适合处理文本文件。- 字节流:以
InputStream
和OutputStream
为基类,用于处理字节数据,更适合处理二进制文件。在选择使用字符流还是字节流时,主要取决于你处理的数据类型:如果是文本数据,通常使用字符流;如果是二进制数据,通常使用字节流。
BufferedWriter
- 缓冲字符输出流,用于高效写入
- 参数:Write(字符流),因此需要使用桥接器OutputStreamWriter将字节流FileOutputStream转换为字符流,实现写入字符,效率高。
FileOutputStream out;
out = new openFileOut("data");
BufferedWriter bufferedwriter = null;
bufferedwriter = new BufferedWriter(new OutputStreamWriter(out));
BufferedReader
FileInputStream in;
BufferedReader reader = null;
in = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(in));
Try-catch的注意点
下面这段代码的意图是:
- 首先检查
writer
对象是否为null
。这是因为如果在try
块中初始化writer
时发生异常,writer
可能不会被赋值,因此直接调用writer.close()
会抛出NullPointerException
。 - 如果
writer
不是null
,则尝试关闭它。关闭文件流可能会抛出IOException
,因此我们需要在finally
块内部再嵌套一个try-catch
块来捕获这个异常。 - 如果在关闭
writer
时发生了IOException
,我们将这个异常包装成一个新的RuntimeException
并重新抛出。这是因为finally
块本身不应该抛出被外部try-catch
块捕获的异常类型,所以通过转换为运行时异常来确保异常能被上层代码捕获并处理。
finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
customToast("关闭文件输出流时发生错误:" +e.getMessage());
e.printStackTrace();
}
}
}
6.3【SharedPreferences】
- SharedPreferences是一个轻量级的存储类
- 当程序中有一些少量数据需要持久化存储时,使用SharedPreferences类存储
数据存入
步骤:
- 调用
getSharedPreferences()
方法获取实例对象,该对象本身只能获取数据 - 调用SharedPreferences类的
edit()
方法获取可编辑的Editor对象 - 通过该对象的
putXxx()
方法存储数据。
commit()
和apply()
的区别与使用场景:
-
apply()
在后台异步执行,不会阻塞主线程,从而提供更好的用户体验。 -
只有在需要确保写入成功(比如处理可能的失败情况)时,才应该使用
commit()
方法。在大多数情况下,apply()
是更好的选择。
getSharedPreferences()
:
- 用途:获取实例对象,该对象本身只能获取数据
- 第一个参数:文件名是 “data”。Android 系统会使用这个名称来创建或检索一个特定的
SharedPreferences
文件。 - 第二个参数:操作模式,这个模式确保了数据的安全性,防止其他应用程序访问您的私有数据。
//获取实例对象
SharedPreferences sp = getSharedPreferences("data", MODE_PRIVATE);
//获取编辑器
SharedPreferences.Editor edit = sp.edit();
//以key-value的形式保存数据
edit.putString("name","张三");
edit.putInt("age",18);
//提交数据,比commit()更好,不会阻塞线程
edit.apply();
注意:
key/value(键值对)的形式保存数据,value值只能是float、int、long、boolean、String、Set类型数据
读取与删除数据
读取SharedPreferences中的数据:
//读取数据
SharedPreferences sp1 = getSharedPreferences("data", MODE_PRIVATE);
String data = sp1.getString("name", "");
删除SharedPreferences中的数据:
edit.remove("name");//删除key值为name的数据
edit.clear();//删除所有数据
注意:
- 获取数据的key值与存入数据的key值的数据类型要一致,否则查找不到数据
- 保存SharedPreferences的key值时可以用静态变量保存,以免存储、删除时写错。如: private final String key = “itcast”
6.4【SQLite数据库】
- 适合存储大量数据
- 对数据进行管理和维护
###6.4.1【SQLite数据库的类和接口】
SQLiteOpenHelper类
-
它是SQLiteDatabse的一个帮助类,用来管理数据额产和版本的更新
-
它有两个回调方法:
onCreate
初次生成数据库时才会被调用,用于生成数据库表结构及添加一些应用使用到的初始化数据。
@Override public void onCreate(SQLiteDatabase sqLiteDatabase) { String sql = "create table user(id integer primary key autoincrement," + "username varchar(20),paswd varchar(20),sex varchar(20),age integer)"; sqLiteDatabase.execSQL(sql); Log.i("SQLite","execSQL(sql)"); }
execSQL()方法执行SQL语句
onUpgrade
在数据库的版本发生变化时会被调用,一般在软件升级时才需
改变版本号,而数据库的版本是由程序员控制的。
SQLiteDatabase类
常用方法:
-
insert
-
update
-
delete
-
query
-
exceSQL
-
close
Cursor接口
- Cursor是一个游标接口,在数据库中作为返回值,相当于结果集ResultSet。
常用方法:
moveToFirst() :移动光标到第一行
moveToLast() :移动光标到最后一行
moveToNext() :移动光标到下一行
moveToPosition(int position) :移动光标到一个绝对的位置
moveToPrevious() :移动光标到上一行
getColumnCount():返回所有列的总数。
getColumnlndex(String columnName):返回指定列的名称,如果不存在返 回-1。
getColumnName(int columnlndex):从给定的索引返回列名 。
getColumnNames():返回一个字符串数组的列名。
getCount():返回Cursor中的行数。
6.4.2【SQLite数据库的创建与操作】
创建SQLite数据库
- Android系统在使用SQLite数据库时, 一般使用SQLiteOpenHelper的子类创建SQLite数据库,因此需要创建一个类继承自 SQLiteOpenHelper,然后重写oncreate()方法.
public class TestSQLite extends SQLiteOpenHelper {
public TestSQLite(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, "user_db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "create table user(id integer primary key autoincrement," +
"username varchar(20),paswd varchar(20),sex varchar(20),age integer)";
db.execSQL(sql);
Log.i("SQLite", "execSQL(sql)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
创建好数据库后的使用方法
示例:
- 在MainActivity类中调用即可。
//dbHelper = new MyDataBaseHelper(MainActivity.this, "BookStore.db", null, 1);
dbHelper = new MyDataBaseHelper(MainActivity.this, "BookStore.db",
null, 2);
//点击按钮后获取可读写SqliteDatabase对象
createDatabase.setOnClickListener(v -> {
dbHelper.getWritableDatabase();
});
添加数据
- 需要获取SQLiteDatabase对象;
- 把数据放入values中(类似集合);
- 调用insert()方法插入数据
insert()方法的三个参数:
table(String):表名,为哪张表插入数据;
nullColumnHack(String):
- 如果你需要主键自增,可以设置为null,即如果表中有字段可以为Null,那么SQLite自动插入值,自动帮你处理它;但是如果表中所有字段都是Not Null,那么这个参数就不能为Null,必须提供一个可以为空的列名。
values(ContentsValues):
public void addData(SQLiteDatabase db){
ContentValues values = new ContentValues();
values.put("name","xialin");
values.put("gender","女");
values.put("age",18);
db.insert("user_db",null,values);
db.close();
}
或者:
/**
* 添加数据
*/
addBtn.setOnClickListener(v -> {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
//插入第一条数据
values.put("name", "Computer book");
values.put("author", "Tom");
values.put("pages", 443);
values.put("price", 16.5);
db.insert("book", null, values);
修改数据
- 还是需要一个ContentValues对象;
- 把要修改的内容放进values;
- 使用update()方法更新。
update的三个参数:
- 第一个参数
"user_db"
是要更新的表的名称。- 第二个参数
values
是一个ContentValues
对象,包含了要更新的列和值。- 第三个参数
"name=?"
是一个WHERE子句,用于指定哪些行应该被更新。问号?
是一个占位符,用于后面的参数数组。- 第四个参数
new String[]{"xialin"}
是一个字符串数组,用于替换WHERE子句中的占位符。在这个例子中,它只有一个元素"xialin"
,这意味着只有name
字段值为xialin
的行会被更新。
public void updateData(SQLiteDatabase db){
ContentValues values = new ContentValues();
//修改name=xialin的年龄
values.put("age",21);
db.update("user_db",values,"name=?",new String[]{"xialin"});
db.close();
}
删除数据
delete的三个参数:
- 第一个参数
"user_db"
是要删除记录的表的名称。- 第二个参数
"name=?"
是一个WHERE子句,用于指定哪些行应该被删除。问号?
是一个占位符,用于后面的参数数组。- 第三个参数
new String[]{"xialin"}
是一个字符串数组,用于替换WHERE子句中的占位符。在这个例子中,它只有一个元素"xialin"
,这意味着只有name
字段值为xialin
的行会被删除。
public void deleteData(SQLiteDatabase db){
db.delete("user_db","name=?",new String[]{"xialin"});
db.close();
}
查询数据
两种方式:
- 使用
query
方法 - 使用原始的SQL查询语句配合
rawQuery
方法来完成 - 两种方式都需要遍历返回的
Cursor
对象来获取查询结果
使用query
方法
query方法的参数:
columns
数组定义了要查询的列,这里查询name
和age
两列。selection
字符串定义了查询条件,使用占位符?
代替具体的值。selectionArgs
数组用于替换selection
中的占位符,这里只有一个值"xialin"
。query
方法的第一个参数是表名,后面跟着的是列名、查询条件、查询条件的参数等。- 查询结果通过
Cursor
对象返回,你可以遍历这个Cursor
对象来获取查询到的每一行数据。
public Cursor queryData(SQLiteDatabase db) {
// 定义要查询的列
String[] columns = new String[]{"name", "age"};
// 定义查询条件,这里查询name为xialin的记录
String selection = "name=?";
String[] selectionArgs = new String[]{"xialin"};
// 使用query方法执行查询
Cursor cursor = db.query("user_db", columns, selection, selectionArgs,
null, null, null);
// 返回查询结果的Cursor对象
return cursor;
}
使用rawQuery
方法
rawQuery的参数:
sql
字符串包含了原始的SQL查询语句。selectionArgs
数组用于替换SQL语句中的占位符。rawQuery
方法接受SQL语句和占位符对应的值作为参数,并返回查询结果的Cursor
对象。
public Cursor rawQueryData(SQLiteDatabase db) {
// 定义原始的SQL查询语句,其中?是占位符
String sql = "SELECT name, age FROM user_db WHERE name=?";
// 创建包含占位符对应值的数组
String[] selectionArgs = new String[]{"xialin"};
// 使用rawQuery方法执行查询
Cursor cursor = db.rawQuery(sql, selectionArgs);
// 返回查询结果的Cursor对象
return cursor;
}
遍历Cursor对象
getWritableDatabase():
- 是
SQLiteOpenHelper
类中的一个方法,用于获取一个可写的数据库对象。这个方法会尝试打开一个数据库。用于插入、删除、修改。
getReadableDatabase():
- 尝试打开一个可写的数据库连接,用于读取数据库文件。
queryBtn.setOnClickListener(v -> {
SQLiteDatabase db = dbHelper.getWritableDatabase();
//查询所有数据
Cursor cursor = db.query("book", null, null, null,
null, null, null);
try {
if (cursor!=null&&cursor.moveToFirst()){
do {
@SuppressLint("Range")
String name = cursor.getString(cursor.getColumnIndex("name"));
String name1 = getString(cursor.getColumnIndexOrThrow("name"));
@SuppressLint("Range")
String author = cursor.getString(cursor.getColumnIndex("author"));
@SuppressLint("Range")
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
@SuppressLint("Range")
double price = cursor.getDouble(cursor.getColumnIndex("price"));
Log. e("queryTagMsg:","book name is"+name);
.......
} while (cursor.moveToNext());
}
cursor.moveToNext();
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} finally {
if (cursor!=null){
cursor.close();
}
}
});
注意:
一、cursor.getColumnIndex(“name”)和cursor.getColumnIndexOrThrow(“name”);
- 前者需要搭配
@SuppressLint("Range")
阻止报错警告,因为本身可以正常运行。- 两者都可以用来从
Cursor
中获取 “name” 列的值,但getColumnIndexOrThrow
提供了更直接的错误处理机制(通过抛出异常),而getColumnIndex
则允许你在稍后的点处理潜在的索引问题(如果发生的话)。使用哪个取决于你的具体需求和错误处理策略。–此处引用文心一言
6.5【Android中JSON数据以及解析】
- 在安卓创建JSON文件并且读取json数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InputStreamReader reader;
try {
//getAssets读取asset文件夹下名为"test.json"的文件
reader = new InputStreamReader(getAssets().open("test.json"));
BufferedReader br = new BufferedReader(reader);
String line = "";
StringBuilder sb= new StringBuilder();
while ((line = br.readLine())!=null){
sb.append(line);
}
//JSONObject获取每个json数据,需要指定key
JSONObject root = new JSONObject(sb.toString());
String cat = (String) root.get("cat");
Log.i("cat", cat);
//如果JSON数据是一个数组,则需要使用JSONArray
JSONArray array = root.getJSONArray("language");
for (int i = 0; i < array.length(); i++) {
//再使用getxxx方法获取里面的每一个JSON
JSONObject job = array.getJSONObject(i);
String id = job.getString("id");
String name = job.getString("name");
String gender = job.getString("gender");
Log.i("id", id);
Log.i("name", name);
Log.i("gender", gender);
}
} catch (IOException | JSONException e) {
throw new RuntimeException(e);
}
7. ContentProvider
背景:
- Android数据持久化技术:文件存储、SharedPreferences存储以及数据库存储,其所保存的数据都只能在当前应用程序中访问。
- 在Android开发中,有时也会访问其他应用程序的数据。实现这种跨程序共享数据的功能,Android系统提供了一个组件ContentProvider(内容提供者)
- 为了观察程序中数据的变化,Android系统提供了一个内容观察者ContentObserver
7.1【ContentProvider概述】
-
ContentProvider(内容提供者)是不同应用程序之间进行数据共享的标准API。
-
它以uri的形式对提供数据,允许其他应用操作本应用程序的数据,其他应用通过ContentResolver类提供的Uri操作指定的数据。
-
它最重要的两个东西:数据模型与Uri
-
ContentResolver的工作原理:
A程序通过ContentProvider将数据暴露出来,供其他应用程序使用;
B程序通过ContentResolver接口操作暴露的数据;
总体:A程序通过ContentProvider将数据返回到ContentResolver,然后ContentResolver把数据返回到B程序。
比喻:
A仆人(A程序)用盘子(ContentProvider)装食物(数据),然后放到桌子上(暴露数据)。B(B程序)从桌子拿起食物享用(通过ContentResolver访问数据)。
如果B想要改变食物(修改数据),它会告诉A仆人(通过ContentResolver向ContentProvider发出请求),A仆人根据B的要求改变食物(修改数据)。当不再需要盘子时,A仆人负责清洗和存放盘子(管理ContentProvider的生命周期和数据清理)。
数据模型
- ContentProvider使用基于数据库模型的简单表格来提供需要共享的数据
- 在该表格中,每一行表示一条记录,而每一列代表特定类型和含义的数据
- 每一条数据记录都包含一个名为“__ID”的字段标识每条数据
Uri
-
ContentResolver提供一系列增、删、改、查的方法对数据进行操作,以Uri的形式对外提供数据。
-
其实就是一个操作方法,没有太复杂的含义,直接拿来用即可。
-
Uri为ContentProvider中的数据建立了唯一标识符主要由三部分组成,scheme、authorities、path
-
使用parse()方法解析Uri,比如Uri.parse(“tel:10086”)
示例:
content://com.example.mycontentprovider/person
scheme:*content://*是一个标准的前缀
authorities:com.example.mycontentprovider是authorities属性值,通常采用程序包名的方式命名。
path:/person代表资源或者数据,可以改变。
7.2【创建ContentProvider】
- 内容提供者创建完成后,AndroidStudio会自动在AndroidManifest.xml中对内容提供者进行注册。
示例代码:
- 根据传入的Uri进行相关操作
public class MyContentProvider extends ContentProvider {
public MyContentProvider() {
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public String getType(Uri uri) {
// TODO: Implement this to handle requests for the MIME type of the data
// at the given URI.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO: Implement this to handle requests to insert a new row.
throw new UnsupportedOperationException("Not yet implemented");
}
7.3【ContentResolver】
- B程序通过ContentResolver来对暴露的数据进行操作。
步骤:
-
通过parse()方法解析Uri
Uri uri=Uri.parse("content://cn.itcast. mycontentprovider/person");
-
通过query()方法查询数据
参数:
- uri:表示查询其他程序的数据需要的Uri
- projection:表示要查询的内容
- selection:表示设置查询的条件
- selectionArgs:需要配合参数selection使用
- sortOrder:表示查询的数据按照什么顺序进行排序
- 通过while()循环语句遍历查询到的数据
while(cursor.moveToNext()){
String address = cursor.getString(0);
long date = cursor.getLong(1);
int type = cursor.getlnt(2);
}
cursor.close();//关闭cursor
7.4【ContentObserver】
概述
-
帮助应用程序实时监听ContentProvider共享的数据是否发生变化
-
当ContentObserver观察到指定Uri代表的数据发生变化时,就会触发onChange()方法
创建ContentObserver
- 在自定义的ContentProvider类里面定义该类即可。
private class MyObserver extends ContentObserver {
public MyObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
}
注册ContentObserver
示例
ContentResolver cr = getContentResolver();
Uri uri = Uri.parse("content://xxxx");
cr.registerContentObserver(uri,true,new MyContentProvider.MyObserver(new Handler()));
取消注册ContentProvider
8. Broadcast
8.1【概述】
-
作用:在Android系统中,广播是一种运用在组件之间传递消息的机制
-
BroadcastReceiver(广播接收者)是Android四大组件之一,接收并过滤广播中的消息
-
广播接收者可以监听系统中的广播消息,实现在不同组件之间的通信
-
广播机制使用了观察者模式,基于消息的发布–订阅模式
- 消息发送者是广播机制中的广播发送者
- 消息订阅者是广播机制中的广播接收者
8.2【广播机制实现流程】
- 开发者在AMS中注册广播接收者
- 广播发送者向AMS发生广播
- AMS寻找合适的接收者并且转发到相应的消息循环队列
- 执行消息循环时接收者获取到此广播
注意:广播发送者与广播接收者的执行是异步的:
发出去的广播不会关心有无接收者接收,也不确定接收者到底何时才能接收到。
8.3【广播的使用场景】
- 同一App内同一组件的通信(能使用广播,但是增加性能开销和复杂性,现实开发这种场景不会用广播)
- 同一App内不同组件的通信
- 同一App内具有多个进程的不同组件的通信
- 不同App的组件之间的通信
- 特定情况下与不同App之间的通信
8.4【广播接收者】
作用
- 为了监听系统或应用程序的广播事件,Android系统提供了BroadcastReceiver(广播接收者)组件。
创建广播接收者
-
两种注册方法:静态和动态注册
-
静态注册:在清单文件中注册
-
动态注册:
public class MainActivity extends AppCompatActivity { MyReceiver receiver; @SuppressLint("NonConstantResourceId") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); receiver = new MyReceiver(); String action = "android.provider.Telephony.SMS.RECEIVED"; IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(action); //注册广播 registerReceiver(receiver,intentFilter); } @Override protected void onDestroy() { super.onDestroy(); //Activity被销毁时,取消注册receiver unregisterReceiver(receiver); }
广播接收器:
public class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // an Intent broadcast. throw new UnsupportedOperationException("Not yet implemented"); } Log.e("DyTag","动态注册:收到广播了!"); }
-
静态注册(Android 8.0后,静态注册已经接收不到广播)
需要在清单文件中添加:
<receiver android:name=".MyReceiverTest" android:enabled="true" android:exported="true"> </receiver>
注意:静态注册的方式则在Activity中设置action的必须要与的一样
8.5【自定义广播】
为什么要自定义广播?
- 当系统提供的广播不能满足实际需求时,可以自定义广播,同时需要编写对应的广播接收者。
整体流程
- 当自定义广播发送消息时,会储存到公共消息区中公共消息区中
- 如存在对应的广播接收者,就会及时的接收这条信息
广播的类型
- 两种广播类型
- 无序广播:发送效率高,但是无法被拦截,所有接收者都会收到该广播
- 有序广播:发送效率低,有先后顺序,可以被拦截
广播接收者优先级
-
指的是有序广播。
-
在动态注册注册广播时,可以使用如下方法设置优先级,值越大,优先级越高
-
如果两个广播接收者的优先级相同,则先注册的广播接收者优先级高,,即先安装的程序优先接收
intentFilter.setPriority(1000);
9. Service
9.1【概述】
- Service服务是安卓四大组件之一
- 具有较长的时间运行特性,可以在后台长时间执行操作、不提供用户界面的应用程序组件
- 一般是Activity启动,但是不依赖于Activity
9.2【主要应用场景】
如:下载文件、播放音乐
-
后台运行
在后台长时间进行操作而不用提供界面信息,只有系统要回收内存资源时,才会被销毁,否则Service会一直在后台运行
-
跨进程访问
当Service被其他应用组件启动时,即使用户切换到其他应用服务仍将在后台继续运行。
注意:
- Service总是在后台运行,其运行并不是在子线程中,而是在主线程中进行的
- 处理的耗时操作时开启子线程进行处理,否则出现ANR异常
9.3【创建服务】
-
AS中,New->Service->Service;
-
或者自行创建java类继承Service类即可;
-
必须在清单文件中注册Service
//类名 //能否实例化 //能否被调用 <service android:name=".MyService" android:enabled="true" android:exported="true" />
9.4【服务的生命周期】
- 不同启动方式的生命周期略有差异
9.5【服务的启动方式】
startService()启动
- 通过startService()方法启动的服务,会长期在后台运行
- 启动服务的组件(如:Activity)与服务之间没有关联,即使启动服务的组件被销毁,服务依旧会运行
示例
//可以搞几个按钮试试
@SuppressLint("NonConstantResourceId")
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.startService:
Intent intent = new Intent(this,MyService.class);
startService(intent);
break;
case R.id.stopService:
Intent intent1 = new Intent(this,MyService.class);
stopService(intent1);
break;
default:break;
}
}
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
Log.e("Service~~~", "启动!!!");
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("Service~~~", "onStartCommand: 来力");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e("Service~~~", "卸载!!!: ");
}
}
bindService()启动
-
这种启动方式需要与Activity建立连接(connection)进行通信
-
通过bindService()方法启动服务时,服务会与组件绑定当调用onUnbind()方法时,这个服务就会被销毁
-
绑定服务(调用onBind方法)时,我们可以进行一下操作,如调用一个方法sum()进行求和等等。
-
bindService(a,b,c)方法的三个参数 :
a=指定启动的Service b=用于监听调用者与Servicei之间的连接状态
c=用于指定绑定时是否自动创建Service
if (myConn == null) { myConn = new MyConn(); } intent intent = new Intent(this, MyService2.class); bindService(intent, myConn, BIND_AUTO_CREATE);
BIND_AUTO_CREATE的含义是一旦Activity与Service建立连接,Service就会被自动创建出来
9.6【服务的通信】
本地服务通信
- 本地服务通信是指应用程序内部的通信需要使用IBinder对象进行本地服务通信
远程服务通信
-
远程服务通信是指两个应用程序之间的通信,远程服务通信是通过AIDL实现的。
-
AIDL定义接口的源代码必须以.aidl结尾
-
AIDL接口中用到的数据类型,除了基本数据类型String、List、Map、CharSequence之外,其他类型全部都需要导入包,即使它们在同一个包中
进行处理,否则出现ANR异常
9.3【创建服务】
-
AS中,New->Service->Service;
-
或者自行创建java类继承Service类即可;
-
必须在清单文件中注册Service
//类名 //能否实例化 //能否被调用 <service android:name=".MyService" android:enabled="true" android:exported="true" />
9.4【服务的生命周期】
- 不同启动方式的生命周期略有差异
9.5【服务的启动方式】
startService()启动
- 通过startService()方法启动的服务,会长期在后台运行
- 启动服务的组件(如:Activity)与服务之间没有关联,即使启动服务的组件被销毁,服务依旧会运行
示例
//可以搞几个按钮试试
@SuppressLint("NonConstantResourceId")
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.startService:
Intent intent = new Intent(this,MyService.class);
startService(intent);
break;
case R.id.stopService:
Intent intent1 = new Intent(this,MyService.class);
stopService(intent1);
break;
default:break;
}
}
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
Log.e("Service~~~", "启动!!!");
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("Service~~~", "onStartCommand: 来力");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e("Service~~~", "卸载!!!: ");
}
}
bindService()启动
-
这种启动方式需要与Activity建立连接(connection)进行通信
-
通过bindService()方法启动服务时,服务会与组件绑定当调用onUnbind()方法时,这个服务就会被销毁
-
绑定服务(调用onBind方法)时,我们可以进行一下操作,如调用一个方法sum()进行求和等等。
-
bindService(a,b,c)方法的三个参数 :
a=指定启动的Service
b=用于监听调用者与Servicei之间的连接状态
c=用于指定绑定时是否自动创建Serviceif (myConn == null) { myConn = new MyConn(); } intent intent = new Intent(this, MyService2.class); bindService(intent, myConn, BIND_AUTO_CREATE);
BIND_AUTO_CREATE的含义是一旦Activity与Service建立连接,Service就会被自动创建出来
9.6【服务的通信】
本地服务通信
- 本地服务通信是指应用程序内部的通信需要使用IBinder对象进行本地服务通信
远程服务通信
-
远程服务通信是指两个应用程序之间的通信,远程服务通信是通过AIDL实现的。
-
AIDL定义接口的源代码必须以.aidl结尾
-
AIDL接口中用到的数据类型,除了基本数据类型String、List、Map、CharSequence之外,其他类型全部都需要导入包,即使它们在同一个包中