Android客户端基础组件 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的第2天
写在前面——什么是程序
认知方法:What, Why, How
开发者视角
- 什么是程序?代码+资源
- 为什么写程序?为了改变世界
- 怎么写程序?知识+经验
用户视角
- What? 界面、互动、记忆
- Why? 工具、视频、游戏
- How? 应用商店、搜索引擎、官网下载、三方引流
基础组件
基础组件是日常开发中常用的组件。包括:
- Activity
- Fragment
- Service
- BroadcastReceiver
- ContentProvider
界面组件: Activity & Fragment
界面组件是界面容器,承载界面。
为什么设计Activity?
- 实现前台交互
- 作为程序入口
- 作为布局容器
如何使用Activity?
- 在程序清单AndroidManifest.xml中声明
- 用Java或Kotlin写Activity文件
- 在res/layout中写布局文件
- 在Java
setContentView(R.layout.布局文件)
这个流程总结起来就是:注册 -> 布局 -> 绑定
GridView, Adaptor, ViewPager
Activity 生命周期
- onCreate(): 创建时回调,一般在此处创建视图和绑定数据
- onStart(): 启动完成,即将进入前台
- onResume(): 与用户开始交互,为英语Activity栈顶
- onPause(): Activity失焦或已暂停Activity界面部分可见,下一个生命周期是onResume()或onStop()。安卓5.0之前一般在这里保存数据。
- onRestart(): Activity不再可见,下一个回调是onRestatr()或onDestory()
- onDestory(): 销毁Activity,释放该Activity的所有资源。不一定能走到,肯可能直接被系统杀掉。
- onSaveInstanceState(): 在非正常关闭时回调(比如系统回收),用于保存数据,,不支持持久化数据
- onRestoreInstanceState()/onCreate(): 用于恢复数据
部分遮挡: Pause -> Resume。
页面全遮挡: Stop -> Restart -> Start -> Resume
配置改变,此时可能需要重建:
- 销毁: Resumed -> onSaveInstanceState() -> onPause() -> onStop() -> onDestory
- 重建: onCreate() -> onStart -> onRestoreInstanceState() -> onResume()
不重建的一些情况:
- Activity: AndroidManifest配置Activity节点的configChange属性
- local: 语言改变
- fontScale: 字体大小改变
- orientation: 旋转屏幕
- keybordHidden: 键盘显示隐藏
配置改变: onConfigurationChanged()
Activity生命周期{
启动退出场景{
启动: onCreate() -> onStart() -> onResume(),
退出: onPause() -> onStop() -> onDestory()
},
部分遮挡{
遮挡: onPause(),
恢复: onResume()
},
完全遮挡{
遮挡: onPause() -> onSaveInstanceState() -> onStop(),
恢复: onStart(), onResume()
},
配置变更{
未配置{
销毁: onSaveInstanceState() -> onPause() -> onStop() -> onDestory(),
重建: onCreate() -> onStart() -> onRestoreInstanceState() -> onResume()
},
已配置: onConfigurationChangedd()
},
后台回收{
销毁: 不会有时机处理,
重建: onCreate() -> onStart() -> onRestoreInstanceState() -> onResume()
}
}
解决接电话崩溃:
- 添加判空逻辑避免空指针问题
- 在页面回收生命周期中储存数据,页面重建时进行恢复,onSaveInstanceState()中写入需要保存的数据, onRestoreInstanceState()进行数据恢复
Activity 启动模式
- standard: 默认模式,允许页面栈中出现重复页面
- singleTop: 不允许连续重复, onNewIntent()
- singleTask: 不允许同个栈内重复
- singleInstance: 整个Android系统中不允许重复,少用,除了对安全性要求很高的银行、电话、通信录
解决多首页问题:讲首页launchmode设为singleTask
Fragment
Fragment设计来解决碎片化的问题。
Fragment比Activity快,因为启动Activity要走ip11,接收系统Service的调度
基本用法:
- 创建Fragment布局文件
- 创建Fragment子类,加载布局文件
- Activity加载Fragment 3.1 静态加载:布局中绑定 3.2 动态加载:FragmentManager加载
Fragment 生命周期
onAttach(): Fragment 和 Activity 建立关联时调用 onCreateView(): 当Fragment创建视图时调用 onActivityCreated(): Activity的onCreate()方法已返回时调用 onDestroy(): 当Fragment中的视图被移除时调用 onDetach(): Frament和Activity取消关联时调用
- 启动:onAttach() -> onCreate() -> onCreateView() -> onActivityCreated() -> onStart() -> onResume() -> Resumed
- 退出:Resumed -> onPaused() -> onStop() -> onDestoryView() -> onDestory() -> onDetach()
- 部分遮盖:Resumed -> onPaused() -> Paused
- 部分遮挡恢复:Paused -> onResume() -> Resumed
- 完全覆盖:Resumed -> onPause() -> onStop() -> onDestroyView()
- 完全遮挡恢复:onCreateView() -> onActivityCreated() -> onStart() -> onResume() -> Resumed
注:可用FragmentTransaction.setMaxLifecycle()手动干预Fragment生命周期
Fragment 与 Activity 交互
// Fragment获取Activity中的组件
getActivity().findViewById(R.id.xxx)
// Activity获取Fragment中的组件
getFragmentManager.findFragmentById(R.id.fragment_xxx)
// Activity传数据给Fragment
setArguments(Bundle bundle)
// Fragment传数据给Activity
// a. 通过对象直接传递(方法调用/接口调用)
// b. 通过 viewmodel / handler / broadcast / eventbus 等通信
关于 Fragment
最早的Fragment出现于'support'包中,后来AndroidApp出现了Fragment。AndroidX兼容包中的Fragment兼容性比较好。
服务组件: Service
跟系统打交道,比如后台放歌、后台下载。
Service 基本用法
- 注册:在AndroidManifest中使用
<service.../>注册 - 创建:建立相应的Service实现类
- 加载:startService() / bindService()
Service 的生命周期
- onStart()
- onBind()
- onCreate()
- onDestory()
Service 与 Activity 通信
- 定义
Binder子类,并实现getService()方法,返回Service对象 - 实现Service类
onBind()方法,返回上述Binder对象 - 实例化ServiceConnection对象,实现onServiceConnected()方法,从中获取到Service实例
- Activity中调用bindService()方法,并传递步骤3的ServiceConnection对象,讲流程跑起来
- Activity既可以通过调用Service实例中的方法进行直接通信
广播组件: Broadcast
广播,用于一对多的发消息。
Broadcast 基本用法
静态广播:
- 注册: AndroidManifest中使用
<receiver.../><intent-filter.../> - 创建:建立相应的
BroadcastReceiver实现类 - 接收: 在
BroadcastRecevier中接收广播 - 发送: Context.sendBroadcast()
动态广播: 注册: Context.registerReceiver()
可以通过广播唤醒软件
系统常用广播
Intent.ACTION_CONNECTIVITY_CHANGE Intent.ACTION_BATTERY_CHANGED Intent.ACTION_SCREEN_ON Intent.ACTION_SCREEN_OFF Intent.ACTION_PACKAGE_INSTALL Intent.ACTION_BOOT_COMPLETED Intent.ACTION_PACKAGE_ADDED Intent.ACTION_PACKAGE_REPLACED Intent.ACTION_PACKAGE_REMOVED
MIUI等改造过的Android不一定会给权限。
数据组件: ContentProvider & ContentResolver
ContentProvider实现数据通信
ContentProvider
生产者:
- 注册: AndroidManifest中使用<provider.../> 属性:authorities / exported / readPermission / writePermission
- 创建:建立相应的ContentProvider实现类 方法: onCreate / getType / insert / delete / update / query
消费者:
- 声明:AndroidManifest中声明权限
- 使用:context.getContentResolver() 方法:insert / delete / update / query
解决:扫描系统现有图片
// cursor
cur = MediaStore.Images.Thumbnails.queryMiniThumbnails(
context.getContentResolver(),
MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI,
MediaStore.Images.Thumbnails.MINI_KIND, projection);
if (cur != null && cur.moveToFirst()) {
do {
String imageId = cur.getString(cur.getColumnIndex(MediaStore.Images.Thumbnails.IMAGE_ID));
String imagePath = cur.getString(cur.getColumnIndex(MediaStore.Images.Thumbnails));
thumbnailMap.put(imagedId, imagePath);
} while (cur.moveToNext() && !cur.isLast());
}
Intent 意图组件
控件的启动需要Intent对象。将需求告诉系统,让系统处理。
Intent 用法
- 显示Intent setComponent / setClass 指定具体类
- 隐式Intent Action(动作) Data(数据) Category(类别) Type(数据类型) Component(组件) Extra(拓展信息) Flag(标志位)
常用系统能力
- 电话:Intent(Intent.ACTION_DIAL, Uri.parse("tel:10086"))
- 短信:Intent(Intent.ACTION_SENDTO, Uri.parse("smsto: 10086"))
- 网页:Intent(Intent.ACTION_VIEW, Uri.parse("ss.scnu.edu.cn"))
- 邮件:Intent(Intent.ACTION_SENDTO, Uri.parse("mailto: someone@domain.com"))
- 地图:Intent(Intent.ACTION_VIEW, Uri.parse("geo:39.9, 116.3"))
- 拍照:Intent(MediaStore.ACTION_IMAGE_CAPTURE) # 打开相机
- 设置:Intent(android.provider.Settings.ACTION_SETTINGS)
- 市场:Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName)) # 应用市场
使用:startActivity(intent)
Context.startActivity(Intent);
Context.startService(Intent);
Context.sendBroadcast(Intent);
基础组件小结
一个个基础组件,就像块块石砖,
通信组件
通信组件用于做线程通信或进程通信。包括:
- Handler
- Binder
Handler
用于处理主线程之间的通信。Android的主线程有特殊地位,更新界面必须由主线程完成。
主线程的资源比较宝贵,因为主线程需要相应用户操作。如果主线程运算时长超过5秒,系统会判定为未响应。所以要将耗时操作放在子线程。
Handler 基本用法
- 创建:新建Handler,实现handleMessage(Message)
- 构造Message:what / setDate()
- 发送:子线程调用Handler.sendMessage(Message)发送Message
- 处理:在Handler的handleMessage(Message msg)主线程更新UI
Handler 核心原理
有个Looper反复循环,等待并处理消息队列。队列里有消息就处理消息,没消息就继续循环。Looper有定时唤醒机制,如果没有任务,Looper会处于休眠状态。Handler调用Message会唤醒Looper。
Binder
进程绑定,实现进程间通信。系统各个服务之间的通信也是通过Binder实现。
使用Binder,实现进程间通信数据要拷贝一次。进程之间没法共享内存。
Binder 基本用法
CS架构:client, server
服务端:
- 定义一个AIDL文件
- 实现描述的接口,编写service
- 如果有实体类,需要提供实体类(jar包形式)
客户端:
- 拿到AIDL文件
- 绑定服务,获得接口持有对象
Binder 核心原理
通过内核空间实现内存交换
APP启动
Android的跨进程通信基本由Binder实现,但AMS和Zygote通过Socket通信,为何?暂无解答。
桌面点击 -> 通过Binder通信找到AMS SystemService进程 -> 通过Socket通信唤醒Zygote进程 -> 启动APP所在进程 -> 通过Binder通信告知AMS"已启动" -> AMS启动Activity
其他
- 其他组件包括文本组件、细致组件等。
- Android系统中有进程,没有协程。kt的携程属于语法糖。