从编译原理看android的编译文件

874 阅读4分钟

  # 分享作者


昵称: arige

从编译原理看android的编译文件

在android的开发过程中,我们会接触到很多文件类型。今天从编译的角度解释下,这些文件类型都是干什么用的。

一、编译的最初过程

image.png

但是我们android的过程中不只2中代码类型文件,都有哪些?都是做什么用的呢?

二、我们先从java项目开始

大家知道Android开发是基于java来做的。所以android和java开发都会有java、class、jar文件类型

1、假如我们写了一个java文件

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

2、用javac命令生成calss文件

 javac HelloWorld.java 

3、然后用javap命令查看编译后的代码

javap -c  HelloWorld

class文件中的内容

Compiled from "HelloWorld.java"
public class com.example.popupsimple.HelloWorld {
  public com.example.popupsimple.HelloWorld();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String Hello World
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

如果是java项目的话,我们就可以生成jar,来用于依赖和执行

jar -cvf hello.jar com/example/popupsimple/HelloWorld.class

因为jar是一个以ZIP格式构建,以.jar为文件扩展名的归档文件,我们将后缀修改为zip,之后解压,然后会找到对应的class文件,查看此class文件的字节码和上面的上文的一样

Compiled from "HelloWorld.java"
public class com.example.popupsimple.HelloWorld {
  public com.example.popupsimple.HelloWorld();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String Hello World
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

到这里java结束了,但是在Android项目中到这里还没有结束。

三、从android的生成物开始

1、我们反编译一个apk

我们在主项目中写的代码都变成来一个个的dex。

image_1.png

这个是一个全新的文件格式。那他是怎么来的呢?

dx --dex --output=Hello.dex com/example/popupsimple/HelloWorld.class 

2、对dex进行反编译

.class public Lcom/example/popupsimple/HelloWorld;
.super Ljava/lang/Object;
.source "HelloWorld.java"


# direct methods
.method public constructor <init>()V
    .registers 1

    .prologue
    .line 3
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
.end method

.method public static main([Ljava/lang/String;)V
    .registers 3

    .prologue
    .line 5
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v1, "Hello World"

    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 6
    return-void
.end method

四、总结

意外不意外?惊喜不惊喜?

1、一个类的字节码表现形式不一样了!!!

是的,就是不一样。这个就是为啥我们老说,Android是基于jvm开发,但是他虚拟机是Dalvik虚拟机

也就是说,class中的字节码是要在jvm上运行的,而dex中的字节码是要在Dalvik中运行的。因为是不同的虚拟机,所以字节码是不一样的。

有的同学又会问,为啥已经又了jvm,android还要再费劲吧啦的搞一个Dalvik虚拟机呢?

Dalvik基于寄存器的虚拟机 而jvm执行是基于虚拟栈的虚拟机

  1. dvm速度快!寄存器存取速度比栈快的多,dvm可以根据硬件实现最大的优化,比较适合移动设备。JAVA虚拟机基于栈结构,程序在运行时虚拟机需要频繁的从栈上读取写入数据,这个过程需要更多的指令分派与内存访问次数,会耗费很多CPU时间。

  2. 指令数小!dvm基于寄存器,所以它的指令是二地址和三地址混合,指令中指明了操作数的地址;jvm基于栈,它的指令是零地址,指令的操作数对象默认是操作数栈中的几个位置。这样带来的结果就是dvm的指令数相对于jvm的指令数会小很多,jvm需要多条指令而dvm可能只需要一条指令。

  3. jvm基于栈带来的好处是可以做的足够简单,真正的跨平台,保证在低硬件条件下能够正常运行。而dvm操作平台一般指明是ARM系统,所以采取的策略有所不同。需要注意的是dvm基于寄存器,但是这也是个映射关系,如果硬件没有足够的寄存器,dvm将多出来的寄存器映射到内存中。

2、全部的编译过程

image_2.png

五、尾声

1、本文没有在具体的写Dalvik和 ART的区别,有机会后续再补上,感兴趣的也可以自己搜索下。

2、后面会专门来写一写使用到的一些反编译工具

六、欢迎关注我们哦

官方网站:
jetpack.net.cn
Github:
github.com/bagutree