mvc mvp mvvm
mvc是最基础的架构,最开始是从web引用过来的,但是在android里面,C就在activity里,activity其实是属于view层,本身是不能做大量业务逻辑处理的,mvc在安卓中就会显得笨拙,使activity变的繁杂不易维护和拓展,但是小项目又比较合适,正因为大项目使用mvc把activity作用变得不易维护所以衍生出了mvp,mvp是把activity里的业务抽出来放到 Presenter里面,把activity的位置还原到view层,去实现用户的所有操作接口,但是这里又会有个问题,presenter和activity也就是view的交互会很频繁,而这些操作都是用接口连接的,所以项目会增加很多接口文件,虽然现在有控件可以直接一键生成这样的接口,但是每次增加一个业务的时候就会比较繁琐,会写很多接口和实现类,但是总的来说用mvp在中大型项目上用的时候可能觉得麻烦一点,但是在单元测试,拓展,维护上会方便很多,而且一个presenter可以用于多个activity,可重用性更高,mvvm是改良版的mvp,它用viewmodel代替了presenter,用dadabinding 做view和viewmode的数据绑定,数据发生改变view就会跟着改变,因为livedata 生命周期通过lifecycle和activity绑定,所以一定程度上app了页面的内存泄露问题,mvvm是现在主流的架构也是Google主推的架构
多线程
http网络请求多线程,照片上传多线程,人像比对多线程 多线程主要是要处理线程之间的共享变量,比如几个线程同时请求,而请求结果需要做一个回调,每个线程请求成功需要做一个计数器,而这个计数器就是共享变量,多线程操作共享变量需要加锁synconizied,并且用volatile修饰保证变量操作的唯一性
join:调用join就是调用当前线程的wait方法,等待join线程执行完毕在调用当前线程的notify方法恢复执行,join方法必须要在线程运行之后才有效
如何保证你的线程执行完
(如何保证你的线程执行完https://blog.csdn.net/wzy_1988/article/details/46875343,用WakeLock,可以在锁屏状态下保持CPU的正常运行,这个WakeLock是用来执行一些关键代码的,用完需要释放,不然app会很耗电)后来发现是我配置的问题,rxjava和retrofit的activity生命周期配置
kotlin
很多只是写法变了,关键字变了,逻辑还是差不多的,比如switch变成了when,for循环变成for in,还有就是一些权限修饰符变了,默认权限是final,open标识可继承,可重写,增加了新的权限internal标识在当前module中使用用起来比java方便,省掉了很多代码,比如那种非空判断只需要语句中加个问号就行,逻辑都是差不多的
kotlin没有static的变量,只能用companion派生对象关键字修饰,全局静态对象用Object关键字(Util工具类)const常量
高阶函数
forEach 遍历数组
map 数组转换
flatmap 多维数组转换
let 变量定义作用域,非空判断 返回最后一行
with 传入对象,作用域最后一行是返回值
run 只接受lambda函数 返回最后一行
apply,返回函数本身作用域,减少重复引用 不改变值
tcp/ip http
这些协议原理还是很深的,首先他们的层级是不一样的,ip协议是用于网络层的,tcp是传输层,http是应用层,然后tcp又涉及到三次握手,对于这些协议我没有研究的很深,平常用的多的还是http协议,http协议是建立在tcp上的一种应用,他的特点就是每次发出请求都需要服务端回送响应,建立连接和关闭连接叫做一次连接 可以参考(https://blog.csdn.net/DavidStar1988/article/details/80400020)
rxjava
rxjava的话,其实大部分的项目,可以说90%在项目里用rxjava都是用在retrofit的adapter上,因为大部分项目业务流程都比较简单,不用rxjava也就只是多几个回调方法而已,但是我用rxjava的目的主要还是那种链式编程可以使项目的逻辑变得清晰,因为很多逻辑如果嵌套太多的话时间长了维护起来会很难看懂,所以我是觉得能使逻辑变清晰的框架都适合我,rxjava关键字很多,我用的比较多的是map,和flatmap,作用就是你可以像流水线一样从Observable开始层层处理得到你想要的数据(两个都是转换数据,但是flatmap可以转换成Observable类型的数据,相当于for循环内又可以再for循环)还有很多关键字比如just,from,timer,interval,这些用到的时候不懂再去查一下很快就可以上手了,还是挺简单的
zip 合并observable
其他操作符https://blog.csdn.net/chaoyangsun/article/details/80373203
创建类型
concat 将Observable依次发送
merge 交错,不按顺序发送
ust 原样发射
from 将数组分成单个依次发射
Interval 间隔发射
timer 延迟发射
转换类型
map 转换数据 比如给我的是url,但是我需要的是bitmap,这时候就用map
filter 筛选,按条件筛选,比如筛选出非空的url
flatmap 相对于map,可以多对多,比如在转换数据的集合里进行拆分
just 和from区别: just是设置什么就返回什么,from是把数组分成单独的发送
https://blog.csdn.net/moonpure/article/details/78402347
retrofit
其实retrofit不是一个网络请求库,它是一个请求库上面的一个封装,建立在okhttp上的一个库,特点就是你可以用注解去构建一个网络请求,他先是用接口去实现一个带有注解的类,然后用retrofit的create去创建一个实体,我看了一下源码,这个create里面是用了动态代理来实现的他就是通过反射在一个class方法的前后增加逻辑代码,这些逻辑代码就是用到okhttp的库,最后生成一个请求的request,里面设计感还是很强的,工厂模式,适配器模式还提供了rxjava的支持,和rxjava结合还是用的很爽的https://www.jianshu.com/p/a8b98e66b1b8
Glide
和其他图片加载框架差不多,glide都有三级缓存,但是glide比较强大的是能加载gif,还有glide的with方法传入的context会进行检查,他会添加一个隐藏的fragment来监听生命周期,达到自动回收图片的目的
Volley
volley的封装比较简单是轻量级的,volley也支持okhttp,但是volley不支持post大文件数据
Dagger
dagger是一个注解处理器,帮助项目解耦少用new关键字,和spring的作用差不多,但是他是编译期间通过apt插件预生成一些代码,而spring是在运行期间,它主要可以帮助你做一些初始化的操作,缺点就是编译时间边长了,每次运行要多很长时间https://www.jianshu.com/p/cc54c5ec9897
事件分发
数据库增加字段
sqllite增加字段只能重新建表,方法就是把表重命名,新建一张带有该字段的表,然后复制数据
数据库框架比较
数据库框架分为关系型数据库和对象型数据库,关系型数据库最好的库是greendao,对象型数据库比较好的也是用的最多的是realm,底层是c语言实现的,性能相对较好就是包会比较大但是业务量不大的话用android的sqlite就行了,功能简单,也方便拓展
数据库性能优化
数据库操作基本由三个过程,创建事务,执行sql,提交,这三个过程,但是我们批量操作,就变成创建事务,执行n个sql,提交,这样效率会提高很多,
在sql语句数量比较多的情况下,用stringbuilder代替string拼接,这样会提高空间分配效率
查询时根据需要返回尽量少的字段,可以减少内存的消耗
查询结束后及时关闭cursor
如果条件允许的话可以在建表的时候记住columnIndex,它是一个int类型下标值,不用每次查询都去调用getColumnIndex,这个在数据量庞大的查询条件下会比较节省时间
调用数据库的时候要用子线程防止卡ui,但是由于sqllite是不支持多线程并发访问的,两个线程同时插入会抛出异常,虽然是有事务控制,可以保证数据库的完整性,但是不能保证线程执行的准确性,也就是说有的线程可能会调用失败或者请求出现线程繁忙错误,所以sqlite最好保证单线程执行
一般情况下,sqlite是多进程可以操作的,不会报错,但是不能保证插入顺序,原则上需要在一个进程里去操作,正确的做法是,启用一个, 单独的进程通过contentprovider或者其他ipc向其他进程提供接口,通过这个单独的进程统一操作
ipc
为什么需要多进程:
Android对进程的内存分配是有限的,用多进程让核心服务和UI分离,可以为用户提供更好的程序稳定性,提升用户体验 开启多进程,用多进程分担内存压力,用前台service保证adj的值最小,最不容易回收
内存分配 管理内存分配的是LMK LowMemoryKiller,通过adb命令proc/pid/com_score_adj可以看到一个应用的adj值,值越大,越容易被回收,通常切换到后台会变大
-
多进程会出现的问题:
共享内存失效,单例,静态变量成员失效,同步锁失效,application多个,sharepreferce可靠性下降
-
应用场景:
音乐播放器,定位服务
开启多进程的方式:在四大组件注册文件中指定 android:process
-
多进程的通信方式:
Bundle 常用于四大组件通讯
文件共享,多进程操作同一个文件交换数据 但是不支持并发
Messenger 轻量级的AIDL ,需要基于Handler
Socket
AIDL 写aidl接口文件,在服务里通过Binder拿到接口对象传递数据,注册服务,客户端绑定服务转换aidl接口获取数据
ContentProvider 例如我们的应用访问系统的通讯录数据
recycleview
嵌套scrollview卡顿,重新scrollview拦截一下事件
嵌套显示不全item,外面再包一层relativelayout 增加一个descendantfcusability属性
布局优化
避免层级太多
开发者模式查看是否过度绘制
使用include标签重用layout
viewstub标签 (调用的时候才加载布局)
不要在onDraw中频繁创建对象和做耗时操作
socket
创建服务端socket连接对象,指定ip和端口号,子线程进行
监听socket流,获取服务端返回数据
发送数据也是利用InputStream的write字节
websocket
是基于类似于http的应用层协义,使用jar包WebSocketClient连接服务器
Android动画
view动画 移动,平移,缩放,旋转
drawable动画 帧动画,一帧一帧的
property动画 view动画升级版,可以渲染还没加载的view
Handler机制
Handler创建Looper,Looper创建MessageQueue Looper消息队列处理者用死循环不断去 MessageQueue 消息队列拿到message然后又调用handler去处理, 类似于生产者消费者https://www.jianshu.com/p/87ee9b5206df
性能优化
1 anr主线程不能处理耗时操作
2 内存溢出,bitmap优化
3 内存抖动,频繁创建临时对象
4 内存泄漏
-
使用LeakCanary检测:
运行后会在另外一个Leaks的app中显示,该库会监听onDistory方法,把activity实例放在弱引用里实时访问,找出内存泄露的变量和类
-
android studio app profiler工具:
可以查看当前运行app的CPU,内存,网络的占用和消耗,可以看当前页面所在类的实例,可以调试内存泄漏和占用比例
webview引起的内存泄露,手动创建,手动销毁,不要在xml使用
线程销毁
context单例引用
handler
弱引用
SQLite使用完记得关闭链接
5 ui布局复杂
6 view重复绘制
7 懒加载setUserVisibleHint
https://www.jianshu.com/p/3e44250ca2de
activity启动模式
standard
默认启动模式,start多少就开启多少
singletop
栈顶唯一,允许有多个实例,但是不允许叠加,通知栏跳转
singleTask
栈唯一,但是start的时候如果此activity之上有新的activity会被销毁,会调用此activity的onNewIntent方法,主界面
singleInstance
栈唯一,一般用于系统应用,launcher
Android 678910 的差异
6.0 运行时权限,休眠模式,app进入休眠模式会断开网络,杀掉一些空置进程
7.0 分屏多任务,VR,通知栏快捷回复,优化编辑器减少安装速度,文件加密
8.0 画中画,Notifcation Dots,自适应桌面图标,后台进程限制,运行时权限优化,安装位置来源apk权限
9.0 WIFI RTT室内精准定位,凹凸屏支持,消息通知优化,多摄像头api,ImageDecoder,神经网络api,NFC api
10.0 暗黑模式,桌面模式(投影),5Gapi,折叠屏支持,手势导航,后台程序位置获取权限,神经网络1.2,文件沙盒
https://www.jianshu.com/p/88409d6f5795
activity fragment互传值
LiveData [https://www.jianshu.com/p/3e8e1fee00fc](https://www.jianshu.com/p/3e8e1fee00fc)
livedata 主要是用于activity的数据共享,比如activity和fragment,fragment和fragment之间的通讯,优点就是它自己会处理生命周期,不用担心内存泄漏,比如网络请求自动刷新ui这种
实现方式
需要配合viewmodel使用,viewmodel 定义mutableLiveData, 用viewModelProviders.of创建实例,在fragment里用.observe绑定,最后调用mutableModel的setvalue刷新数据,就可以自动刷新
外部获取自定义view的宽高
不能直接获取,view没有进行onmeasure方法获取到是0,有两种方法
——自己调用view的onmeasure测量
——需要设置一个监听,View.ViewTreeObserver.addGlobalLayoutLinstener在这个回调里获取宽高
hashcode
hashcode是用对象的字段算出来的一个数字,用来比较两个对象是否相等,但是他需要和 equals一起使用,当hashcode相等的时候比较equals,这样比较更高效 当hashcode相等时两个对象不一定相等
StringBuffer和StringBuilder
stringbuffer是线程安全,stringbuilder不能多线程访问,但是stringbuilder速度更快,另外string因为每次都是新建对象,所以string也是线程安全
接口的意义
接口主要为了弥补类不能实现多继承的缺点,有了接口一个类可以实现多个接口的操作
内部类,静态内部类,局部内部类
内部类可以隐藏一些外部类的变量和方法,不让调用者知道
静态内部类不依赖外部对象,但是可以指向表达一种从属关系
局部类只是在方法里面使用
设计模式
- 单例模式
- 工厂模式(工厂方法,抽象工厂)
- 策略模式
- 代理模式 (动态代理静态代理)
- 观察者模式
- 责任链模式
模块化组件化与插件化和热修复
模块化是把基础业务分成一个模块,其他业务引用基础业务,这些模块都在同一个工程
组件化是指一个app有多个module,每个module负责一部分业务,每个业务组件可以单独打包也可以做依赖库,最后打包成为一个apk,
插件化是指一个项目分为一个宿主和多个插件,每个插件是一个apk,最后这个宿主app加载多个apk,可以实现动态加载
热修复是指为一个业务或者一个class单独进行加载,已达到修复bug的目的
排序算法
- 冒泡排序
int a = [2,1,5,9,5]
int temp = 0;
for (int i = 0; i < a.length-1; i++) {
for (int j = 0;j < a.length-1-i; j++) {
if (a[j] > a[j+1]) {
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
原理,像挤跑跑一样,一个一个挤出来,第二层for循环每一次选出一个最大值,外面一层for循环控制第二层循环的排序范围
-
选择排序
总结,就是把里面的循环每次找到一个最小值,外面的循环控制轮询范围
glide 和 Picasso的选择
特性 | glide | Picasso |
---|---|---|
gif加载 | 支持gif加载 | 不支持 |
生命周期 | 支持 | 不支持 |
内存 | 根据image尺寸 | full 尺寸 |
加载时间 | 慢-先改变尺寸 | 快-直接加载到内存 |
图片加载时,传入Context对象或者fragment对象,glide会生成一个无UI的fragment,因为glide无法从传入的context中获取生命周期 通过这个fragment可以拿到传入context相同的生命周期,从而实现了对生命周期变化做出相同的暂停和销毁操作
总结:小项目用Picasso,功能简单,加载速度快,大项目用glide,功能配置丰富,对内存优化比较好,对于内存方面,glide会对不同尺寸的图片缓存多份缓存,
而Picasso都是同一份缓存,根据尺寸要求在内存中进行resize,所以下载速度Picasso>glide,内存加载速度glide> Picasso
activity启动过程
首先从点击桌面图标开始,launcher进程通过binder ipc向system service发起通信startActivity,system service进程的AMS(activity manager service)会判断app进程是否已经创建,若没有创建则通过binder ipc向zygote通信fork app进程, app进程会通过ipc向ams 发起通信 请求attachapplication 和得到schedulelancheractivity请求然后和主线程通信,主线程开始调用activity oncreate app正式启动
协成
什么是协成
协成是轻量级线程,建立在线程之上,和线程的区别就是它不会阻塞任务,而是挂起任务,当一个任务出现阻塞的时候这个线程会被挂起,挂起之后这个线程还可以去执行其他任务,而这个任务阻塞结束后又会被线程执行,相当于是把线程进行分段,相当于用最少的线程做更多的事情,节约资源,协成的另外一个好处就是它的调用是同步的,不需要回调函数,只需要return回结果,看上去更简洁易懂
如何开启协成
- launch
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(5000L) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束
println("main: Now I can quit.")
launch 返回的是job对象 只能控制协成状态,不能返回数据结构,异步方法需要用suspend修饰
- async
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
async 返回的是defferred对象,可以控制协成状态可以返回数据结构,defferred是Job的子类
以上两种方式都可以用Dispather 指定协成在那个线程运行