美文网首页Android 知识程序员天下Android开发积累
[视频笔记] - Android 高级开发瓶颈突破系列课

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

作者: New_X | 来源:发表于2019-02-11 10:32 被阅读698次

    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个字符组成的字符串编码算法
    • 用途
      1. 让原数据具有字符串所具有的特性,如可放在URL中传输、可保存到文本文件、可通过普通的聊天软件进行文本传输
      2. 防偷窥
        疑问: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,读取图片性能优化?
    步骤:

    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要不要拦截,根据已消费的距离来处理谁优先

    • 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优化:

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

    • 先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抽取成接口:

    1. 通用性,Presenter可以对接多种View
    2. 暴露统一接口,程序员只需要知道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生成的文件)

    问题:

    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:应用范围

    • 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():获取一级子元素,根据条件过滤遍历(为什么不能直接拿注解)

    简历:

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

    相关文章

      网友评论

        本文标题:[视频笔记] - Android 高级开发瓶颈突破系列课

        本文链接:https://www.haomeiwen.com/subject/jrofeqtx.html