前语

  • 2022年对大部分人来说真的是不容易的一年,有不少粉丝私信问我,本年行情欠好,但是现在公司又欠好怎么办,我的建议便是学习。无论过去,现在,未来,投资自己一定是不会错的,只有当你满足强壮,哪怕日子一地鸡毛,你也能垫起脚尖独揽星空。
  • 对于Android来说,我觉得有两个才能和一个情绪一定要把握

阅览源码的才能

  • 个人技巧:我个人阅览源码喜欢自己给自己提问题,随后带着问题去读源码的流程,当遇到不确定的能够看看其他大神写的博客和视频。
为什么需要具有阅览源码的才能呢?

当咱们经过百度查找视频,博客,stackOverflow找不到咱们问题处理办法的时分,能够经过阅览源码来寻觅问题,并处理问题,如以下两个事例

一、AppBarLayout暗影问题

  • 源码地址:github.com/Peakmain/Ba…
  • 咱们每次在项目增加头部的时分,一般做法都是说界说一个共用的布局,但是这其实并不友好,而且每次都需要findVIewById,为了处理上述问题,我用了Builder规划形式规划了NavigationBar,能够动态增加头部
  • 其中有个默许的头部规划DefaultNavigationBar,运用的是AppBarLayout+ToolBar,AppBarLayout有个问题便是会存在暗影,我想要在不改变布局的情况下,动态设置撤销暗影,在百度中得到的前篇一律的答案是,设置主题,布局中设置暗影

BasicLibrary架构设计旅程(一)—Android必备技能

  • 既然说布局中设置elevation有用,那么是否能够经过findViewById找到AppBarLayout然后设置elevation=0
findViewById<AppBarLayout>(R.id.navigation_header_container).elevation=0f

运转之后,发现暗影还仍然存在

  • 既然布局中设置elevation有用,那它的源码怎么写的呢? 咱们能够在AppBarLayout的结构函数中找到这行代码

BasicLibrary架构设计旅程(一)—Android必备技能

咱们能够发现最终调用的是一个非公正类的静态办法,直接将办法拷贝到咱们自己的项目,之后调用该办法

  static void setDefaultAppBarLayoutStateListAnimator(
      @NonNull final View view, final float elevation) {
    final int dur = view.getResources().getInteger(R.integer.app_bar_elevation_anim_duration);
    final StateListAnimator sla = new StateListAnimator();
    // Enabled and liftable, but not lifted means not elevated
    sla.addState(
        new int[] {android.R.attr.state_enabled, R.attr.state_liftable, -R.attr.state_lifted},
        ObjectAnimator.ofFloat(view, "elevation", 0f).setDuration(dur));
    // Default enabled state
    sla.addState(
        new int[] {android.R.attr.state_enabled},
        ObjectAnimator.ofFloat(view, "elevation", elevation).setDuration(dur));
    // Disabled state
    sla.addState(new int[0], ObjectAnimator.ofFloat(view, "elevation", 0).setDuration(0));
    view.setStateListAnimator(sla);
  }

BasicLibrary架构设计旅程(一)—Android必备技能

二、Glide加载图片读取设备类型问题

  • 再比方App加载网络图片时分,App移动运用检测的时分说咱们运用本身获取个人信息行为,描述说的是咱们有图片上传行为,看了堆栈,首要问题是加载图片的时分,user-Agent有读取设备类型行为

BasicLibrary架构设计旅程(一)—Android必备技能

  • 关于这篇文章的源码剖析,咱们能够看我之前的文章:隐私政策整改之Glide结构封装
  • glide加载图片默许用的是HttpUrlConnection
  • 加载网络图片的时分,默许是在GlideUrl中设置了Headers.DEFAULT,它的内部会在static中增加默许的User-Agent。

小总结

  • 优异的阅览源码才能能够帮咱们快速定位并处理问题。
  • 优异的阅览源码才能也能够让咱们快速上手任何一个热门结构并了解其原理

阅览字节码的才能的重要性

当咱们熟练把握字节码才能,咱们能够深入了解JVM,经过ASM完结一套埋点+阻拦第三方频频调用隐私办法的问题

字节码基础常识
  • ​因为跨渠道性的规划,java的指令都是根据栈来规划的,而这个栈指的便是虚拟机栈
  • JVM运转时数据区分为本地办法栈、程序计数器、堆、办法区和虚拟机栈

局部变量表

  • 每个线程都会创立一个虚拟机栈,其内部保存一个个栈帧,对应一次次办法的调用
  • 栈帧的内部结构是分为:局部变量表、操作数栈、动态链接(指向运转时常量池的办法引用)和回来地址
  • 局部变量表内部界说了一个数字数组,首要存储办法参数和界说在办法体内的局部变量
  • 局部变量表存储的基本单位是slot(槽),long和double存储的是2个槽,其他都是1个槽
  • 非静态办法,默许0槽位存的是this(指的是该办法的类目标)

操作数栈

  • ​在办法履行进程中,根据字节码指令,往栈中写入数据或提取数据,即入栈和出栈
  • ​首要用于保存核算进程的中心成果,同时作为核算进程中变量临时的存储空间
  • ​办法调用的开端,默许的操作数栈是空的,但是操作数栈的数组现已创立,并且巨细已知
  • ​操作数栈并非选用拜访索引的办法来进行数据拜访的,而是只能经过规范的入栈和出栈操作来完结一次数据拜访

一些常用的助记符

  • 从局部变量表到操作数栈:iload,iload_,lload,lload_,fload,fload_,dload,dload_,aload,aload
  • 操作数栈放到局部变量表:istore,istore_,lstore,lstore_,fstore,fstore_,dstore,dstor_,astore,astore_
  • 把常数放到到操作数栈:bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_,lconst_,fconst_,dconst_
  • 取出栈顶两个数进行相加,并将成果压入操作数栈:iadd,ladd,fadd,dadd
  • iinc:对局部变量表的值进行加1操作
i++和++i差异
public class Test {
    public static void main(String[] args) {
       int i=10;
       int a=i++;
       int j=10;
       int b=++j;
        System.out.println(i);
        System.out.println(a);
        System.out.println(j);
        System.out.println(b);
    }
}
  • 咱们能够思考下,这个成果会是什么呢?
  • 成果分别是11 10 11 11

字节码成果剖析

  • 检查字节码指令:javap -v Test.class
  • 咱们也能够运用idea自带的jclasslib东西,或许ASM Bytecode Viewer东西
 0 bipush 10
 2 istore_1
 3 iload_1
 4 iinc 1 by 1
 7 istore_2
 8 bipush 10
10 istore_3
11 iinc 3 by 1
14 iload_3
15 istore 4
17 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
20 iload_1
21 invokevirtual #3 <java/io/PrintStream.println : (I)V>
24 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
27 iload_2
28 invokevirtual #3 <java/io/PrintStream.println : (I)V>
31 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
34 iload_3
35 invokevirtual #3 <java/io/PrintStream.println : (I)V>
38 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
41 iload 4
43 invokevirtual #3 <java/io/PrintStream.println : (I)V>
46 return
  • 因为咱们对错静态办法,所以局部变量表0的方位存储的是this
    BasicLibrary架构设计旅程(一)—Android必备技能
  • bipush 10:将常量10压入操作数栈
    BasicLibrary架构设计旅程(一)—Android必备技能
  • istore1:将操作数栈的栈顶元素放入到局部变量表1的方位
    BasicLibrary架构设计旅程(一)—Android必备技能
  • iload1:将局部变量表1的方位放入到操作数栈

BasicLibrary架构设计旅程(一)—Android必备技能

  • iinc 1 by 1:局部变量表1的方位的值+1

BasicLibrary架构设计旅程(一)—Android必备技能

  • istore2:将操作栈的栈顶元素压入局部变量表2的方位
    BasicLibrary架构设计旅程(一)—Android必备技能
  • 至此最上面两行代码履行结束,下面的代码我就不再画图论述了,我相信机敏聪敏的你一定现已学会剖析了
  • 最后来一个小小的总结吧
    • ​i++是先iload1,后局部变量表自增,再istore2,所以a的值仍是10
    • ​++i是先局部变量表自增,随后iload,再istore,所以b的值现已变成了11
ASM 处理隐私办法问题
  • 项目地址:github.com/Peakmain/As…
  • 咱们能够去看下我的源码和文章,具体细节我就不论述了,里边涉及到了很多的opcodec的操作符,比方Opcode.ILOAD
    BasicLibrary架构设计旅程(一)—Android必备技能

置疑的情绪

  • 无论是视频仍是博客,咱们对不确认的常识坚持一颗置疑的情绪,因为一篇文章或许视频都有或许是不对的,包含我现在写的这篇文章。
kotlin object完结的单例类是懒汉式仍是饿汉式

BasicLibrary架构设计旅程(一)—Android必备技能

BasicLibrary架构设计旅程(一)—Android必备技能

  • 以上两个都是网上的文章截取的文章,那kotlin完结的object单例到底是饿汉式仍是懒汉式的呢?
  • 假定咱们有以下代码
object Test {
    const val TAG="test"
}

经过东西看下反编译后的代码

BasicLibrary架构设计旅程(一)—Android必备技能

BasicLibrary架构设计旅程(一)—Android必备技能
static代码块什么时分初始化呢?

  • 首要咱们需要知道JVM的类加载进程:loading->link->初始化
  • link又分为:验证、准备、解析
  • 而static代码块()是在初始化的进程中调用的
  • ​虚拟机会必须保证一个类的办法在多线程下被同步加锁
  • Java运用办法分为两种:自动和被动
    BasicLibrary架构设计旅程(一)—Android必备技能
  • 自动运用才会导致static代码块的调用

单例的懒汉式和饿汉式的差异是什么呢

  • 懒汉式:类加载不会导致该实例被创立,而是初次运用该目标才会被创立
  • 饿汉式:类加载就会导致该实例目标被创立

BasicLibrary架构设计旅程(一)—Android必备技能

public class Test {
    private static Test mInstance;
    static {
        System.out.println("static:"+mInstance);
    }
    private Test() {
        System.out.println("init:"+mInstance);
    }
    public static Test getInstance() {
        if (mInstance == null) {
            mInstance = new Test();
        }
        return mInstance;
    }
    public static void main(String[] args) {
        Test.getInstance();
    }
}
  • 当调用getInstance的时分,类加载进程中会进行初始化,也便是调用static代码块
  • static代码块履行时,因为类没有实例化,所以获取到是null。
  • 也便是说,类加载的时分并没有对该实例进行创立(懒汉式)
public class Test1 {
    private static final Test1 mInstance=new Test1();
    private Test1(){
        System.out.println("init:"+mInstance);
    }
    static {
        System.out.println("static:"+mInstance);
    }
    public static Test1 getInstance(){
        return mInstance;
    }
    public static void main(String[] args) {
        Test1.getInstance();
    }
}
  • 类的初始化次序是由代码的次序来决议的,上面的代码首要对mInstance进行初始化,但是因为此刻结构函数履行完结后才完结类的初始化,所以结构函数回来的是null
  • static代码块履行的时分,类实例现已创立结束
  • 正如上面说的static代码块履行的时分还处于类加载中的初始化状况,所以实例是在初始化之前完结(饿汉式)

咱们现在回到kotlin的object,咱们将其转成Java类

public class Test2 {
    public static final String TAG = "test";
    private Test2() {
        System.out.println("init:" + mInstance);
    }
    public static Test2 mInstance;
    static {
        Test2 test2 = new Test2();
        mInstance = test2;
        System.out.println("static:" + mInstance);
    }
    public static void main(String[] args) {
        System.out.println(Test2.TAG);
    }
}
  • 上面代码在static代码块的时分(类加载的初始化时)进行了类的实例初始化(饿汉式)

总结

  • Android必备的技能,其实很多,比方JVM、高并发、binder、泛型、AMS,WMS等等
  • 我个人觉得源阅览码才能和把握字节码属于必备技能,能进步自己常识领域
  • 当然如我上面所说,要坚持置疑的情绪,本文说的或许也不对。
  • 下一篇文章,我将介绍BasicLibrary中根据职责链规划形式搭建的Activity Results API权限封装结构,欢迎咱们讨论。