基础知识面试准备Android面试

2020年 中级Android面试总结

2020-03-31  本文已影响0人  _明川

update time 2020年4月24日10点41分 ,阅读时间20分钟。主要收集在面试过程中普遍问到的基础知识(面试收集 主要来自于bilibili 嵩恒 蚂蚁金服等互联网公司)

TODO

目录在简书平台不可用,误点!!!

网络 Java 基础

网络

网络模块 面试主要涉及到 应用层(HTTP DNS等) 和 传输层 (TCP UDP)的东西,

TCP UDP

TCP 连接 :传输可靠;有序;面向字节流;速度慢;较重量;全双工; 适用于文件传输、浏览器等

全双工:A 给 B 发消息的同时,B 也能给 A 发
半双工:A 给 B 发消息的同时,B 不能给 A 发

UDP 无连接 :传输不可靠;无序;面向报文;速度快;轻量; 适用于即时通讯、视频通话等

三次握手四次挥手

参考文章

  1. 建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
  2. 服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
  3. 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成。
  1. 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
  2. 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
  3. 客户端收到服务器的确认请求后,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
  4. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
  5. 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
  6. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。所以服务器结束TCP连接的时间要比客户端早一些。

HTTP HTTPS

HTTP 是超文本传输协议,明文传输;HTTPS 使用 SSL 协议对 HTTP 传输数据进行了加密
HTTP 默认 80 端口;HTTPS 默认 443 端口

优点:安全
缺点:费时、SSL 证书收费,加密能力还是有限的,但是比 HTTP 更加安全,也是大势所趋。

Get 参数放在 url 中;Post 参数放在 request Body 中
Get 可能不安全,因为参数放在 url 中,并且对于传送的数据长度有限制。

JVM

内存模型

  1. 栈:储存局部变量 (线程私有 使用完毕就会释放)
  2. 堆:储存 new 出来的东西 成员方法等 (线程共享 使用完毕 等待gc清理)
  3. 方法区: 对象的运行过程 (线程共享)
  4. 本地方法区:为系统方法使用 (线程私有)
  5. 寄存器:为CPU提供
  6. 程序计数器:指向当前线程正在执行的指令的地址,确保多线程下正常运行(线程私有)

Jvm 调用方法过程

GC清理 及 清理算法

清理算法

线程 进程

进程:进程是系统进行资源分配和调度的一个独立单位 (拥有独立内存空间),一个app就是一个进程。

线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一些在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

Android 进程通讯方式

Android 线程通信

HandlerAsyncTask (AsyncTask:异步任务,内部封装了Handler)

sleep wait 等 区别

注意:wait方法,notify方法,notifyAll方法必须放在synchronized block中,否则会抛出异常。

数据结构

ArrayList 、LinkedList 和SparseArray

HashMap HashTable 和 ArrayMap

设计模式

多线程

synchronized 参考文章

锁问题

可重入锁 :已经获取到锁后,再次调用同步代码块/尝试获取锁时不必重新去申请锁,可以直接执行相关代码。 ReentrantLock 和 synchronized 都是可重入锁
公平锁 : 等待时间最久的线程会优先获得锁 , synchronized lock 默认都为非公平锁

锁主要分为:悲观锁 (线程一旦得到锁,其他线程就挂起等待。用于写入频繁的 synchronized)和 乐观锁(假设没有冲突,不加锁,更新数据时判断该数据是否过期,过期的话则不进行数据更新,适用于读取操作频繁的场景,比如 AtomicInteger、AtomicLong、AtomicBoolean)

锁的状态依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。锁可以从偏向锁升级到轻量级锁,再升级的重量级锁。但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。

双重检查单例加volatile的原因 : 将instance =newInstance(); 创建实例分为三个过程 ,1.分配内存 2.初始化 3.将instance指向分配的内存空。但是如果 在另一个线程中想要使用instance,发现instance!=null,但是实际上instance还未初始化完毕这个问题。

synchronized 原理

Synchronized可以把任何一个非null对象作为"锁",其中都有一个监视器锁:monitor。Synchronized的语义底层是通过一个monitor的对象来完成。

  1. 同步代码块(synchronize(this)的方式)会执行 monitorenter 开始,motnitorexit 结束,当线程进入monitor,如果为0 则改为1 并分配使用,如果为1 则需要挂起等待。其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
  2. 同步代码块采用( synchronized void method()的方式)调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

两种同步方式本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。

synchronized 在JDK 1.6以后的优化

  1. 自适应自旋锁 :指一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放(自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定),而不是进入线程挂起或睡眠状态(因为为了很短的等待时间就去挂起唤醒会 更低效)。

  2. 锁消除:但是在有些情况下,JVM检测到不存在共享数据竞争,JVM会对这些同步锁进行锁消除。

  3. 锁粗化 :就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。

  4. 偏向锁、轻量级锁:轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时通过标识,避免再走各种加锁/解锁流程,达到进一步提高性能。

synchronized 添加在非静态方法上为 对象锁,如果在静态方法上为类锁。

ClassLoader

类加载过程

  1. 加载:获取类的二进制字节流;生成方法区的运行时存储结构;在内存中生成 Class 对象
  2. 验证:确保该 Class 字节流符合虚拟机要求
  3. 准备:初始化静态变量
  4. 解析:将常量池的符号引用替换为直接引用
  5. 初始化:执行静态块代码、类变量赋值
  6. 使用
  7. 卸载

Android

常用设计模式及源码使用

public class Singleton {
    private static volatile Singleton s;
    private Singleton(){};
    public static Singleton getInstance() {  
        if(s == null) {
            synchronized (Singleton.class) {
                if(s == null) {
                    s = new Singleton(); 
                }
            }
        }
        return s; 
    }
}
public class TestClient {
    
    private int index;
    private String name;
    public TestClient() {
        this(new Builder());
    }
    public TestClient(Builder builder){
        this.index = builder.index;
        this.name = builder.name;
    }

    public static final class Builder {
        private int index;
        private String name;
        public Builder() {
            this.index = 1;
            this.name = "xxx";
        }
        public Builder(TestClient testClient){
            this.index = testClient.index;
            this.name = testClient.name;
        }
        public Builder setIndex(int index) {
            this.index = index;
            return this;
        }
        public Builder setName(String name) {
            this.name = name;
            return this;
        }
        public TestClient build(){
            return new TestClient(this);
        }
    }
}

线程池

线程池参考文章

启动模式

  1. standard 标准模式
  2. singleTop 栈顶复用模式 (例如:推送点击消息界面)
  3. singleTask 栈内复用模式 (例如:首页)
  4. singleInstance 单例模式 (单独位于一个任务栈中,例如:拨打电话界面)

序列化

进程

IPC 进程通讯方式

进程保活

进程被杀原因:1.切到后台内存不足时被杀;2.切到后台厂商省电机制杀死;3.用户主动清理

保活方式:

  1. Activity 提权:挂一个 1像素 Activity 将进程优先级提高到前台进程
  2. Service 提权:启动一个前台服务(API>18会有正在运行通知栏)
  3. 广播拉活 (监听 开机 等系统广播)
  4. Service 拉活
  5. JobScheduler 定时任务拉活 (android 高版本不行)
  6. 双进程拉活
  7. 监听其他大厂 广播 (tx baidu 全家桶互相拉)

Hook

Hook android 使用

Hook 的选择点:静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。
Hook 过程:

寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。
选择合适的代理方式,如果是接口可以用动态代理。
偷梁换柱——用代理对象替换原始对象。

多数插件化 也使用的 Hook技术

内存泄漏

View

Window WindowManager WMS

个人源码文章

activity启动流程

image

View 工作流程

通过 SetContentView(),调用 到PhoneWindow ,后实例DecorView ,通过 LoadXmlResourceParser() 进行IO操作 解析xml文件 通过反射 创建出View,并将View绘制在 DecorView上,这里的绘制则交给了ViewRootImpl 来完成,通过performTraversals() 触发绘制流程,performMeasure 方法获取View的尺寸,performLayout 方法获取View的位置 ,然后通过 performDraw 方法遍历View 进行绘制。

事件分发

一个 MotionEvent 产生后,按 Activity -> Window -> DecorView(ViewGroup) -> View 顺序传递,View 传递过程就是事件分发,因为开发过程中存在事件冲突,所以需要熟悉流程:

View.post

参考博客1
参考博客2

想要在 onCreate 中获取到View宽高的方法有:

  1. ViewTreeObserver 监听界面绘制事件,在layout时调用,使用完毕后记得removeListener
  2. 就是View.post

源码分析

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        getRunQueue().post(action);
        return true;
    }

View.post(runable) 通过将runable 封装为HandlerAction对象,如果attachInfo为null 则将Runnable事件 添加到等待数组中, attachInfo初始化是在 dispatchAttachedToWindow 方法,置空则是在detachedFromWindow方法中,所以在这两个方法生命周期中,调用View.post方法都是直接让 mAttachInfo.handler 执行。

ViewRootImpl.class

mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);

final ViewRootHandler mHandler = new ViewRootHandler();

通过查找 mAttachInfo.handler 是在主线程中声明的,没有传参则 Looper 为主线程Looper,所以在View.post中可以更新UI。

但是为什么可以再View.post()中获取控件尺寸呢?
android 运行是消息驱动,通过源码 可以看到 ViewRootImpl 中 是先将 TraversalRunnable添加到 Handler 中运行的 之后 才是 View.post()

ViewRootImpl.class

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            // 该方法之后才有 view.post() 
            performTraversals();
            ...
        }
    }

因此,这个时候Handler正在执行着TraversalRunnable这个Runnable,而我们post的Runnable要等待TraversalRunnable执行完才会去执行,而TraversalRunnable这里面又会进行measure,layout和draw流程,所以等到执行我们的Runnable时,此时的View就已经被measure过了,所以获取到的宽高就是measure过后的宽高。

动画

提升动画 可以打开 硬件加速,使GPU 承担一部分CPU的工作。

Handler线程间通信

作用:线程之间的消息通信

流程:主线程默认实现了Looper (调用loop.prepare方法 向sThreadLocal中set一个新的looper对象, looper构造方法中又创建了MsgQueue) 手动创建Handler ,调用 sendMessage 或者 post (runable) 发送Message 到 msgQueue ,如果没有Msg 这添加到表头,有数据则判断when时间 循环next 放到合适的 msg的next 后。Looper.loop不断轮训Msg,将msg取出 并分发到Handler 或者 post提交的 Runable 中处理,并重置Msg 状态位。回到主线程中 重写 Handler 的 handlerMessage 回调的msg 进行主线程绘制逻辑。

问题:

  1. Handler 同步屏障机制:通过发送异步消息,在msg.next 中会优先处理异步消息,达到优先级的作用
  2. Looper.loop 为什么不会卡死:为了app不挂掉,就要保证主线程一直运行存在,使用死循环代码阻塞在msgQueue.next()中的nativePollOnce()方法里 ,主线程就会挂起休眠释放cpu,线程就不会退出。Looper死循环之前,在ActivityThread.main()中就会创建一个 Binder 线程(ApplicationThread),接收系统服务AMS发送来的事件。当系统有消息产生(其实系统每 16ms 会发送一个刷新 UI 消息唤醒)会通过epoll机制 向pipe管道写端写入数据 就会发送消息给 looper 接收到消息后处理事件,保证主线程的一直存活。只有在主线程中处理超时才会让app崩溃 也就是ANR。
  3. Messaage复用: 将使用完的Message清除附带的数据后, 添加到复用池中 ,当我们需要使用它时,直接在复用池中取出对象使用,而不需要重新new创建对象。复用池本质还是Message 为node 的单链表结构。所以推荐使用Message.obation获取 对象。

Android 和WebView 通信

     webView.evaluateJavascript(String.format("javascript:callH5Re('测试数据')"), new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            Log.e("Test", "onReceiveValue: "+value );
        } });

app优化 (项目中处理的一些难点)

主要分为 启动优化,布局优化 ,打包优化 等

启动优化

  1. 闪屏页 优化,设置theme 默认欢迎背景
  2. 懒加载 第三方库,不要都放在application 中初始化
  3. 如果项目中有 webview ,可以提前在app空闲时间加载 webview 的内核,如果多处使用 可以创建缓存池,缓存webview,
  4. 如果android 5.0- 在applicaton 的 attchbaseContext() 中加载MultiDex.install 会更加耗时,可以采用 子线程(子线程加载 需要担心ANR 和ContentProvider 未加载报错的问题)或者单独开一个进程B,进程B开启子线程运行MultiDex.install ,让applicaton 进入while 循环等待B进程加载结果。
    MultiDex 优化,apk打包分为 android 5.0 + 使用 ART虚拟机 不用担心

布局UI优化

看过布局绘制源码流程后,可以知道 setContextView中 在ViewRootImpl 中使用 pull 的方法(这里可以扩展xml读取方式 SAX :逐行解析、dom:将整个文件加载到内存 然后解析,不推荐、pull:类似于 SAX 进行了android平台的优化,更加轻量级 方便)迭代读取 xml标签,然后对view 进行 measure,layout 和draw 的时候都存在耗时。通常优化方式有:

  1. 减少UI层级、使用merge、Viewstub标签 优化重复的布局
  2. 优化 layout ,尽量多使用ConstraintLayout,因为 relalayout 和 linearlayout 比重的情况下都存在多次测量
  3. recyclerView 缓存 ( 可扩展 说明 rv的缓存原理 )
  4. 比较极端的 将 measure 和 layout 放在子线程,在主线程进行draw。或者 子线程中 加载view 进行IO读取xml,通过Handler 回调主线程 加载view(比如android 原生类 AsyncLayoutInflate )
  5. 将xml直接通过 第三方工具(原理 APT 注解 翻译xml)直接将xml 转为 java代码
    更多UI优化文章

打包优化

Analyze APK 后可以发现代码 和 资源其实是 app包的主要内存

  1. res 文件夹下 分辨率下的图片 国内基本提供 xxhdpi 或者 xhdpi 即可,android 会分析手机分辨率到对应分辨率文件夹下加载资源
  2. res中的 png 图片 都可以转为 webg 或者 svg格式的 ,如果不能转 则可以通过 png压缩在减少内存
  3. 通过在 build.gradle 中配置 minifyEnabled true(混淆)shrinkResources true (移除无用资源)
  4. Assests 中的 mp4 /3 可以在需要使用的时候从服务器上下载下来,字体文件 使用字体提取工具FontZip 删除不用的文字格式,毕竟几千个中文app中怎么可能都使用
  5. lib 包如果 适配机型大多为高通 RAM ,可以单独引用abiFilters "armeabi-v7a"
  6. build文件中 resConfigs "zh" 剔除掉 官方中或者第三方库中的 外国文字资源

第三方库 源码总结

LeakCanary 原理

参考博客

通过 registerActivityLifecycleCallbacks 监听Activity或者Fragment 销毁时候的生命周期(如果不想那个对象被监控则通过 AndroidExcludedRefs 枚举,避免被检测)

public void watch(Object watchedReference, String referenceName) {
   if (this == DISABLED) {
     return;
   }
   checkNotNull(watchedReference, "watchedReference");
   checkNotNull(referenceName, "referenceName");
   final long watchStartNanoTime = System.nanoTime();
   String key = UUID.randomUUID().toString();
   retainedKeys.add(key);
   final KeyedWeakReference reference =
       new KeyedWeakReference(watchedReference, key, referenceName, queue);

   ensureGoneAsync(watchStartNanoTime, reference);
 }

然后通过弱引用和引用队列监控对象是否被回收(弱引用和引用队列ReferenceQueue联合使用时,如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。即 KeyedWeakReference持有的Activity对象如果被垃圾回收,该对象就会加入到引用队列queue

void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }

IdleHandler,就是当主线程空闲的时候,如果设置了这个东西,就会执行它的queueIdle()方法,所以这个方法就是在onDestory以后,一旦主线程空闲了,就会执行一个延时五秒的子线程任务,任务:检测到未被回收则主动 gc ,然后继续监控,如果还是没有回收掉,就证明是内存泄漏了。 通过抓取 dump文件,在使用 第三方 HAHA 库 分析文件,获取到到达泄露点最近的线路,通过 启动另一个进程的 DisplayLeakService 发送通知 进行消息的展示

OkHttp

参考博客
☆平头哥 博客链接

同步和异步 网络请求使用方法


        // 同步get请求
        OkHttpClient okHttpClient=new OkHttpClient();
        final Request request=new Request.Builder().url("xxx").get().build();
        final Call call = okHttpClient.newCall(request);
        try {
            Response response = call.execute();
        } catch (IOException e) {
        }

        
        //异步get请求 
        OkHttpClient okHttpClient=new OkHttpClient();
        final Request request=new Request.Builder().url("xxx").get().build();
        final Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
            }
        });

        // 异步post 请求
        OkHttpClient okHttpClient1 = new OkHttpClient();
        RequestBody requestBody = new FormBody.Builder()
                .add("xxx", "xxx").build();
        Request request1 = new Request.Builder().url("xxx").post(requestBody).build();
        okHttpClient1.newCall(request1).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
            }
        });

同步请求流程
通过OkHttpClient new生成call实例 Realcall
Dispatcher.executed() 中 通过添加realcall到runningSyncCalls队列中
通过 getResponseWithInterceptorChain() 对request层层拦截,生成Response
通过Dispatcher.finished(),把call实例从队列中移除,返回最终的response

异步请求流程
生成一个AsyncCall(responseCallback)实例(实现了Runnable)
AsyncCall通过调用Dispatcher.enqueue(),并判断maxRequests (最大请求数)maxRequestsPerHost(最大host请求数)是否满足条件,如果满足就把AsyncCall添加到runningAsyncCalls中,并放入线程池中执行;如果条件不满足,就添加到等待就绪的异步队列,当那些满足的条件的执行时 ,在Dispatcher.finifshed(this)中的promoteCalls();方法中 对等待就绪的异步队列进行遍历,生成对应的AsyncCall实例,并添加到runningAsyncCalls中,最后放入到线程池中执行,一直到所有请求都结束。

责任链模式 和 拦截器

责任链
源码跟进 execute() 进入到 getResponseWithInterceptorChain() 方法

Response getResponseWithInterceptorChain() throws IOException {
    //责任链 模式
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    return chain.proceed(originalRequest);
  }

chain.proceed() 方法核心代码。每个拦截器 intercept()方法中的chain,都在上一个 chain实例的 chain.proceed()中被初始化,并传递了拦截器List与 index,调用interceptor.intercept(next),直接最后一个 chain实例执行即停止。

//递归循环下一个 拦截器
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    while (true) {
        ...
        // 循环中 再次调用了 chain 对象中的 proceed 方法,达到递归循环。
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
        ...
    }
}

拦截器

  1. RetryAndFollowUpInterceptor :重连并跟踪 拦截器。
  2. BridgeInterceptor : 将用户请求构建为网络请求(hander cooker content-type 等) 并发起请求 。
  3. CacheInterceptor : 缓存拦截器 负责从缓存中返回响应和把网络请求响应写入缓存。
  4. ConnectInterceptor : 与服务端 建立连接,并且获得通向服务端的输入和输出流对象。

OkHttp 流程

  1. 采用责任链方式的拦截器,实现分成处理网络请求,可更好的扩展自定义拦截器(采用GZIP压缩,支持http缓存)
  2. 采用线程池(thread pool)和连接池(Socket pool)解决多并发问题,同时连接池支持多路复用(http2才支持,可以让一个Socket同时发送多个网络请求,内部自动维持顺序.相比http只能一个一个发送,更能减少创建开销))
  3. 底层采用socket和服务器进行连接.采用okio实现高效的io流读写

ButterKnife

参考文章

butterKnife 使用的是 APT 技术 也就是编译时注解,不同于运行时注解(在运行过程中通过反射动态地获取相关类,方法,参数等信息,效率低),编译时注解 则是在代码编译过程中对注解进行处理(annotationProcessor技术),通过注解获取相关类,方法,参数等信息,然后在项目中生成代码,运行时调用,其实和直接手写代码一样,没有性能问题,只有编辑时效率问题。
ButterKnife在Bind方法中 获取到DecorView,然后通过Activity和DecorView对象获取xx_ViewBinding类的构造对象,然后通过构造方法反射实例化了这个类 Constructor。
在编写完demo之后,需要先build一下项目,之后可以在build/generated/source/apt/debug/包名/下面找到 对应的xx_ViewBinding类,查看bk 帮我们做的事情,

xx_ViewBinding.java
@UiThread
  public ViewActivity_ViewBinding(ViewActivity target, View source) {
    this.target = target;

    target.view = Utils.findRequiredView(source, R.id.view, "field 'view'");
  }

Utils.java
public static View findRequiredView(View source, @IdRes int id, String who) {
    View view = source.findViewById(id);
    if (view != null) {
      return view;
    }
    String name = getResourceEntryName(source, id);
    throw new IllegalStateException("Required view ...."}

通过上述上述代码 可以看到 注解也是帮我们完成了 findviewbyid 的工作。

butterknife 实现流程

  1. 扫描Java代码中所有的ButterKnife注解
  2. 发现注解, ButterKnifeProcessor会帮你生成一个Java类,名字<类名>$$ViewBinding.java,这个新生成的类实现了Unbinder接口,类中的各个view 声明和添加事件都添加到Map中,遍历每个注解对应通过JavaPoet生成的代码。

Rxjava 2

Rxjava源码

切换到子线程用的 线程池 ,切换到主线程则用的Handler

算法

冒泡

public static void bubble(int[] arr){
        
        int temp;
        //根据角标进行比较,
        for(int i = 0; i<arr.length; i++){
            //j是数组的最后一个角标
            for (int j = arr.length-1; j > i; j--) {
                
                if (arr[j] < arr[j - 1]) {
                    //从后往前进行比较,小数往前,一轮之后最小数就在最前面了
                    temp = arr[j - 1];
                    arr[j - 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }

二分法

public static int myBinarySearch(int[] arr,int value) {
        int low=0;
        int high=arr.length-1;
        while(low<=high) {
            int mid=(low+high)/2;
            if(value==arr[mid]) {
                return mid;
                }
            if(value>arr[mid]) {
                low=mid+1;  
            }
            if(value<arr[mid]) {
                high=mid-1;
            }
            
        }
        return -1;//没有找到返回-1
    }

上一篇 下一篇

猜你喜欢

热点阅读