1.序言及体验课
2.HTTP 的概念、原理、工作机制、数据格式和REST
HTTP请求报文格式:
Image.png
HTTP想要报文格式:
Image [2].png
GET:获取资源,没有Body
POST:增加或修改资源,有Body
PUT:修改资源,有Body
DELETE:删除资源,没有Body
RESTFul,就是遵循HTTP规范的请求交互格式(Method、status)
Header:
作用:HTTP消息的元数据(meta data)
- Host:
- Content-Type/Content-Length:Body的类型/长度
- Location:重定向的目标URL
- User-Agent:用户代理
- Range/Accept-Range:指定Body的内容范围(可针对大图片做加载部分)
- Cookie/Set-Cookie:发送Cookie/设置Cookie
- Authorization:授权信息
- accept:客户端能接受的数据类型,如text/html
- accept-charset:客户端接受的字符集,如utf-8
- accept-encoding:客户端接受的压缩编码类型,如gzip
- Content-Encoding:压缩类型
Transfer-Encoding:chunked,表示Body长度无法确定,Content-Length不能使用
目的:在服务端还未获取到完整内容时,更快对客户端做出相应,减少用户等待
Body格式:(最后传输0表示内容结束)
image.png
Cache:
Cache和Buffer的区别:
Cache是在科幻段或中间网点缓存数据,降低从服务器去数据的频率,以提高网络性能,Buffer是网络数据预加载(视频播放缓冲)
cache过期判断:
- Last-Modified:if-Modified-Since
- Etag:If-None-Match
缓存策略: - no-cache、no-store、max-age
- private/public:不仅用户可以缓存,一个HTTP请求经过的所有中间网络节点都可缓存
3.各种「转换」的作用和对比——编码、加密、Hash、序列化和字符集
加密:
古代密码学(密码棒缠绕)/移位式加密 -> 替换式加密 -> 现代密码学(不仅可用于文本,还可用于各种二进制数据)
对称加密:加解密算法不同,密钥相同
DES、AES(码表比DES的长,所有更安全些)
image.png
非对称加密:加解密算法相同,密钥不同(利用溢出)
RSA、DSA
image.png
拓展:数字签名
image.png
签名是抓取摘要来验证,没必要对整个数据进行签名,传输的时候浪费带宽(对Hash值进行签名)
组合使用:
image.png
密码学密钥和登陆密码区别:
密钥:key - > 数据
- 场景:用于加密和解密
- 目的:保证数据被盗时不会被人读懂内容
登陆密码:password -> 身份 - 场景:用户进入网站或游戏前的身份验证
- 目的:数据提供方或应用服务方对帐户拥有者数据的保护,保证[你是你]的时候才提供权限
编码:
- 将二进制数据转换成由64个字符组成的字符串编码算法
- 用途
- 让原数据具有字符串所具有的特性,如可放在URL中传输、可保存到文本文件、可通过普通的聊天软件进行文本传输
- 防偷窥
疑问:Base64加密传输图片,可以更安全和高效?
肯定不对的,编码不是加密,不安全;编码以后,源数据肯定变大的(注意不要多次编码)
变种:Base58(去掉0、O、l、1这些容易看错的字符,避免有的时候有人需要手抄抄错了<比如比特币交易>)
URL Encoding:将URL中的保留字符使用百分号“%”进行编码
压缩与解压缩:
压缩:把数据换一种方式来存储,以减少存储空间
解压缩:把压缩后的数据还原成原先的形式,以便使用
(按照一定的算法,把数据进行重新定义)
常见压缩算法:DEFLATE、JPEG、MP3
- 压缩属于编码吗?
编码没有官方的定义。一般定义的编码就是把数据从A格式转成B格式,并且可以转回来,压缩和解压缩符合这个条件(有个说法叫压缩编码)
媒体数据的编解码:所有需要对应的解码器(解压缩算法)
序列化:
序列化:把数据对象(一般是内存中的,如JVM中的对象)转成字节序列的过程
反序列化:把字节序列重新转换成内存中的对象
目的:让内存中的对象可以被存储和传输
- 序列化是编码吗?
严格说来不属于,原数据是在内存里
Hash:
定义:把任意数据转换成指定大小范围(通常很小)的数据,单向的
作用:摘要、数字指纹
经典算法:MD5、SHA1、SHA256(彩虹表:存储了MD5值和数据 -> 对策:加盐)
实际用途:
- 数据完整性验证(上传下载要比对hash)
- 快速查找:hashCode()和HashMap
- 隐私保护
Hash是编码吗?
肯定不算了,hash是抽取特征
Hash是加密吗?为什么MD5是不可逆加密?
Hash是摘摘要,怎么可能根据摘要复原数据
完整的签名验证过程:
image.png
字符集:
含义:一个由整数向现实世界中的文字符号的Map
4.登录与授权、HTTPS和 TCP/IP 协议族
问题:https被篡改、伪造?
不存在的,需要签名验证,签名就是为了防止篡改伪造(对host、域名等hash后进行签名,伪造了签名校验不通过的)
拓展:Fiddler可以抓https包,是因为主动安装了Fiddler的证书作为根证书
5.从Retrofit的原理来看 HTTP
核心是动态代理创建Api,动态代理是实现AOP功能,把相同的处理封装
创建过程:
1.ServiceMethod解析Api的参数/配置CallAdapter/配置Converter
2.把ServiceMethod解析的参数存储到HttpCall中
3.用ServiceMethod配置的适配器适配Call和Response
建造者模式使用场景:
1.设置参数需要成本,比如角色设置的时候,更改状态需要更改UI
2.参数非常多的时候
Converter和CallAdapter都是List存储的,不是替代关系,可以配置多个,按需适配
6.从OkHttp的原理来看 HTTP
okhttp是自己建立TCP,根据https建立TLS连接,并且根据http协议与Server交互,用okio读写数据
okhttp的亮点在拦截器,采用责任链,逐级往下调用。proceed为界限,前置操作写在前面,后置操作写在后面,最终返回Response。
BridgeInterceptor是根据http协议,处理Request,比如添加头信息,比较特殊的是,默认用gzip进行了优化
ConnectionInterceptor是和TCP和https连接相关
OkHttp配置参数的详解
7.绘制一:图形的位置测量及 Xfermode 的使用
文字、Resource、Color等资源,可以用Resources.getSystem()来取,不用context
onSizeChanged里绘制的好处,性能高,测量的时候不会绘制;更合理,界面大小改变,重新绘制
画刻度:PathDashEffect
BitmapFactory的inJustDecodeBounds设置为true,读取图片性能优化?
步骤:
- 设置 inJustDecodeBounds为true,只获取图片的大小信息
- 根据需要的大小和获取的图片大小计算缩放比例设置 inSampleSize
- 设置 nJustDecodeBounds为false重新读取图片
8.文字的测量和几何变换的本质与实用技巧
静态文字垂直居中:用textBounds,当前文字中心
动态文字垂直居中:用fontMetrics,大多文字的中心(用textBounds会上下跳)
居左字体大小不同,导致没有左对齐:因为文字本身间距,绘制的时候减去textBounds的左距离
多行文字绘制:
- 简单绘制多行,使用 StaticLayout(TextView也是调用StaicLayout来换行的)
- 需要主动截取,使用 breakText
范围裁切:
clipPath裁剪是有锯齿的,因为是从原有图里裁剪(反裁剪)一部分,所以无法去锯齿,裁剪使用 Xfermode 实现
二维/三维变换:变换顺序逆序
Camera的setLocation的单位是英寸,需要适配下dp
在Camera变化前裁剪,因为三维变化后,图片可能会很大,免得裁剪不到
感觉并不需要记那么细,很多概念上的东西,出问题肯定能搜索到的
9.属性动画和硬件加速
- View.animate()
- ObjectAnimator/ViewPropertyAnimator ValueAnimator
invalidate() -> 把界面标记为无效,重新调用onDraw() - AnimatorSet/PropertyValuesHolder/KeyFrame
10ms(针对动画)/16ms(界面刷新标准)
不用太纠结,很少有复杂的动画需求的
插值器(入场/出场) - TypeEvaluator(Point/String)
- Listeners(动画监听)
reverse():有一对完全相反动画,用reverse()实现更简洁,要设置起始和结束的value
软件绘制:使用CPU用绘制代码绘制Bitmap(绘制了全部的,改一部分,其他的也可能得重绘),渲染屏幕
硬件绘制:把绘制代码转成GPU操作(存储的中间过程,最终渲染到屏幕),渲染到屏幕 -> 硬件加速(有些操作GPU不支持,导致不能绘制某些图形)
离屏缓冲(saveLayer 用 setLayerTyper 替代)是一个重方法,影响性能
把绘制区域单独拿出来或者截取部分来做绘制View的内容或者裁剪部分的内容,免得背景什么影响绘制
saveLayer (save+离屏缓冲)
10.Bitmap 和 Drawable,以及手写 MaterialEditText
Bitmap:存储图片的信息
Drawable:负责绘制的工具Drawable.draw(Canvas),比较像View,但是没有触摸之类的功能,只负责绘制
自定义Drawable:单纯绘制的操作,可以写些公用的绘制,比自定义View轻量,可以在自定义View加入自定义Drawable
自定义View讲的有点絮叨,感觉像是在教0基础的...
讲的时候一直思考为什么要编译成R
11.自定义尺寸和内部布局、手写 TagLayout
自定义布局:
-
测量
onMeasure(做事)测量模式,基本是固定的,调用resolveSize()/resolveSizeAndState()保留些状态值/标志位,做位运算,但是应该开发不规范,连sdk的也并没有全部用到
measure (调度用) -
布局
layout:存储父View布局结果位置左上右下(做事)
onLayout:对子View进行布局 (调度用)
onDraw:做事
draw:调度用
直接改layout,改写自己的尺寸,没有经过父View的方法,显示会有问题,父View测量布局才正常显示,所以需要改写onMeasure
getMeasureWidth:父View计算得到的super.onMeasure()
getWidth:真正的
measureChildWithMargins()
12.触摸反馈的机制和原理全解析、手写触摸反馈算法
为了点击合理onTouchEvent() 做了很多处理,写的很完善
MotionEvent.getAction() = MotionEvent.getActionIndex() + MotionEvent.getActionMask()
触摸事件序列,第一个是ACTION_DOWN,要拦截,ACTION_DOWN中返回true,其他返回什么是不相关的。平时简单就直接最后return true了。
CONTEXT_CLICKABLE:实现类似鼠标右键的功能,微信长按触发
tooltipText:提示用的,description界面化
预按下:准备显为按下,暂时不置为点击状态,做下等待延迟,可以区分滑动还是点击
(houldDelayChildPressedState() -> 判断是否是在滑动控件里)
checkForLongClick():设置长按的等待器,时间是定的,会减去预按下等待的时间
TouchSlop:点击溢出,减少误操作,免得超出范围就判断了,用户体验不好
View的dispatchTouchEvent():
直接调用onTouchEvent() :dispatchTouchEvent() 调度用的,真正功能在onTouchEvent() 里实现,类似draw()和onDraw()的关系
ViewGroup的dispatchTouchEvent():
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result;
if (interceptTouchEvent()) { // 拦截,ViewGroup特有;1.拦截;2.存储初始状态
result = onTouchEvent(); // 记录值
} else {
result = 调用 View 的 dispatchTouchEvent() //怎么调用?基于TouchTarget(更纯粹的为了实现Touch的功能)
}
}
TouchTarget:实现了View的责任链模式的核心
TouchTargets:自己实现的单链表结构,应该是因为View的事件分发是责任链,记录有哪些子View声明了要消费事件
image.png
13.手写双向滑动的 ScalableImageView
GestureDecotorCompact(GestureDecotor)
接口单一职责设计的不是很好,需要实现的接口方法太多,解决方法:用SimpleOnGestureListener(),重写需要的方法
onSingleTapUp()和onSingleTapConfirmed()区别:
onSingleTapUp:点击抬起就触发
onSingleTapConfirmed:等双击延时过了,确认是单击
onDoubleTap()做了防抖
理解onFling()物理模型,理解fling()参数意义
postOnAnimation():等到下一帧立即到主线程执行
OverScroller.computeScrollOffset():计算滑动的值
14.多点触控的原理和常见多点触控场景的写法
ScaleGestureDetector.getScaleFactor()原理:
- 接力(抢夺):更改index
- 协作:多个点计算中心点,手指抬起的时候不加入弹起手指的坐标,弹起的时候,点也是给了
- 互不干扰:比如画图软件多个画笔,存多个Path实现(没有ACTION_POINTER_MOVE的,移动需要遍历加入)
更多场景...
View的触摸事件序列:ACTION_DOWN开始,......,ACTION_UP结束
ACTION_POINTER_DOWN/ACTION_POINTER_UP
event.getX() -> event.getX(0)
event.getActionIndex():多点触控才有用,按下/抬起的index
p(x,y,index,id):index是序列,连续的,会改变,用来遍历,id是不变的,用来追踪index的
ACTION_MOVE:有个点移动了,但是不能判断是哪个/几个点
15.手写 ViewPager,以及 Android 中的拖拽操作
getParent().requestDisallowInterceptTouchEvent(true):让父View不要拦截,嵌套滚动会用到
没有子View,没有TouchTarget,不调用interceptTouchEvent(),直接调用ViewGroup的onTouchEvent()
ViewConfiguration:一些View临界的默认值
VelocityTracker:类似OverScroller,记录速度,并且可以从中获取速度
ViewGroup的interceptTouchEvent()和onTouchEvent() 的功能一般很像,都是记录初始位置
拖拽:
- DragListener
View.startDrag(),ViewCompat.startDragAndDrop(),拖动支持跨进程了。参数有些松手才能拿到(重量级数据),有些都能跨进程了,拖到其他应用。
拖起来的是像素不是View,所以可以覆盖到任何地方,拖到某个地方做些操作,松手做些操作
View不设置DragListener也能收到事件,因为有可能其他View和脱出来的像素需要做些重合预览效果
使用场景:内容的拖动,把东西拖过去
- ViewDragHelper
默认吸住四个边,让用户直线滑动,免得手抖
使用场景:DrawerLayout/底部滑出 - 自己手写
contentDescription:无障碍模式
嵌套滑动:
同向(会卡住,场景:文章里加代码,需要滑动)
NestedScrollView:子View优先
自定义View实现(情况很少):
NestedScrollingChild2
子View询问父View要不要拦截,根据已消费的距离来处理谁优先
- dispatchNestedPreScroll():先问父View
- dispatchNestedScroll():子View滑完了再问
一般用CoordinatorLayout即可
不同向(触摸事件分发已经差不多兼容了)
插播:RecyclerView
findViewById是DLS,深度优先算法,O(n)
ViewHolder:把子View存到里面,减少findViewById()
ViewHolder和itemView是一对一,像ListView,每个ViewHolder都存储在Tag里
ListView的缓存机制:
Active View(活跃的),Scrap View(废弃的)
RecyclerBin先从Active View找再从Scrap View找,没有createView
android界面刷新,界面上的,可能会改变位置,但是数据并没有变化,所以需要ListView跳过Adapter的绑定操作,只需要从Active View获取View即可,不需要再进getView重新绑定一次数据
RecyclerView:
Scrap:通过position找,屏幕内的
Cache:和Scrap差不多,通过position找,刚滑出去屏幕要拉回来(可设置缓存position大小)
ViewCacheExtension:用户自定义缓存(比如广告渲染耗时)
RecycledViewPool:通过viewType找,但数据是脏(dirty)的,需要重新绑定
ListView是缓存itemView,RecyclerView是ViewHolder,但是本质是一样的
RecyclerView没有onItemClick、onLongItemClick:RecylerView的作者希望统一接口和行为,希望item和item的View的点击都在Adapter设置
RecyclerView优化:
- 在onCreateViewHolder中设置监听,bind绑定一次,设置一次,不妥
View - ViewHolder - View-OnClickListener - LinearLayoutManager.setInitalPrefetchItemCount():
- RecyclerView.setHasFixSize():省去requestLayout
- 多个RecyclerView共用RecyclerViewPool
- DiffUtil:对item做增量更新
getChangePayload()不实现,只要部分有改动,整个item全部绑定变化
计算item的差异是在主线程更新的,数据很大很复杂的时候也很耗时,需要切到异步:- Handler/Thread、RxJava切换到子线程
- 使用Google提供的AsyncListDiffer(Executor)/ListAdapter
ItemDecration:可叠加,装饰Item,操作屏幕内的内容
为什么要ItemDecration:
- 性能,存储View要多一个,findViewById
- 动画要带上,移动要带上
onDrawOver:是画在Item上面
16.Java 的多线程简介及线程同步的本质原理
start0():native方法,和平台相关,操作系统拿出线程来执行run,JDK是不具有开辟线程的功能的,线程调度本身也是操作系统的概念
ThreadFactory是方便做线程的统一处理的
Executor:
shutdown():保守型关闭,回收线程,处理完工作就不要线程了(节约资源,Bitmap.recycle())
shutdownNow():严格关闭,终止正在执行的线程
CachedThreadPool:带创建/带缓存/带回收,数量不限
SingleThreadPool:单个线程
FixedThreadPool:固定的线程数量,但是带回收,比如可以做固定线程数的下载,一次性的批量操作
ScheduledThreadPool:带延时
线程池大小和CPU核心数关系:
1个核心一个线程这种写法,不同核心数的线程调度积极性是一样的
Callable实现多线程(很少用):有返回值
Executor使用submit调度,返回Future,Future.get()是阻塞方法,Future.isDone():判断是否线程完成
进程和线程的区别:线程共享资源
Thread:时间分片,在同个时间运行各个任务
UI线程死循环不会卡界面,是UI线程循环过程中发生耗时或者死循环才会卡界面
线程同步线程安全
synchronized:资源互斥访问,数据同步
保护的是资源不是动作
synchronized修饰方法(monitor都是该类,两个不相关资源用的同个monitor,加了同个锁,两个用到不相关资源的都需要互相等待,效率低) -> synchronized修饰方法的部分,可以用new Object指定monitor,资源不共享(锁细化化)
一般操作是CPU的告诉缓存执行,数据同步是操作时从内存拷出来,操作完拷回去内存
为什么耗时?其他线程要来问,被monitor阻碍,要等待,排队拿monitor
Monitor:保证资源只被一个线程访问
数字写法:1_000_000_000
死锁:
public void fun1() {
synchronized(monitor1){
// do something
// waiting...
synchronized(monitor2){}
}
}
public void fun2() {
synchronized(monitor2){
// do something
// waiting...
synchronized(monitor1){}
}
}
数据库相关(数据修改,和线程无关):
悲观锁:synchronized就是悲观锁,锁着,别人不能改
乐观锁:先不加锁,先改,后面发现被改过了,再改一次
静态方法加锁:Object.class,即类也能作为monitor
volatile:小型的synchronized,对基本类型(对象本身的赋值操作,其他的不行,不能保证改变对象属性)的赋值可以保证原子操作,可见性
不能保证i++(实际上是先加1,后赋值)是原子性,需要配合synchronized,可以用Automatic
double可能不是原子操作,是复杂操作
Lock:功能和synchronized没区别,抛异常不会主动执行unlock,写着还麻烦
public void fun() {
lock.lock();
try {
......
} finally{
lock.unlock()
}
}
一般拿来实现读写锁(进一步锁细化),读(可一起读)和写的加锁程度不一样,读加读锁,写加写锁
17.线程间通信的本质和原理,以及 Android 中的多线程
线程交互:
stop:太强势了,和断电一样,直接终止,会影响程序的结果
interrupt:温和版本,设置线程为打断标志,通知线程可以终止,需要线程内isInterrupted()/Thread.interrupted()<判断并设置会非打断状态>配合来写
终止线程是为了避免资源浪费,不处理没意义的工作
Thread.sleep()为什么要捕获异常:
线程sleep的时候,外部可能会interrupt打断,就像睡觉被叫醒(如需中断,抛异常也需要return,否则中断失效,interrupt重置为false,但是为什么这样设计)
Object的wait():和notify/notifyAll配合使用,wait释放monitor(锁),进入等待队列,等待唤醒
wait可能不知道会被谁唤醒(interrupt也可以),所以不能用if判断,需要while判断,因为唤醒后是wait后开始执行的
因为是monitor调用的,所以wait是Object方法
wait/notify/notifyAll 需要包裹在synchronized里,需要拿到monitor才能去判断是否需要等待/唤醒,不是多线程共享资源,没有这个需求
join:插入线程操作,A线程希望在B线程执行完以后,在继续后续的方法
yield:暂时让出自己的时间片,把执行分配给同优先级的线程 -> 往后排队
但是让的幅度不确定,与操作系统有关。
ThreadLocal使得不同线程持有不同副本互不干扰,Looper可以拿ThreadLocal来存储(像Map,key是线程)
Handler是往运行中的线程放入任务(线程)
AsyncTask:是个GC Root,所以静态类,会内存泄漏(但是线程运行完就释放了,线程时间很久的需要防范)
GC Root:
- 运行中的线程
- 静态对象
- 本地代码的引用
具体选择:
AsyncTask:小任务,推到前台
Handler:推到前台
Executors:推到后台,能用就用
HandlerThread:把任务丢给后台执行的单线程任务,非UI线程(newSingleThreadPool),HandlerThread可以利用Handler来终止线程(Handler.removeCallback),场景很特殊,很少用到
Service:后台任务的活动空间
IntentService:需要context,运行一次就结束
android performance patterns:android性能典范
18.RxJava 的原理完全解析
都在subscribeActual方法处理或者装饰,实现功能
Single:
single.just/SingleJust:订阅后马上取消(Dispose.dispose())并且成功,不会出现Error,无延迟无后续(只有一个数据操作)
image.png
操作符:对数据流进行操作
map:变成了SingleMap,不和常规的一样返回this
image.png
下游:A的回调方法调用到B,B是下游
Dispose:丢弃
- 传递取消引用:ObserveableInterval.AtomicReference,取消下游的,会把上游的取消
- 实施转换,实时移交给上游
ObserveableInterval:用的Executors实现
Dispose.replace():把内部上游替换为下游
线程切换:Scheduler做线程调度(DisposeTask:可取消的任务),mainThread,通过Handler实现
subscribeOn:Retrofit2可以统一设置(只生效一次)
控制订阅的,针对上面部分的操作
多次切换只有第一次生效
image.png
observeOn:收到以后切线程,针对下面部分操作
连续多次切换,生效的只有最后一个,但是多次切换,是可以把需要部分切换的
19.Java I/O 和 Okio
IO:插管道,Stream
JDK7:把可以关闭的资源(创建操作)放在try(...)里面可以帮我们做关闭操作
最直接是InputStream读字节,OutputStream写字节
Reader是装饰InputStream的,实现更方便的读操作
BufferedReader:加了缓冲,一次性读很多字节,减少io操作。
BufferedWriter:原理和读一样,但是是写,要把东西写过去,需要调用flush()把缓冲的字节数据刷过去(不一定要手动,缓冲满了会flush,文件关闭的时候会flush,算是考虑优化了)
InputStream.read():返回表示读到的字节数量,-1是没数据了,其他包装类的readLine()之类同理
Socket通过InputStream/OutputStream读写数据
NIO:插管道,Channel(双向),buffer可以被操作,强制使用buffer,非阻塞式支持(仅网络,不支持文件)
buffer原理:
就是可以操作buffer,所以复杂了,拿数据前要把limit置为position置为0(flip():翻页),可能要继续读。
ByteBuffer.clear():重置,把position置为0,limit放到最后(capicity)
改为非阻塞式:Channel.configureBlocking(false)
Selector:选择器,负责分配接收/读/写
Okio:单向插管
输入:Source,输出:Sink
ObjectOutputStream和Okio的Buffer的readUTF()不同,Okio的是以UTF字符读,但是ObjectOutputStream是先加个表示是UTF编码的头(历史原因,之前字符串编码不确定)
okio的Buffer:
- 可作为内部对象,拿来存储读取的数据
- 可作为外部对象,对外暴露了InputStream/OutputStream(就可以用Buffer的API了)
AIO:异步IO
20.Gradle 配置文件拆解
闭包:传递方法,稍后执行
用了methodMissing机制,没必要把各个方法完整调用搞清楚的
buildType:debug/release版本会自动关联文件夹名,可以自己配(initWith:延用配置)
productFlavors:多渠道包,免费收费版,国际版(flavorDimensions实现多维,来定制版本)
implementation和compile(api)的区别:
compile会传递依赖,依赖库(包括开源库)发生改变,编译过程依赖他的库也要重新编译(但是会影响打包)
grale wrapper:gradlew
检查本地有没有安装对应版本gradle,没有则去下载
通过setting.gradle,判断项目结构,确定项目层级
task:
task分为配置(运行gradle都会执行)和执行部分(doFirst/doLast)
doFirst/doLast都在配置后执行,有个任务队列,写task肯定要写在任务队列里
doFirst:把任务插到最前面,相当于add(0,xxx)
doLast:相当于add(xxx)
调用:./gradlew <task名称>
dependOn:在任务A执行后执行B -> task B(dependOn: A)
task的阶段:
- 初始化:,找到有哪些project,1和2阶段操作插操作setting.gradle下添加
- 定义:生成有效无环图,为执行task做准备
- 执行:执行task,2和3阶段插操作在project的gradle下加afterEvaluate
clean的delete
21.Groovy 语法和自己编写 Gradle Plugin
plugin:做配置操作,配置灵活,可复用,具有通用性
动态配置参数(extension),执行放在afterEvaluate执行
buildSrc:最先运行,gradle开始执行前会找buildSrc目录下的plugin,可给后面使用(apply 的名称是resources->META-INF->properties的名称),没必要作为子项目配置在setting.gradle中
groovy默认实现set/get方法
使用场景:比如apply 'com.android.application'可以简化build.gradle,灵活配置。
Transform:依赖com.android.tools.build:gradle(平时是classpath)
从把class文件打包到dex插一脚(方法:字节码操作)
对java字节码进行操作(Javassist/ASM),要改写有段固定代码不能少,不写就没有class了(其实感觉是库写的不合理)
class(directoryInput),jar(jarInput)
性能优化软件应该是利用了Transform,可以给所有方法加个执行时间
22.Git 深入之核心概念:一切皆引用
分布式:
- 提交的时候可以不联网(中央式提交前不能改动)
- 仓库比较大(包含了很多分支信息和远程镜像)
中央仓库:(游戏公司一般采用中央式,媒体文件很大)
working-tree
add:把改动推到暂存区
仓库:.git目录,记录了所有改动
clone:把东西从远端仓库把最新的东西拿到本地(一个接一个改动历史记录,包含分支,把引用也拿来了,在本地创建镜像[origin/xxxx])
push:把本地最新提交和之前没提交的,提交到远程,并且如果当前是master远程更新HEAD、本地更新origin/master、origin/HEAD,不是则更新orgin/(brach名)
pull:fetch选择分支的所有的commit,在本地创建镜像(orgin/xxx),再merge选择分支的更新
引用:指向一个commit的指针
HEAD:永远指向当前的位置(工作区域的位置),当下位置的别称;主动移动Head,工作区域也会改变(配合branch使用),只有HEAD可以指向别的branch(引用),分支之间不能互相指向
master:默认分支,远端仓库的HEAD指向的分支
切分支:切HEAD,把HEAD指向的引用换一下
merge:commit是不可改变的,merge从两个commit新建commit,并记录提交信息
- 先pull,把master指向master<-HEAD最新的拿到本地,才能merge
- merge好,push发现远端又有人push了东西,需要再pull一次
status:看当前状态(文件改动状态、暂存区存储状态),git diff:可以看得更仔细。结合commit本地可以多次提交
git pull = git fetch + git merge
checkout:把HEAD指向某个branch或者某个,可以在各个提交之间切换,
commit(hash值表示哪个commit)
git checkout --detach
fast-forward:master没有修改过,merge直接把分支的提交即可
Feature Branching:功能都放分支做,合到主分支
Pull Request:在github上可以提交,code review,进行merge(git merge <bransh> --no--ff)
--no--ff:推荐merge的时候使用,禁止fast-forward,需要创建一个commit,记录提交日志
LGTM:looks good to me
23.Git 深入之二:交互式 rebase、交互式 add 和工作流
rebase:变基,把还没merge分支拉直
commit不会变
image.png
交互式rebase:修改本地commit和branch(增删改,改是增加了commit丢了原来的commit)
git rebase -i HEAD~3:HEAD往前三个
git commit --amend:可以快捷改动最新的commit
git revert <hash值>:撤回某个commit
git reset:
--hard:不考虑本地修改,移动HEAD
--soft:考虑本地修改,比较文件差异
和checkout的区别:checkout仅移动HEAD,reset拖着Branch
交互式add:(一个文件可拆分多次提交,)
git add -i
git diff --cached/stage:工作区和缓冲的区别
git commit -a -m "xxxxx"
tag:一般拿来打版本号,同一个commit可以打多个tag
和branch区别:tag不能更改位置,不能被HEAD指向(HEAD是为了提交的时候拖着branch)
github利用tag来定版本发布
git tag v1.0.0 -a (annotation:功能和提交日志很像)
reflog(默认是HEAD):查看引用移动历史
场景:移动了HEAD,忘记之前是哪里切过来的
(.git存储了引用的移动历史/日志等信息)
git cherry-pick:把多个commit的内容拿过来
场景:突然通知哪个分支废弃了,把分支上的改动同步到主分支
git flow:
image.png
可以理解为推荐的git开发模式,规范团队开发,需要成员都遵守,适合复杂的大项目
合并到release、hotfix分支后,要往develop上合并,做个记录
git的分布式:
- fork:两个远端仓库可以互相pull request,表现出了分布式的特性。
使用场景:看到开源库以后想优化下 - 本地仓库可以clone(receive.denyCurrentBranch:设为为clone的仓库也能push)
实质上是分布式的,但是有个中央式的管理对开发比较方便
fork以后怎么继续同步作者的改动? git fetch upstream
24.常见项目架构的示例及培养自己的架构思路
MVC也没有强制把Model和View联系起来
MVC把拆View抽取,实现方式就是1个Activity对应多View
MVC在Web开发好用,web请求是单次的,但是移动端会自己做跳转,
Android SDK是M-VC,一般接触到的MVP实质上更像MVC
MVP是在实现上把View和Presenter代码分离了,强制性隔离View和Model
View抽取成接口:
- 通用性,Presenter可以对接多种View
- 暴露统一接口,程序员只需要知道View的方法
MVC/MVP(架构:开发规范)
MVVM:(框架:framework,功能特性)
加了双向绑定的MVP
界面数据和内存数据互相关联,实时更新
架构思路:拆,要在项目中体会
25.组件化、插件化和热更新
插件化(要点反射和ClassLoader):热更新,其实不合适,不然审核还干啥用(国内应用商店多,且不自动更新)
组件化和模块化,我认为的区别是拆分粒度,组件化粒度细,模块化是根据业务分的功能块
为什么允许通过反射拿到本来拿不到的类/方法?(xxx.class -> Class.forName()/setAccessible())
<感觉上来说setAccessible设计上不合理,本地把控权限了>
反射目的:
- Java可见性访问(private/protect)是为了保证Safety(内部成员由于不熟悉等情况在代码里下毒),不是为了保证Security(外界恶意攻击)。
- 所以需要突破限制的开发者承担责任的。
- 提供个后门入口,给有特殊需求的人用。
注释 @hide:写framework的人本地不需要限制访问,但是不想被外界(应用开发者)调用
ClassLoader:(实现插件化:DexClassLoader)
groovy/kotlin都能写java程序,编译生成class文件
jvm读class文件的方式都是一样的,机器读jvm转成的机器码方法是不同的(比如不同操作系统开线程不同)
类似https的证书,有个根ClassLoader
类的完整名字的包含包名的(javac 编译个简单java文件做测试不要带包名)
dex:
odex:optimized dex,优化过的dex,针对手机(CPU)做了优化
AOT:Ahead-Of-Time compilation,提前解释成机器码(oat:Optimized Android file Type,AOT生成的文件)
问题:
- Activity需要Manifest注册,要插件化跳转,需要绕路处理的(代理Activity,插桩)
- 资源文件获取,需要重写getAssets、getResources等方法,通过反射获取AssetManager,手动加入插件的资源
assets目录不可获取
AAB:Android App Bundles,需要应用商店支持,商店帮开发人员合并插件和宿主,自动更新
26.手写热更新
ClassLoader.loadClass:(sdk重写了和jdk不同)
双亲委托:
自己从缓存找,然后从上往下找class
image.png
JVMClassLoader.findLoadClass:从缓存区找有没有这个Class
ClassLoader.findClass:给自定义ClassLoader实现
ClassLoader实现热更新(替换class):
缺点:需要重新启动App生效,需要在程序启动的时候替换dexElements
原理:替换DexPathList的dexElements(数组)
步骤:
- 把需要更改的class重新编译
- PathClassLoader根据补丁apk(dex)创建dexElements
- 反射替换DexPathList的dexElements,补丁是dex的时候反射添加进dexElements数组的前面(class按顺序加载的,不是后面的替换前面的)
看源码:
- http://androidxref.com/
-
https://android.googlesource.com/ 下载对应api的源码,把需要看的源码目录放到使用的sdk的sources目录
d8(新版)/dx(旧版):把class转dx的工具
热更新的方式:
热部署:无需要重启Application和Activity(修改指针)
温部署:需要重启Activity(修改指针)
冷部署:需要重启Application(multidex更新方式)
修改指针更新方式,兼容性很成问题
27.简历与面试,以及总结简历、面试与方向,以及总结
注释:给开发者看
注解:给开发者和程序看
Retention:应用范围
- Source:只是程序员,IDE使用,不参与编译
- Class: 能放到class里,但虚拟机不一定能看到
- Runtime:编译到class,运行时可见
Target:应用到什么对象上
@interface:是个特殊的Interface
注解里 value()是默认方法,使用时省略了 value=""
ButterKnife:利用了引用传递,把View的东西传给一个工具类,然后利用注解赋值,其实我感觉乱糟糟的
ButterKnife不是依赖注入,是个绑定框架,应该叫引用注入吧(所以InjectView改为BindView了)
依赖注入:Dagger,通过依赖图获取对象,把对象注入
(把内部依赖的赋值放到其他类里)
AnnotationProcessor:提前把有些可写到class的反射提前实现,生成class
ButterKnife还是用了反射来执行findViewById的,不需要用反射来找注解;生成类用$分隔是为了防止命名冲突
Class.getClass().getCanonicalName():和getName()取得的值有点差异
roundEnv.getRootElements():获取一级子元素,根据条件过滤遍历(为什么不能直接拿注解)
简历:
- 能力需要具体,尽量不要写精通熟悉之类,方便别人问
- 博客/github/发布的个人作品
- 回顾下做过的项目
- 提前了解下对方公司和面试官,针对优点提下问题
- 遇到宽泛的问题:先讲大概框架,再抓具体点讲
- 遇到不会的问题:直说,要求换一个,问倒程序员很简单(我擅长xxx,你问xxx吧)
- 面试主要想验证简历对不对,看下公司需要会的求职者会不会
- 面试失败怎么办:偶合很正常,无缘,没问到会的;多次需要考虑自己的问题
网友评论