Android客户端基础组件 | 青训营笔记

175 阅读7分钟

Android客户端基础组件 | 青训营笔记

这是我参与「第四届青训营 」笔记创作活动的第2天

写在前面——什么是程序

认知方法:What, Why, How

开发者视角

  • 什么是程序?代码+资源
  • 为什么写程序?为了改变世界
  • 怎么写程序?知识+经验

用户视角

  • What? 界面、互动、记忆
  • Why? 工具、视频、游戏
  • How? 应用商店、搜索引擎、官网下载、三方引流

基础组件

基础组件是日常开发中常用的组件。包括:

  • Activity
  • Fragment
  • Service
  • BroadcastReceiver
  • ContentProvider

界面组件: Activity & Fragment

界面组件是界面容器,承载界面。

为什么设计Activity?

  1. 实现前台交互
  2. 作为程序入口
  3. 作为布局容器

如何使用Activity?

  1. 在程序清单AndroidManifest.xml中声明
  2. 用Java或Kotlin写Activity文件
  3. res/layout中写布局文件
  4. 在JavasetContentView(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(): 用于恢复数据

Android应用生命周期.JPG

部分遮挡: Pause -> Resume。

页面全遮挡: Stop -> Restart -> Start -> Resume

配置改变,此时可能需要重建:

  1. 销毁: Resumed -> onSaveInstanceState() -> onPause() -> onStop() -> onDestory
  2. 重建: 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()
    }
}

解决接电话崩溃:

  1. 添加判空逻辑避免空指针问题
  2. 在页面回收生命周期中储存数据,页面重建时进行恢复,onSaveInstanceState()中写入需要保存的数据, onRestoreInstanceState()进行数据恢复

Activity 启动模式

  • standard: 默认模式,允许页面栈中出现重复页面
  • singleTop: 不允许连续重复, onNewIntent()
  • singleTask: 不允许同个栈内重复
  • singleInstance: 整个Android系统中不允许重复,少用,除了对安全性要求很高的银行、电话、通信录

解决多首页问题:讲首页launchmode设为singleTask

Fragment

Fragment设计来解决碎片化的问题。

Fragment比Activity快,因为启动Activity要走ip11,接收系统Service的调度

基本用法:

  1. 创建Fragment布局文件
  2. 创建Fragment子类,加载布局文件
  3. Activity加载Fragment 3.1 静态加载:布局中绑定 3.2 动态加载:FragmentManager加载

Fragment 生命周期

onAttach(): Fragment 和 Activity 建立关联时调用 onCreateView(): 当Fragment创建视图时调用 onActivityCreated(): Activity的onCreate()方法已返回时调用 onDestroy(): 当Fragment中的视图被移除时调用 onDetach(): Frament和Activity取消关联时调用

  1. 启动:onAttach() -> onCreate() -> onCreateView() -> onActivityCreated() -> onStart() -> onResume() -> Resumed
  2. 退出:Resumed -> onPaused() -> onStop() -> onDestoryView() -> onDestory() -> onDetach()
  3. 部分遮盖:Resumed -> onPaused() -> Paused
  4. 部分遮挡恢复:Paused -> onResume() -> Resumed
  5. 完全覆盖:Resumed -> onPause() -> onStop() -> onDestroyView()
  6. 完全遮挡恢复: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 基本用法

  1. 注册:在AndroidManifest中使用<service.../>注册
  2. 创建:建立相应的Service实现类
  3. 加载:startService() / bindService()

Service 的生命周期

  • onStart()
  • onBind()
  • onCreate()
  • onDestory()

Service 与 Activity 通信

  1. 定义Binder子类,并实现getService()方法,返回Service对象
  2. 实现Service类onBind()方法,返回上述Binder对象
  3. 实例化ServiceConnection对象,实现onServiceConnected()方法,从中获取到Service实例
  4. Activity中调用bindService()方法,并传递步骤3的ServiceConnection对象,讲流程跑起来
  5. Activity既可以通过调用Service实例中的方法进行直接通信

广播组件: Broadcast

广播,用于一对多的发消息。

Broadcast 基本用法

静态广播:

  1. 注册: AndroidManifest中使用<receiver.../><intent-filter.../>
  2. 创建:建立相应的BroadcastReceiver实现类
  3. 接收: 在BroadcastRecevier中接收广播
  4. 发送: 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

生产者:

  1. 注册: AndroidManifest中使用<provider.../> 属性:authorities / exported / readPermission / writePermission
  2. 创建:建立相应的ContentProvider实现类 方法: onCreate / getType / insert / delete / update / query

消费者:

  1. 声明:AndroidManifest中声明权限
  2. 使用: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 用法

  1. 显示Intent setComponent / setClass 指定具体类
  2. 隐式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 基本用法

  1. 创建:新建Handler,实现handleMessage(Message)
  2. 构造Message:what / setDate()
  3. 发送:子线程调用Handler.sendMessage(Message)发送Message
  4. 处理:在Handler的handleMessage(Message msg)主线程更新UI

Handler 核心原理

有个Looper反复循环,等待并处理消息队列。队列里有消息就处理消息,没消息就继续循环。Looper有定时唤醒机制,如果没有任务,Looper会处于休眠状态。Handler调用Message会唤醒Looper。

Binder

进程绑定,实现进程间通信。系统各个服务之间的通信也是通过Binder实现。

使用Binder,实现进程间通信数据要拷贝一次。进程之间没法共享内存。

Binder 基本用法

CS架构:client, server

服务端:

  1. 定义一个AIDL文件
  2. 实现描述的接口,编写service
  3. 如果有实体类,需要提供实体类(jar包形式)

客户端:

  1. 拿到AIDL文件
  2. 绑定服务,获得接口持有对象

Binder 核心原理

Binder核心原理 Binder核心原理

通过内核空间实现内存交换

APP启动

Android的跨进程通信基本由Binder实现,但AMS和Zygote通过Socket通信,为何?暂无解答。

桌面点击 -> 通过Binder通信找到AMS SystemService进程 -> 通过Socket通信唤醒Zygote进程 -> 启动APP所在进程 -> 通过Binder通信告知AMS"已启动" -> AMS启动Activity

其他

  • 其他组件包括文本组件、细致组件等。
  • Android系统中有进程,没有协程。kt的携程属于语法糖。