Android 知识程序员天下Android开发积累

[视频笔记] - Android 高级开发瓶颈突破系列课

2019-02-11  本文已影响698人  New_X

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)

Transfer-Encoding:chunked,表示Body长度无法确定,Content-Length不能使用
目的:在服务端还未获取到完整内容时,更快对客户端做出相应,减少用户等待
Body格式:(最后传输0表示内容结束)


image.png

Cache:
Cache和Buffer的区别:
Cache是在科幻段或中间网点缓存数据,降低从服务器去数据的频率,以提高网络性能,Buffer是网络数据预加载(视频播放缓冲)
cache过期判断:

3.各种「转换」的作用和对比——编码、加密、Hash、序列化和字符集

加密:

古代密码学(密码棒缠绕)/移位式加密 -> 替换式加密 -> 现代密码学(不仅可用于文本,还可用于各种二进制数据)

对称加密:加解密算法不同,密钥相同
DES、AES(码表比DES的长,所有更安全些)


image.png

非对称加密:加解密算法相同,密钥不同(利用溢出)
RSA、DSA


image.png
拓展:数字签名
image.png
签名是抓取摘要来验证,没必要对整个数据进行签名,传输的时候浪费带宽(对Hash值进行签名)

组合使用:


image.png
密码学密钥和登陆密码区别:
密钥:key - > 数据
编码:

URL Encoding:将URL中的保留字符使用百分号“%”进行编码

压缩与解压缩:

压缩:把数据换一种方式来存储,以减少存储空间
解压缩:把压缩后的数据还原成原先的形式,以便使用
(按照一定的算法,把数据进行重新定义)
常见压缩算法:DEFLATE、JPEG、MP3

序列化:

序列化:把数据对象(一般是内存中的,如JVM中的对象)转成字节序列的过程
反序列化:把字节序列重新转换成内存中的对象
目的:让内存中的对象可以被存储和传输

Hash:

定义:把任意数据转换成指定大小范围(通常很小)的数据,单向的
作用:摘要、数字指纹
经典算法:MD5、SHA1、SHA256(彩虹表:存储了MD5值和数据 -> 对策:加盐)
实际用途:

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,读取图片性能优化?
步骤:

  1. 设置 inJustDecodeBounds为true,只获取图片的大小信息
  2. 根据需要的大小和获取的图片大小计算缩放比例设置 inSampleSize
  3. 设置 nJustDecodeBounds为false重新读取图片

8.文字的测量和几何变换的本质与实用技巧

静态文字垂直居中:用textBounds,当前文字中心
动态文字垂直居中:用fontMetrics,大多文字的中心(用textBounds会上下跳)
居左字体大小不同,导致没有左对齐:因为文字本身间距,绘制的时候减去textBounds的左距离
多行文字绘制:

  1. 简单绘制多行,使用 StaticLayout(TextView也是调用StaicLayout来换行的)
  2. 需要主动截取,使用 breakText

范围裁切:
clipPath裁剪是有锯齿的,因为是从原有图里裁剪(反裁剪)一部分,所以无法去锯齿,裁剪使用 Xfermode 实现

二维/三维变换:变换顺序逆序
Camera的setLocation的单位是英寸,需要适配下dp
在Camera变化前裁剪,因为三维变化后,图片可能会很大,免得裁剪不到

感觉并不需要记那么细,很多概念上的东西,出问题肯定能搜索到的

9.属性动画和硬件加速

  1. View.animate()
  2. ObjectAnimator/ViewPropertyAnimator ValueAnimator
    invalidate() -> 把界面标记为无效,重新调用onDraw()
  3. AnimatorSet/PropertyValuesHolder/KeyFrame
    10ms(针对动画)/16ms(界面刷新标准)
    不用太纠结,很少有复杂的动画需求的
    插值器(入场/出场)
  4. TypeEvaluator(Point/String)
  5. 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

自定义布局:

  1. 测量
    onMeasure(做事)测量模式,基本是固定的,调用resolveSize()/resolveSizeAndState()保留些状态值/标志位,做位运算,但是应该开发不规范,连sdk的也并没有全部用到
    measure (调度用)

  2. 布局
    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()原理:

  1. 接力(抢夺):更改index
  2. 协作:多个点计算中心点,手指抬起的时候不加入弹起手指的坐标,弹起的时候,点也是给了
  3. 互不干扰:比如画图软件多个画笔,存多个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() 的功能一般很像,都是记录初始位置

拖拽:

  1. DragListener
    View.startDrag(),ViewCompat.startDragAndDrop(),拖动支持跨进程了。参数有些松手才能拿到(重量级数据),有些都能跨进程了,拖到其他应用。
    拖起来的是像素不是View,所以可以覆盖到任何地方,拖到某个地方做些操作,松手做些操作

View不设置DragListener也能收到事件,因为有可能其他View和脱出来的像素需要做些重合预览效果
使用场景:内容的拖动,把东西拖过去

  1. ViewDragHelper
    默认吸住四个边,让用户直线滑动,免得手抖
    使用场景:DrawerLayout/底部滑出
  2. 自己手写

contentDescription:无障碍模式

嵌套滑动:
同向(会卡住,场景:文章里加代码,需要滑动)
NestedScrollView:子View优先
自定义View实现(情况很少):
NestedScrollingChild2
子View询问父View要不要拦截,根据已消费的距离来处理谁优先

不同向(触摸事件分发已经差不多兼容了)

插播: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优化:

  1. 在onCreateViewHolder中设置监听,bind绑定一次,设置一次,不妥
    View - ViewHolder - View-OnClickListener
  2. LinearLayoutManager.setInitalPrefetchItemCount():
  3. RecyclerView.setHasFixSize():省去requestLayout
  4. 多个RecyclerView共用RecyclerViewPool
  5. DiffUtil:对item做增量更新
    getChangePayload()不实现,只要部分有改动,整个item全部绑定变化
    计算item的差异是在主线程更新的,数据很大很复杂的时候也很耗时,需要切到异步:
    1. Handler/Thread、RxJava切换到子线程
    2. 使用Google提供的AsyncListDiffer(Executor)/ListAdapter

ItemDecration:可叠加,装饰Item,操作屏幕内的内容
为什么要ItemDecration:

  1. 性能,存储View要多一个,findViewById
  2. 动画要带上,移动要带上

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:

  1. 运行中的线程
  2. 静态对象
  3. 本地代码的引用

具体选择:
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:丢弃

  1. 传递取消引用:ObserveableInterval.AtomicReference,取消下游的,会把上游的取消
  2. 实施转换,实时移交给上游

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:

  1. 可作为内部对象,拿来存储读取的数据
  2. 可作为外部对象,对外暴露了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的阶段:

  1. 初始化:,找到有哪些project,1和2阶段操作插操作setting.gradle下添加
  2. 定义:生成有效无环图,为执行task做准备
  3. 执行:执行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 深入之核心概念:一切皆引用

分布式:

  1. 提交的时候可以不联网(中央式提交前不能改动)
  2. 仓库比较大(包含了很多分支信息和远程镜像)
    中央仓库:(游戏公司一般采用中央式,媒体文件很大)

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,并记录提交信息

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的分布式:

24.常见项目架构的示例及培养自己的架构思路

MVC也没有强制把Model和View联系起来
MVC把拆View抽取,实现方式就是1个Activity对应多View
MVC在Web开发好用,web请求是单次的,但是移动端会自己做跳转,
Android SDK是M-VC,一般接触到的MVP实质上更像MVC

MVP是在实现上把View和Presenter代码分离了,强制性隔离View和Model
View抽取成接口:

  1. 通用性,Presenter可以对接多种View
  2. 暴露统一接口,程序员只需要知道View的方法

MVC/MVP(架构:开发规范)
MVVM:(框架:framework,功能特性)
加了双向绑定的MVP
界面数据和内存数据互相关联,实时更新

架构思路:拆,要在项目中体会

25.组件化、插件化和热更新

插件化(要点反射和ClassLoader):热更新,其实不合适,不然审核还干啥用(国内应用商店多,且不自动更新)

组件化和模块化,我认为的区别是拆分粒度,组件化粒度细,模块化是根据业务分的功能块

为什么允许通过反射拿到本来拿不到的类/方法?(xxx.class -> Class.forName()/setAccessible())
<感觉上来说setAccessible设计上不合理,本地把控权限了>
反射目的:

注释 @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生成的文件)

问题:

  1. Activity需要Manifest注册,要插件化跳转,需要绕路处理的(代理Activity,插桩)
  2. 资源文件获取,需要重写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(数组)
步骤:

  1. 把需要更改的class重新编译
  2. PathClassLoader根据补丁apk(dex)创建dexElements
  3. 反射替换DexPathList的dexElements,补丁是dex的时候反射添加进dexElements数组的前面(class按顺序加载的,不是后面的替换前面的)

看源码:

  1. http://androidxref.com/
  2. https://android.googlesource.com/ 下载对应api的源码,把需要看的源码目录放到使用的sdk的sources目录
    d8(新版)/dx(旧版):把class转dx的工具

热更新的方式:

热部署:无需要重启Application和Activity(修改指针)
温部署:需要重启Activity(修改指针)
冷部署:需要重启Application(multidex更新方式)
修改指针更新方式,兼容性很成问题

27.简历与面试,以及总结简历、面试与方向,以及总结

注释:给开发者看
注解:给开发者和程序看
Retention:应用范围

@interface:是个特殊的Interface
注解里 value()是默认方法,使用时省略了 value=""
ButterKnife:利用了引用传递,把View的东西传给一个工具类,然后利用注解赋值,其实我感觉乱糟糟的
ButterKnife不是依赖注入,是个绑定框架,应该叫引用注入吧(所以InjectView改为BindView了)

依赖注入:Dagger,通过依赖图获取对象,把对象注入
(把内部依赖的赋值放到其他类里)

AnnotationProcessor:提前把有些可写到class的反射提前实现,生成class
ButterKnife还是用了反射来执行findViewById的,不需要用反射来找注解;生成类用$分隔是为了防止命名冲突

Class.getClass().getCanonicalName():和getName()取得的值有点差异

roundEnv.getRootElements():获取一级子元素,根据条件过滤遍历(为什么不能直接拿注解)

简历:

  1. 能力需要具体,尽量不要写精通熟悉之类,方便别人问
  2. 博客/github/发布的个人作品
  3. 回顾下做过的项目
  4. 提前了解下对方公司和面试官,针对优点提下问题
  5. 遇到宽泛的问题:先讲大概框架,再抓具体点讲
  6. 遇到不会的问题:直说,要求换一个,问倒程序员很简单(我擅长xxx,你问xxx吧)
  7. 面试主要想验证简历对不对,看下公司需要会的求职者会不会
  8. 面试失败怎么办:偶合很正常,无缘,没问到会的;多次需要考虑自己的问题
上一篇下一篇

猜你喜欢

热点阅读