文章目录
- 一. onSaveInstanceState()
- 二. Fragment加入/不加入回退栈的区别
- 三. measureText和getTextBounds区别
- 四. 大厂Android端可视化埋点的实现
- 五. Activity事件传递机制
- 六. ViewGroup视图树
- 四. LiveEvent和LiveData区别
- 五. 旋转动画简单实现
- 六. 软键盘输入模式
- 七. Activity类:onBackPressed()方法
- 八. Sealed Class
- 九. Reducer
- 十. ListAdapater常用方法
- 十一. RecyclerView.Holder
- 十二. Fragment:getActivity和requireActivity方法区别
- 十三. Fragment not attached to Activity 异常分析
- 十四. Activity Fragment生命周期
- 十五. 开发者模式Do not keep activities选项分析
- 十六. Kotlin invoke
- 十七. Kotlin suspendCoroutine用法
- 十八. Kotlin typealias别名
- 十九. Context
- 二十一. Kotlin注解 @JvmOverloads
- 二十二. 自定义View相关
- 二十三. Fragment相关
- 二十四. AIDL相关
一. onSaveInstanceState()
onSaveInstanceState()
方法是在 Android Activity 的生命周期中,特定的时机被调用。具体来说,它是在以下情况下被调用的:
-
当 Activity 即将被暂停(onPause)时:在某些情况下,例如屏幕旋转、切换到其他应用、按下 Home 键等,Activity 可能会被系统暂停。在此之前,系统会调用
onSaveInstanceState()
来保存当前的状态。 -
在 Activity 被销毁之前:如果系统因为资源紧张而需要销毁一个不在前台的 Activity,
onSaveInstanceState()
也会被调用,以便保存当前状态。
生命周期关系
onSaveInstanceState()
的调用通常在以下生命周期方法中间:
- onPause():Activity 正在从前台变为后台时。
onPause()
之前会调用onSaveInstanceState()
。 - onStop():Activity 不再可见时。
onStop()
之前也会调用onSaveInstanceState()
(如果 Activity 即将被停止并且需要保存状态)。
生命周期示例
以下是 Activity 生命周期的一个基本示意图,onSaveInstanceState()
的调用时机在其中得以体现:
onCreate()
|
onStart()
|
onResume()
|
(onPause())
(onSaveInstanceState()) // 在 onPause() 之前调用
|
(onStop())
|
(onDestroy())
总结
onSaveInstanceState()
主要在 onPause() 之前被调用,允许你保存当前的状态,以便在 Activity 重新创建时恢复这些状态。- 它不保证在每次 Activity 被关闭时都会被调用,主要用于临时状态保存。
二. Fragment加入/不加入回退栈的区别
在Android开发中,Fragment的回退栈是用于管理Fragment的历史记录的。当你使用FragmentTransaction来添加或替换Fragment时,可以选择是否将该操作加入回退栈。加入和不加入回退栈的主要区别在于Fragment的导航行为和用户体验。以下是这两种情况的详细说明:
1. 加入回退栈(addToBackStack)
-
行为:当你调用
addToBackStack()
方法后,当前的Fragment事务会被推入到回退栈中。当用户按下返回键时,系统会弹出回退栈中的上一个Fragment,展示该Fragment。 -
适用场景:适合用于需要用户能够返回到上一个Fragment的场景。例如,在一个购物应用中,从商品列表Fragment跳转到商品详情Fragment,用户可能希望能够返回到商品列表。
-
代码示例:
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.fragment_container, new MyFragment()); transaction.addToBackStack(null); // 将此事务加入回退栈 transaction.commit();
-
用户体验:用户可以自然地使用返回键在Fragment之间导航,体验更为流畅。
2. 不加入回退栈(不调用 addToBackStack)
-
行为:如果不调用
addToBackStack()
,则该Fragment事务不会被记录在回退栈中。当用户按下返回键时,Activity将结束或返回到前一个Activity,而不会返回到之前的Fragment。 -
适用场景:适合在需要完全替换当前Fragment或者在特定流程中不需要返回的情况。例如,当用户完成某个操作后跳转到“完成”页面时,可能不希望用户能够返回到之前的页面。
-
代码示例:
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.fragment_container, new MyFragment()); // 不调用 addToBackStack transaction.commit();
-
用户体验:用户按下返回键时,可能会返回到Activity而不是Fragment,这可能会导致用户体验的中断。
总结
- 加入回退栈:允许用户通过返回键返回到之前的Fragment,适合需要多层级界面导航的场景。
- 不加入回退栈:直接替换当前Fragment,不允许用户返回,适合完成或结束流程的场景。
通过合理地使用回退栈,可以改善用户的导航体验,使应用更加友好和易于使用。
三. measureText和getTextBounds区别
在 Android 开发中,measureText()
和 getTextBounds()
都是 Paint
类中的方法,用于测量文本的尺寸,但它们的用途和返回结果有所不同。下面我将详细解释这两个方法的区别。
1. measureText()
- 用途:
measureText()
方法用于获取字符串的宽度,适用于测量单行文本的长度。 - 返回值: 返回一个
float
值,表示文本的宽度,单位为像素。 - 适用场景: 主要用于需要知道文本宽度而不关心文本的高度和边界的情况,例如在绘制文本时确定文本的水平位置。
示例代码
Paint paint = new Paint();
paint.setTextSize(50); // 设置文本大小
String text = "Hello, Android!";
float textWidth = paint.measureText(text);
System.out.println("Text width: " + textWidth);
2. getTextBounds()
- 用途:
getTextBounds()
方法用于获取指定文本的边界框(bounding box),包括文本的宽度和高度。它将结果存储在一个Rect
对象中。 - 返回值: 该方法没有返回值,而是将计算得到的边界框信息填充到传入的
Rect
对象中,包含文本的宽度和高度。 - 适用场景: 适合需要获取文本的完整边界信息的情况,特别是在需要进行复杂布局或文本处理时。
示例代码
Paint paint = new Paint();
paint.setTextSize(50); // 设置文本大小
String text = "Hello, Android!";
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
System.out.println("Text bounds: " + bounds.toString());
System.out.println("Width: " + bounds.width() + ", Height: " + bounds.height());
主要区别总结
特性 | measureText() | getTextBounds() |
---|---|---|
返回值 | 文本的宽度(float) | 无返回值,将结果填充到 Rect 对象 |
适用场景 | 主要用于测量单行文本的宽度 | 获取文本的边界框,包括宽度和高度 |
高度信息 | 不包含 | 包含 |
使用方式 | 直接返回文本宽度 | 需要传入一个 Rect 对象 |
结论
- 如果你只需要知道文本的宽度,可以使用
measureText()
。 - 如果你需要更全面的信息,包括文本的高度,或者需要用于复杂布局,应该使用
getTextBounds()
。
选择合适的方法可以帮助你更有效地处理文本测量和布局。
四. 大厂Android端可视化埋点的实现
添加链接描述
// TODO 需要进一步阅读
可视化埋点:根据可视化界面进行配置然后上报后台,后台向终端下发配置文件,终端点击时获取当前点击的控件根据配置文件进行选择上报。
五. Activity事件传递机制
Android 的 Activity 事件传播机制主要涉及用户输入事件(如触摸事件、键盘事件等)在应用中的处理流程。理解这一机制对于开发响应良好和用户友好的 Android 应用至关重要。以下是关于 Android Activity 事件传播机制的详细解析:
1. 事件的来源
在 Android 中,事件通常源于用户的操作,比如触摸屏幕、按下物理按钮等。这些事件由系统的输入事件管理器(Input Event Manager)捕获,并最终传递到应用的 UI 组件。
2. 事件的传递流程
当用户在屏幕上进行操作时,事件传播的基本流程如下:
2.1. 事件的分发
- Activity 的
dispatchTouchEvent()
方法:- 每当一个触摸事件发生时,系统会首先调用 Activity 的
dispatchTouchEvent(MotionEvent event)
方法。这个方法的责任是将事件分发给相应的视图(View)。
- 每当一个触摸事件发生时,系统会首先调用 Activity 的
2.2. 事件的传递
- View 的
dispatchTouchEvent()
方法:dispatchTouchEvent()
方法会被调用以将事件传递到具体的视图组件(如 Button、TextView 等)。- 在这个方法中,事件会被传递给视图的子视图。如果事件在某个视图中被处理,后续的视图将不会再接收到这个事件。
2.3. 事件的拦截
- View 的
onInterceptTouchEvent()
方法:- 对于 ViewGroup(如 LinearLayout、RelativeLayout 等),在接收到事件之前会先调用
onInterceptTouchEvent(MotionEvent event)
方法。 - 这个方法决定是否拦截事件。如果返回
true
,那么这个事件将直接传递给 ViewGroup 的onTouchEvent()
方法;如果返回false
,则事件将继续传递给子视图。
- 对于 ViewGroup(如 LinearLayout、RelativeLayout 等),在接收到事件之前会先调用
2.4. 事件的处理
- View 的
onTouchEvent()
方法:- 如果事件没有被拦截,最终会传递到具体的 View 的
onTouchEvent(MotionEvent event)
方法中,该方法负责处理具体的触摸事件。
- 如果事件没有被拦截,最终会传递到具体的 View 的
3. 事件的处理顺序
事件的处理顺序通常是从上到下的:
- Activity 的
dispatchTouchEvent()
- ViewGroup 的
onInterceptTouchEvent()
- 子 View 的
dispatchTouchEvent()
- 子 View 的
onTouchEvent()
- 如果子 View 没有处理,事件会返回给 ViewGroup 的
onTouchEvent()
方法(如果没有被拦截的话)。
4. 关键方法总结
dispatchTouchEvent(MotionEvent event)
:用于分发触摸事件。onInterceptTouchEvent(MotionEvent event)
:用于决定是否拦截事件。onTouchEvent(MotionEvent event)
:用于处理触摸事件。
5. 事件的消费
- 事件消费:在
onTouchEvent()
方法中,返回true
表示事件已被消费,返回false
则表示该事件未被消费,允许其继续向上传播。 - 事件的优先级:如果一个 View 处理了触摸事件,后续的视图将不会再接收到该事件,这保证了事件不会被多个视图同时处理。
6. 其他事件类型
除了触摸事件,Android 还支持其他类型的事件,例如键盘事件、滚轮事件等。这些事件的传播机制与触摸事件类似,但具体实现可能会有所不同。
总结
Android 中的 Activity 事件传播机制涉及多个层级,包括 Activity、ViewGroup 和具体的 View。理解这一机制有助于开发者有效管理用户输入,提高应用的响应性和用户体验。在实际开发中,可以根据需要重写相应的方法来控制事件的传播和处理。
六. ViewGroup视图树
在 Android 中,ViewGroup
是用于管理一组视图(View
)的特殊视图,它可以包含多个子视图并负责它们的布局和绘制。理解 ViewGroup
的树状结构对于有效地管理和优化 Android UI 至关重要。以下是关于 ViewGroup
和其视图树关系的详细分析。
1. 视图树的基本概念
在 Android 中,视图树是由 View
和 ViewGroup
形成的结构,其中每个 ViewGroup
可以作为一个节点,包含多个子视图。这个树状结构的根节点通常是一个 Activity 的窗口。
视图树的结构示例
Activity
│
├── ViewGroup (LinearLayout)
│ ├── View (TextView)
│ ├── View (Button)
│ └── ViewGroup (RelativeLayout)
│ ├── View (ImageView)
│ └── View (EditText)
│
└── ViewGroup (FrameLayout)
└── View (Button)
在上面的示例中,Activity
是视图树的根节点,包含两个 ViewGroup
(LinearLayout
和 FrameLayout
),以及它们的子视图。
2. ViewGroup 的特性
- 容器功能:
ViewGroup
作为容器,可以包含多个子视图(View
),并负责它们的布局和绘制。 - 布局管理:
ViewGroup
具备不同的布局策略(如LinearLayout
、RelativeLayout
、ConstraintLayout
等),决定子视图的排列方式。 - 事件处理:
ViewGroup
负责触摸和其他用户输入事件的分发。它可以拦截和处理这些事件,或者将其传递给子视图。
3. 视图的添加和移除
- 添加视图:可以通过
addView(View child)
方法将子视图添加到ViewGroup
中。 - 移除视图:可以通过
removeView(View view)
方法从ViewGroup
中移除子视图。
4. 视图树的遍历
视图树可以通过递归或迭代的方式进行遍历,以访问每个节点的属性或执行某些操作。例如,可以遍历树以更新 UI 或执行动画。
遍历示例
public void traverseViewTree(ViewGroup viewGroup) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
// 处理子视图
if (child instanceof ViewGroup) {
// 递归遍历子 ViewGroup
traverseViewTree((ViewGroup) child);
}
}
}
5. 视图树的管理
- 布局测量与布局:在
ViewGroup
的测量和布局过程中,系统会调用onMeasure()
和onLayout()
方法。这些方法负责计算子视图的大小和位置。 - 绘制顺序:
ViewGroup
会按照子视图的添加顺序进行绘制,通常是从上到下(即在包括在ViewGroup
中的顺序)。
6. 视图树的优化
- 视图重用:在可能的情况下,重用视图可以提高性能,特别是在列表或网格中。
- 使用合适的 ViewGroup:根据需求选择合适的
ViewGroup
,如使用RecyclerView
来处理大量数据,避免性能问题。 - 减少嵌套:尽量减少视图的嵌套层级,以提高布局和绘制的性能。
总结
Android 的 ViewGroup
结构为构建复杂的用户界面提供了灵活的方式,视图树的设计使得视图的管理和事件处理变得高效。理解视图树的关系,有助于开发者更好地设计和优化应用的 UI。通过合理使用不同类型的 ViewGroup
和有效的视图管理策略,可以显著提升应用的性能和用户体验。
四. LiveEvent和LiveData区别
在 Android 中,LiveEventBus
和 LiveData
都是用于观察数据变化的机制,但它们的使用场景和优缺点有所不同。以下是对两者的比较,并解释为什么在某些情况下选择 LiveEventBus
而非单纯的 LiveData
。
1. LiveData 与 LiveEventBus 的区别
-
LiveData:
LiveData
是一个生命周期感知的数据持有者,通常用于在ViewModel
和UI
组件之间传递数据。LiveData
只能通过setValue()
或postValue()
方法来更新数据。LiveData
只能在拥有相同的生命周期的Observer
中观察和接收数据变化。- 它是
ViewModel
的一部分,适合于单向的数据流动。
-
LiveEventBus:
LiveEventBus
是在LiveData
的基础上扩展的,支持更灵活的事件传递。- 它允许跨不同组件(如
Activity
、Fragment
、Service
等)发送和监听事件,而不仅限于ViewModel
中。 LiveEventBus
可以发送一次性事件,即使观察者在事件发布时没有注册也不会丢失事件(这对于一些特殊事件,如网络状态变化,尤其有用)。- 它适用于需要广播消息或事件的场景,比如网络状态、用户登录、消息通知等。
2. 为什么选择 LiveEventBus
-
跨组件通信:
- 如果需要在多个
Activity
或Fragment
之间共享网络状态,LiveEventBus
更加方便。它不需要将状态保留在ViewModel
中,允许不同组件之间直接进行事件的发布和监听。
- 如果需要在多个
-
一次性事件:
LiveEventBus
可以处理一次性事件(例如网络连接变化),而LiveData
需要在数据变化时重新发送值。对于某些事件,只有在网络恢复时需要处理一次,而不是在每次观察时都要处理。
-
简化代码:
- 使用
LiveEventBus
可以减少在ViewModel
和UI
组件之间的耦合,避免了需要在ViewModel
中维护额外的状态的复杂性。
- 使用
3. 示例场景
-
网络连接管理:
- 当应用需要监测网络连接状态并在网络恢复时自动重连,可以使用
LiveEventBus
在整个应用范围内广播网络状态变化,而不需要依赖于每个ViewModel
来管理这个状态。
- 当应用需要监测网络连接状态并在网络恢复时自动重连,可以使用
-
用户事件通知:
- 在用户登录、登出等事件发生时,可以通过
LiveEventBus
广播通知,而不需要在每个涉及的组件中维护相同的LiveData
。
- 在用户登录、登出等事件发生时,可以通过
4. 总结
虽然 LiveData
是一种强大且常用的工具,但在需要跨组件监听和处理事件的情况下,使用 LiveEventBus
可以提供更好的灵活性和简化实现的可能性。具体的选择应基于项目的需求和架构设计,灵活使用这两者。
五. 旋转动画简单实现
toolbar_loading?.startAnimation(getRotateAnimation(0f, 360f))
toolbar_loading?.clearAnimation() //需要清除动画
这行代码用于启动一个旋转动画,通常用于显示加载状态的 UI 元素,如一个加载中的图标或进度指示器。以下是对这行代码的详细解析及上下文。
-
toolbar_loading
:toolbar_loading
是一个可选的视图对象(例如一个ImageView
或ProgressBar
),通常表示 UI 中的一个加载或进度指示元素。- 使用可选链操作符
?.
,表示如果toolbar_loading
不为null
,则调用它的方法;如果为null
,则不会执行后面的代码,避免空指针异常。
-
startAnimation(...)
:startAnimation
是View
类的方法,用于开始对视图应用动画。- 它接收一个
Animation
对象作为参数,在这个例子中,传入的是getRotateAnimation(0f, 360f)
返回的旋转动画。
-
getRotateAnimation(0f, 360f)
:getRotateAnimation
是一个自定义的函数(假设已在代码中定义),用于创建一个旋转动画。0f
和360f
表示动画的起始和结束角度,单位是度(0 度到 360 度的旋转)。这将创建一个完整的旋转动画。
getRotateAnimation
函数的实现示例
假设 getRotateAnimation
函数的定义如下:
fun getRotateAnimation(fromDegrees: Float, toDegrees: Float): Animation {
val rotateAnimation = RotateAnimation(
fromDegrees,
toDegrees,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
)
rotateAnimation.duration = 1000 // 设置动画持续时间为 1000 毫秒(1 秒)
rotateAnimation.repeatCount = Animation.INFINITE // 设置动画无限重复
rotateAnimation.interpolator = LinearInterpolator() // 设置线性插值器
return rotateAnimation
}
整体含义
结合以上分析,整体含义如下:
- 如果
toolbar_loading
视图不为null
,则开始一个从 0 度到 360 度的旋转动画。 - 该动画将持续一定时间(例如 1 秒),并且会无限循环,通常用于表示正在加载的状态。
使用场景
这种旋转动画常用于以下场景:
- 显示正在加载的状态,例如在网络请求或数据加载期间。
- 提示用户等待某些操作完成,如文件上传或下载。
总结
通过这行代码,您可以轻松启动一个简单的旋转动画,以增强用户界面交互体验,提供视觉反馈,指示某项操作正在进行中。确保在适当的时候(例如加载完成后)停止或取消这个动画,以避免造成用户界面的困惑。
六. 软键盘输入模式
在 Android 开发中,window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
这一行代码用于设置当前窗口的软键盘输入模式。具体来说,它控制当软键盘显示时,窗口如何调整其内容的显示方式。以下是对这行代码的详细解析。
逐部分解析
-
window
:window
通常是指当前的活动窗口(Activity
的Window
对象)。在活动中,你可以通过调用getWindow()
方法来获取。
-
setSoftInputMode(...)
:setSoftInputMode()
是Window
类的方法,用于设置软键盘的显示模式。- 该方法接收一个整型参数,通常使用
WindowManager.LayoutParams
中定义的常量来表示不同的输入模式。
-
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
:SOFT_INPUT_ADJUST_PAN
是一个常量,表示当软键盘显示时,窗口将根据软键盘的位置进行“平移”调整。在这种模式下,窗口的内容会向上平移,以确保输入框或焦点元素始终在可见区域内,而不会被软键盘遮挡。- 这种模式适合需要用户输入的场景,确保用户可以看到他正在输入的内容。
整体含义
整行代码的意思是:设置当前窗口的软键盘输入模式为“调整平移模式”。当软键盘弹出时,窗口内容会向上移动,这样用户可以看到输入框而不被软键盘遮挡。
使用场景
这种设置通常用于需要用户输入的界面,例如:
- 表单输入:当用户在输入框中输入信息时,确保输入框不被软键盘遮挡。
- 聊天应用:在聊天界面输入消息时,确保消息输入框始终可见。
- 登录界面:在登录表单中输入用户名和密码时,确保输入框不被软键盘遮挡。
其他输入模式
除了 SOFT_INPUT_ADJUST_PAN
,WindowManager.LayoutParams
还有其他一些常用的软键盘模式,包括:
SOFT_INPUT_ADJUST_RESIZE
:在软键盘弹出时,调整窗口的大小,以便为软键盘留出空间。适合需要动态调整 UI 的情况。SOFT_INPUT_STATE_VISIBLE
:在窗口显示时,强制软键盘可见。SOFT_INPUT_STATE_HIDDEN
:在窗口显示时,强制软键盘隐藏。
示例代码
以下是如何在 Activity
中使用这行代码的示例:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 设置软键盘输入模式
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
}
总结
通过设置软键盘输入模式为 SOFT_INPUT_ADJUST_PAN
,可以改善用户在输入时的体验,确保输入框内容始终可见,从而减少用户的困扰,提升应用的易用性。
七. Activity类:onBackPressed()方法
onBackPressed()
是 Android 中 Activity
类的一个方法。它用于处理用户按下设备的返回按钮时的逻辑。通过重写这个方法,你可以在按下返回按钮时执行自定义的行为,比如关闭当前活动、返回到上一个活动,或执行其他特定的操作。
基本用法
在一个 Activity
中,默认情况下,调用 onBackPressed()
方法会结束当前活动并返回到上一个活动。如果你想在用户按下返回按钮时执行特定的操作,可以重写这个方法。例如:
override fun onBackPressed() {
// 在这里添加你的自定义逻辑,例如弹出确认对话框
if (shouldShowExitConfirmation()) {
showExitConfirmationDialog()
} else {
super.onBackPressed() // 调用父类的方法,执行默认的返回行为
}
}
示例
下面是一个简单的例子,展示如何重写 onBackPressed()
方法:
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
toolbar_left_image_back.setOnClickListener {
onBackPressed() // 调用 onBackPressed() 方法
}
}
override fun onBackPressed() {
// 自定义逻辑,例如显示对话框
val builder = AlertDialog.Builder(this)
builder.setMessage("确定要退出吗?")
.setCancelable(false)
.setPositiveButton("是") { dialog, id -> super.onBackPressed() } // 确定,调用父类的 onBackPressed()
.setNegativeButton("否") { dialog, id -> dialog.dismiss() } // 取消,关闭对话框
val alert = builder.create()
alert.show()
}
}
说明
- 自定义行为:在重写的
onBackPressed()
中,你可以添加任何自定义的逻辑,比如显示对话框请求用户确认是否退出。 - 调用父类方法:如果你希望在执行自定义逻辑后仍然执行默认的返回行为,记得在适当的地方调用
super.onBackPressed()
。 - 返回按钮的灵活性:这种方式使你能够根据用户的操作状态动态决定返回的行为,例如在某些情况下禁止返回,或在某些条件下执行特定操作。
小结
onBackPressed()
是处理 Android 应用程序中返回按钮行为的一个重要方法。通过重写此方法,开发者可以提供更好的用户体验和控制应用的导航逻辑。
八. Sealed Class
在 Kotlin 中,sealed class
(密封类)是一种特殊类型的类,用于限制类的继承。它的主要作用是提供一种更严格的类型安全和更清晰的代码结构,尤其是在处理状态、事件或返回类型时。下面是 sealed class
的一些主要特点和使用场景:
1. 限制继承
sealed class
的一个主要目的就是限制可以继承的子类。所有子类必须在密封类的同一文件中定义,这使得类型的使用更加明确和受控。
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val exception: Exception) : Result()
}
在这个示例中,Result
是一个密封类,只有 Success
和 Error
可以作为子类。其他文件无法继承 Result
类。
2. 增强类型安全
由于所有的子类都是在密封类的定义中明确的,编译器可以在使用时提供更严格的检查。这意味着你可以在 when
表达式中安全地处理所有可能的子类,而不需要使用 else
分支。
fun handleResult(result: Result) {
when(result) {
is Result.Success -> println("Data: ${result.data}")
is Result.Error -> println("Error: ${result.exception.message}")
}
}
在上面的代码中,when
表达式会覆盖所有可能的子类。编译器会确保所有子类都被处理,确保代码的安全性和可维护性。
3. 更清晰的代码结构
使用密封类可以使代码更加清晰,尤其是在处理状态和事件时。密封类可以明确地表示一组相关的类型,使得代码逻辑更加简洁。
sealed class NetworkResponse {
object Loading : NetworkResponse()
data class Success(val data: String) : NetworkResponse()
data class Failure(val error: Throwable) : NetworkResponse()
}
4. 使用场景
- 状态管理:适合用于表示一个操作的不同状态,比如网络请求的状态(加载中、成功、失败)。
- 事件处理:在处理复杂事件时,可以用密封类来定义所有可能的事件类型。
- 返回类型:在函数中返回不同类型的结果时,可以使用密封类来封装这些结果,提供更好的类型安全。
5. 总结
sealed class
是 Kotlin 提供的强大功能,允许你在保持类型安全的同时,清晰地表达一组相关类型。它使得代码更易于理解和维护,特别是在需要处理多个状态或事件的场景中。
九. Reducer
在Android开发中,"Reducer"通常与状态管理和数据流的概念相关,尤其是在使用像MVP(Model-View-Presenter)、MVVM(Model-View-ViewModel)或Redux这样的架构模式时。
Reducer的概念
-
状态管理:Reducer是一个函数,它的作用是接收当前的状态和一个动作(action),并返回一个新的状态。在Redux架构中,Reducer用于描述如何通过特定的动作来更新应用的状态。
-
纯函数:Reducer通常是一个纯函数,这意味着它不会修改输入参数,而是返回一个新对象。这样可以确保状态的不可变性,方便调试和维护。
-
动作(Action):Reducer的输入通常包括一个动作对象,这个对象描述了发生了什么(例如,用户点击按钮、数据加载完成等),以及可能需要更新的状态部分。
在Android中的应用
在Android应用中,尤其是使用Kotlin和Jetpack组件时,Reducer的思想可以通过以下方式实现:
-
ViewModel + LiveData:在MVVM架构中,ViewModel可以视作一种Reducer的实现,处理用户输入并更新LiveData对象。UI组件(如Activity或Fragment)观察这些LiveData,以响应状态变化。
-
MVI(Model-View-Intent):在MVI架构中,Reducer是核心组件之一,它负责根据用户意图(Intent)和当前状态生成新的状态。
示例
以下是一个简化的Reducer示例:
data class State(val count: Int)
sealed class Action {
object Increment : Action()
object Decrement : Action()
}
fun reducer(state: State, action: Action): State {
return when (action) {
is Action.Increment -> state.copy(count = state.count + 1)
is Action.Decrement -> state.copy(count = state.count - 1)
}
}
在这个示例中,reducer
函数接收当前的状态和一个动作,然后返回一个新的状态。这个过程确保了状态管理的一致性和可预测性。
总结
Reducer在Android开发中是处理状态变化的重要工具,尤其是在实现复杂的UI逻辑时,能够帮助管理应用状态并提高代码的可维护性。
十. ListAdapater常用方法
在 Android 的 ListAdapter
中,有几个重要的方法和概念需要了解。这些方法使得 ListAdapter
能够高效地处理列表数据,并在数据变化时自动更新 RecyclerView
。以下是一些重要的方法和它们的用途:
1. submitList(List<T>? list)
- 功能: 提交一个新的数据列表给适配器。这个方法会触发
DiffUtil
的计算,以找出要更新的项目。 - 参数:
list
是一个可空的列表,表示要显示的新数据。 - 使用示例:
userAdapter.submitList(newUserList)
2. onCreateViewHolder(ViewGroup parent, int viewType)
- 功能: 创建一个新的
ViewHolder
实例,并将其绑定到适配器中的视图。 - 返回值: 返回一个
ViewHolder
对象。 - 实现: 在这个方法中,你通常会使用
LayoutInflater
来加载列表项的布局。 - 示例:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false) return UserViewHolder(view) }
3. onBindViewHolder(ViewHolder holder, int position)
- 功能: 将数据绑定到指定位置的
ViewHolder
。 - 实现: 在这个方法中,你会获取数据列表中的相应项,并将其显示在
ViewHolder
的视图中。 - 示例:
override fun onBindViewHolder(holder: UserViewHolder, position: Int) { val user = getItem(position) holder.bind(user) }
4. getItem(int position)
- 功能: 获取指定位置的项。这个方法是
ListAdapter
提供的,返回的数据类型与适配器的泛型类型一致。 - 使用示例:
val user = getItem(position)
5. getItemCount()
- 功能: 返回列表中的项数,通常不需要重写,因为
ListAdapter
会自动处理。 - 返回值: 返回当前列表中项的数量。
6. DiffUtil.ItemCallback<T>
- 功能: 用于确定新旧列表之间的差异。你需要实现两个方法:
areItemsTheSame(oldItem: T, newItem: T)
: 检查两个项是否是同一项(通常使用唯一标识符)。areContentsTheSame(oldItem: T, newItem: T)
: 检查两个项的内容是否相同。
- 示例:
class UserDiffCallback : DiffUtil.ItemCallback<User>() { override fun areItemsTheSame(oldItem: User, newItem: User): Boolean { return oldItem.id == newItem.id // 这里假设 User 有一个唯一的 id } override fun areContentsTheSame(oldItem: User, newItem: User): Boolean { return oldItem == newItem } }
7. getItemId(int position)
- 功能: 可以用于获取某个项的 ID。默认情况下,返回
NO_ID
,可以通过重写该方法来提供自定义 ID。 - 使用场景: 适用于需要为列表项启用持久化状态的情况。
8. onViewRecycled(ViewHolder holder)
- 功能: 在视图被回收时调用,可以在此进行任何清理工作。
- 实现: 如果需要释放资源或重置状态,可以在这里处理。
- 示例:
override fun onViewRecycled(holder: UserViewHolder) { super.onViewRecycled(holder) // 清理操作 }
小结
这些方法和概念构成了 ListAdapter
的主要功能,允许开发者高效地管理列表数据。使用 ListAdapter
可以简化列表的更新逻辑,并提高性能,特别是当列表数据频繁变化时。理解这些方法的作用和用法将有助于在 Android 应用中实现复杂的列表界面。
十一. RecyclerView.Holder
在 Android 的 RecyclerView
中,ViewHolder
是一个重要的概念,负责持有视图并缓存其引用,以提高性能。ViewHolder
通常与 RecyclerView.Adapter
一起使用,以便在列表中显示数据项。以下是 ViewHolder
常用的方法和相关概念的详细介绍。
ViewHolder 的基本结构
首先,一个典型的 ViewHolder
类通常会如下定义:
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
// 在这里定义视图组件
val textView: TextView = itemView.findViewById(R.id.textView)
fun bind(data: MyData) {
textView.text = data.name
// 绑定其他数据
}
}
常用的 ViewHolder 相关方法和属性
-
构造函数:
ViewHolder(View itemView)
: 构造函数用于接受一个View
对象(通常是列表项的布局),并通过itemView
保存引用。
-
itemView
属性:itemView
: 这个属性是ViewHolder
的一个成员,代表当前的视图,可以用来访问和操作布局中的视图组件。
-
bind()
方法:- 自定义的
bind()
方法通常用于将数据项绑定到视图中。你可以在这个方法内设置视图的内容,例如文本、图像、状态等。
fun bind(data: MyData) { textView.text = data.name // 绑定其他数据 }
- 自定义的
-
getAdapterPosition()
方法:getAdapterPosition()
: 返回当前ViewHolder
在适配器中的位置。这在处理点击事件时非常有用。
val position = adapterPosition
-
setIsRecyclable(boolean isRecyclable)
方法:setIsRecyclable(boolean isRecyclable)
: 设置该ViewHolder
是否可以被回收。通常情况下,您不需要手动调用此方法。
-
itemView.findViewById()
方法:itemView.findViewById()
: 用于查找布局中的视图组件。可以在ViewHolder
的构造函数或相关方法中调用。
val imageView: ImageView = itemView.findViewById(R.id.imageView)
-
点击事件处理:
- ViewHolder 通常会处理点击事件,您可以在
ViewHolder
中为视图设置点击监听器。
init { itemView.setOnClickListener { // 处理点击事件 } }
- ViewHolder 通常会处理点击事件,您可以在
示例
以下是一个完整的示例,展示了如何创建一个 ViewHolder
并在 RecyclerView
中使用它。
// 数据模型
data class CommentItem(val author: String, val text: String)
// ViewHolder 类
class CommentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val authorTextView: TextView = itemView.findViewById(R.id.authorTextView)
private val commentTextView: TextView = itemView.findViewById(R.id.commentTextView)
fun bind(comment: CommentItem) {
authorTextView.text = comment.author
commentTextView.text = comment.text
}
}
// Adapter 类
class CommentAdapter(private val comments: List<CommentItem>) : RecyclerView.Adapter<CommentViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_comment, parent, false)
return CommentViewHolder(view)
}
override fun onBindViewHolder(holder: CommentViewHolder, position: Int) {
val comment = comments[position]
holder.bind(comment)
}
override fun getItemCount(): Int = comments.size
}
总结
- ViewHolder 是
RecyclerView
中用于持有视图的类,能够有效地提高列表的性能。 - 通过持有视图的引用,
ViewHolder
减少了多次查找视图的开销。 ViewHolder
主要包含构造函数、bind()
方法、视图查找、点击事件处理等常用方法和属性。
十二. Fragment:getActivity和requireActivity方法区别
requireActivity()
和 getActivity()
是 Android Fragment 中用于获取与 Fragment 关联的 Activity 的两个方法,但它们之间有一些关键的区别。
1. 返回值
-
getActivity()
:- 返回一个
Activity?
类型的对象,这意味着它可能返回null
。如果 Fragment 未附加到任何 Activity 上(例如,在onDetach()
之后),调用getActivity()
时会返回null
。
- 返回一个
-
requireActivity()
:- 返回一个
Activity
类型的对象,永远不会返回null
。如果调用时 Fragment 未附加到 Activity,将抛出IllegalStateException
。
- 返回一个
2. 使用场景
-
getActivity()
:- 使用
getActivity()
时,开发者需要处理可能的null
返回值。这在某些情况下可能需要额外的空值检查,以确保不会导致NullPointerException
。
val activity = getActivity() if (activity != null) { // Safe to use activity } else { // Handle the situation when activity is null }
- 使用
-
requireActivity()
:- 使用
requireActivity()
,开发者无需担心null
值,因为如果 Fragment 尚未附加到 Activity,直接调用将导致崩溃。这种方法更适合在你确认 Fragment 已附加时使用。
val activity = requireActivity() // If Fragment is not attached, it will throw an exception
- 使用
3. 适用场景
-
getActivity()
:- 适合在不确定 Fragment 是否附加时使用,或者你需要返回
null
时的情况。
- 适合在不确定 Fragment 是否附加时使用,或者你需要返回
-
requireActivity()
:- 更适合于确保 Fragment 并且已附加的情况下使用,从而减少空指针异常的风险。
例子
以下是一个简单的使用示例,展示了两者的用法:
class MyFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_my, container, false)
}
fun someFunction() {
// Using getActivity()
val activity1 = getActivity()
if (activity1 != null) {
// Safe to use activity1
}
// Using requireActivity()
val activity2 = requireActivity() // This will throw an exception if Fragment is not attached
// Use activity2 directly
}
}
总结
- 使用
getActivity()
时需要处理可能的null
返回值,而requireActivity()
则提供了一种更安全的方式来获取 Activity,但如果 Fragment 未附加,它将抛出异常。根据你的使用场景选择合适的方法。
十三. Fragment not attached to Activity 异常分析
1. 异常发生情况及兜底解决方案
在 onAttach() 之前,或者 onDetach() 之后,调用任何和 Context 相关的方法,都会抛出 " not attached to Activity " 异常。
发生的原因往往是因为异步任务导致的,比如一个网络请求回来以后,再调用了 startActivity() 进行页面跳转,或者调用 getResources() 获取资源文件等
2. Fragment的状态
static final int INITIALIZING = -1; // Not yet attached.
static final int ATTACHED = 0; // Attached to the host.
static final int CREATED = 1; // Created.
static final int VIEW_CREATED = 2; // View Created.
static final int AWAITING_EXIT_EFFECTS = 3; // Downward state, awaiting exit effects
static final int ACTIVITY_CREATED = 4; // Fully created, not started.
static final int STARTED = 5; // Created and started, not resumed.
static final int AWAITING_ENTER_EFFECTS = 6; // Upward state, awaiting enter effects
static final int RESUMED = 7; // Created started and resumed.
int mState = INITIALIZING;
3. mHost的赋值
源码里只有一个地方会给 mHost 对象赋值,在 FragmetnManager#moveToState() 方法中
class FragmentManager {
FragmentHostCallback mHost; // 内部持有 Context 对象,其本质是宿主 Activity
void moveToState(f,newState){
switch(f.mState){
case Fragment.INITIALIZING: // // 必走逻辑,mState的默认值就是INITIALIZING
f.mHost = mHost; // 赋值 Fragment 的 mHost 对象
f.onAttach(mHost.getContext());
}
f.mState = newState;
}
}
第一次调用 moveToState() 方法时,不管接下来 Fragment 要转变成什么状态(根据 newState 的值来判断)
首先,它都得从 INITIALIZING 状态变过去!那么,case = Fragment.INITIALIZING 这个分支必然会被执行!!这时候,mHost 也必然会被赋值!!!再然后,才会有 onAttach() / onCreate() / onStart() 等等这些生命周期的回调!
4. mHost的置空
class FragmentManager {
void moveToState(f,newState){
if (f.mState < newState) {
switch(f.mState){
case Fragment.INITIALIZING:
f.mHost = mHost; // mHost 对象赋值
}
} else if (f.mState > newState) {
switch (f.mState) {
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
f.performDetach(); // 调用 Fragment 的 onDetach()
if (!f.mRetaining) {
makeInactive(f); // 重点1号,这里会清空 mHost
} else {
f.mHost = null; // 重点2号,这里也会清空 mHost 对象
}
}
}
}
f.mState = newState;
}
void makeInactive(f) {
f.initState(); // 此调用会清空 Fragment 全部状态,包括 mHost
}
}
核心结论:执行完 performDetach() 方法后,无论如何,mHost 也都活不了了
5. performDetach执行时机
- (1) Activity 销毁重建
Activity#onDestroy() -> Fragment#onDestroyView() - > Fragment#onDestroy() - >Fragment#onDetach() - (2) 调用 FragmentTransaction#remove() 方法移除 Fragment)
Fragment#onPause() -> onStop() -> onDestroyView() - > onDestroy() - >onDetach() - (3) 调用 FragmentTransaction#replace() 方法显示新的 Fragment
6. 小结
如果场景是:一个 Fragment 在点击按钮跳转一个新的 Activity 的时候,报崩溃异常:Fragment not attached to Activity
结论:必然存在一个异步任务持有 Fragment 引用,并且内部调用了 startActivity() 方法。
十四. Activity Fragment生命周期
Activity生命周期
Fragment生命周期
Activity和Fragment生命周期的具体变化详解
手机旋转的时候 activity会走什么生命周期?
十五. 开发者模式Do not keep activities选项分析
参考资料
当这个开关被打开,所有被stop的activity都会被立即销毁.
打开Do not keep activities开关, 可以很方便地模拟出内存不足时,系统kill activity的极端情况.
打开此开关可以用来检验应用是否在activity停止时保存了需要的数据, 即是否可以在activity回到前台时复原数据, 甚至可以检查出一些没有做好保护的异常.
十六. Kotlin invoke
在 Kotlin 中,使用 invoke
调用一个函数接口或 Lambda 表达式是非常常见的。callback.invoke(editText.text.toString())
这一行代码可以理解为你在调用一个函数,而这里的 callback
实际上是一个函数类型的变量。
1. 理解 invoke
-
函数类型:
在 Kotlin 中,可以将函数作为一等公民,这意味着你可以将函数赋值给变量,作为参数传递,甚至作为返回值返回。函数类型的变量可以用一种特殊的方式调用,即使用invoke
。 -
Lambda 表达式:
当你定义一个函数类型的变量时,例如callback: (String) -> Unit
,它可以是一个接收String
类型参数并返回Unit
(即没有返回值)的 Lambda 表达式。在这种情况下,你可以使用invoke
方法来调用这个函数。 -
简化调用:
在 Kotlin 中,如果你定义的变量是一个函数类型(或 Lambda 表达式),你可以直接使用callback(param)
的方式来调用它,而不需要显式地使用callback.invoke(param)
。这两种调用方式是等效的。- 例如:
val callback: (String) -> Unit = { message -> println(message) } callback("Hello") // 直接调用 callback.invoke("Hello") // 使用 invoke 调用
- 例如:
2. 总结
虽然你可以使用 invoke
方法来调用一个函数类型的变量,但在大多数情况下,你会发现直接使用 callback(param)
更加简洁和易读。使用 invoke
的方式主要是在某些情况下需要显式地强调调用函数的意图,或者在特定的上下文中使用它。
十七. Kotlin suspendCoroutine用法
suspendCoroutine
是 Kotlin 协程的一部分,主要用于将一个普通的回调式 API 转换为挂起函数。它的返回值是一个 T
类型的结果,其中 T
是你在 suspendCoroutine
中定义的类型。更具体地说,suspendCoroutine
的作用和返回值可以从以下几个方面进行理解:
1. 定义和返回值
suspendCoroutine
的函数签名如下:
suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T
-
参数:
block
是一个接收Continuation<T>
的 Lambda 表达式。在这个 Lambda 中,你可以通过continuation
对象与协程的执行进行交互。 -
返回值:
suspendCoroutine
会在协程被恢复时返回T
类型的结果。也就是说,当你在block
中调用continuation.resume(value)
时,value
就会被作为suspendCoroutine
的返回值。
2. 使用示例
下面是一个简单的示例,说明了 suspendCoroutine
如何工作:
import kotlinx.coroutines.*
import kotlin.coroutines.*
fun main() = runBlocking {
val result = suspendCoroutine<String> { continuation ->
// 模拟异步操作
Thread {
Thread.sleep(1000) // 模拟延迟
continuation.resume("Hello, World!") // 恢复协程,并返回结果
}.start()
}
println(result) // 输出: Hello, World!
}
在这个例子中:
- 我们定义了一个挂起函数,使用
suspendCoroutine
创建一个挂起点。 - 在
block
中,我们启动了一个新的线程,模拟了一个耗时的异步操作。 - 当线程完成后,调用
continuation.resume("Hello, World!")
,这将恢复挂起的协程,并返回字符串"Hello, World!"
。 - 最后,打印这个结果,输出为
"Hello, World!"
。
3. 重要注意事项
- 挂起: 当调用
suspendCoroutine
时,当前协程会被挂起,直到在block
中调用continuation.resume(value)
或continuation.resumeWithException(e)
。 - 协程恢复: 一旦协程被恢复,返回值就会作为
suspendCoroutine
的返回值传递给调用者。
总结
suspendCoroutine
的返回值是在协程恢复时你传递给 continuation.resume
的值。它使得在协程中可以方便地与传统的回调式 API 进行交互,能够以一种更加直观的方式处理异步操作。
十八. Kotlin typealias别名
比较详细的教程
typealias
在 Kotlin 中提供了一种为现有类型创建别名的方式,这可以在多个场景中发挥重要作用。下面列出了一些常见的使用场景:
1. 简化复杂类型
当你需要使用复杂的泛型类型或嵌套类型时,typealias
可以帮助你简化代码。
typealias Callback<T> = (T) -> Unit
typealias StringList = List<String>
fun registerCallback(callback: Callback<Int>) {
// 处理回调
}
在这个例子中,Callback<T>
和 StringList
使得代码更易读,尤其是在需要多次使用这些复杂类型的情况下。
2. 提高可读性
使用描述性的别名可以帮助其他开发者(或未来的自己)更快地理解代码的意图。
typealias UserId = String
typealias Email = String
fun sendEmail(userId: UserId, email: Email) {
// 发送电子邮件
}
在这个例子中,UserId
和 Email
明确了参数的目的,而不仅仅是使用 String
类型。
3. 处理函数类型
为函数类型定义别名可以简化方法签名,特别是在高阶函数或回调函数中。
typealias Transformation<T> = (T) -> T
fun applyTransformation(value: Int, transform: Transformation<Int>): Int {
return transform(value)
}
在此示例中,为函数类型定义的别名 Transformation<T>
使得 applyTransformation
函数的签名更加简洁明了。
4. 代码重用
通过定义别名,你可以重用类型定义,而不需要重复书写复杂的类型。
typealias User = Pair<String, Int> // 用户名和年龄
fun printUser(user: User) {
println("Name: ${user.first}, Age: ${user.second}")
}
在这个例子中,User
被定义为一个 Pair
类型,使得代码更简洁,并且可读性更强。
5. 集成第三方库
当使用第三方库时,库中的类型可能较复杂或冗长。通过定义 typealias
,你可以使自己的代码看起来更整洁。
typealias JsonObject = Map<String, Any>
typealias HttpResponse = Pair<Int, String>
fun handleResponse(response: HttpResponse) {
// 处理 HTTP 响应
}
在这个例子中,JsonObject
和 HttpResponse
使得处理来自第三方库的数据更为便捷。
6. 作为文档
typealias
可以充当文档,帮助其他开发者理解某个类型的具体用途。
typealias OrderId = String // 订单 ID
typealias ProductId = String // 产品 ID
fun processOrder(orderId: OrderId) {
// 处理订单
}
通过使用类型别名,代码中的注释可以更简洁,因为类型本身就可以传达很多信息。
小结
typealias
在 Kotlin 编程中是一个非常有用的工具,能够在多个场景中提升代码的可读性和可维护性。通过为复杂类型、函数类型和其他类型定义别名,你可以使代码更加简洁和易于理解。在实际开发中,合理使用 typealias
将大大提高代码的质量和可读性。
十九. Context
- Context从字面上理解就是上下文的意思,在实际应用中它也确实是起到了管理上下文环境中各个参数和变量的总用,方便我们可以简单的访问到各种资源。
- 虽然Activity和Application都是Context的子类,但是他们维护的生命周期不一样。前者维护一个Acitivity的生命周期,所以其对应的Context也只能访问该activity内的各种资源。后者则是维护一个Application的证明周期。
二十一. Kotlin注解 @JvmOverloads
@JvmOverloads 注解在 Kotlin 中用于为带有默认参数的构造函数(或函数)生成重载方法,以便在 Java 代码中调用时能够方便地使用这些默认参数。由于 Java 不支持默认参数,因此 @JvmOverloads 可以帮助 Kotlin 开发者在与 Java 代码互操作时提供更好的兼容性。
二十二. 自定义View相关
1. MotionEvent.X 和 rawX区别
getX():返回相对于事件发生的视图的 X 坐标,适合在视图内部进行处理。
getRawX():返回相对于整个屏幕的 X 坐标,适合需要全局位置的情况。
2. OnTouchEvent和GestureDetector
GestureDetector 是 Android 提供的一个工具类,用于处理手势识别。它可以帮助你检测用户在屏幕上的各种手势,比如点击、滑动、长按等。通过 GestureDetector,你可以更轻松地处理触摸事件,而不需要手动计算位置变化和手势类型。
- onDown(MotionEvent e): 这个方法在每次触摸事件开始时被调用。返回 true 表示你希望后续的事件继续接收处理。
- onShowPress(MotionEvent e): 这个方法在用户按下屏幕但未移动时被调用。
- onSingleTapUp(MotionEvent e): 这个方法在用户快速点击后抬起手指时调用。通常用来处理单击事件。
- onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY): 这个方法用于处理滑动事件。e1 是按下时的事件,e2 是当前移动的事件,distanceX 和 distanceY是在 X 和 Y 方向上的滑动距离。
- onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY): 这个方法用于处理快速滑动手势(抛掷手势),velocityX 和 velocityY 表示滑动的速度。
- onLongPress(MotionEvent e): 这个方法在用户长按屏幕时调用。
override fun onTouchEvent(event: MotionEvent): Boolean {
// 将触摸事件传递给 GestureDetector 进行处理
return gestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
}
3. Android View 四个构造函数详解
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
第三个参数和第四个参数的关系:
这个参数说,只有在第三个参数defStyleAttr为0,或者主题中没有找到这个defStyleAttr属性的赋值时,才可以启用。而且这个参数不再是Attr了,而是真正的style。其实这也是一种低级别的“默认主题”,即在主题未声明属性值时,我们可以主动的给一个style,使用这个构造函数定义出的View,其主题就是这个定义的defStyleRes(是一种写死的style,因此优先级被调低)
参数解释:
- Context:上线文,这个不用多说
- AttributeSet attrs:从xml中定义的参数
- intdefStyleAttr:主题中优先级最高的属性
- intdefStyleRes: 优先级次之的内置于View的style
在android中的属性可以在多个地方进行赋值,涉及到的优先级排序为:
Xml直接定义 > xml中style引用 > defStyleAttr>defStyleRes > theme直接定义
二十三. Fragment相关
1. supportFragmentManager和childFragmentManager
特性 | supportFragmentManager | childFragmentManager |
---|---|---|
管理范围 | 管理整个 Activity 的所有 Fragment | 仅管理当前 Fragment 的子 Fragment |
使用场景 | 顶层 Fragment 的添加、替换和管理 | 在子 Fragment 中管理内部 Fragment |
生命周期 | 受 Activity 生命周期影响 | 受父 Fragment 的生命周期影响 |
二十四. AIDL相关
1. 具体应用例子
当然可以!以下是一个使用 AIDL 进行进程间通信的具体应用示例:
应用场景:音乐播放应用
假设我们正在开发一个音乐播放应用,其中包含一个后台服务负责播放音乐,同时我们有一个前台活动,用户可以通过该活动控制音乐播放(例如播放、暂停、跳过等)。
步骤:
-
创建 AIDL 文件:首先,我们需要定义一个 AIDL 接口,用于描述服务提供的功能。我们可以创建一个名为
IMusicService.aidl
的文件,内容如下:// IMusicService.aidl package com.example.musicservice; interface IMusicService { void play(); void pause(); void skip(); String getCurrentTrack(); }
-
实现服务:接下来,我们实现这个服务。在服务中,我们需要实现 AIDL 接口的方法:
// MusicService.java package com.example.musicservice; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; public class MusicService extends Service { private final IMusicService.Stub binder = new IMusicService.Stub() { @Override public void play() { // 实现播放音乐的逻辑 } @Override public void pause() { // 实现暂停音乐的逻辑 } @Override public void skip() { // 实现跳过音乐的逻辑 } @Override public String getCurrentTrack() { // 返回当前播放的曲目 return "Song Title"; } }; @Override public IBinder onBind(Intent intent) { return binder; } }
-
在 AndroidManifest.xml 中注册服务:
<service android:name=".MusicService" />
-
在活动中绑定服务:在活动中,我们可以绑定这个服务并通过 AIDL 接口与其交互:
// MainActivity.java package com.example.musicapp; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import androidx.appcompat.app.AppCompatActivity; import com.example.musicservice.IMusicService; public class MainActivity extends AppCompatActivity { private IMusicService musicService; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { musicService = IMusicService.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { musicService = null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 绑定服务 Intent intent = new Intent(this, MusicService.class); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); } private void playMusic() { if (musicService != null) { try { musicService.play(); } catch (Exception e) { Log.e("MainActivity", "Error playing music", e); } } } @Override protected void onDestroy() { super.onDestroy(); unbindService(serviceConnection); } }
总结
在这个例子中,我们通过 AIDL 实现了一个音乐播放服务和一个前台活动之间的通信。活动可以调用服务的方法来控制音乐的播放,并获取当前播放曲目的信息。这个示例展示了 AIDL 在复杂应用中的实际应用场景,特别是在需要进行进程间通信时的便利性。