KFC疯狂星期四
1.探究Activity
在Activity中使用Toast
1 | val button3 = findViewById<Button>(R.id.button3) |
ViewBinding的使用
- 在
build.gradle.kts导入ViewBinding - 在活动中导入自动生成的
ViewBinding类并对其进行声明(根据活动) - 绑定布局
1 | android { |
1 | import com.example.activitytest.databinding.FirstLayoutBinding // 自动生成的 ViewBinding 类(根据该活动) |
在布局中直接调用图像
1 | android:src="@android:drawable/ic_menu_call" |
在活动中使用Menu
- 在
res目录下新建menu文件夹并生成main文件 - 在活动中重写
onCreateOptionsMenu()方法与onOptionsItemSelected()方法 - 修改主题
1 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { |
1 | <resources xmlns:tools="http://schemas.android.com/tools"> |
使用Intent在Activity之间穿梭
显式Intent
1 | binding.button1.setOnClickListener { |
隐式Intent(指定对应action和category等信息去响应)
- 指定当前
Activity能够响应的action和category - 调用
addCategory()方法添加一个category
1 | <activity |
1 | binding.button1.setOnClickListener { |
打开百度
1 | binding.button1.setOnClickListener { |
打开电话
1 | binding.button1.setOnClickListener { |
向下一个Activity传递数据
运用putExtra()方法的重载
1 | binding.button1.setOnClickListener { |
1 | override fun onCreate(savedInstanceState: Bundle?) { |
返回数据给上一个Activity
- 用
startActivityForResult()启动活动 - 用
setResult()方法向上一个Activity返回数据 - 在上一个活动重写
onActivityResult方法
1 | binding.button1.setOnClickListener { |
1 | class SecondActivity : AppCompatActivity() { |
1 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
Activity的生命周期
1. onCreate()
-
作用
:当 Activity 第一次被创建时调用。在这个方法中,你通常会完成以下工作:
- 设置布局文件(例如调用
setContentView()) - 初始化控件、变量和数据
- 配置基本组件(如 Toolbar、Fragment 等)
- 设置布局文件(例如调用
-
示例代码
1
2
3
4
5override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化操作
}
2. onStart()
-
作用:在
onCreate()之后调用,此时 Activity 已经对用户可见,但还未获得焦点,也就是还不能进行用户交互。通常在这里启动一些即将进入前台的操作。 -
示例代码
1
2
3
4override fun onStart() {
super.onStart()
// Activity 对用户可见,但尚未获得焦点
}
3. onResume()
-
作用:在
onStart()之后调用,此时 Activity 已经位于前台,并且可以接收用户的输入和交互。这也是应用处于“运行”状态的主要阶段。 -
示例代码
1
2
3
4override fun onResume() {
super.onResume()
// 开始处理用户交互,如启动动画、注册传感器等
}
4. onPause()
-
作用:当系统准备启动或恢复另一个 Activity 时调用,此时当前 Activity 仍然部分可见,但失去了用户焦点。通常在这里进行轻量级的保存工作,比如暂停动画、保存数据或释放一些占用较多资源的操作。
-
注意:此方法的执行时间应该尽可能短,以免阻塞其他 Activity 的启动。
-
示例代码
1
2
3
4override fun onPause() {
super.onPause()
// 暂停动画、保存数据、释放资源等
}
5. onStop()
-
作用:当 Activity 完全对用户不可见时调用,可能是因为新的 Activity 覆盖在上面或 Activity 被销毁。通常在这里释放不再需要的资源,并执行较重的保存工作。
-
示例代码
1
2
3
4override fun onStop() {
super.onStop()
// 释放资源、保存数据等
}
6. onDestroy()
-
作用:在 Activity 被完全销毁之前调用。这可能是因为用户主动关闭 Activity 或系统由于资源不足而销毁 Activity。在此方法中需要清理所有占用的资源,确保内存没有泄漏。
-
示例代码
1
2
3
4override fun onDestroy() {
super.onDestroy()
// 清理资源、取消注册监听器等
}
7. onRestart()
-
作用:当一个已停止的 Activity 将要重新启动时调用,此方法紧接在
onStop()之后调用,然后进入onStart()。常用于重新初始化在onStop()中释放的资源或状态恢复。 -
示例代码
1
2
3
4override fun onRestart() {
super.onRestart()
// 准备 Activity 重新对用户可见
}
生命周期的常见调用顺序
- 初次启动 Activity:
onCreate()→onStart()→onResume() - 用户离开 Activity(例如启动另一个 Activity 或按下 Home 键):
onPause()→onStop() - 用户返回到 Activity:
onRestart()→onStart()→onResume() - 销毁 Activity:
onPause()→onStop()→onDestroy()
如果Activity被回收了怎么办
- 使用
onSaveInstanceState回调方法保存数据 - 在
onCreate()方法里面对其进行恢复
1 | class MainActivity : AppCompatActivity() { |
Activity的启动方式
1. AndroidManifest.xml 配置
在 AndroidManifest.xml 中,为每个 Activity 配置不同的启动模式:
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
2. Kotlin 代码示例与注释
(1) StandardActivity
说明:Standard 模式是 Android 的默认模式,每次启动该 Activity 都会新建一个实例,即使之前已有相同类型的 Activity 存在于任务栈中。
1 | /** |
(2) SingleTopActivity
说明:SingleTop 模式下,如果目标 Activity 已经处于任务栈的顶部,则不会创建新的实例,而是调用 onNewIntent() 方法传递新的 Intent;如果不在栈顶,则会创建新实例。
1 | /** |
(3) SingleTaskActivity
说明:SingleTask 模式确保在系统中只存在一个该 Activity 的实例。当再次启动时,如果实例已存在,系统会将其上面的所有 Activity 清除,并调用 onNewIntent() 方法传递 Intent。
1 | /** |
(4) SingleInstanceActivity
说明:SingleInstance 模式是最严格的启动模式,该 Activity 所在的任务栈中只允许存在它本身,其它 Activity 会在其他任务栈中运行,从而实现高度隔离。
1 | /** |
3. 总结
- standard
每次启动时都会创建新的 Activity 实例,适用于不需要保留之前状态的场景。 - singleTop
如果目标 Activity 已位于任务栈顶部,则复用实例(调用 onNewIntent()),否则创建新实例。适合频繁接收新 Intent 的场景。 - singleTask
保证全局只有一个实例,启动时会清除目标实例之上的所有 Activity,并调用 onNewIntent()。适用于需要全局唯一实例的核心界面。 - singleInstance
该 Activity 独占任务栈,其他 Activity 不会与其共存。适用于需要高度隔离的场景,如系统级界面或特殊业务场景。
Activity的最佳实践
知晓当前是在哪一个活动
1 | open class BaseActivity : AppCompatActivity(){ |
随时随地退出程序
1 | package com.example.activitytest |
1 | open class BaseActivity : AppCompatActivity(){ |
1 | val button3 = findViewById<Button>(R.id.button3) |
启动Activity的最佳写法
1 | // 在要启动的活动内添加该静态方法 |
1 | binding.button1.setOnClickListener { |
2.UI开发
布局
ListView控件
1.定义 Fruit 类,并在 initFruits() 方法中构建一个包含多个水果对象的数据列表。
2在 activity_main.xml 中定义 ListView,接着在 MainActivity 的 onCreate() 方法中设置布局、初始化数据、获取 ListView 控件。
3.实现 FruitAdapter 类,通过重写 getView() 方法和使用 ViewHolder 模式,将数据与列表项布局(fruit_item.xml)绑定,并返回每一项的视图。
4.在 fruit_item.xml 中定义每个列表项的视图结构,包括图片和文本。
5.在 MainActivity 中创建适配器、绑定 ListView 并设置点击事件,实现用户点击列表项时显示相应水果名称的 Toast 提示。
1 | class MainActivity : AppCompatActivity() { |
1 | class FruitAdapter(activity: Activity, val resourceId: Int, val data: List<Fruit>) : ArrayAdapter<Fruit>(activity, resourceId, data) { |
1 | class Fruit(val name: String, val imageId: Int) |
1 |
|
1 |
|
RecyclerView控件
1.添加依赖库
2.数据模型定义
Fruit 类定义了水果的数据结构。
3.适配器实现
在 FruitAdapter 中:
- 定义 ViewHolder 缓存 item 内的控件;
- 在
onCreateViewHolder中加载 item 布局、创建 ViewHolder 并设置点击事件; - 在
onBindViewHolder中绑定数据; getItemCount返回数据总数。
4.item 布局定义
fruit_item.xml 描述了每个列表项的外观(图片和名称)。
5.MainActivity 配置
在 onCreate() 中:
-
调用
initFruits()初始化数据; -
设置 RecyclerView 的布局管理器(瀑布流效果);
-
绑定适配器,实现数据展示。
1 | implementation("androidx.recyclerview:recyclerview:1.3.2") |
1 | class MainActivity : AppCompatActivity() { |
1 | class Fruit(val name: String, val imageId: Int) |
1 | class FruitAdapter(val fruitList: List<Fruit>) : RecyclerView.Adapter<FruitAdapter.ViewHolder>() { |
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
1 |
|
Fragment碎片
Fragment是一种可以嵌入在Activity的UI片段
Fragment的简单用法
- 写左右两个Fragment布局
- 新建左右两个Fragment类
- 在
activity_main中引入两个Fragment
1 |
|
1 |
|
1 | class LeftFragment : Fragment() { |
1 | class RightFragment: Fragment() { |
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
动态添加Fragment
-
新建
another_right_fragment和AnotherRightFragment -
动态添加到
activity_main中 -
修改
MainActivity(1) 创建待添加 Fragment 的实例。
(2) 获取getSupportFragmentManager()方法。
(3) 开启一个事务,通过调用beginTransaction()方法开启。
(4) 向容器内添加或替换 Fragment,一般使用replace()方法实现,需要传入容器的 id 和待添加的 Fragment 实例。
(5) 提交事务,调用commit()方法来完成。
1 |
|
1 | class AnotherRightFragment : Fragment(){ |
1 |
|
1 | class MainActivity : AppCompatActivity() { |
在Fragment中实现返回栈
1 | val fragmentTransaction = fragmentManager.beginTransaction() |
Fragment和Activity之间的交互
1.在Activity调用Fragment
1 | val fragment = leftFrag as LeftFragment |
2.在Fragment调用Activity
1 | if (activity!=null) { |
Fragment的生命周期
1. onAttach(Context context)
- 作用: 当 Fragment 被添加到 Activity 时,会首先调用该方法。这时 Fragment 与宿主 Activity 建立关联,但还没有创建自身的视图。
- 注意: 此时你可以获取宿主 Activity 的上下文,进行一些必要的初始化工作。
2. onCreate(Bundle savedInstanceState)
- 作用: 在 Fragment 创建时调用,用于进行一些非视图相关的初始化操作,比如初始化数据、设置选项等。
- 注意: 此阶段不适合初始化界面相关的内容,因为视图尚未创建。
3. onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
- 作用: 用于创建并返回 Fragment 的视图层次结构(通常通过 XML 布局文件膨胀得到)。
- 注意: 你可以在这里绑定视图,设置控件的属性或事件监听。
4. onViewCreated(View view, Bundle savedInstanceState)
- 作用: 当视图被创建后立即调用,此时可以对视图进行进一步的初始化(例如查找子视图、设置适配器等)。
- 注意: 虽然这是可选的,但有助于将视图相关的逻辑与视图创建分离。
5. onActivityCreated(Bundle savedInstanceState)
- 作用: 当宿主 Activity 的 onCreate() 方法执行完毕后调用,这时 Fragment 的宿主 Activity 已经完全初始化,可以安全地与 Activity 进行交互。
- 注意: 可以在此阶段恢复状态或进行依赖于 Activity 的操作。
6. onStart()
- 作用: Fragment 对用户可见。此时界面已经呈现,但还没有获得用户焦点。
- 注意: 通常在这里开始一些较轻的 UI 刷新工作。
7. onResume()
- 作用: Fragment 进入活动状态,此时它与用户进行交互。界面完全处于前台,响应用户输入。
- 注意: 可以在这里启动动画、开始监听传感器数据等与用户交互密切相关的操作。
8. onPause()
- 作用: 当 Fragment 失去焦点,用户无法与之交互时调用(例如,用户离开应用或启动新的 Activity)。
- 注意: 在这里应停止可能影响性能或不必要的操作,比如暂停动画、释放资源等。
9. onStop()
- 作用: Fragment 不再对用户可见,但仍保留在内存中。
- 注意: 此阶段可进行资源释放、停止更新 UI 等操作,确保不会浪费资源。
10. onDestroyView()
- 作用: Fragment 的视图层次结构被销毁。此时与视图相关的资源(如绑定的视图、适配器等)应当被清理。
- 注意: 如果需要保存视图状态,可以在此方法之前进行处理。
11. onDestroy()
- 作用: Fragment 自身被销毁,用于释放所有剩余资源和进行最终清理工作。
- 注意: 此阶段与 onCreate() 相对,用于做一些彻底的清理工作。
12. onDetach()
- 作用: Fragment 与宿主 Activity 的关联被断开,生命周期结束。
- 注意: 此时所有与 Activity 相关的引用都应该被释放,以防止内存泄漏
3.广播(Broadcast)
广播机制(Broadcasting)指的是将消息、数据或信号从一个源同时发送给网络中所有或一部分节点或设备。它与单播(Unicast,一对一通信)和多播(Multicast,一对多通信)不同,广播主要面向整个网络或一个较大的受众群体。
1. 标准广播
定义与特点
- 无序性:标准广播在消息传输时不对接收顺序做严格保证。不同接收者收到消息的顺序可能不一致,这种特性使其实现相对简单。
- 高效性:由于不需要额外的排序和同步机制,标准广播通常具有较低的延时和较高的传输效率。
- 适用场景:
- 用于事件通知、状态更新或日志广播等场景,这些场景对消息顺序没有严格要求。
- 在对实时性要求高、能够容忍一定顺序差异的应用中,标准广播是常用的选择。
2. 有序广播
定义与特点
- 顺序一致性:有序广播要求所有接收者必须按照相同的顺序接收到所有消息。也就是说,无论消息是从哪个节点发送,所有节点在消息的交付顺序上都必须保持一致。
- 实现复杂性:为了确保全局顺序一致,常需要采用额外的排序机制或共识算法(如逻辑时钟、矢量时钟或专门的总排序服务)。这种机制虽然能够确保系统状态的一致性,但同时也会增加系统的复杂性和延时。
- 适用场景:
- 在需要高度一致性的分布式系统中,如分布式数据库复制、一致性协议、金融交易系统等,保证操作顺序一致是关键。
- 有序广播有助于防止由于消息乱序而引发的数据冲突或状态不一致问题,从而提高系统的容错性和数据一致性。
3. 对比与选择
| 特性 | 标准广播 | 有序广播 |
|---|---|---|
| 消息顺序 | 无严格顺序保证 | 全局顺序一致 |
| 实现难度 | 实现简单,效率高 | 需要额外排序/同步机制,复杂度高 |
| 延时 | 较低 | 可能因排序/同步而增加延时 |
| 适用场景 | 实时性要求高、顺序要求不严格 | 状态一致性要求高、需要全局顺序一致 |
4 静态注册广播
定义
静态注册广播是在系统配置或设计阶段就预先确定好所有参与广播的节点,广播目标在系统运行过程中保持不变。
特点与优势
- 实现简单:广播目标在配置文件或代码中硬编码,开发和调试过程中较为直接。
- 低开销:无需额外的注册和管理机制,消息直接发送给预定的目标。
- 确定性高:由于广播组成员固定,网络拓扑和消息传递路径相对固定,有助于预期性能分析。
局限性
- 灵活性不足:如果系统中的节点发生增减或迁移,必须手动更新配置,无法自动适应环境变化。
- 扩展性受限:适用于节点较少且变化不频繁的系统,对于大规模动态环境来说不够高效。
5. 动态注册广播
定义
动态注册广播允许系统中节点在运行时主动注册成为广播组成员或退出广播组。此类机制通常依赖一个注册中心或服务发现模块,实时维护当前可用的广播目标列表。
特点与优势
- 高灵活性:节点上线、下线、扩容或故障恢复时,注册中心会自动更新广播组成员,保证广播消息能够覆盖最新的系统状态。
- 自动管理:利用心跳检测或健康检查机制,动态剔除不可用节点,提升系统整体的容错能力。
- 便于扩展:适合大规模、动态变化频繁的分布式系统,能够根据实际负载和环境变化自动调整广播对象。
局限性
- 实现复杂:需要部署和维护额外的注册服务,涉及网络延时、状态一致性等问题。
- 管理开销:动态更新广播组可能带来短暂的不一致或额外的网络通信开销,需仔细设计以减少延迟和资源消耗。
6. 对比与适用场景
| 特性 | 静态注册广播 | 动态注册广播 |
|---|---|---|
| 注册方式 | 在配置阶段固定节点列表 | 节点可在运行时自动注册或注销 |
| 实现复杂度 | 实现简单,依赖硬编码或配置文件 | 需要额外的注册中心和健康检查机制 |
| 灵活性与扩展性 | 节点变化需要人工干预,适合节点稳定的小规模系统 | 适应节点频繁变动,大规模系统中自动维护广播组成员 |
| 系统管理开销 | 管理和维护简单,但缺乏自动适应性 | 运维自动化,但引入额外通信和状态同步的开销 |
动态注册监听时间变化(记得取消注册)
-
新建一个类继承自
BroadcastReceiver -
重写
onReceiver()方法,执行具体逻辑1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class MainActivity : AppCompatActivity() {
lateinit var timeChangeReceiver: TimeChangeReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intentFilter = IntentFilter()
intentFilter.addAction("android.intent.action.TIME_TICK")
timeChangeReceiver = TimeChangeReceiver()
registerReceiver(timeChangeReceiver, intentFilter)
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(timeChangeReceiver) //取消注册
}
inner class TimeChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "Time has changed", Toast.LENGTH_SHORT).show()
}
}
}
静态注册实现开机启动
- 用快捷方式创建一个广播接收器
- 在
AndroidManifest添加对应权限与action
1 | class BootCompleteReceiver : BroadcastReceiver() { |
1 | <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> |
发送标准广播
- 定义一个广播接收器
- 在AndroidManifest添加自定义广播
- 在MainActivity发送广播
1 | class MyBroadcastReceiver : BroadcastReceiver() { |
1 | <receiver |
1 | val button = findViewById<androidx.appcompat.widget.AppCompatButton>(R.id.button) |
发送有序广播
- 新建
AnotherBroadcastReceiver - 在AndroidManifest添加自定义广播
- 在
MainActivity利用sendOrderedBroadcast发送广播 - 在
AndroidManifest设置优先级 - 在
MyBroadcastReceiver利用abortBroadcast截断广播
1 | class AnotherBroadcastReceiver : BroadcastReceiver() { |
1 | <receiver |
1 | val button = findViewById<androidx.appcompat.widget.AppCompatButton>(R.id.button) |
1 | <intent-filter android:priority="100"> |
1 | class MyBroadcastReceiver : BroadcastReceiver() { |
4.持久化技术
数据持久化技术就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或计算机关机的情况下,这些数据仍然不会消失.
文件存储
- 新建
save函数,通过openFileOutput()方法存入数据 - 让Activity在
Destroy之前掉用save方法 - 在
load中通过openFileInput读取数据
1 | class MainActivity : AppCompatActivity() { |
SharedPreferences存储
- 通过
getSharedPreferences()指定文件名并得到SharedPreferences.Editor对象 - 添加新数据,用
apple()进行提交 - 运用一系列
get方法提取数据
1 | class MainActivity : AppCompatActivity() { |
SQLite数据库存储
SQLite是一个轻量级的关系型数据库,嵌入在Android系统中,适合存储结构化数据。与传统的客户端-服务器数据库不同,SQLite是一个内嵌式数据库,所有的数据操作都是在本地进行,非常适合需要离线存储的小型或中型数据应用。
- 通过SOL语句建表,在
onCreate方法里面调用db.execSQL()创建数据库 - 在
onUpgrade()执行DROP语句对数据库进行更新 - 增加数据库帮助类的版本号
1 | class MainActivity : AppCompatActivity() { |
1. 添加(插入)数据
核心步骤
- 获取数据库实例(通常使用
writableDatabase)。 - 使用
ContentValues封装待插入的数据。 - 调用
insert()方法将数据插入表中,并返回新记录的ID。
1 | fun insertCity(dbHelper: DBHelper, name: String, code: String) { |
注意事项
- 插入操作一般要在子线程中执行,避免阻塞主线程。可以使用Kotlin协程或其它异步处理方式。
- 可以通过事务优化批量插入的性能。
2. 更新数据
核心步骤
- 获取数据库实例(使用
writableDatabase)。 - 构造一个
ContentValues对象来表示要更新的字段及其新值。 - 使用
update()方法,传入更新条件和条件参数。
1 | fun updateCityName(dbHelper: DBHelper, cityId: Int, newName: String) { |
注意事项
- 更新操作需要谨慎构造条件(WHERE子句),避免误更新多条记录。
- 同样建议在子线程中执行更新操作以避免主线程阻塞。
3. 删除数据
核心步骤
- 获取可写数据库实例。
- 构造删除条件(WHERE子句)及其参数。
- 调用
delete()方法删除记录。
1 | fun deleteCity(dbHelper: DBHelper, cityId: Int) { |
注意事项
- 删除数据时一定要构造准确的删除条件,避免误删数据。
- 如需删除所有数据,可以传入
null作为条件,但要慎重使用。
4. 查询数据
核心步骤
- 获取数据库实例(通常使用
readableDatabase)。 - 构造要查询的列数组、WHERE条件(可选)以及排序等参数。
- 调用
query()方法或者使用rawQuery()执行SQL语句。 - 使用
Cursor遍历查询结果,并通过getColumnIndexOrThrow获取列数据。
1 | fun queryCities(dbHelper: DBHelper): List<Triple<Int, String, String>> { |
注意事项
- 查询操作后要及时关闭
Cursor和数据库。 - 当需要查询大量数据时,可考虑使用分页查询以提高性能。
- 对于复杂查询,可以使用
rawQuery()方法执行自定义的SQL语句。
5. 事务
- 开启事务:调用
beginTransaction()开始一个事务。 - 执行数据库操作:在事务中执行多条数据库操作(如插入、更新等)。
- 标记事务成功:当所有操作执行成功后,调用
setTransactionSuccessful()标记事务成功。如果没有调用这一方法,后续endTransaction()时将会回滚事务。 - 结束事务:调用
endTransaction()结束事务。如果事务成功,则提交所有操作,否则回滚所有操作。
1 | fun insertMultipleCities(dbHelper: DBHelper, cities: List<Pair<String, String>>) { |
5.探究ContentProvider
- 定义:ContentProvider 是一种Android组件,允许应用程序以标准化接口对数据进行访问,无论数据存储在 SQLite 数据库、文件系统还是其他位置。
- 数据共享:它主要用于跨进程或跨应用间的数据共享,通过定义 URI(统一资源标识符)来标识数据资源,并提供增删改查(CRUD)的接口。
申请权限
- 借助
ContextCompat.checkSelfPermission()方法判断是否授予权限 - 调用
ActivityCompat.requestPermissions()方法向用户申请权限 - 在
AndroidManifest申请权限
1 | class MainActivity : AppCompatActivity() { |
1 | <uses-permission android:name="android.permission.CALL_PHONE" /> |
ContentResolver的基本用法
ContentResolver 是 Android 提供的一个接口,用于与 ContentProvider 交互,从而实现跨进程或跨应用的数据访问。通过 ContentResolver,你可以对 ContentProvider 中的数据执行查询、插入、更新和删除等操作.
1. 获取 ContentResolver 实例
在 Android 中,通常通过上下文(Context)来获取 ContentResolver 实例:
1 | val contentResolver = context.contentResolver |
2.查询数据
query() 方法用于从 ContentProvider 查询数据,参数说明如下:
- uri:需要访问的数据的 URI,例如:
content://com.example.app.provider/city - projection:需要查询的列数组,如果传入 null,则返回所有列
- selection:过滤条件(WHERE 子句),可使用 ? 占位符
- selectionArgs:过滤条件对应的参数数组
- sortOrder:排序规则(例如
"name ASC")
1 | val uri = Uri.parse("content://com.example.app.provider/city") |
注意:查询结束后需要关闭 Cursor(使用
use {}块可自动关闭)。
3. 插入数据
insert() 方法用于在 ContentProvider 中插入新数据,参数说明如下:
- uri:目标数据的 URI
- ContentValues:存储键值对数据
1 | val uri = Uri.parse("content://com.example.app.provider/city") |
4.更新数据
update() 方法用于更新 ContentProvider 中的数据,参数说明如下:
- uri:目标数据的 URI
- ContentValues:存储需要更新的字段和值
- selection:指定更新条件
- selectionArgs:条件参数数组
1 | val uri = Uri.parse("content://com.example.app.provider/city") |
5.删除数据
delete() 方法用于删除 ContentProvider 中的数据,参数说明如下:
- uri:目标数据的 URI
- selection:删除条件
- selectionArgs:条件参数数组
1 | val uri = Uri.parse("content://com.example.app.provider/city") |
创建ContentProvider的步骤
1.继承 ContentProvider 类
创建一个新的类,继承自 ContentProvider,并重写所有抽象方法(如 onCreate()、query()、insert()、update()、delete()、getType())。
实现 onCreate() 方法
在 onCreate() 中进行必要的初始化操作,比如数据库连接的创建或其他资源的初始化。此方法在 ContentProvider 实例被创建时调用。
2.实现数据操作方法
- query():根据传入的 URI 查询数据,并返回一个 Cursor 对象。
- insert():根据传入的 URI 插入数据,返回新插入数据的 URI。
- update():根据 URI 更新数据,返回受影响的行数。
- delete():根据 URI 删除数据,返回删除的行数。
- getType():返回指定 URI 对应的数据 MIME 类型。
3.配置 URI 匹配器
使用 UriMatcher 对传入的 URI 进行匹配,以便确定请求类型并执行相应操作。可以在静态代码块中添加匹配规则,例如:
1 | private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); |
4.在 AndroidManifest.xml 中注册 ContentProvider
在清单文件中注册你的 ContentProvider:
1 | <provider |
注意:android:exported 的值根据你的需求设置为 true 或 false。
1 | class DatabaseProvider : ContentProvider() { |
6.运用手机多媒体
使用通知
-
获取
NotificationManager实例 -
使用
NotificationChannel创建一个通知渠道(通道ID,通道名称,重要等级) -
动态请求检查
POST_NOTIFICATIONS权限 -
创建
NotificationBuilder,用于构建通知 -
调用
notify()方法发送通知 -
在
AndroidManifest申请权限
1 | class MainActivity : AppCompatActivity() { |
1 | <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> |
调用摄像头和相册
-
创建一个File对象,用于存放图片
-
创建一个新的图片文件
output_image.jpg并生成Uri,创建Intent启动图库应用,指定image/*类型,允许用户选择图片 -
处理从图库返回的图片:获取图片的
Uri -
getBitmapFromUri()方法从Uri获取文件描述符并解码为Bitmap对象。 -
处理从相机返回的图片:解码图片并显示到
ImageView上,使用rotateIfRequired()检查是否需要旋转图片 -
注册内容提供器并在xml目录下创建
file_paths文件
1 | class MainActivity : AppCompatActivity() { |
1 |
|
1 | <provider |
播放音频
MediaPlayer类中常用控制方法:
| 方法 | 描述 |
|---|---|
setDataSource(String path) |
设置数据源,指定要播放的音频或视频文件的路径。 |
setDataSource(Context context, Uri uri) |
设置数据源,指定要播放的音频或视频文件的 URI。 |
prepare() |
准备 MediaPlayer,使其可以开始播放。该方法在播放前必须调用。 |
prepareAsync() |
异步准备 MediaPlayer,用于播放较大的文件或需要较长时间准备的文件。 |
start() |
启动播放音频或视频。 |
pause() |
暂停音频或视频的播放。 |
stop() |
停止播放音频或视频,重置播放器状态。 |
seekTo(int msec) |
跳转到音频或视频的指定时间位置(毫秒)。 |
getCurrentPosition() |
获取当前播放的时间位置(毫秒)。 |
getDuration() |
获取音频或视频的总时长(毫秒)。 |
isPlaying() |
检查 MediaPlayer 是否正在播放。返回 true 或 false。 |
setLooping(boolean looping) |
设置是否循环播放。true 为循环播放,false 为正常播放。 |
setVolume(float leftVolume, float rightVolume) |
设置音量,leftVolume 和 rightVolume 的值范围为 0.0 到 1.0。 |
reset() |
重置 MediaPlayer 到初始状态,释放所有资源。 |
release() |
释放 MediaPlayer 所占用的所有资源,调用后无法再使用该实例 |
1 | class MainActivity : AppCompatActivity() { |
播放视频
VideoView 类中常用的控制方法及其功能:
| 方法 | 描述 |
|---|---|
setVideoPath(String path) |
设置视频文件的路径,通过文件路径指定要播放的视频文件。 |
setVideoURI(Uri uri) |
设置视频的 URI,指定要播放的视频的 URI。 |
start() |
开始播放视频。 |
pause() |
暂停视频播放。 |
stopPlayback() |
停止视频播放,并释放播放器资源。 |
seekTo(int msec) |
跳转到指定时间位置(毫秒)。 |
getCurrentPosition() |
获取当前播放进度(毫秒)。 |
getDuration() |
获取视频的总时长(毫秒)。 |
isPlaying() |
检查视频是否正在播放。返回 true 或 false。 |
setLooping(boolean looping) |
设置是否循环播放视频,true 表示循环播放,false 表示不循环播放。 |
1 | class MainActivity : AppCompatActivity() { |
7.Service
Service是Android中实现程序后台运行的解决方案,它非常适合执行那些不需要和用户交互而且还要长期运行的任务,要记得在Service内部创建子线程,并在这里执行具体的任务
在子线程更新UI(异步处理)
在 Android 开发中,主线程(也称为 UI 线程)负责处理用户界面操作和事件分发。如果在主线程上执行耗时操作,会导致 UI 阻塞,应用程序看起来会“冻结”甚至出现“应用程序无响应”(ANR)错误。因此,当需要执行任何可能耗时的操作时,都应该开启新的线程(或使用其他并发机制)。
以下是一些 Android 中需要开启线程的常见例子:
- 网络请求 (Network Requests) 网络请求 (Network Requests)
- 例子: 从服务器下载图片、获取 JSON 数据、上传文件等。
- 原因: 网络操作需要等待服务器响应,耗时不可预测,可能几秒甚至更长。如果在主线程上执行,会导致 UI 卡顿。
- 数据库操作 (Database Operations)
- 例子: 查询大量数据、插入/更新/删除大量记录、进行复杂的数据关联操作。
- 原因: 数据库操作,尤其是涉及大量数据的操作,可能会非常耗时。
- 文件 I/O 操作 (File I/O Operations)
- 例子: 读取或写入大文件、处理图片或视频文件、进行文件压缩/解压缩。
- 原因: 磁盘读写速度相对较慢,耗时操作可能阻塞 UI。
- 图片处理 (Image Processing) 图片处理 (Image processing)
- 例子: 对图片进行缩放、旋转、裁剪、滤镜处理,或从网络加载并解码大图。
- 原因: 图片处理通常涉及大量的计算和内存操作,非常耗时。
- 耗时计算 (Heavy Computations)
耗时计算 (Heavy Computations)- 例子: 复杂的数学计算、算法执行、数据排序、数据分析。
- 原因: 任何需要大量 CPU 时间的计算都应在后台线程进行,以避免阻塞主线程。
- 音视频处理 (Audio/Video Processing)
- 例子: 编码/解码音视频、录音、播放媒体流。
- 原因: 这些操作通常是实时且计算密集型的。
- 传感器数据处理 (Sensor Data Processing)
- 例子: 持续获取和处理大量的传感器数据,例如加速度计或陀螺仪数据。
- 原因: 虽然单个传感器事件可能不耗时,但持续、高频率的处理可能导致性能问题。
Android 中实现多线程的常见方式:
虽然可以直接使用 Java 的 Thread 和 Runnable,但在 Android 中,通常会使用更高级别的抽象和框架来简化多线程开发,并更好地处理线程间通信(特别是将结果更新到 UI):
- Kotlin Coroutines (协程): 推荐用于 Kotlin 项目,提供更简洁、安全的异步编程方式。
- Java
Executor框架/ThreadPoolExecutor: 用于管理线程池,避免频繁创建和销毁线程。 AsyncTask(已废弃/不推荐): 早期 Android 提供的一种简化异步任务的类,但现在已不推荐使用,因为其内部实现存在一些问题,且容易导致内存泄漏和行为不一致。Handler和Looper: 用于在不同线程之间发送和处理消息,特别是在后台线程完成任务后更新 UI。Service和IntentService(已废弃/不推荐使用): 用于执行不需要 UI 的长时间运行操作。IntentService会在任务完成后自动停止。- Jetpack WorkManager: 推荐用于可延迟、保证执行的任务,即使应用程序退出或设备重启也能完成。
- 新增一个
Handler对象,并重写一个handleMessage()方法 - 设定一个匹配
what的值updateText - 在主线程调用
Handler
1 | class MainActivity : AppCompatActivity() { |
解析异步消息处理机制
1. Message
- 作用
Message作为数据载体,用于封装需要传递的信息。它包含的主要字段有:- what:标识消息类型,便于在处理时区分不同的业务逻辑。
- arg1/arg2:用于传递简单的数值数据。
- obj:可以传递复杂的数据对象。
- Runnable:如果设置了Runnable对象,则在消息处理时直接执行该任务。
- 复用机制
通过Message.obtain()获取消息对象,可以复用系统内部的消息池,降低内存分配开销。
2. Handler
- 作用
Handler主要负责发送和处理消息。它将消息放入MessageQueue中,并在消息被取出后执行相应的逻辑。 - 主要方法
- 发送消息:
sendMessage()、sendMessageDelayed()、post()等方法用于将消息或Runnable提交到消息队列。 - 处理消息:通过重写
handleMessage(Message msg)方法,对不同类型的消息(根据msg.what)进行处理。
- 发送消息:
- 线程关联
每个Handler都与创建它时所在的线程的Looper绑定,这确保了消息在创建Handler的线程中被处理,从而避免线程安全问题。
3. MessageQueue
- 作用
MessageQueue是消息存储和调度的数据结构,负责保存待处理的消息。 - 特点
- 消息按先进先出(FIFO)的顺序(结合延时机制)排列。
- 保证消息的顺序性与调度的及时性,确保Looper能依次处理每个消息。
- 实际上,每个拥有消息循环的线程都会对应一个MessageQueue。
4. Looper
- 作用
Looper为线程提供一个持续运行的消息循环环境,其主要任务是不断从MessageQueue中取出消息,并交由相应的Handler处理。 - 工作流程
- 初始化:在线程启动时调用
Looper.prepare()为当前线程创建Looper和MessageQueue(例如主线程在应用启动时自动初始化)。 - 循环处理:调用
Looper.loop()进入消息循环,不断取出消息并分发到目标Handler。
- 初始化:在线程启动时调用
- 线程绑定
每个线程只能有一个Looper,这样确保了同一线程内消息的有序执行,避免并发冲突。
5. 协同工作流程
- 初始化消息循环
- 当线程(如UI线程或自定义线程)启动时,调用
Looper.prepare()初始化Looper和MessageQueue。 - 之后,通过
Looper.loop()进入无限循环状态,等待并分发消息。
- 当线程(如UI线程或自定义线程)启动时,调用
- 发送消息
- 通过Handler的
sendMessage()或post()方法,将消息或Runnable对象封装成Message后加入到MessageQueue中。 - 可设置延时或定时任务(例如
sendMessageDelayed()),让消息在指定时间后被处理。
- 通过Handler的
- 消息分发与处理
- Looper不断从MessageQueue中取出最前面的消息。
- 根据消息中绑定的目标Handler,将消息交由Handler的
handleMessage(Message msg)方法处理。 - 如果Message中包含Runnable,则直接执行对应的任务。
- 线程安全与解耦
- 通过Handler与Looper的绑定,确保消息在创建Handler的线程中执行,无需额外的线程同步。
- 各组件之间松耦合,生产者只需将任务封装成消息,消费者(Handler)负责执行处理逻辑。
AsyncTask(简单线程切换)
- 异步任务执行
AsyncTask 允许开发者在后台线程中运行耗时任务,并在任务执行过程中或完成后与 UI 线程进行交互,无需手动处理线程间通信。 - 轻量级任务
它适用于执行短时间任务(一般几秒以内),而不适合长期运行的任务,因为 AsyncTask 内部是通过线程池来管理任务的,长时间运行任务可能导致线程池饱和。 - 生命周期方法
AsyncTask 提供了四个关键的回调方法,用以控制任务的不同阶段:- onPreExecute():在任务开始前调用,一般用于在 UI 上展示进度条或进行初始化操作。
- doInBackground(Params… params):在后台线程中执行耗时任务,是整个任务的核心逻辑所在。该方法运行在子线程中,不能直接更新 UI。
- onProgressUpdate(Progress… values):在任务执行期间调用,用于更新任务进度,可以在 UI 线程中更新进度条等视图。
- onPostExecute(Result result):任务执行完毕后调用,此时可以将结果传递给 UI 线程,进行界面更新。
AsyncTask 运行流程
- 任务准备阶段:onPreExecute()
在调用 execute() 方法后,AsyncTask 会先在 UI 线程中执行 onPreExecute() 方法。此阶段常用于进行初始化操作,比如在 UI 上显示加载提示或初始化进度条。 - 后台任务执行阶段:doInBackground()
onPreExecute() 执行完成后,AsyncTask 会启动一个后台线程,在该线程中调用 doInBackground(Params… params) 方法。- 该方法中执行耗时操作,不能直接更新 UI。
- 如果需要在任务执行过程中更新进度,可以调用 publishProgress(),从而触发 onProgressUpdate()。
- 进度更新阶段:onProgressUpdate()
当后台任务中调用 publishProgress() 后,onProgressUpdate(Progress… values) 方法会在 UI 线程中被调用。- 这个方法用于更新界面,比如更新进度条、显示当前进度等。
- 任务完成阶段:onPostExecute()
当 doInBackground() 执行完毕后,返回的结果会传递给 onPostExecute(Result result) 方法,该方法也运行在 UI 线程中。- 常用于将后台任务的结果展示到 UI 上,或者隐藏加载提示等。
- 任务取消:onCancelled()
如果在任务执行过程中调用 cancel() 方法取消任务,则会触发 onCancelled() 方法(或 onCancelled(Result result)),用于处理任务取消后的逻辑。
1 | private inner class DownloadTask : AsyncTask<String, Int, String>() { |
Service的基本用法
第一步:MainActivity 的整体结构
- 继承与初始化
MainActivity继承自AppCompatActivity,是 Android 应用中的主界面。- 使用
lateinit var downloadBinder: MyService.DownloadBinder声明一个延迟初始化的变量,用于在服务绑定成功后接收服务返回的 Binder 实例。这样可以在绑定服务后调用服务内部的方法。
- 作用
- 主要负责展示 UI、处理用户点击事件,并通过按钮控制服务的启动、停止、绑定和解绑。
第二步:ServiceConnection 实现
- 定义 ServiceConnection 对象
- 代码中创建了一个匿名内部类
connection实现了ServiceConnection接口,其主要方法为:onServiceConnected:当与服务连接成功后,该方法被调用。这里将传入的IBinder强制转换为MyService.DownloadBinder类型,然后调用服务中的方法:downloadBinder.startDownload():启动下载操作。downloadBinder.getProgress():获取当前下载进度。
onServiceDisconnected:当服务意外断开时调用(例如服务崩溃),此处暂未实现具体逻辑。
- 代码中创建了一个匿名内部类
- 作用
- 该模块负责与
MyService进行连接,获取服务中的接口,从而使 Activity 能调用服务的方法进行业务操作。
- 该模块负责与
第三步:MainActivity 的 onCreate 方法
- UI 初始化与事件绑定
- 在
onCreate方法中,首先调用setContentView加载布局文件activity_main。 - 通过
findViewById获取多个按钮:- 启动服务按钮 (
startServiceBtn):点击后创建 Intent 指定MyService,调用startService(intent)启动服务。 - 停止服务按钮 (
stopServiceBtn):点击后创建 Intent 指定MyService,调用stopService(intent)停止服务。 - 绑定服务按钮 (
bindServiceBtn):点击后创建 Intent 指定MyService,调用bindService(intent, connection, Context.BIND_AUTO_CREATE)绑定服务,并在绑定成功后自动创建服务。 - 解绑服务按钮 (
unbindServiceBtn):点击后调用unbindService(connection)解除服务绑定。 - 启动 IntentService 按钮 (
startIntentServiceBtn):点击后创建 Intent 指定MyIntentService,调用startService(intent)启动 IntentService。
- 启动服务按钮 (
- 在
- 作用
- 该部分代码实现了与用户交互的界面逻辑,不同按钮对应不同的服务操作,从而使用户可以控制服务的生命周期和行为。
第四步:MyService 的详细解析
- 继承与结构
MyService继承自Service,用于实现后台任务,并且通过 Binder 提供给客户端接口。- 内部定义了
DownloadBinder类,继承自Binder,该内部类包含两个方法:startDownload():用于模拟开始下载操作,同时在日志中记录该操作。getProgress():用于获取下载进度,当前返回值为 0,并在日志中记录调用过程。
- onBind 方法
- 当客户端调用
bindService后,会触发onBind方法,返回mBinder对象,供客户端与服务交互。
- 当客户端调用
- onCreate 方法与前台服务设置
- 在
onCreate中:- 先通过日志记录服务的创建过程。
- 获取系统的通知管理器,并在 Android 8.0(API 26)及以上版本创建通知渠道,确保通知能正确显示。
- 构建一个前台通知(包含标题、内容、图标、点击事件等),并调用
startForeground(1, notification)将服务置于前台运行,从而降低被系统杀死的风险。
- 在
- onStartCommand 方法
- 记录服务启动的日志,允许服务通过 Intent 启动,并返回父类方法的返回值。
- onDestroy 方法
- 当服务销毁时,记录销毁日志,可用于调试和资源清理。
- 作用
MyService模块主要用于模拟一个后台下载服务,通过前台通知确保长时间运行,同时通过 Binder 提供下载控制和进度查询的接口给绑定的客户端。
第五步:MyIntentService 的详细解析
- 继承与构造
MyIntentService继承自IntentService,在构造函数中传入服务名称"MyIntentService",用于标识服务线程的名称。
- onHandleIntent 方法
- 该方法在工作线程中运行,用于处理传递进来的 Intent。这里简单记录了当前线程的名称,表明任务在子线程中执行,不会阻塞主线程。
- onDestroy 方法
- 重写了
onDestroy方法,用于在服务销毁时记录日志,方便调试和资源释放。
- 重写了
- 作用
MyIntentService主要用于处理异步任务。由于它内置了工作线程,因此可以在不阻塞 UI 的前提下执行长时间任务。任务处理完成后,服务会自动停止。
总结
- MainActivity:作为用户界面控制中心,通过按钮触发对服务的启动、停止、绑定、解绑和异步任务的执行。
- MyService:实现一个前台服务,主要用于模拟下载任务,通过 Binder 提供接口供 Activity 调用,并使用前台通知来提升服务的优先级。
- MyIntentService:用于在后台线程中处理异步任务,确保耗时操作不会影响 UI 线程,且任务完成后自动停止服务。
1 | class MainActivity : AppCompatActivity() { |
1 | class MyService : Service() { |
1 | class MyIntentService : IntentService("MyIntentService") { |
Activty与Service进行通信
1. 基本原理
在 Android 开发中,Activity 与 Service 之间的通信主要有两种方式:
- 启动服务(Start Service):
通过调用startService()启动服务,服务在后台独立运行,适用于执行长时间任务。但这种方式下,Activity 与服务之间无法直接交互。 - 绑定服务(Bind Service):
通过调用bindService(),Activity 与 Service 建立绑定连接,此时双方可以通过 Binder 机制直接通信。Activity 可以调用 Service 内的方法,而 Service 也可以向 Activity 返回数据或状态更新。
在我们的示例中,主要采用了绑定服务的方式实现 Activity 与 Service 的通信。
2. Binder 机制
Binder 是 Android 系统提供的一种高效进程间通信(IPC)机制。在同一进程内使用 Binder 也非常简单,只需要通过继承 Binder 类实现具体的业务接口。
2.1 在 Service 端实现 Binder
- 自定义 Binder 类:
在MyService中,我们定义了内部类DownloadBinder,它继承自Binder,并提供了两个方法:startDownload():模拟开始下载任务。getProgress():获取下载进度(当前示例返回 0)。
- 返回 Binder 对象:
当 Activity 调用bindService()时,系统会调用 Service 的onBind()方法,此时返回DownloadBinder对象,让客户端(Activity)可以通过该对象调用相应方法。
2.2 在 Activity 端使用 Binder
- 创建 ServiceConnection:
在MainActivity中,通过匿名内部类实现ServiceConnection接口:- 在
onServiceConnected方法中,将系统返回的IBinder强制转换为MyService.DownloadBinder类型,并保存到downloadBinder变量中。 - 之后,就可以通过
downloadBinder调用 Service 中的方法,如startDownload()和getProgress()。
- 在
- 绑定和解绑服务:
通过调用bindService(intent, connection, Context.BIND_AUTO_CREATE)建立连接;完成任务或退出 Activity 后,调用unbindService(connection)断开绑定,避免内存泄漏。
3. 详细实现步骤
(1):在 Service 中定义 Binder
1 | class MyService : Service() { |
- 作用:
通过自定义 Binder 类,将 Service 中的方法封装起来供 Activity 调用。
(2):在 Activity 中实现 ServiceConnection
1 | private val connection = object : ServiceConnection { |
- 作用:
当绑定成功时,获取 Binder 对象,并调用 Service 中的方法,实现通信。
(3):绑定与解绑服务
-
绑定服务:
1
2
3
4bindServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
} -
解绑服务:
1
2
3unbindServiceBtn.setOnClickListener {
unbindService(connection)
} -
作用:
通过点击不同按钮,实现与 Service 的建立连接和断开连接,保证在需要时可以调用 Service 中的功能,同时避免资源泄漏。
前台Service
前台 Service(Foreground Service)是 Android 提供的一种特殊 Service,它在运行时会在通知栏显示一个持久化通知,以提高应用的存活能力,并避免被系统回收。适用于:
- 长时间运行的任务,如音乐播放、文件下载、GPS 追踪等。
- 需要用户感知的任务,避免因后台优化机制被杀死。
1. 创建前台 Service 的步骤
创建 Service 并继承 Service
1 | class MyService : Service() { |
- 作用:
该 Service 继承Service,并定义了DownloadBinder,用于提供服务端的方法。
在 onCreate() 中启动前台 Service
1 |
|
- 作用:
- 创建通知渠道(Android 8.0 及以上必须创建)。
- 使用
startForeground(1, notification)将服务提升为前台服务。 - 让服务在后台运行时不会被系统杀死。
在 AndroidManifest.xml 声明权限
1 | <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> |
- 作用:
申请运行前台服务的权限,否则 Android 9.0 及以上版本无法正常运行。
2. 启动与停止前台 Service
启动前台 Service
在 MainActivity 中,添加启动前台 Service 的代码:
1 | val startServiceBtn = findViewById<Button>(R.id.startServiceBtn) |
- 作用:
通过startService()启动MyService,并触发onCreate()方法,使其进入前台模式。
停止前台 Service
在 MyService 中,添加 onDestroy() 方法:
1 | override fun onDestroy() { |
在 MainActivity 里:
1 | val stopServiceBtn = findViewById<Button>(R.id.stopServiceBtn) |
- 作用:
通过stopService()停止前台 Service,并销毁通知。
3. 停止前台 Service 但不销毁
有时,我们希望前台 Service 转为后台 Service,而不是完全销毁。可以调用:
1 | stopForeground(true) // true 表示移除通知 |
- 作用:
让 Service 退出前台模式,但仍然保持后台运行。
示例:
1 | fun stopForegroundService() { |
4. 适配 Android 11+(前台服务类型)
在 AndroidManifest.xml 中,Android 11+ 需要指定 Service 的类型:
1 | <service |
mediaPlayback:适用于音乐播放。location:适用于 GPS 追踪。dataSync:适用于数据同步任务。
JobIntentService 及其用法
JobIntentService 是 Android 提供的一个特殊 Service,用于替代 IntentService,主要用于适配 Android 8.0+(API 26+)的后台限制。
从 Android 8.0 开始,普通后台 Service 可能会被系统强制终止,而 JobIntentService 结合了 JobScheduler,可在合适的时间自动执行后台任务,确保任务不会因后台限制而被杀死。
JobIntentService 的特点
✅ 自动创建子线程:不需要手动创建 Thread 或 AsyncTask,不会阻塞主线程。
✅ 按队列依次执行任务:如果多次调用 enqueueWork(),任务会排队顺序执行。
✅ 自动停止:任务执行完毕后,JobIntentService 会自动停止,无需手动 stopSelf()。
✅ 兼容 Android 5.0 及以上:即使在 Android 8.0 之后,也能正常运行。
✅ 适合短时任务:适用于数据同步、文件上传等短时间后台任务。
自定义 JobIntentService
创建一个 MyJobIntentService 继承 JobIntentService,并实现 onHandleWork() 方法:
1 | class MyJobIntentService : JobIntentService() { |
🔹 代码解析:
onHandleWork(intent: Intent):- 这个方法在子线程执行,避免阻塞主线程。
- 通过
intent.getStringExtra("task_data")获取任务参数。 - 使用
Thread.sleep(1000)模拟任务执行,表示任务进度。
enqueueWork(context, work):- 这个方法用于启动
JobIntentService,代替startService(),确保兼容 Android 8.0+。 - 任务会按照队列顺序执行,不会并发。
- 这个方法用于启动
在 AndroidManifest.xml 注册 Service
1 | <service |
🔹 注意:
android:permission="android.permission.BIND_JOB_SERVICE"允许JobScheduler绑定 Service。android:exported="false"让 Service 只能被应用内调用,保证安全性。
启动 JobIntentService
在 MainActivity 中,创建按钮来启动 MyJobIntentService:
1 | val startJobIntentServiceBtn = findViewById<Button>(R.id.startJobIntentServiceBtn) |
🔹 这里不使用 startService(),而是用 enqueueWork(),确保兼容 Android 8.0+。
JobIntentService 处理多个任务
与 IntentService 类似,JobIntentService 不会并行执行任务,而是按顺序排队执行:
1 | MyJobIntentService.enqueueWork(this, Intent(this, MyJobIntentService::class.java).putExtra("task_data", "任务 1")) |
执行顺序:
1 | 任务 1 开始执行 |
- 多个任务不会并发执行,而是按顺序执行。
- 任务执行完毕后,Service 会自动停止。
JobIntentService 与 IntentService 对比
| 特性 | JobIntentService | IntentService |
|---|---|---|
| 运行线程 | 自动创建子线程 | 自动创建子线程 |
| 任务执行方式 | 按队列顺序执行 | 按队列顺序执行 |
| 任务执行完是否自动停止 | ✅ 是 | ✅ 是 |
| 是否兼容 Android 8.0+ | ✅ 是 | ❌ 可能受限制 |
| 适用于长时间任务 | ❌ 不适合 | ❌ 不适合 |
| 适用于短时任务 | ✅ 适合 | ✅ 适合 |
🔹 总结:
JobIntentService适用于 Android 8.0+,替代IntentService。- 如果需要后台执行短时间任务(如上传数据、数据库操作),建议使用
JobIntentService。
适用场景
| 应用场景 | 建议的 Service 方式 |
|---|---|
| 后台持续运行的任务(如音乐播放) | 前台 Service |
| 短时间后台任务(如数据同步) | JobIntentService |
| 需要兼容 Android 8.0+ 的后台任务 | JobIntentService |
| 需要并行执行多个任务 | 普通 Service + 线程池 |
8.使用网络技术
WebView用法(网页浏览)
- 添加
WebView控件 - 调佣
javaScriptEnabled与loadUrl方法 - 申请权限
1 |
|
1 | class MainActivity : AppCompatActivity() { |
1 | <uses-permission android:name="android.permission.INTERNET" /> |
使用HTTP访问网络(OkHttp:传输数据和调用API)
- 导入依赖库
- 创建
OkHttp实例,获取Request对象与Call对象,并调用execute()方法获取实例
1 | implementation("com.squareup.okhttp3:okhttp:4.9.3") |
1 | class MainActivity : AppCompatActivity() { |
解析XML格式数据
1.Pull解析方式
• 创建XmlPullParser对象
需要先创建一个XmlPullParser对象,这个对象负责解析XML数据。可以通过Xml.newPullParser()来创建。
• 设置输入源
可以设置文件、字符串、或InputStream作为输入源,通常通过setInput方法来绑定数据源。
• 开始解析
通过next()方法进行解析,返回的值表示当前解析到的XML元素的类型,例如:START_TAG、END_TAG、TEXT等。
• 处理解析事件
根据next()返回的事件类型进行不同的处理。例如,遇到START_TAG时,可以获取标签的名称和属性;遇到TEXT时,处理标签中的内容。
• 结束解析
解析完成后,需要确保关闭XmlPullParser对象,释放资源。
1 | <book> |
1 | class XmlPullParserExample { |
2.SAX解析方式
- 初始化解析器
创建SAX解析器,并设置一个ContentHandler接口的实现类来处理不同的解析事件。 - 解析XML文档
使用parse()方法启动解析过程,解析器会按照文档顺序处理XML内容。 - 处理事件
在解析过程中,SAX解析器会触发相应的事件(如开始标签、结束标签、文本节点等),应用程序可以通过实现ContentHandler接口的不同方法来响应这些事件。 - 结束解析
解析完成后,解析器会关闭并释放资源。
1 | <book> |
1 | // SaxExample类,用于演示SAX解析的使用 |
解析JSON格式数据
1.使用JSONObject
• 创建 JSONObject 对象
通过 new JSONObject(jsonString) 将 JSON 字符串转换为 JSONObject 对象。
• 获取 book 对象
使用 jsonObject.getJSONObject("book") 获取嵌套的 book 对象。
• 获取字段值
使用 getString()、getDouble() 和 getBoolean() 等方法从 book 对象中提取书籍的标题、作者、价格和可用性。
• 处理 genres 数组
使用 getJSONArray("genres") 获取嵌套的数组,并遍历数组中的元素。
1 | fun main() { |
2.使用GSON
• 在项目中使用 Gson 之前,确保在 build.gradle 中添加 Gson 库的依赖
• 使用 Gson() 创建 Gson 实例。
• 使用 fromJson() 方法将 JSON 字符串转换为 Kotlin 对象。
• 使用 toJson() 方法将 Kotlin 对象转换为 JSON 字符串。
• 处理 JSON 数组,使用 Array<Book>::class.java 解析为列表。
1 | implementation 'com.google.code.gson:gson:2.8.8' |
1 | fun main() { |
网络请求回调的实现方式(避免阻塞主线程)
- 定义回调接口。
- 在发起网络请求的类中,接受该接口并在适当时机调用回调方法。
- 在需要接收回调的地方实现该接口。
1. 定义回调接口:
1 | interface NetworkCallback { |
2. 发起网络请求的类:
1 | class NetworkRequest { |
3. 在 Activity 或 Fragment 中实现回调接口:
1 | class MainActivity : AppCompatActivity(), NetworkCallback { |
最好用的网络库:Retrofit
Retrofit 是一个类型安全的 HTTP 客户端,用于简化与 RESTful API 的通信。它由 Square 公司开发,广泛用于 Android 和 Java 项目中。通过 Retrofit,我们可以轻松地将 HTTP 请求转换为方法调用,处理网络请求和响应时,自动将 JSON 数据转换为 Java 对象,提供简洁而强大的 API。
1. Retrofit 的特点
- 简化 HTTP 请求:通过注解和接口的方式将 HTTP 请求转换为方法调用,避免了手动编写大量网络请求代码。
- 类型安全:通过返回特定的 Java 对象,避免了直接操作 JSON 字符串的复杂性。
- 与 Gson、Moshi 等库集成:支持 Gson 等 JSON 转换器,自动将 JSON 数据转换为 Java 对象。
- 异步与同步请求支持:Retrofit 支持异步请求和同步请求,方便根据需求选择合适的方式。
- 支持文件上传和下载:支持上传和下载文件,非常适合处理文件传输任务。
2. 添加 Retrofit 依赖
首先,在项目中添加 Retrofit 依赖。你可以在 build.gradle 文件中添加如下代码:
1 | // Retrofit 依赖 |
如果你需要支持其他数据转换格式(如 Moshi),可以更改转换器的依赖。例如,使用 Moshi 作为 JSON 转换器:
1 | implementation 'com.squareup.retrofit2:converter-moshi:2.9.0' |
3. 使用 Retrofit 的基本步骤
(1):创建 API 接口
在 Retrofit 中,网络请求通过定义接口方法来实现。每个接口方法对应一个 HTTP 请求。你可以通过注解定义请求的类型(如 GET、POST、PUT 等)。
例如,假设我们要请求一个返回用户信息的 API:
1 | interface ApiService { |
在这个例子中,getUser 方法通过 @GET 注解定义了一个 GET 请求,并且通过 @Path 注解动态传入 userId。
(2):创建数据模型类
假设我们收到的 JSON 响应是:
1 | { |
你需要创建一个 Java 或 Kotlin 数据类来映射 JSON 响应:
1 | data class User( |
(3):创建 Retrofit 实例
Retrofit 需要一个 Retrofit 实例,你可以配置 Retrofit 的基本 URL 和转换器(如 Gson):
1 | val retrofit = Retrofit.Builder() |
baseUrl:指定服务器的基本 URL。注意,URL 必须以/结尾。addConverterFactory:指定数据转换器,告诉 Retrofit 如何将响应转换为相应的对象。
(4):执行网络请求
Retrofit 支持同步和异步请求。如果你希望同步地执行网络请求,可以使用 execute() 方法。如果你需要异步执行请求并处理回调,可以使用 enqueue() 方法。
1. 异步请求:
1 | val call = apiService.getUser("123") // 发起网络请求 |
2. 同步请求:
1 | val call = apiService.getUser("123") |
(5):处理请求结果
在响应成功的情况下,response.body() 将返回解析后的对象(此例中是 User 对象)。如果响应失败,可以通过 response.message() 或 response.code() 来获取错误信息。
4. 其他功能
支持请求体和请求参数
你可以使用 Retrofit 支持的注解来处理请求体和请求参数,例如:
@Body:将对象作为请求体。@Query:将参数作为 URL 查询字符串。@Field:用于POST请求,将字段作为表单数据发送。
1 |
|
上传文件
Retrofit 支持通过 @Multipart 注解上传文件,结合 @Part 注解上传文件和其他表单字段。
1 |
|
下载文件
通过 Retrofit 也可以轻松地下载文件:
1 |
|
9.最佳UI体验,Material Design
Material Design 是由 Google 于 2014 年推出的一种设计语言,旨在为跨平台的应用程序提供一致的用户体验。它不仅是一种视觉设计规范,还结合了交互、动画和功能设计的原则,适用于 Android 应用开发以及其他平台(如 Web 和 iOS)。Material Design 的核心理念是通过模拟现实世界中的物理材质(如纸张和墨水)来创建直观且美观的用户界面。
1. Toolbar
作用:
Toolbar 是应用程序的操作栏,可替换传统的 ActionBar,支持自定义布局、菜单和导航图标。
XML 示例:
1 | <androidx.appcompat.widget.Toolbar |
2. DrawerLayout
作用:
DrawerLayout 用于实现侧滑菜单(导航抽屉)的根布局,它允许在主内容之外滑出一个侧边栏。
XML 示例:
1 | <androidx.drawerlayout.widget.DrawerLayout |
3. NavigationView
作用:
NavigationView 用于在侧滑菜单中显示导航菜单项,可方便地配置菜单资源、头部布局等。
XML 示例(已嵌入在 DrawerLayout 中):
1 | <com.google.android.material.navigation.NavigationView |
1 | implementation("com.google.android.material:material:1.12.0") |
4. FloatingActionButton
作用:
FloatingActionButton(浮动操作按钮)是一种圆形按钮,常用于突出主要的交互操作。
XML 示例:
1 | <com.google.android.material.floatingactionbutton.FloatingActionButton |
5. Snackbar
作用:
Snackbar 用于短暂地在屏幕底部显示信息反馈。虽然 Snackbar 主要在代码中调用显示,但其依赖于包含在布局中的 CoordinatorLayout 来获得自动动画和交互。
代码示例(Kotlin 代码中调用):
1 | Snackbar.make(findViewById(R.id.coordinator_layout), "这是一个Snackbar", Snackbar.LENGTH_LONG) |
说明: Snackbar 本身不需要在 XML 中声明,而是依赖于存在的布局容器(例如 CoordinatorLayout)。
6. CoordinatorLayout
作用:
CoordinatorLayout 是一个高级布局容器,可以协调其内部子控件的交互,如 FloatingActionButton 的自动隐藏、Snackbar 的自动上移等。
XML 示例:
1 | <androidx.coordinatorlayout.widget.CoordinatorLayout |
7. MaterialCardView
作用:
MaterialCardView 是基于 CardView 的扩展,提供更多 Material Design 风格的卡片效果,例如圆角、阴影以及状态变化。
XML 示例:
1 | <com.google.android.material.card.MaterialCardView |
1 | implementation("androidx.recyclerview:recyclerview:1.3.1") |
8. AppBarLayout
作用:
AppBarLayout 是用于实现应用栏(如 Toolbar、Tabs)的容器,它通常配合 CollapsingToolbarLayout 实现折叠效果。
XML 示例:
1 | <com.google.android.material.appbar.AppBarLayout |
9. SwipeRefreshLayout
作用:
SwipeRefreshLayout 用于实现下拉刷新效果,当用户下拉时可以触发刷新操作。
XML 示例:
1 | <androidx.swiperefreshlayout.widget.SwipeRefreshLayout |
1 | implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") |
10. CollapsingToolbarLayout
作用:
CollapsingToolbarLayout 用于实现可折叠的 Toolbar 效果,在滚动时可以收缩标题区域,与 AppBarLayout 协同工作。
XML 示例:
1 | <com.google.android.material.appbar.CollapsingToolbarLayout |
1 |
|
1 |
|
10.高级程序开发组件Jetpack
Jetpack 是 Android 官方推出的一套组件库,旨在简化 Android 应用的开发,提升开发效率并保证代码质量。Jetpack 提供了大量的功能组件,可以帮助开发者轻松实现常见的 Android 开发任务,比如 UI 组件、数据存储、生命周期管理等。
| 分类 | 组件 | 描述 |
|---|---|---|
| 架构组件 (Architecture Components) | LiveData | 用于持有和管理 UI 相关的数据,自动观察数据变化,适用于需要更新 UI 的情况。 |
| ViewModel | 用于管理和持有 UI 相关的数据,能够在配置变化时(如屏幕旋转)保持数据不丢失。 | |
| Room | SQLite 数据库的高级封装,提供 ORM(对象关系映射)功能,简化数据库操作。 | |
| WorkManager | 管理后台任务,支持定期任务和长期任务,确保任务在适当时机执行,并考虑任务的依赖和约束条件。 | |
| UI 组件 (UI Components) | Navigation | 管理页面导航,避免 Fragment 事务和 Intent 操作,使用声明式方式管理页面跳转。 |
| ConstraintLayout | 灵活高效的布局组件,允许通过设置约束关系实现复杂的布局,支持动态调整。 | |
| Paging | 高效加载大量数据,支持分页加载,在滑动列表时异步加载数据,避免卡顿。 | |
| Fragment | 用于构建可复用的 UI 组件,支持动态加载内容,并且能够更好地管理多屏幕、设备和生命周期。 | |
| 行为组件 (Behavior Components) | Permissions | 简化权限请求管理,支持 Android 6.0+ 运行时权限请求规范,确保应用符合最佳实践。 |
| Broadcast | 用于发送和接收广播消息,简化了广播机制的使用,提供统一的接口。 | |
| 基础组件 (Foundation Components) | AppCompat | 提供对旧版本 Android 设备的支持,使得应用能够在不同 Android 版本上保持一致的外观。 |
| Android KTX | 提供 Kotlin 扩展函数,简化 Android 开发中的代码,使其更加简洁和易用。 | |
| Test | 提供了 Android 应用的测试框架,帮助开发者更容易编写和运行单元测试、UI 测试等。 |
**ViewModel **
ViewModel 是 Jetpack 架构组件之一,它用于存储和管理 UI 相关的数据,并能够在配置变化(如屏幕旋转)时持久化这些数据。通过使用 ViewModel,可以确保即使 Activity 或 Fragment 被销毁和重建,UI 数据也不会丢失,避免了因重新加载数据而造成的性能问题。
- 生命周期感知:ViewModel 与 Activity 或 Fragment 的生命周期绑定,但不会随着其被销毁而销毁。它的生命周期是与 UI 控件(如 Activity 和 Fragment)不同的。
- 数据持久化:即使发生配置变化(如屏幕旋转),ViewModel 中的数据依然保持不变。
- 轻松管理 UI 数据:可以集中处理 UI 相关的数据和逻辑,使 UI 代码更加简洁。
1. 添加依赖
首先,确保你的项目中已经加入了 Jetpack 的 ViewModel 依赖。通常情况下,这些依赖已经包含在 Jetpack 中,但需要显式声明。
1 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" |
2. 创建 ViewModel 类
你需要继承 ViewModel 类来创建自定义的 ViewModel。通常,它用于持有和管理 UI 数据。
1 | class MyViewModel : ViewModel() { |
3. 在 Activity 或 Fragment 中使用 ViewModel
在你的 Activity 或 Fragment 中,通过 ViewModelProvider 来获取 ViewModel 实例。
在 Activity 中使用:
1 | class MainActivity : AppCompatActivity() { |
4. 使用 LiveData 观察数据变化
在 ViewModel 中,通常使用 LiveData 来封装数据,Activity 或 Fragment 通过观察 LiveData 来更新 UI。当数据变化时,UI 会自动更新。
1 | // LiveData 观察 |
5. 向 ViewModel 传递参数并保存数据
有时,你可能需要在创建 ViewModel 时向其传递参数。例如,可以通过构造函数传递一个参数并在 ViewModel 中使用。
为了向 ViewModel 传递参数,你可以使用 ViewModelProvider.Factory 来提供自定义的 ViewModel 创建逻辑。
1 | // 定义一个需要参数的 ViewModel |
在 Activity 中传递参数:
1 | class MainActivity : AppCompatActivity() { |
- ViewModel 通过持有 UI 相关的数据并且与 Activity/Fragment 的生命周期分离,帮助开发者管理数据和 UI 更新。
- 使用
LiveData来响应数据变化并更新 UI,是与 ViewModel 配合的常见做法。 - ViewModel 能够在配置变化时保存数据,避免了因 Activity 或 Fragment 被销毁而重新加载数据的性能问题。
**LiveData **
LiveData 是 Jetpack 中的一个数据持有类,它是一个生命周期感知的数据容器。LiveData 允许你将数据与生命周期相关联,只有在相关组件(如 Activity 或 Fragment)处于活跃状态时,数据才会被推送给观察者。这使得 LiveData 非常适用于更新 UI 数据,因为它能够自动处理生命周期变化(如屏幕旋转)时的 UI 更新。
- 生命周期感知:LiveData 会在与其绑定的组件(如 Activity 或 Fragment)处于活跃状态时发送数据更新。它自动管理观察者的生命周期,无需手动移除观察者。
- 数据驱动 UI:UI 会自动根据 LiveData 中数据的变化进行更新。
- 支持异步数据流:LiveData 非常适合处理从后台线程传来的数据,可以确保 UI 只在适当的时机更新。
1. 添加依赖
首先,确保你的项目中已加入 LiveData 所需的依赖。
1 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" |
2. 创建 LiveData
LiveData 通常作为 ViewModel 的一部分来使用,来持有 UI 相关的数据并与 Activity 或 Fragment 交互。
1 | class MyViewModel : ViewModel() { |
- MutableLiveData: 可变的 LiveData 类型,允许数据更新。
- LiveData: 不可变的 LiveData 类型,只能观察数据,不能直接修改数据。
3. 在 Activity 或 Fragment 中观察 LiveData
在 Activity 或 Fragment 中,你可以通过 observe() 方法观察 LiveData。只有当观察者处于活跃状态时,LiveData 才会发送更新。
1 | class MainActivity : AppCompatActivity() { |
在这个例子中,observe() 会监听 counter 的变化,并在数据发生变化时更新 UI。Observer 会在 ViewModel 的 LiveData 数据更新时自动收到通知。
4. LiveData 的生命周期感知
LiveData 是生命周期感知的,这意味着它会在组件的生命周期内自动管理观察者的注册和注销。例如,如果 Activity 被销毁,LiveData 会自动停止发送数据更新,避免内存泄漏。
1 | myViewModel.counter.observe(this, Observer { count -> |
- 当 Activity 进入前台时,LiveData 会开始推送数据。
- 当 Activity 离开前台时,LiveData 会停止推送数据,防止不必要的资源消耗。
5. 从后台线程更新 LiveData
LiveData 也支持从后台线程更新数据,但你需要确保在主线程中更新 LiveData。
1 | class MyViewModel : ViewModel() { |
setValue():只能在主线程中调用,用于更新 LiveData。postValue():可以在后台线程中调用,用于更新 LiveData,LiveData 会在主线程中自动处理更新。
- LiveData 是一个生命周期感知的数据持有类,可以帮助开发者在配置变化时管理 UI 数据。
- MutableLiveData 用于存储和修改数据,LiveData 用于观察和展示数据。
- LiveData 会自动管理观察者的生命周期,无需手动移除观察者。
- 可以通过
postValue()和setValue()方法来更新 LiveData 的数据,其中setValue()只能在主线程中调用,postValue()可以在后台线程中调用。
map 和 switchMap 介绍及用法
在 Jetpack 中,map 和 switchMap 是两个常用的转换操作符,通常与 LiveData 或 Flow 等响应式数据流一起使用。它们用于将一个数据流的输出转换成另一个形式的输出。理解这两个操作符的区别和应用场景对于有效地管理数据流非常重要。
map 操作符
map 是一种转换操作符,用于将一个数据流(如 LiveData)中的值转换为另一个数据类型。它不会改变数据流的生命周期,它只是对数据进行映射,返回一个新的 LiveData 或其他数据流。
特点:
- 适用于需要对现有数据进行转换的场景。
- 保持原有数据流的生命周期,不会触发新的数据流。
使用场景:
- 将 API 返回的数据转换为 UI 需要的格式。
- 将单一数据类型转换为其他类型(如从字符串转换为数字)。
- 储存缓存数据。
1 | class MyViewModel : ViewModel() { |
说明:
map操作符将userName的值(字符串类型)转换为一个问候语(例如 “Hello, John”)。userGreeting是userName数据的转换结果,当userName改变时,userGreeting会随之更新。
switchMap 操作符
switchMap 也是一种转换操作符,它类似于 map,但有一个重要的区别:switchMap 会根据新的数据值切换到一个新的数据流。当源数据改变时,它会取消之前的数据流,重新发起一个新的数据请求。
特点:
- 每次输入数据改变时,
switchMap都会取消之前的工作并发起新的工作。 - 适用于需要根据某个数据触发新的网络请求或数据库查询的场景。
- 处理异步操作时,
switchMap非常有用,它保证了只有最后一个请求的结果会被消费。
使用场景:
- 需要根据输入数据动态发起网络请求或数据库查询,并且只关心最新的结果。
- 用于防止过时的数据影响 UI,例如输入框搜索时,只显示用户最终输入的结果。
1 | class MyViewModel : ViewModel() { |
说明:
searchQuery是一个包含搜索关键字的 LiveData。switchMap将每次更新的searchQuery映射到一个新的 LiveData(这里模拟为搜索结果)。- 当
searchQuery更新时,switchMap会取消之前的查询并触发新的查询。
map vs switchMap
| 特性 | map | switchMap |
|---|---|---|
| 数据流转换 | 对数据进行简单的转换,保持原数据流的生命周期。 | 根据新的数据发起新的数据流,取消先前的数据流。 |
| 数据更新 | 更新数据时,转换结果会随之更新。 | 只会关注最新的输入数据,之前的请求会被取消。 |
| 适用场景 | 用于简单的计算和数据转换。 | 用于根据数据发起新的异步操作(例如网络请求、数据库查询)。 |
- map 是用于将 LiveData 中的数据进行转换的操作符,它不会改变数据流的生命周期,只是对数据进行简单的映射。
- switchMap 是用于处理数据流转换的操作符,特别适用于处理异步请求或网络操作,它会根据新的输入数据切换到新的数据流,并取消先前的操作。
Room
Room 是 Jetpack 提供的一个数据库库,简化了 SQLite 的操作。它提供了一个抽象层,允许开发者更方便、更安全地访问本地数据库。Room 提供了注解(annotation)支持,结合 Kotlin 的数据类和架构组件,能够更高效地管理数据持久化操作。
Room 提供了一个 DAO(数据访问对象) 层来进行数据库操作,DAO 使用 SQL 查询语句来与数据库交互。Room 会在编译时生成实际的实现代码,减少了手动编写 SQL 语句的繁琐。
- 简单易用:相比直接使用 SQLite,Room 提供了更简单、类型安全的 API。
- SQLite 完全支持:它是基于 SQLite 的,可以执行 SQL 查询、插入、更新和删除操作。
- 注解支持:使用注解定义数据库实体、DAO 和数据库版本。
- 与 LiveData 兼容:Room 支持与 LiveData 结合使用,当数据改变时,Room 可以自动通知观察者。
1. 添加依赖
在 build.gradle 文件中添加 Room 的依赖:
1 | plugins { |
2. 定义实体类 (Entity)
Room 使用 实体类 (Entity) 来表示数据库表。实体类中的每个字段都会映射到数据库中的列。
1 |
|
- @Entity 注解标识这个类是一个数据库表。
- @PrimaryKey 注解指定主键,并可以设置
autoGenerate = true让 Room 自动生成主键。
3. 定义 DAO(数据访问对象)
DAO 是与数据库交互的接口。通过 DAO 定义数据库操作(增、查、改、删)。
1 |
|
- @Dao 注解标识这个接口是 DAO。
- @Insert 注解用于插入数据。
- @Query 注解定义 SQL 查询语句。
4. 定义数据库 (Database)
RoomDatabase 是抽象类,用于创建数据库实例并提供对 DAO 的访问。
1 |
|
- @Database 注解用于定义数据库,并指定包含的实体类和数据库版本。
abstract fun userDao()方法提供对 DAO 的访问。
5. 获取数据库实例
可以通过 Room.databaseBuilder() 方法获取数据库实例。在 Activity 或 Application 中进行初始化。
1 | val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app_database") |
6. 在 ViewModel 中使用 Room 和 LiveData
Room 可以与 LiveData 和 ViewModel 结合使用,确保数据在界面更新时保持一致性,并自动处理生命周期。
1 | class UserViewModel(application: Application) : AndroidViewModel(application) { |
在这个例子中,allUsers 是一个 LiveData 类型的数据流,当数据库中的用户数据改变时,UI 会自动更新。
7. 在 Activity 或 Fragment 中使用 ViewModel
在 Activity 或 Fragment 中,使用 ViewModel 来获取数据并更新 UI。
1 | class MainActivity : AppCompatActivity() { |
8. 数据库迁移
当你修改数据库结构时(如添加新的表或列),Room 提供了数据库迁移的机制。
1 |
|
1 |
|
- Room 是一个简化数据库操作的库,提供了更简洁、安全的方式来访问 SQLite 数据库。
- Entity 用于定义数据库表,DAO 用于定义操作数据库的方法,RoomDatabase 用于创建数据库实例。
- 使用 LiveData 和 ViewModel 可以让 Room 的数据与 UI 组件轻松集成,确保数据在生命周期变化时的一致性。
- Room 支持数据库迁移,确保在数据库结构变化时不会丢失数据。
**WorkManager **
WorkManager 是 Android Jetpack 提供的一个库,用于管理后台任务,尤其适用于需要保证任务执行的场景,如定期任务、长时间运行的任务或设备重启后的任务。它能够自动处理任务的执行、失败重试、网络条件以及设备充电状态等复杂的任务执行策略。
WorkManager 适用于那些需要确保任务即使在应用关闭、设备重启后仍然能够执行的场景。它通过任务链的方式,提供了更高效、更灵活的后台任务管理方案。
- 可靠性:WorkManager 保证任务会在适当的时候执行,即使应用关闭、设备重启,或者处于低电量状态。
- 条件控制:可以控制任务的执行条件,如网络连接状态、电池状态等。
- 任务重试机制:如果任务失败,可以设置重试策略。
- 兼容性:WorkManager 可以在 API 级别 14 及以上的设备上运行。
1. 添加依赖
首先,需要在 build.gradle 文件中添加 WorkManager 的依赖:
1 | implementation "androidx.work:work-runtime-ktx:2.7.1" |
2. 创建 Worker 类
Worker 是 WorkManager 中用来执行任务的核心类。你需要创建一个继承自 Worker 或 CoroutineWorker 的类,并重写 doWork() 方法来定义任务的具体操作。
1 | class MyWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { |
- doWork():在这个方法中定义具体的任务逻辑,任务完成后返回一个
Result对象,表示任务是成功还是失败。Result.success():任务成功完成Result.failure():任务失败Result.retry():任务需要重试
3. 创建 WorkRequest
WorkRequest 用于定义任务的执行计划。在 WorkManager 中,可以使用 OneTimeWorkRequest 来执行一次性任务,或使用 PeriodicWorkRequest 来执行定期任务。
OneTimeWorkRequest 示例:
1 | val workRequest = OneTimeWorkRequest.Builder(MyWorker::class.java) |
PeriodicWorkRequest 示例(定期执行任务):
1 | val periodicWorkRequest = PeriodicWorkRequest.Builder(MyWorker::class.java, 1, TimeUnit.HOURS) |
- OneTimeWorkRequest:一次性任务,适合执行单次的后台工作。
- PeriodicWorkRequest:定期任务,适合执行周期性重复的任务,如每隔一段时间执行某个任务。
4. 设置任务约束(Constraints)
你可以为任务设置约束条件,例如要求任务只有在网络连接、设备充电等情况下执行。
1 | val constraints = Constraints.Builder() |
5. 观察任务状态
你可以使用 LiveData 来观察任务的状态,例如任务是否完成,或者是否失败。
1 | WorkManager.getInstance(applicationContext) |
6. 取消任务
你可以通过 WorkManager 来取消正在执行的任务。
1 | WorkManager.getInstance(applicationContext).cancelWorkById(workRequest.id) |
7. 设置任务重试策略
如果任务执行失败,你可以设置任务的重试策略。可以指定最大重试次数和重试间隔。
1 | val workRequest = OneTimeWorkRequest.Builder(MyWorker::class.java) |
- BackoffPolicy.EXPONENTIAL:指数回退,表示任务失败后,重试的间隔会逐渐增加。
- BackoffPolicy.LINEAR:线性回退,表示任务失败后,重试的间隔是固定的。
8. 链式任务
你可以将多个任务按顺序串联执行。WorkManager 提供了 beginWith() 和 then() 方法来执行链式任务。
1 | val firstRequest = OneTimeWorkRequest.Builder(MyWorker::class.java).build() |
- WorkManager 是用于管理后台任务的 Jetpack 库,确保任务能够在设备重启后继续执行,适用于长期任务或需要保证执行的任务。
- Worker 是执行任务的核心类,重写
doWork()方法来定义任务的具体操作。 - 通过 WorkRequest 可以定义任务的执行计划,包括一次性任务和定期任务。
- Constraints 允许设置任务的执行条件,如网络连接、电池状态等。
- WorkManager 支持任务的重试策略、任务链、任务观察等功能,能够轻松管理复杂的后台任务。
10.全局获取Context的技巧
1 | class MyApplication : Application() { |
1 | <application |
1 | Toast.makeText(MyApplication.context, "onCreate", Toast.LENGTH_SHORT).show() |
11.使用Intent传递对象
1. Serializable
什么是 Serializable?
Serializable 是 Java 提供的一个接口,它标志着一个类可以被序列化,也就是可以将对象转换成字节流(byte[]),从而方便地存储或传输。Serializable 接口没有强制要求实现任何方法,Java 在运行时会自动处理对象的序列化和反序列化。
如何使用 Serializable
要使用 Serializable 传递对象,首先需要让对象类实现 Serializable 接口。然后,你可以通过 Intent.putExtra() 方法将对象传递给另一个 Activity,在接收方通过 Intent.getSerializableExtra() 来获取该对象。
1 | // 定义一个可以序列化的对象 |
优点与缺点
优点:
- 实现简单:只需让对象类实现
Serializable接口,无需额外的操作。 - 适用于简单的数据传输。
缺点:
- 性能问题:由于
Serializable依赖于 Java 的反射机制,序列化和反序列化的过程相对较慢,尤其是当对象较复杂时。 - 内存消耗较高:序列化通常会产生较大的字节数组,占用较多内存。
2. Parcelable
什么是 Parcelable?
Parcelable 是 Android 提供的一种对象序列化机制,它与 Serializable 类似,目的是将对象转换为可以通过 Intent 或 Bundle 等传递的字节流,但它的实现更加高效。与 Java 标准的 Serializable 相比,Parcelable 更加高效,因为它不会依赖于反射,使用了特定的内存块和优化的序列化方式。
如何使用 Parcelable
要使用 Parcelable 传递对象,需要让对象类实现 Parcelable 接口,并且重写一些方法(如 writeToParcel() 和 describeContents())。此外,还需要提供一个 CREATOR 静态字段,用于反序列化对象。
1 | // 定义一个实现 Parcelable 的对象 |
优点与缺点
优点:
- 性能更高:
Parcelable被设计为更高效,特别是对内存和性能的优化,使得它在 Android 中传递对象时比Serializable更有优势。 - 可控制性:开发者可以完全控制序列化和反序列化过程,因此可以根据需求进行优化。
缺点:
- 实现复杂:相比于
Serializable,Parcelable的实现较为繁琐。需要手动写writeToParcel()和createFromParcel()方法,且必须提供CREATOR静态字段。 - 容易出错:手动处理的序列化和反序列化代码容易出错,特别是涉及到嵌套对象和数组时。
3. Serializable 与 Parcelable 的比较
| 特性 | Serializable |
Parcelable |
|---|---|---|
| 实现复杂度 | 简单,只需实现接口 | 较复杂,需要手动编写序列化和反序列化代码 |
| 性能 | 较低,基于 Java 反射机制,性能较差 | 更高效,专为 Android 优化 |
| 内存消耗 | 较高,序列化后生成的字节数组较大 | 较低,优化了内存和性能 |
| 兼容性 | 适用于所有 Java 类 | 仅适用于 Android,且需要手动实现 |
| 适用场景 | 适用于轻量级的传输和简单对象 | 适用于复杂对象、性能敏感的场景 |
12.定制自己的日志工具
1 | object LogUtil { |
1 | LogUtil.d("TAG", "debug log") |
13.深色与浅色主题的切换
1 | fun toggleTheme() { |
说些什么吧!