Android学习
2023-03-09 16:07:26 0 举报
AI智能生成
Android学习
作者其他创作
大纲/内容
数据存储
SharedPreferences
存储key-value键值对,多用来存放用户的简单偏好配置
使用
通过getSharedPreferences(String spName, int mode)获取当前应用的sp
通过sp.edit()可以获取sp对应的SharedPreferences.Editor
通过sp.getX(key, X default)可以根据key查询值
通过editor.putX(key, X value)可以插入或更新键值对
通过editor.remove(key)可以删除对应的key
SQLite
Android内置的嵌入式关系型数据库
原生
创建一个约定类,该类存储表名、创建命令等,包含一个静态内部类,存储表中字段的信息
SQLiteOpenHelper,重写onCreate定义创建表的逻辑以及onUpgrade定义升级表的逻辑
在调用类中通过SQLiteOpenHelper对象的getReadableDatabase/getWritableDatabase获取到SQLiteDatabase数据库操作对象
通过SQLiteDatabase对象来调用query、insert、update、delete来实现增删该查
room
创建一个与表同名的实体类,加上@Entity注解,同时定义域,与数据库字段通过@ColumnInfo等注解进行关联
创建表名为前缀的Dao接口,加上@Dao注解。将需要做的读写方法抽象为接口,并加上注解,交给Room实现
拓展RoomDatabase,通过@Database指定对应的实体类和版本号,创建一个获取Dao的抽象方法交给room实现。
通过Room.databaseBuilder().build()获取RoomDatabase对象,再通过RoomDatabase.getDao获取到Dao
通过Dao定义的读写接口构建任务,交给线程池执行
网络
HttpURLConnection
Java用来建立http连接的HTTP客户端库
使用
通过URL.openConnection开启一个与url的连接,将其强转为HttpURLConnection
通过setRequestMethod指定请求方法
通过setDoInput设置允许输入流,通过setDoOutput设置允许输出流(POST下)
通过getInputStream拿到输入流,getOutputStream拿到输出流(POST)
如果是POST,根据Content-Type格式化请求体的数据并写入输出流
通过getResponseCode获取响应码并进行判断和处理
通过InputStream读如服务端响应报文
关闭输入流、输出流、http连接
其他
网络请求属于IO操作,不应该在主线程中操作,可以通过Service来后台通信,也可以直接子线程操作
要使数据得到后通知主线程而不是主线程等待同步工作线程的Future,可以使用Handler向主线程发送消息,主线程自定义消息处理机制,进而来异步更新
Android操作系统
进程和线程
android启动应用的过程
系统加载并启动应用
系统启动后立即显示应用空白启动窗口
系统创建应用进程
应用进程创建应用对象
应用进程启动主线程
主进程创建主Activity
主线程扩充视图
主线程布局屏幕
主线程执行初始绘制
系统替换启动窗口为主Activity
启动状态
冷启动
系统为应用创建进程
温启动
进程没有被系统回收,但需要重新创建Activity,执行onCreate
热启动
activity仍驻留在内存中,需要从onStart开始
进程与组件
默认情况下,android中所有的组件元素均属于同一进程
各类组件元素在menifast注册时,都可以通过android:process属性指定组件所属进程
application元素中也可以设置android:process属性,设置所有组件的默认值
Android进程分类
前台进程
条件
拥有一个与用户交互的Activity,正在onResume
有一个正在响应广播的BroadcastReceiver,正在onReceive
有一个正在执行回调的Service,onCreate | onStart | onDestroy
特点
除非系统内存过低,导致前台进程都无法继续运行,才会终止这些进程。
可见进程
条件
有一个可见的Activity,但没有获得焦点(没有进行onResume)
有一个正在启动的前台Service,正在执行Service.startForeground
有一个托管的服务某功能正在被系统使用,比如壁纸、输入法
特点
除非为了保证前台进程运行而终止可见进程,否则一般不会被终止
服务进程
条件
包含一个已经启动的服务,已经startService
特点
系统会尽量保证其运行。但如果该进程已经持续执行了很长时间,该进程可能会降位
缓存进程
条件
目前不需要的进程,不可见、没有提供服务
特点
是内存管理涉及的唯一进程
在紧急情况下,系统会终止所有缓存进程
缓存进程会采取LRU进行淘汰
线程
主线程
应用启动时,系统会创建一个主线程main,负责将事件分派给相应的界面微件。
几乎所有的界面工具包组件都与是主线程交互
系统不会为每一个组件创建单独的线程,一些耗时长的逻辑会阻塞主线程
如果界面线程被阻塞超过一定时间,5s左右,就会弹出ANR应用无响应
不要阻塞UI线程
UI线程之外的线程不能访问Android UI包
工作线程
为了防止阻塞主线程导致ANR,一般将耗时的IO操作通过新线程来处理
工作线程访问界面线程的方法
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
主线程中实例化一个并Hanlder重写handleMessage方法,其他线程通过Handler发送Message
拓展AsyncTask<Params, Progress, Result>调用execute(Params...)执行
void onPreExecute()
在task执行之前在UI线程中调用,用于启动task,做进度条展示之类的
Result doInBackground(Params...)
在一个后台线程中执行任务,返回任务结果
void onProgressUpdate(Progress...)
在后台任务执行publishProgress(Progress...)后,在UI线程中调用,被用来更新进度条
void onPostExecute(Result)
在后台任务执行完毕后,拿到返回结果并回到主线程来执行,用来根据结果更新UI
内存
RAM
4k分页机制
缓存页
有存储器中的文件(例如代码或内存映射文件)支持的内存
私有页
有一个进程拥有且未共享
“干净”页
读如内存后还没有修改过或修改已经提交,可用kswapd删除
脏页
有修改的内容还没有写回磁盘,可用kswapd移动到zRAM进行压缩
共享页
由多个进程使用
“干净”页
同上
脏页
可用kswapd或者msync、munmap将脏数据写回磁盘
匿名页
没有存储器文件支持的内存(不是磁盘上的文件数据,而是堆、栈这类)
脏页
可用kswapd移动到zRAM进行压缩
zRAM
用于交换空间的RAM区
所有数据放入zRAM时都会被压缩,而从zRAM向外复制时会进行解压
存储器
不像其他linux的实现用于交换空间,因为频繁写入会导致这种内存出现损坏
kswapd-内核交换守护进程
Linux内核的一部分,用于将已用内存转化为可用内存。
当内存不足时,可用内存下降至Linux内核内存下限阈值以下时,该守护进程进入活动状态,开始回收内存
回收页帧方式
非脏页直接回收
后续缺页时,直接查页表从磁盘读入内存
私有页和匿名页的脏页放入zRAM压缩
缺页时从zRAM解压到RAM
关联进程终止时页面从zRAM移除
共享页脏页写回磁盘再回收
缺页时再调入
相关接口
实现ComponentCallback2并重写onTrimMemory(int)
系统会在内存不足时发起onTrimMemory回调,可以再其中自定义面对不同内存不足事件时的处理方案
通过getMemoryInfo获取ActivityManger.MemoryInfo
可通过该对象获取设备当前内存状态信息
内存效率
尽量少用Service,能不用则不用。如果必须使用,除非Service必须还要运行作业,否则完成任务后要直接关闭服务
系统会更倾向于让服务进程一直运行,导致其占用LRU,从而淘汰其他可能会用到的缓存进程
使用JobSchedual代替
使用Android提供的集合类来替代Java库中对移动设备内存不友好的集合类
HashMap<K, Object> =>SparseArray<K>
HashMap会带来频繁的自动装箱拆厢,会多创建出很多对象,造成内存效率低
HashMap<Integer, Boolean> => SparseBooleanArray
HashMap<Integer, Long> => SparseLongArray
Android开发中如果抽象无法带来很好的收益,则慎用抽象
抽象会增添很多类,会增加逻辑实现的代码量,需要更多时间和内存来load class
压缩外部库
GC
ART和Dalvik虚拟机的垃圾回收机制类似于CMS或是G1
分代、分区
GC过程中会出现STW
四大组件
Activity
出现原因
使用
必须在manifest中注册才能使用
父子Activity必须拥有相同的权限
Intent
生命周期
onCreate()
在系统创建Activity时触发
必须实现该callback
配置Activity组件,setContentView()设置布局文件……
onStart()
在onCreate()执行完成后触发
此时Activity已启动,对用户可见
完成Activity 进入前台与用户进行互动之前的最后准备工作
onStart()初始化的资源需要在onStop()中回收
onResume()
在 Activity 开始与用户互动之前调用此回调
此时该Activity位于Activity stack顶部,并会捕捉所有用户输入
大部分核心功能都是在 onResume() 方法中实现的
总会跟着一个onPause()
onResume()中初始化的资源需要在onPause()中回收
onPause()
失去焦点或暂停时调用
不能使用onPause()来保存应用或用户数据、进行网络呼叫或执行数据库事务。
执行完毕后会跟上onStop()或onResume()
onStop()
在Activity对用户不可见时(已结束并即将终止)会执行该callback
现有Activity被销毁或被新Activity覆盖,再也不可见
可以用来完成CPU相对密集的关闭操作(例如将数据存储到SQLite)
执行完毕后跟上onRestart()或onDestory()
onRestart()
处于停止状态的Activity重启时会调用
调用完成后总会再调用onStart()
onDestory()
Activity被销毁前(设备旋转)调用
此时可以清理该Activity中的任何数据
确保在销毁 Activity 或包含该 Activity 的进程时释放该 Activity 的所有资源
可复用的生命周期callback定义
Lifecycle
用于存储activity或fragment的生命周期状态信息
DefaultLifecycleObserver
可以通过实现该类来监控相应的生命周期
LifecycleOwner
表示类具有Lifecycle,可以通过addObserver()添加生命周期监听器,与DefaultLifecycleObserver完美协作
Activity与系统RAM
系统会在需要释放RAM时终止进程,终止进程的可能性取决于进程中Activity的状态
已创建、已开始、已恢复被终止可能性很小
已暂停被终止可能性较大
已停止、已销毁可能性最大
系统不会直接终止Activity来释放RAM,而会终止Activity所在的进程,销毁进程中的所有内容。
Activity的恢复
Activity可以通过Bundle恢复
当Activity被销毁后,可以通过Bundle中存储的数据来设置新建的Activity成员变量,进而恢复Activity。
Activity中的每个视图必须拥有唯一的android:id才能恢复Activity的视图状态
Bundle不适合保留大量数据,因为它需要在主线程中序列化。大量数据应组合使用持久化、onSaveInstanceState()和ViewModel
在Activity开始停止时,系统会调用onSaveInstanceState()
此方法可以方便地将状态信息保存到Bundle中
如果希望默认实现保存视图层次结构状态,需要调用super.onSaveInstanceState(Bundle)
用户显式关闭Activity或调用finish()时,系统不会调用onSaveInstanceState()
重建被销毁的Activity
onCreate(Bundle)
会接收到系统传递给Activity的Bundle保存的状态。需要判断是否为null
onRestoreInstanceState(Bundle)
在onStart()之后调用
只有在需要恢复状态时才会调用,因此无需判断Bundle是否为null
Activity之间导航
Activity启动Activity
startActivity(Intent)
新启动的Activity不需要返回结果
startActivityForResult(Intent, int)
结果通过onActivityResult(int, int, Intent)返回
子Activity退出时可以调用setResult(int)将数据返回给父Activity,int为flag
生命周期转换
A: onPause() -> B: onCreate() onStart() onResume()获得用户焦点 -> A: onStop()不再可见
Activity状态更改
配置更改
配置更改时Activity会销毁(onPause->onStop->onDestory)然后重建(onCreate->onStart->onResume)。
结合使用ViewModels、onSaveInstanceState()可以让Activity在配置改变后界面状态保持不变
进入多窗口模式或多窗口模式中应用调整了大小,系统会向当前Activity发送配置更改通知,并进行上述生命周期状态更改
多窗口模式下,当焦点由A移到B,A会调用onPause,B会调用onResume
可以自行处理配置更改
对话框
新的Activity或对话框出现在前台
部分覆盖了当前Activity
失去焦点并调用onPause
重新获得焦点时调用onResume
完全覆盖了当前Activity
进入已停止状态调用onStop
重新获得焦点时调用onRestart->onStart->onResume
返回按钮
当用户点击返回,退出应用
Activity会依次调用onPause->onStop->onDestory
Activity不仅会被销毁,而且会从返回堆栈中移除
该情况下默认不触发onSaveInstanceState
可以通过替换onBackPressed实现自定义行为,建议在其中调用super.onBackPressed()
通用应用需要注意的表现
被其他应用中断
系统销毁并重建Activity
Activity被放在新窗口环境中,例如画中画、多窗口
可以使用AndroidX Test库中的ActivityScenario实例来测试
任务与Activity堆栈
任务
用户在执行某项工作时与之互动的一系列Activity的集合
多窗口下每一个窗口都是一个任务
当新启动一个应用时,会将该应用的主Activity作为该任务的Activity堆栈的根Activity
当Activity启动新activity时,会将新Activity压入Activity堆栈的栈顶并获得焦点;当用户按下返回按钮时,当前Activity会从堆栈顶弹出并销毁,新栈顶获取焦点。
当Activity栈所有元素都被弹出,堆栈为空,任务不复存在
自定义activity堆栈管理
通过资源文件
使用<activity>的launchMode 属性指定
launchMode="standard"
默认模式。
launchMode="singleTop"
如果栈顶已存在Activity实例,系统会调用onNewIntent来将Intent转送给实例,而不创建实例
launchMode="singleTask"
如果另外的任务中已经存在Activity实例,会直接使用onNewIntent将Intent传给现有实例,而不重新创建
launchMode="singleInstance"
任务中只有一个根activity,当需要新建activity时,如果系统中已存在,则直接使用onNewIntent发送给已有实例;如果不存在,则新建一个任务,并将新activity作为新任务的根activity
通过Intent
使用Intent的flag来确定
FLAG_ACTIVITY_NEW_TASK
相当于上述的singleTask
FLAG_ACTIVITY_SINGLE_TOP
相当于上述的singleTop
FLAG_ACTIVITY_CLEAR_TOP
如果需要启动的activity已在当前任务中,会将栈顶activity以此弹出销毁,直至栈顶为需要启动的activity,然后通过onNewIntent()将intent传送给目标activity
清除返回堆栈
用户离开任务较长时间后,系统会清除根Activity外的所有Activity
可以通过修改Activity的属性来修改清除行为。
alwaysRetainTaskState
根Activity中该属性设为true,则会保留任务中的所有activity
clearTaskOnLaunch
根Activity中该属性设为true,只要用户离开再返回,堆栈就会只剩下根activity
finishOnTaskLaunch
某Activity中该属性设为true,只要用户离开再返回,该activity就不存在于任务
启动任务
为入口Activity添加过滤器,并添加"android.intent.action.MAIN"作为指定操作,将"android.intent.category.LAUNCHER"作为指定类别
该操作可以在启动器中现实Activity的图标和标签
只有进行了该操作,才能使用"singleTask"和"singleInstance",才会被标记为始终启动任务。否则当跳转到其他任务后再离开该任务,就无法回到根任务。
Service
可在后台执行长时间运行操作而不提供界面的应用组件。
前台
执行用户能注意到的操作,必须显示通知。如音乐播放应用,前台服务用来播放音频。
后台
执行用户不会直接注意到的操作。例如压缩存储空间服务。
使用服务的时机
当必须在主线程之外进行操作,但操作只在用户与应用交互时进行,就新建线程来进行操作
其他后台操作可以考虑使用服务
服务运行在主线程中,服务中的密集型和阻塞类任务要创建线程来处理
创建服务
必须在manifest中注册服务
两种服务
Service
适用于所有服务的基类,如果继承该类,需要为执行的工作创建新线程,因为其默认使用应用的主线程。
IntentService
是Service的子类,其使用工作线程逐一处理所有启动请求。如果不需要同时处理多个请求,最好用该类。
继承Service并重写回调方法
onCreate()
首次创建服务时会调用(在onStartCommand和onBind前)。如果服务正在执行,不会调用。
onStartCommand()
其他组件请求启动服务时会调用,执行该方法时服务就会启动并且无期限运行。服务执行完需要使用Service.stopSelf()或Context.stopService()停止服务
返回值
START_NOT_STICKY
如果系统在 onStartCommand() 返回后终止服务,则除非有待传递的挂起 Intent,否则系统不会重建服务。
START_STICKY
在返回后,如果系统终止服务,就会重建服务并且调用onStartCommand(),除非有挂起的Intent要启动服务,否则会调用空的Intent
START_REDELIVER_INTENT
在返回后,如果系统终止服务,就会重建服务并通过最后一个Intent调用onStartCommand()
onBind()
其他组件请求绑定服务时会调用。如果启用绑定,则必须在此返回一个IBinder,以供客户端与服务通信。如果不允许绑定则直接返回null
onDestory()
准备销毁服务时调用。在此方法中可以清理线程、监听器等资源
继承IntentService并给出构造函数和onHandleIntent()
IntentService提供了默认的onBind和onStartCommand,且在处理完所有请求后自动停止服务,所以只需要重写onHandleIntent即可
如果需要重写生命周期callback,需要确保调用父类实现。
onHandleIntent()
IntentService会将收到的Intent送往工作队列,依次交给onHandleIntent处理
绑定服务的创建方法
Binder
用途
服务仅供本地使用,无需跨进程
操作
拓展Binder,提供获取Service的方法
在onBind()中将Binder返回给客户端
在需要绑定的组件中创建匿名ServiceConnection重写onServiceConnection,拿到onBind返回的IBinder进而获取到Service
通过ServiceConnection调用bindService绑定服务,通过获取的Service调用服务工作。
Messenger
用途
无需AIDL就可以实现IPC
操作
拓展Handler为服务端实现消息处理句柄,在handleMessage中定义消息处理逻辑
在onBind中基于Handler构建Messenger,获取Messenger内部IBinder返回给客户端
在需要绑定的组件中创建匿名ServiceConnection重写onServiceConnection,基于服务端返回的IBinder构建一个向服务端发送消息的Messenger
通过Message.obtain生成要发送的消息,并调用Messenger.send将消息发送给服务端,交给Handler.handleMessage处理
如果需要CS双向通信,客户端也需要拓展Handler来处理消息,并基于Handler创建一个Messenger,发送消息时将Message的replyTo指定为客户端的Messenger,即可在服务端接收消息后回复客户端。
AIDL
...
启用服务
bindService()
一个或多个应用绑定同一个服务,当没有应用绑定该服务时,服务立即销毁
会引起onBind()
只有第一个客户端绑定时,系统会效用onBind生成并返回IBinder,后续会直接将该IBinder绑定到
绑定服务通常不允许使用startService启动
可用来IPC
此操作是异步操作
客户端销毁时会自动取消绑定,但最好是交互完成立即手动取消绑定
startService()
创建服务并一直在后台运行,直到调用stopSelf()或stopService()停止服务
会引起onStartCommand()
最好使用stopSelf(int id)来停止onStartCommand(Intent intent, int flags, int startId)所启动的特定的已完成的工作
同时绑定和启动
如果确实要同时具有已启动和已绑定状态,则服务不会在所有客户端均取消绑定后销毁,而必须调用stopService或stopSelf
音乐播放器
Activity可以启动服务来播放音乐,在用户离开或Activity销毁后音乐服务不中断。
在Activity重新启动时,可以通过绑定,让Activity重新获得播放器控制权
系统回收服务资源
内存过低以致焦点activity无法正常工作时系统才会停止服务
如果服务绑定到焦点activity上,则该服务不太可能被回收
如果是前台服务,则几乎永远不会终止
服务长时间运行,系统会逐渐降低其在后台任务列表的位置,被终止的概率也会大幅提升
Broadcast Receiver
应用注册广播接收器
资源清单注册
AndroidManifest.xml添加<receiver>并指定<intent-filter>
拓展BroadcastReceiver并重写onReceive(Context, Intent)定义接收广播后的回调逻辑
清单注册广播接收器,系统会在发出广播后启动该应用
上下文注册
拓展BroadcastReceiver并重写onReceive指定逻辑
创建BroadcastReceiver实例
创建IntentFilter并调用registerReceiver(BroadcastReceiver, IntentFilter)注册接收器
如果需要停止接收,调用unregisterReceiver(BroadcastReceiver)
上下文注册的接收器仅在创建处的上下文会接收广播
CONNECTIVITY_ACTION只会传送给上下文注册的接收器
如果注册接收器指定了权限参数,广播方应用必须具有该权限才能向接收器发送Intent
发送广播
sendOrderedBroadcast(Intent, String)
每次向一个接收器发送广播,按照android:priority控制顺序,相同优先级随机顺序。
接收器可以向下传递结果,也可以终止广播
sendBroadcast(Intent)
随机顺序向所有接收器发送广播
无法从其他接收器读取结果
效率更高
LocalBroadcastManager.sendBroadcast(Intent)
将广播发送给同一应用内的接收器
效率更高,本地广播
可以通过在发送方法中指定权限参数,那么接收方应用必须要具有指定的权限才能接受该广播
Content Provider
主要作用
提供对应用数据存储的统一封装
对外暴露便于操作内部数据的接口
Android中一般数据存储技术
关系型数据库SQLite
非关系型键值存储LevelDB
非结构化数据--文件存储
ContentProvider与SQL的关系
Cursor query(Uri, projection, selection, selectionArgs, sortOrder)
Uri -> FROM table_name
Uri content://user_dictionary/words
projection -> col1, col2, ...
String[]查询的列名
selection -> WHERE col = value
String查询的条件:"_ID = ?"
selectionArgs没有完全等价项
String[]查询条件中替换占位符的值: new String[] {"1"} => "_ID = 1"
sortOrder -> ORDER BY col1, col2, ...
String排序
Cursor:返回结果集合的包装类
Uri insert(uri, newValues)
uri -> FROM table_name
Uri content://user_dictionary/words
newValues
ContentValues插入值集合包装对象,通过put(col_name, value)来设置一行中各字段的值
Uri:返回的Uri e.g. content://user_dictionary/words/<id_value>,id_value是插入行的_ID
int update(uri, updateValues, selectionClause, selectionArgs)
uri -> FROM table_name
同上
updateValues: ContentValues需要更新的一行的值
selectionClause: String查询条件"_id LIKE ?"
selectionArgs: String[]替换占位符的值new String[] {"1%"} => "_id LIKE 1%"
返回更新的行数
delete(uri, selectionClause, selectionArgs)
全部同上
返回删除的行数
创建
设计数据存储方式以及Uri
创建对数据存储的内部封装类,比如SQLite需要拓展SQLiteOpenHelper,重写创建和更新的逻辑
拓展ContentProvider,借助上一步的封装类提供的读写数据接口重写增删该查以及getType
在资源清单中注册<provider>配置authorites,enabled,exported等属性并加上申请权限
使用
找到需要调用的ContentProvider的授权路径
通过getContentResolver()获取应用内容解析器
使用Uri.Builder根据授权路径、表、id构建Uri
调用contentResolver.xxx,将跨进程的数据读写操作交给对应的ContentProvider来完成
Notification
外观设计
模板设计
由操作系统决定
可自定义布局
内容
用户只需定义模板中个部分的内容
小图标
setSmallIcon()
应用名称
系统提供
时间戳
setWhen()设置时间
setShowWhen()是否显示
可选
大图标
setLargeIcon()
标题
setContenttTiltle()
文本
setContentText()
按钮
addAction()
其他配置
点击事件
点击打开新组件
setContentIntent(PendingIntent)
设置全屏紧急提示
setFullScreenIntent(PendingIntent, true)
点击后移除通知
setAutoCancel()
设置进度条
setProcess(MAX, current, indeterminate)
每次进度变更时,需要重新调用setProgress和notify
创建及发送
如果版本大于Build.VERSION.O,需要根据自己定义的CHANNEL_ID来创建一个新的channel,并且通过NotificationManager创建该Channel
创建NotificationCompat.Builder,并设置外观、按钮等配置
通过NotificationManagerCompat.from(Context)获取到NotificationManagerCompat对象,可以通过该对象发出、销毁通知
然后通过该对象调用notify()传入自定义的notification id以及builder.build构建的Notification对象发出通知
0 条评论
下一页