Java作为一种面向对象语言。最为重要的两个概念那就是类和对象:
类:类是一个模板,它描述一类对象的行为和状态。
对象:对象是类的一个实例,有状态和行为。
类和对象之间的关系就像人类和某个人之间的关系,人类拥有一些行为和状态,这些行为和状态作为了人类的特征;用面向对象的思维来说,每个人可以说是人类这个类实例化的对象,在拥有人类的行为和状态下还拥有各自的特点。
数据类型分为四大类八种,四大类是:整形、浮点型、字符型、布尔型;整形可以细分为byte、short、int、long;浮点可以细分为float、double。
1、整数类型默认为int类型,占4字节,当使用字节大小是大于它的long类型时,除了需要定义变量为long类型,还需要在值后面加L或者l才能将该变量表示为long类型。如:long l = 123456789l;
2、浮点类型默认为double类型,占8字节,在值后面加上F/f就是float类型了。如:float f = 3.14f;
Java提供了两类修饰符。分别是访问修饰符和非访问修饰符。
访问修饰符是一种用来限制类、接口、类成员(字段、方法、构造函数等)访问权限的关键字,主要包括以下四种:
为了实现一些其他的功能,Java 提供了很多非访问修饰符,如:
switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。
switch case 执行时,一定会先进行匹配,匹配成功就开始执行case 语句之后的语句,再根据是否有 break,判断是否继续输出,或是跳出判断。
面向对象是Java的重中之重,之间简单介绍了对象和类的关系,现在正式来讲解一下面向对象相关知识。
每个类都有构造器。如果没有主动的为类定义构造器,Java 编译器将会为该类提供一个默认的无参构造器。构造器的主要作用是初始化对象的数据成员,在创建一个对象时,构造器会被自动调用来为对象的各个数据成员赋初值。
面向对象基础实例:
ClassMethod.java文件
Zoo.java文件
封装指的是将数据和操作数据的方法封装在一个单元内部,并通过访问权限控制来限制外部对该单元的访问。
例如,下面是一个简单的Java类,它封装了一个学生的姓名和年龄:
可以看到在这个类中,姓名和年龄都是私有成员变量,只能通过公开的getter和setter方法来访问和修改。这样,我们就可以在外部控制和限制对这些成员变量的访问。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
Java中的继承是单继承的,也就是说每个子类只能继承一个父类。Java中通过使用关键字"extends"来实现继承。
继承的语法格式如下所示:
其中,SubClass是子类,SuperClass是父类。子类可以继承父类的属性和方法,同时可以添加自己的属性和方法。
Java中的所有类都继承自Object类。因此,如果没有指定父类,Java中的类默认继承自Object类。
Java中的继承关系可以形成继承层次结构,也就是说一个子类可以成为另一个子类的父类。Java中的继承层次结构可以使用继承关系图来表示。
在子类继承父类后,构造器会有以下特点:
子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。这是因为子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
如果父类中没有无参的构造器,子类必须使用super关键字显式调用父类的构造器,并传入相应的参数。例如:
子类通过this(...)去调用本类的其他构造器,本类其他构造器会通过super去手动调用父类的构造器,最终还是会调用父类构造器的。例如:
注意:this(...)和super(...)都只能放在构造器的第一行,所以二者不能共存在同一个构造器中。
继承的优点在于它可以提高代码的重用性和可维护性,同时可以使代码更加灵活和可扩展。通过继承,子类可以继承父类的属性和方法,从而减少了代码的编写量。此外,继承也可以使代码更加灵活,能够更好地适应需求的变化。
继承的缺点在于它可能会引入过多的复杂性,使程序难以维护和扩展。如果继承关系设计不当,会导致代码的耦合性过高,增加了代码的复杂度和维护成本。因此,在设计继承关系时,需要仔细考虑继承的层次,避免出现过多的继承关系。
在 Java 中,多态是一种基于继承、多态和重载的特性。它允许同一种类型的对象在不同的场景下表现出不同的行为。
Java 实现多态的方式主要有两种:继承和接口。
子类可以继承父类的方法,并且可以重写父类的方法,从而实现多态。当子类重写了父类的方法后,当通过父类对象调用该方法时,实际上会调用子类重写后的方法。
示例代码:
输出结果:
接口是一种规范,它定义了一组方法,但并不提供方法的具体实现。不同的类可以实现同一个接口,并且每个类都可以根据自己的实际情况来实现接口中的方法,从而实现多态。
示例代码:
输出结果:
无论是通过继承还是接口实现多态,都可以使代码更加灵活、可扩展性更强。
虚函数的存在是为了多态,虚方法是实现多态的重要手段,可以使不同的子类对象调用同一个方法时产生不同的行为。虽然Java 中没有虚方法这个概念,但是Java中每个方法都是虚方法,除了被 final 关键字修饰的方法。
在Java中,虚方法的实现依赖于方法表和虚方法表。方法表是每个类的一部分,它包含了该类所有的方法的信息,包括方法名、参数类型、返回值类型等。虚方法表是每个类的一个隐藏的数据结构,它包含了该类所有的虚方法的信息,包括方法的地址、偏移量等。每个对象在内存中都有一个指向其所属类的虚方法表的指针,称为vtable指针。
当调用一个虚方法时,Java虚拟机首先根据对象的实际类型找到其对应的虚方法表,然后根据方法的偏移量找到要调用的方法的地址,并执行该方法。如果子类重写了父类的虚方法,则子类的虚方法表中会覆盖父类相应的方法地址,从而实现了多态。
以下是一个示例代码:
在上面的代码中,Animal、Cat和Dog都有一个eat方法,但是它们的行为不同。在main方法中,分别创建了一个Animal、一个Cat和一个Dog对象,并调用它们的eat方法。由于eat方法是虚方法,因此在运行时会根据对象的实际类型来确定要调用哪个方法,从而实现了多态。
方法重写指的是在子类中定义一个与父类方法名、返回类型、参数列表都相同的方法,但是方法体不同的过程。
当子类对象调用被重写的方法时,将优先调用子类中的方法,而不是父类中的方法。
方法重写的条件为:
下面是一个方法重写的例子:
在上面的例子中,Animal类中定义了一个move()方法,输出“动物在移动”。在Dog类中重写了这个方法,并输出“狗在奔跑”。当我们调用animal.move()时,输出的是父类Animal中的move()方法的内容;当我们调用dog.move()时,输出的是子类Dog中重写的move()方法的内容。
static是静态的意思,可以修饰成员变量和成员方法;static修饰的成员变量和成员方法在内存中只用存储一份,因为可以被类和对象共享访问、修改。
static注意事项:
1、静态方法只能访问静态成员(静态变量或者静态方法),不能直接访问实例成员,因为静态成员是属于类的,而不是直接属于对象的,静态成员会同类一同创建。
2、实例方法可以访问静态成员,也可以访问实例成员,因为静态成员是可以被共享访问的。
3、静态方法中不可以出现this关键字,因为this只能代表当前对象,而静态方法不一定使用对象调用。
4、静态方法中也不能使用super关键字,因为super关键字代表父类对象,而静态方法没有对象的概念。
5、静态方法也不能被子类重写,因为静态方法是属于类的,而不是属于对象的。
抽象类是不能被实例化的类,如果你是新手,那你是不是很疑惑,一个类不能被实例化那还有什么卵用?
别急嘛!抽象类的存在主要是为了被子类继承和实现。抽象类中可以包含抽象方法,抽象方法是一种没有实现的方法,只有方法的声明,没有方法体。
抽象类的特点如下:
下面是一个抽象类的例子:
在上面的例子中,Animal类是一个抽象类,它包含了一个抽象方法move()、一个非抽象方法eat()和一个非抽象方法getName()。在Dog类中继承了Animal类,并实现了move()方法。在Test类中,我们创建了一个Animal类型的对象,实际上是一个Dog对象,然后调用了它的move()、eat()和getName()方法。由于Dog类重写了move()方法,因此输出的是“小狗在奔跑”,而eat()方法是从父类继承而来,输出的是“小狗在吃东西”,getName()方法也是从父类继承而来,用来获取动物的名称。
接口是一种特殊的抽象类,它只包含了抽象方法和常量,没有任何实现。接口中的所有方法都是公共的,不能包含实例域或构造器,因此不能被实例化。
一个类可以实现多个接口,但只能继承一个类。
接口的定义格式如下:
接口中的方法默认为public abstract类型,可以省略这两个关键字。接口中的常量必须是public static final类型的(这三个修饰符表示该变量是公共的、静态的、不可改变的,也叫该变量为常量),可以省略这三个关键字。接口中的方法不能包含方法体,必须由实现类去实现。
下面是一个接口的例子:
在上面的例子中,Animal是一个接口,包含了一个常量LEGS和一个抽象方法move()。Dog类实现了Animal接口,并实现了move()方法。在Test类中,我们创建了一个Animal类型的对象,实际上是一个Dog对象,然后调用了它的move()方法。由于Dog类实现了Animal接口,因此可以使用Animal类型来引用Dog对象,从而实现了多态性。输出的结果为“狗在奔跑,狗有4条腿”。
内部类其实就是在一个类里面再定义一个类,内部类可以访问其外部类的所有成员,包括私有成员。内部类可以分为成员内部类、静态内部类、局部内部类和匿名内部类。
成员内部类就是在一个类的内部定义的另一个类,它可以访问外部类的所有成员,包括私有成员,并且可以使用外部类的引用来访问外部类的成员。成员内部类的定义格式如下:
在上面的例子中,Inner是Outer的成员内部类,它可以访问Outer的私有成员x。在外部类中创建Inner对象的方法如下:
2.静态内部类
静态内部类是在一个类的内部定义的静态类,它和普通的类一样,不依赖于外部类的实例,因此可以直接通过类名来访问。静态内部类不能访问外部类的非静态成员,只能访问外部类的静态成员。静态内部类的定义格式如下:
在上面的例子中,Inner是Outer的静态内部类,它可以访问Outer的静态成员x。在外部类中创建Inner对象的方法如下:
局部内部类是定义在方法内部的类,它只能在该方法内部使用,对外部不可见。局部内部类可以访问外部类的所有成员,包括私有成员。局部内部类的定义格式如下:
在上面的例子中,Inner是Outer方法内部的局部内部类,它可以访问Outer的私有成员x。在方法内部创建Inner对象的方法如下:
匿名内部类是没有名字的内部类,它通常用于创建一个只需要使用一次的类。匿名内部类可以继承一个类或者实现一个接口,它没有构造方法,但可以使用构造代码块进行初始化。匿名内部类的定义格式如下:
在上面的例子中,父类构造器可以是有参数的构造器,也可以是无参数的构造器;接口可以是有多个方法的接口,也可以是只有一个方法的接口。下面是一个使用匿名内部类实现接口的例子:
在上面的例子中,我们创建了一个实现Animal接口的匿名内部类,并实现了move()方法。在main方法中,我们创建了一个Animal类型的对象,实际上是一个匿名内部类的对象,然后调用了它的move()方法。输出的结果为“动物在移动”。
Java中的异常指程序在运行过程中出现的错误或异常情况,例如类型错误、数组下标越界等。Java提供了异常处理机制来处理这些异常情况。
Java能提供的异常类是有限的,不可能为全世界出现的问题提供异常类来代表出现的问题。如果某个企业出现自己的问题,想要通过异常类来表示,那就需要自己来定义异常类了。接下来我们自己自定义异常类,假设我们需要收集每个用户合理的年龄,如果检测到某个用户输入的年龄不合理就抛出我们自定义的异常。
自定义运行时异常类:
自定义编译时异常类:
创建一个Java类,用于实现收集每个用户合理的年龄,如果检测到某个用户输入的年龄不合理就抛出我们自定义的异常:
当输入的年龄合理时:
当输入的年龄不合理时:
虽说当将异常对象抛给main方法或者main方法出现异常都可以将异常对象抛给JVM虚拟机,然后JVM虚拟机会进行处理后将异常信息抛给用户,这对于用户来说是极其影响体验的,所以接下来讲讲开发中一般会怎么处理异常。一般情况下处理异常会用以下几种方式:
第一种、捕获异常,然后将合适的信息响应给用户。
第二种、捕获异常,然后尝试修复异常。
在前面我们简单处理异常的时候,如果用户输入非int型的数据,那肯定是会抛出异常的。所以我们还是围绕收集每个用户合理的年龄这个主题,使用这两种异常处理的方式将之前的代码完善。
捕获异常,然后将合适的信息响应给用户:
我们进行输入测试结果:
捕获异常,然后尝试修复异常:
我们进行输入测试结果:
问:Java中的反射是什么呢?
答:在Java中,反射是指在程序运行时能够动态地获取类的信息并操作类的成员(属性、方法、构造方法等)的机制。
问:那Java中的反射需要学习些什么呢?
答:
反射学习第一步:将类的字节码文件加载到内存中,然后获取类的class对象。因为Java提供了一个class对象来代表字节码,所以获取类的字节码其实就是获取类的class对象,class对象会封装类的各种信息。
反射学习第二步:获取类的构造器,而构造器也是一个对象,去获取类中的构造器,那就会返回一个constructor对象,该对象就会封装构造器的各种信息。
反射学习第三步:获取类的成员变量和成员方法等,而成员变量和成员方法等都是一种对象。比如去获取类中的成员变量,会返回一个Field对象,就可以获取该对象的各种信息或者操作该对象;去获取类中的成员方法,会返回一个Method对象,就可以获取该对象的各种信息或者操作该对象。
总结起来反射就是学习动态地获取类的信息并学习如何操作它们。接下来我们一步一步讲。
反射学习第一步:将类的字节码文件加载到内存中,然后获取类的class对象:
获取class对象的几种方式:
接下来通过运行结果来验证结果是否正确:
反射学习第二步:获取类的构造器:
Class对象既然代表类,那么Class肯定会提供相应的方法去获取类的各种信息。所以获取类的构造器有以下几种方法:
首先我们需要写一个简单的类来学习:
第一种方式:获取全部构造器(只能获取public修饰的):
得到类的Class对象后,可以通过Class对象调用类的getConstructors方法得到类的全部构造器。但是这个方法会把类的每一个构造器都封装成构造器对象,然后把所有构造器对象放到一个构造器数组里面去。所以要使用以下方式获取类的全部构造器:
我们获取类的全部构造器后,可以进行验证:遍历构造器数组中的每一个构造器对象,并打印构造器名称和参数个数。
打印结果:
可以看到只打印出了一个有参数构造器,而无参数构造器并没有打印出来,因为getConstructors方法虽然可以得到类的全部构造器,但是只能获取public修饰的。
第二种方式:获取全部构造器(只要存在就能拿到):
得到类的Class对象后,想要通过Class对象得到类的全部构造器且只要存在就能拿到,那就需要使用getDeclaredConstructors方法。但是这个方法也会把类的每一个构造器都封装成构造器对象,然后把所有构造器对象放到一个构造器数组里面去。所以要使用以下方式获取类的全部构造器:
验证:遍历构造器数组中的每一个构造器对象,并打印构造器名称和参数个数。
打印结果:
这次可以看到将所有的构造器全部都拿到了,没有了只能获取public修饰的构造器这种限制。
剩下的两种方式与前两种方式类似,我就简单讲讲。
第三种方式:获取某一个构造器(只能获取public修饰的):
得到类的Class对象后,想要通过Class对象得到类的某一个构造器,可以通过getConstructor方法获取到,但是问题也是只能获取public修饰的。那怎么精准获取到想要的类的构造器呢?那就需要通过参数的差别:
获取某一个构造器的格式:Constructor 变量 = 类的class.getConstructor(Class<?>... parameterTypes);
获取有参数构造器:
获取无参数构造器:
打印结果:
可以看到打印出来了有参数构造器对象的名称和参数个数,但是获取无参数构造器却报错了,因为这个方式能获取public修饰的,而无参构造器却是private修饰的,所以获取不到报错了!
第四种方式:获取某一个构造器(只要存在就能拿到):
得到类的Class对象后,使用getConstructor方法去获取类的某一个构造器最大的问题就是只能获取public修饰的,而使用getDeclaredConstructor方法去获取类的某一个构造器就没有这个问题,因为getDeclaredConstructor方法也是只要存在就能拿到。
获取某一个构造器的格式:Constructor 变量 = 类的class.getDeclaredConstructor(Class<?>... parameterTypes);
获取有参数构造器:
获取无参数构造器:
打印结果:
这次可以看到不管是public修饰的构造器还是private修饰的构造器都打印出来了,没有了只能获取public修饰的构造器这种限制。
这四种获取类的构造器的方式全部讲完了,以下是完整代码:
我们获取到了构造器自然要使用,而反射获取构造器的作用主要是初始化对象并返回。要怎么使用呢?我简单的讲解一下,我先把Zoon类的有参数构造器从public修饰变为private修饰,然后如下使用反射获取的构造器:
现在反射第二步也算是完成了。
反射学习第三步:获取类的成员变量和成员方法:
获取类的成员变量和成员方法与获取类的构造器类似,我就简单讲讲。
我先把Zoon类的成员变量age和成员方法setName从public修饰的变为了private修饰的。
获取并使用类的成员变量:
打印结果:
获取类的成员方法并执行:
打印结果:
到这我们就将反射全部讲解完毕,我们已经充分认识了什么是反射,以及反射的核心作用是用来获取类的各个组成部分并执行他们。
1、先来了解一下dalvik虚拟机:
dalvik虚拟机是Android 5.0以前用于运行安卓应用的虚拟机,从 Android 4.4 开始,Google 开始引入了全新的虚拟机 ART(Android Runtime),直到Android5.0开始ART虚拟机就替代了dalvik虚拟机。既然dalvik虚拟机被ART虚拟机替代了,那我们还有学的必要吗?ART 是向下兼容的,ART虚拟机对DEX字节码的运行是兼容的,因此针对 Dalvik 开发的应用也能在 ART 环境中运作。但是Dalvik 采用的一些技术并不适用于 ART,但不妨碍我们了解和学习dalvik字节码。
2、我们再来了解一下dalvik寄存器和寄存器的命名方法:
Dalvik寄存器中的寄存器都是32位大小,支持所有类型,对于小于或等于32位的类型,使用一个寄存器就可以了;对于64位(long和double)类型,需要使用两个相邻的寄存器来存储。
寄存器的命名方法有两种:v命名法和p命名法:
v命名法:局部变量寄存器用v开头数字结尾的符号来表示,如v0、 v1、v2。
p命名法:函数参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2。
特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this",p1表示函数的第一个 参数,p2代表函数中的第二个参数。而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)
寄存器赋值:
const/4和const/16是表示定义一个4位和16位值,这里只是修饰一下,记住Dalvik寄存器是32位寄存器,这只是表示32位寄存器中存的值是4位和16位而已,不要被这个给影响了。
3、再来了解一下dalvik字节码的类型与Java数据类型的对应关系:
数据类型对应:
long类型和double类型都是64位,需要两个寄存器来存储,有时在smali代码中,原本从0到n的寄存器在中间少了一个,那就有可能是其中有数据是long类型或者double类型的,在赋值代码中虽然用到了两个寄存器,但隐藏了其中一个。
4、了解smali和Java之间的转换流程:
xxx.java ==> xxx.class ==> 使用dx工具将.class打包成.dex文件 ==> 使用baksmali工具将.dex文件反编译成.smali文件 ==> 反编译成smali文件后对该文件进行修改成功 ==> 使用samli工具将.smali文件打包成.dex文件(回编译)
5、了解dalvik方法:
.method表示定义了一个方法;
private、static、final表示该方法是一个一个私有、静态、不可变的方法;
onCreate$lambda-2是该方法定义的方法名,方法名后面括号中是该方法的参数;
[Ljava/lang/String;该参数表示接收字符串类型的一维数组,其中[表示一维数组,在smali的方法参数中每一个[就表示一维数组,比如[[就表示二维数组;java中类是一种数据类型,类用L表示,L后面接的是完整类名,也就是包名和类名。
6、了解dalvik字段:
dalvik创建字段需要指定字段的访问标志(修饰符)、字段名、字段类型和初始值等信息。下面是一个示例字段的创建代码:
.field private static count:I = 0
上述代码创建了一个名为count的私有静态字段,类型为整型,初始值为0。
1、dalvik指令集格式:
基础字节码-名称后缀/字节码后缀 目的寄存器 源寄存器/常量
Dalvik指令集中参数采用从源寄存器到目标寄存器的方式。
根据dalvik字节码的大小与类型不同,可能会添加名称后缀以消除岐义。具体来说,字节码的类型可以分为常规类型和特殊类型,根据类型的不同添加的后缀也不同,常规类型的字节码不添加任何后缀,而特殊类型的字节码根据具体类型添加后缀,可能是以下几种后缀之一:
常规类型的字节码主要有以下几种:
此外,根据字节码的大小与布局的不同,也可能添加字节码后缀以消除岐义。这些后缀通过在字节码主名称后添加斜杠“/”来分隔开。例如,一些指令可能会添加以下后缀:
-wide:64位宽度
-from16:源为16位寄存器引用
-to16:目标为16位寄存器引用
-range:指示该指令的操作数是一个范围。
32位常规类型的字节码没有添加任何后缀。
64位常规类型的字节码添加 -wide后缀。
例如这条指令:“move-wide/from16 vAA, vBBBB”:
2、dalvik指令集的使用:
空操作指令
数据操作指令
move指令的三种作用:
move指令集的使用:
返回指令(重点)
数据定义指令(重点)
const/4 vA,#+B:用于将一个4位的数值符号扩展为32后赋值给寄存器vA。其中,vA表示目标寄存器,#+B表示常量值,取值范围为-8到7。
const/16 vAA, #+BBBB:用于将一个16位的数据符号扩展为32位后赋给寄存器vAA。其中,vAA表示目标寄存器,#+BBBB表示常量值,取值范围为-32768到32767。
const vAA, #+BBBBBBBB:用于将一个32位的数值赋给寄存器vAA。其中,vAA表示目标寄存器,#+BBBBBBBB表示常量值,取值范围为-2147483648到2147483647。
const/high16 vAA, #+BBBB0000:用于将一个高16位的数值以低16位为零的方式扩展为32位后赋给寄存器vAA。其中,vAA表示目标寄存器,#+BBBB0000表示常量值的高16位。
const-wide/16 vAA,#+BBBB:用于将16位的数值符号扩展为64位后赋值给寄存器vAA和vAA+1。其中,vAA和vAA+1表示目标寄存器(因为dalvik寄存器是32位的,所以存储64位数据需要两个寄存器存储),#+BBBB表示常量值,取值范围为-32768到32767。
const-wide/32 vAA, #+BBBBBBBB:用于将32位的数值符号扩展为64位后赋值给寄存器vAA和vAA+1。其中,vAA和vAA+1表示目标寄存器,#+BBBBBBBB表示常量值。
const-wide vAA, #+BBBBBBBBBBBBBBBB:用于将64位的数值赋给寄存器vAA和vAA+1。其中,vAA和vAA+1表示目标寄存器,#+BBBBBBBBBBBBBBBB表示常量值。
const-wide/high16 vAA, #+BBBB000000000000:用于将一个高16位的数值以其他位为零的方式扩展为64位后赋给寄存器vAA和vAA+1。其中,vAA和vAA+1表示目标寄存器,#+BBBB000000000000表示常量值的高16位。
const-string vAA,string@BBBB:用于将字符串常量引用存储到寄存器vAA中,通过字符串ID或字符串。其中,vAA表示目标寄存器,string@BBBB表示字符串在常量池中的索引。
const-string/jumbo vAA, string@BBBBBBBB:与const-string类似,但用于存储较长的字符串常量引用。
const-class vAA,type@BBBB:用于将类对象常量存储到寄存器vAA中,通过类型ID或类型(如Object.class)。其中,vAA表示目标寄存器,type@BBBB表示类在常量池中的索引。
const-class/jumbo vAAAA, type@BBBBBBBB:与const-class类似,但const-class/jumbo指令占用两个字节,值为0xooff,并且其操作数比常规的const-class指令操作数更大。这是Android4.0中新增的指令。
实例操作指令
数组操作指令
array-length vA, vB:计算vB寄存器中数组引用的元素长度并将长度存入vA寄存器。
new-array vA, vB, type@CCCC:根据指定类型或类型ID(type@CCCC)与大小(vB寄存器存入数组的长度)构造一个数组,并将数组的引用赋给vA寄存器。
filled-new-array {vC, vD, vE, vF, vG}, type@BBBB:根据指定类型或类型ID(type@BBBB)与大小(vA)构造一个数组并填充数组内容,vA寄存器是隐含使用的,除了指定数组的大小外还指定了参数的个数,vC~vG是使用到的参数寄存器序号。指令会将寄存器 vC 到 vG 中的值填充到新数组中,并将新数组引用保存到寄存器 vA 中。还有一点需要注意,filled-new-array 指令创建的数组是一个新的对象,它与原数组没有任何关系。另外,该指令只能用于创建引用类型的数组,不能用于创建基本类型的数组。
filled-new-array/range {vCCCC .. vNNNN}, type@BBBB:filled-new-array/range 指令与 filled-new-array 指令非常类似,只是它的参数寄存器是以范围形式给出的,vCCCC 和 vNNNN 分别表示参数寄存器的起始位置和结束位置,type@BBBB 表示数组元素的类型。vC 是 filled-new-array/range 指令的第一个参数寄存器,表示数组大小。N=A+C-1 表示参数寄存器的结束位置,其中 A 表示数组大小,C 表示第一个参数寄存器的编号。因为 filled-new-array/range 指令的参数寄存器是连续的一段,所以 N 的值等于 A+C-1,即参数寄存器的结束位置。例如,如果 filled-new-array/range {v2 .. v5}, type@BBBB 指令被执行,那么数组大小为 v2 中存储的整数值,参数寄存器的编号为 v2、v3、v4、v5,因此 N=A+C-1=4+v2-1=3+v2。
fill-array-data vAA, 偏移量:用指定的字面量数组数据填充到目标数组中,字面量数组数据的位址是当前指令位址加偏移量的和。该指令填充的数组类型必须为基本类型的数组和字符串数组,其中基本类型包括 boolean、byte、short、char、int、float 和 double。填充数组时,字面量数组数据的每个元素都必须与目标数组的元素类型相同。另外,该指令只能用于填充已经创建的目标数组,不能用于创建新的数组对象。
举个例子,假设有如下代码:
异常指令
跳转指令(重点)
无条件跳转指令
分支跳转指令
packed-switch vAA, 索引表偏移量:实现一个switch语句,case常量是连续的,这个指令使用索引表,vAA是在表中找到具体case的指令偏移量的索引,如果无法在表中找到vAA对应的索引将继续执行下一个指令(即default case),执行完case中的代码后,如果Java源代码设置了break强制跳出switch,就会无条件跳转到所有case的出口处,如果没有强制跳出switch,就会执行完default case中的代码后再跳转到所有case的出口处继续执行代码。
举个例子,假设有如下代码:
Java代码:
smali代码:
sparse-switch vAA, 查询表偏移量:实现一个switch语句,case常量是非连续的,这个指令使用查询表,用于表示case常量和每个case常量的偏移量。如果vAA寄存器中的值无法在表中匹配将继续执行下一个指令(即default case)。执行完case中的代码后,如果Java源代码设置了break强制跳出switch,就会无条件跳转到所有case的出口处,如果没有强制跳出switch,就会执行完default case中的代码后再跳转到所有case的出口处继续执行代码。
举个例子,假设有如下代码:
Java代码:
smali代码:
条件跳转指令
if-eq vA, vB, 目标:如果vA == vB,跳转到目标。
if-ne vA, vB, 目标:如果vA != vB,跳转到目标。
if-lt vA, vB, 目标:如果vA < vB,跳转到目标。
if-ge vA, vB, 目标:如果vA >= vB,跳转到目标。
if-gt vA, vB, 目标:如果vA > vB,跳转到目标。
if-le vA, vB, 目标:如果vA <= vB,跳转到目标。
if-eqz vA, 目标:如果vA == 0,跳转到目标。
if-nez vA, 目标:如果vA != 0,跳转到目标。
if-ltz vA, 目标:如果vA < 0,跳转到目标。
if-gez vA, 目标:如果vA >= 0,跳转到目标。
if-gtz vA, 目标:如果vA > 0,跳转到目标。
if-lez vA, 目标:如果vA <= 0,跳转到目标。
比较指令
字段操作指令
iput-object vAA,vBB,字段ID:根据字段ID将vAA寄存器的值存入实例的对象引用字段,vBB寄存器中是该实例的引用。例如:
可以看到例子中将v0寄存器中Ljava/lang/String;类型的值(实例的对象引用)存入到p0寄存器中,p0寄存器中是Lbin/mt/apksignaturekillerplus/HookApplication;类型下字段名为appPkgName的实例字段,并且实例字段appPkgName的存储值类型为Ljava/lang/String;
iput-boolean vAA,字段ID:根据字段ID将vAA寄存器的值存入实例的boolean型字段,vBB寄存器中是该实例的引用。例如:
可以看到例子中将p1寄存器中boolean型的值存入到p0寄存器中,p0寄存器中是Lbin/mt/plugin/api/translation/TranslationEngine$Configuration;类型下字段名为acceptTranslated的实例字段,并且实例字段acceptTranslated的存储值类型为boolean。
iput-wide vAA,vBB,字段ID:根据字段ID将vAA,vAA+1寄存器的值存入实例的double/long型字段,vBB寄存器中是该实例的引用。例如:
可以看到例子中将v1,v2寄存器中long型的值存入到p0寄存器中,p0寄存器中是Lcom/alipay/android/phone/mrpc/core/l;类型下字段名为e的实例字段,并且实例字段e的存储值类型为long。
iput vAA,vBB,字段ID:根据字段ID将vAA寄存器的值存入实例的int型字段,vBB寄存器中是该实例的引用。例如:
可以看到例子中将v0寄存器中int型的值存入到p0寄存器中,p0寄存器中是Lcom/alipay/android/phone/mrpc/core/e;类型下字段名为a的实例字段,并且实例字段a的存储值类型为boolean。
iget-object vAA,vBB,字段ID:根据字段ID读取一个实例的对象引用字段到vAA,vBB寄存器中是该实例的引用。例如:
可以看到例子中将p0寄存器中Ljava/lang/String;类型的值(实例的对象引用)存入到v4寄存器中,p0寄存器中是Lbin/mt/apksignaturekillerplus/HookApplication;类型下字段名为appPkgName的实例字段,并且实例字段appPkgName的存储值类型为Ljava/lang/String;
iget-boolean vAA,vBB,字段ID:根据字段ID读取实例的boolean型字段到vAA,vBB寄存器中是该实例的引用。例如:
可以看到例子中将p0寄存器中boolean型的值存入到v1寄存器中,p0寄存器中是Lbin/mt/plugin/api/translation/TranslationEngine$ConfigurationBuilder;类型下字段名为acceptTranslated的实例字段,并且实例字段acceptTranslated的存储值类型为boolean。
iget-wide vAA,vBB,字段ID:根据字段ID读取实例的double/long型字段到vAA,vAA+1,vBB寄存器中是该实例的引用。例如:
可以看到例子中将p0寄存器中long型的值存入到v3,v4寄存器中,p0寄存器中是Lcom/alipay/android/phone/mrpc/core/l;类型下字段名为g的实例字段,并且实例字段g的存储值类型为long。
iget vAA,vBB,字段ID:根据字段ID读取实例的int型字段到vAA,vBB寄存器中是该实例的引用。例如:
可以看到例子中将v8寄存器中int型的值存入到v1寄存器中,v8寄存器中是Landroid/util/TypedValue;类型下字段名为data的实例字段,并且实例字段data的存储值类型为int。
sput-object vAA,字段ID:根据字段ID将vAA寄存器中的对象引用赋值到对象引用静态字段。例如:
可以看到例子中将v0寄存器中Ljava/text/SimpleDateFormat;类型的值(对象引用)存入到Lbin/tools/inject/InjectedLog;类型下的存储值为Ljava/text/SimpleDateFormat;类型的对象引用静态字段TIME_FORMAT1中。
sput-boolean vAA,字段ID:根据字段ID将vAA寄存器中的值赋值到boolean型静态字段。例如:
可以看到例子中将v6寄存器中boolean类型的值存入到Lcom/alipay/sdk/cons/a;类型下的存储值为boolean类型的静态字段r中。
sput-wide vAA,字段ID:根据字段ID将vAA,vAA+1寄存器中的值赋值到double/long型静态字段。例如:
可以看到例子中将v0,v1寄存器中long类型的值存入到Lcom/alipay/sdk/app/PayTask;类型下的存储值为long类型的静态字段i中。
sput vAA,字段ID:根据字段ID将vAA寄存器中的值赋值到int型静态字段。例如:
可以看到例子中将v0寄存器中int类型的值存入到Lcom/google/android/material/appbar/AppBarLayout;类型下的存储值为int类型的静态字段DEF_STYLE_RES中。
sget-object vAA,字段ID:根据字段ID读取静态对象引用字段到vAA。例如:
可以看到例子中将Ljava/lang/System;类型下的静态字段out中的Ljava/io/PrintStream;类型的值(对象引用)存入v17寄存器中。
sget-boolean vAA,字段ID:根据字段ID读取静态boolean型字段到vAA。例如:
可以看到例子中将Lbin/mt/plus/Features;类型下的静态字段f中的boolean类型的值存入v1寄存器中。
sget-wide vAA,字段ID:根据字段ID读取静态double/long型字段到vAA,vAA+1。例如:
可以看到例子中将Lcom/alipay/android/phone/mrpc/core/b;类型下的静态字段a中的long类型的值存入v2,v3寄存器中。
sget vAA,字段ID:根据字段ID读取静态int型字段到vAA。例如:
可以看到例子中将Landroid/os/Build$VERSION;类型下的静态字段SDK_INT中的int类型的值存入v0寄存器中。
方法调用指令(重点)
invoke-virtual {参数},方法名:调用带参数的实例的虚方法(虚方法相当于Java中的普通方法)。
invoke-virtual/range {vAA .. vBB},方法名:调用以寄存器范围为参数的虚方法。该指令第一个寄存器和寄存器的数量将会传递给方法。该指令与普通的invoke-virtual指令相比,可以同时传递多个参数,提高了效率。例如:
在这条指令中,寄存器范围为{v17 .. v18},调用java.io.PrintStream类的println(String)方法,该方法需要一个参数,参数类型为java.lang.String,并且不返回任何值。该指令的第一个寄存器为v17,寄存器的数量为2,因此v17和v18两个寄存器都将作为参数传递给方法,其中v18存储了传递给println()方法的参数值,v17则存储了PrintStream对象的引用。在执行该指令之前,需要先将参数值存储在寄存器v18中,将PrintStream对象的引用存储在寄存器v17中。该虚方法的作用是将参数值打印到控制台上。在Dalvik字节码中,方法的参数和返回值都是通过寄存器进行传递的。
invoke-super {参数},方法名:调用带参数的直接父类的虚方法。
invoke-super/range {vAA .. vBB},方法名:调用以寄存器范围为参数的直接父类的虚方法。该指令第一个寄存器和寄存器的数量将会传递给方法。
invoke-direct {参数},方法名:不解析直接调用带参数的方法,例如构造方法、静态语句块。
invoke-direct/range {vAA .. vBB},方法名:不解析直接调用以寄存器范围为参数的方法。该指令第一个寄存器和寄存器的数量将会传递给方法。
invoke-static {参数},方法名:调用带参数的静态方法。
invoke-static/range {vAA .. vBB},方法名:调用以寄存器范围为参数的静态方法。该指令第一个寄存器和寄存器的数量将会传递给方法。
invoke-interface {参数},方法名:调用带参数的接口方法。
invoke-interface/range {vAA .. vBB},方法名:调用以寄存器范围为参数的接口方法。该指令第一个寄存器和寄存器的数量将会传递给方法。
数据转换指令
数据运算指令
无论是普通类、抽象类、接口类还是内部类,在反编译出来的代码中,它们都是以单独的smali文件进行存放的。每个smali文件中的语句都遵循着一套语法规范,这里想要讲解的正是这套smali文件中的语法规范。
Smali中开头的包信息以及包信息格式:
.class <访问权限> [修饰关键字] <类名>
.super <父类名>
.source <源文件名>
例如:
静态字段定义
格式:
例如:
实例字段定义(java中的普通字段)
相比起静态字段定义,实例字段就少了一个静态修饰符static而已,格式:
例如:
直接方法定义
例如:
虚方法(java中的普通方法)
除了顶头的注释和直接方法不同,其他没什么区别,我们来看一个例子,例如:
接口
例如:
注解
在讲注解之前先解释一下什么是类型签名:在Java中,每个类、接口、数组、枚举、注解和基本类型都有一个唯一的类型签名。类型签名是一个字符串,用来描述该类型的全限定名、类型参数、维度等信息。类型签名的格式如下:
下面是类型签名的详细解释:
在Smali代码中,类型签名的格式与Java基本一致,只是用L来表示类类型,用[来表示数组类型,用T来表示类型变量。例如,类型签名Ljava/lang/Deprecated表示的是Java内置类Deprecated的类型签名。
注解的格式:
在Smali中,可以使用注解来提供额外的信息和指令给编译器和虚拟机。
以下是Smali注解的一些常见规范和详解:
注解的作用范围可以是类、方法或者字段。
如果注解的作用范围是类,".annotation"指令会直接定义在smali文件中,例如:
如果是方法或字段,".annotation"指令则会包含在方法或字段中定义。
注解方法,例如:
注解字段,例如:
到此Java和smali基础已经学习结束,接下来我们一起学习写一个简单的安卓APP,写完后我们一起把我们写的这个安卓APP逆向分析后破解掉,这样我们就会对安卓逆向和安卓开发之间的关系也有一定了解,并且也会提升我们的逆向水平。孙子曰:“知彼知己者,百战不殆;不知彼而知己,一胜一负,不知彼,不知己,每战必殆。”
那我们开始写一个简单的安卓APP吧!我们使用的工具是Android Studio,在写之前,我们需要安装Android Studio并创建一个项目:
安装Android Studio并创建项目可以参考这篇帖子:
Android studio安装与创建第一个helloworld项目_安卓helloworld项目下载_王 三 二的博客-CSDN博客
但是我们不使用empty activity项目类型,而是使用empty views activity项目类型,因为我的empty activity项目类型的项目和网上的完全不一样,反而是empty views activity项目类型的项目和网上的empty activity项目类型的项目是一致的,所以我就创建empty views activity项目类型的项目了。
配置好环境后我们就来写一个简单的安卓APP,作用就是从主Activity跳转到我们创建的Activity。
写一个安卓APP与写一个网站颇有相似之处,第一步我们需要去编写布局文件,下面是主Activity布局的XML代码:
让我们逐行解释每个元素的作用:
既然我们需要从主Activity跳转到我们创建的Activity,那么第二步我们就创建我们自己的Activity,那自然需要先去编写布局文件,下面是我们需要创建的Activity的布局文件:
让我们逐行解释每个元素的作用:
这个Activity的需要显示字符串被我写到了res/values/strings.xml文件中:
写完了我们需要创建的Activity布局文件,第三步我们需要在Java代码里面完成交互的逻辑编写:
让我们逐行解释每个元素的作用:
我们完成了我们自己创建的Activity在Java代码里面的交互逻辑编写,第四步是主Activity在Java代码里面的交互逻辑编写:
让我们逐行解释每个元素的作用:
现在完成了主Activity在Java代码里面的交互逻辑编写,最后一步便是将我们自己编写的Activiity在应用清单文件中声明:
到现在简单的安卓APP已经写完了,下面让我们看看我们自己编写的简单的安卓APP:
点击跳转后:
从结果来看,这个简单的安卓APP是编写成功了。但是我们开发安卓APP还未结束,还需将APK进行打包才可以将其安装在手机或者模拟器上,Android Studio打包APK可以参考下面这篇文章:
Android开发之打包APK详解_android打包apk_不服输的小乌龟的博客-CSDN博客
将APK打包成功后便可以成功安装在我们自己的手机或者模拟器上了,到此我们自己写的这个简单的安卓APP也就编写完成了。
下一步我们需要将自己写的安卓APP逆向了做一件事情——>那就是替换掉点击跳转按钮后跳转的Activity。
首先软件逆向第一步找到关键代码的位置是重中之重,而我们要完成替换掉点击跳转按钮后跳转的Activity那首先需要先找到跳转按钮,如果我们需要找的内容并未写在Native层,没有被加密,也不是从服务器返回的,那就主要从这几个方向找:首先肯定是直接搜索字符串;其次通过工具获取到要找内容的资源ID后再去资源文件夹寻找相关的资源调用;还有就是通过一些相关的消息框、按钮等这些控件的调用代码来寻找位置;最次就是从程序入口处一步步往下追去寻得蛛丝马迹。
而我们写的安卓APP简单,后三者皆可使用,要问我为什么第一种在这不好使,那是因为显示在屏幕上的字符串全写在了资源文件中。这里我就打算使用最次的方法,因为我们写的这个简单,而且还好运用刚刚学会的smali汇编语言。
首先我们可以直接通过MT管理器的Activity记录功能直接获得入口点Activity的类名,然后便可搜索类名"com.example.myapplication.MainActivity"寻到以下内容:
让我们逐行解释以上代码:
分析完以上代码后,我们下一步就需要将我们要替换成的Activity的新布局文件给写出来。
我并不打算去改Java代码,而是去添加一个布局文件供MyActivity类调用,你可能会问为什么不将这个布局文件替换掉原先的布局文件,这不是更加轻松吗?确实这样就不用去声明新添加资源的资源ID了,但是我们主要是要讲smali代码如何在实战中运用,当然要给smali代码多点戏份了。
点击跳转按钮后会触发MainActivity1的实例,那下一步我们去看看MainActivity1的实例:
让我们逐行解释以上代码:
分析完以上代码,我们可以去看看MyActivity类被反编译成smali代码后是什么样:
让我们逐行解释以上代码:
因为我们是给这个类替换一个新的布局文件,所以我们先不改它,我们需要先去将新的布局文件声明好,这我们需要做三件事情:
第一件:将新的布局文件丢res/layout文件夹里去;
第二件:在values\public.xml文件中声明我们新的布局文件的资源ID;
第三件:在R$layout类中声明我们新的布局文件的资源ID;
我们完成以上步骤后,我们需要将MyActivity类中的activity_my替换成activity_new:
以上寄存器要联系上下文,比如以上代码是将activity_new
布局的资源ID储在寄存器v0
中,再将v0寄存器中的资源ID作为参数传递给setContentView
方法。所以寄存器要联系上下文,不要直接复制粘贴。
然后回编译成功后,再使用MT管理器对修改后的apk文件进行重新签名,让我们来看看最终的结果如何:
点击跳转后:
从结果来看我们成功完成了替换掉点击跳转按钮后跳转的Activity!现在这篇文章也到此结束,这篇文章重点是让大家对Java基础、smali汇编基础、安卓开发基础有简单的了解,最后还是那句话:“知彼知己者,百战不殆;不知彼而知己,一胜一负,不知彼,不知己,每战必殆。”
package
com.java.TypeDome;
public
class
TypeDome {
public
static
void
main(String[] args) {
byte
a =
10
;
int
b = a;
System.out.println(a);
System.out.println(b);
System.out.println(
"---------------------------------------------"
);
int
i =
10
;
int
j =
10
;
double
ij =
1.0
;
double
ji = i + j + ij;
System.out.println(ji);
byte
one =
20
;
byte
two =
25
;
int
three = one + two;
System.out.println(three);
System.out.println(
"---------------------------------------------"
);
int
o =
1500
;
byte
t = (
byte
) o;
System.out.println(t);
double
dou =
81.5
;
int
interesting = (
int
) dou;
System.out.println(interesting);
}
}
package
com.java.TypeDome;
public
class
TypeDome {
public
static
void
main(String[] args) {
byte
a =
10
;
int
b = a;
System.out.println(a);
System.out.println(b);
System.out.println(
"---------------------------------------------"
);
int
i =
10
;
int
j =
10
;
double
ij =
1.0
;
double
ji = i + j + ij;
System.out.println(ji);
byte
one =
20
;
byte
two =
25
;
int
three = one + two;
System.out.println(three);
System.out.println(
"---------------------------------------------"
);
int
o =
1500
;
byte
t = (
byte
) o;
System.out.println(t);
double
dou =
81.5
;
int
interesting = (
int
) dou;
System.out.println(interesting);
}
}
package
com.java.ProcessControl;
import
java.util.Scanner;
public
class
IfElse {
public
static
void
main(String[] args) {
Scanner sca =
new
Scanner(System.in);
System.out.println(
"请输入该同学的成绩:"
);
int
grades = sca.nextInt();
if
(grades <
0
|| grades >
100
) {
System.out.println(
"成绩不存在"
);
}
else
if
(grades >=
90
) {
System.out.println(
"该同学必成龙凤!!"
);
}
else
if
(grades >=
80
) {
System.out.println(
"该同学成绩优秀"
);
}
else
if
(grades >=
70
) {
System.out.println(
"该同学成绩一般"
);
}
else
if
(grades >=
60
) {
System.out.println(
"该同学成绩及格,还需努力!"
);
}
else
{
System.out.println(
"挂科"
);
}
}
}
package
com.java.ProcessControl;
import
java.util.Scanner;
public
class
IfElse {
public
static
void
main(String[] args) {
Scanner sca =
new
Scanner(System.in);
System.out.println(
"请输入该同学的成绩:"
);
int
grades = sca.nextInt();
if
(grades <
0
|| grades >
100
) {
System.out.println(
"成绩不存在"
);
}
else
if
(grades >=
90
) {
System.out.println(
"该同学必成龙凤!!"
);
}
else
if
(grades >=
80
) {
System.out.println(
"该同学成绩优秀"
);
}
else
if
(grades >=
70
) {
System.out.println(
"该同学成绩一般"
);
}
else
if
(grades >=
60
) {
System.out.println(
"该同学成绩及格,还需努力!"
);
}
else
{
System.out.println(
"挂科"
);
}
}
}
package
com.java.circulate;
public
class
Circulate {
public
static
void
main(String[] args) {
}
}
package
com.java.circulate;
public
class
Circulate {
public
static
void
main(String[] args) {
}
}
public
class
Student {
private
String name;
private
int
age;
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name = name;
}
public
int
getAge() {
return
age;
}
public
void
setAge(
int
age) {
this
.age = age;
}
}
public
class
Student {
private
String name;
private
int
age;
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name = name;
}
public
int
getAge() {
return
age;
}
public
void
setAge(
int
age) {
this
.age = age;
}
}
未知的叫声
汪汪汪
喵喵喵
汪汪汪
喵喵喵
Outer.Inner inner =
new
Outer.Inner();
inner.print();
Outer.Inner inner =
new
Outer.Inner();
inner.print();
package
com.java.day5_exception;
import
java.util.Scanner;
public
class
ExceptionThree {
public
static
void
main(String[] args) {
while
(
true
) {
try
{
runs();
break
;
}
catch
(Exception e) {
System.out.println(
"您需要输入整数型数据,而非其他类型数据,需要您重新输入!"
);
}
}
}
public
static
void
runs()
throws
Exception{
while
(
true
) {
System.out.println(
"请输入合理的年龄:"
);
Scanner scan =
new
Scanner(System.in);
int
num = scan.nextInt();
try
{
detection(num);
detection2(num);
break
;
}
catch
(AgeUnreasonableRuntimeException e) {
System.out.println(
"您输入的年龄不合理!触发了运行时异常。"
);
}
catch
(AgeUnreasonableException e) {
System.out.println(
"您输入的年龄不合理!触发了编译时异常。"
);
}
}
}
public
static
void
detection2(
int
age)
throws
AgeUnreasonableException{
if
(age>
0
&& age<
150
) {
System.out.println(age +
"岁年龄是合理的"
);
}
else
{
throw
new
AgeUnreasonableException(
"The age you entered is"
+ age +
"years old is not reasonable"
);
}
}
public
static
void
detection(
int
age) {
if
(age>
0
&& age<
150
) {
System.out.println(age +
"岁年龄是合理的"
);
}
else
{
throw
new
AgeUnreasonableRuntimeException(
"The age you entered is"
+ age +
"years old is not reasonable"
);
}
}
}
package
com.java.day5_exception;
import
java.util.Scanner;
public
class
ExceptionThree {
public
static
void
main(String[] args) {
while
(
true
) {
try
{
runs();
break
;
}
catch
(Exception e) {
System.out.println(
"您需要输入整数型数据,而非其他类型数据,需要您重新输入!"
);
}
}
}
public
static
void
runs()
throws
Exception{
while
(
true
) {
System.out.println(
"请输入合理的年龄:"
);
Scanner scan =
new
Scanner(System.in);
int
num = scan.nextInt();
try
{
detection(num);
detection2(num);
break
;
}
catch
(AgeUnreasonableRuntimeException e) {
System.out.println(
"您输入的年龄不合理!触发了运行时异常。"
);
}
catch
(AgeUnreasonableException e) {
System.out.println(
"您输入的年龄不合理!触发了编译时异常。"
);
}
}
}
public
static
void
detection2(
int
age)
throws
AgeUnreasonableException{
if
(age>
0
&& age<
150
) {
System.out.println(age +
"岁年龄是合理的"
);
}
else
{
throw
new
AgeUnreasonableException(
"The age you entered is"
+ age +
"years old is not reasonable"
);
}
}
public
static
void
detection(
int
age) {
if
(age>
0
&& age<
150
) {
System.out.println(age +
"岁年龄是合理的"
);
}
else
{
throw
new
AgeUnreasonableRuntimeException(
"The age you entered is"
+ age +
"years old is not reasonable"
);
}
}
}
Class<?> a
=
Zoon.
class
;
/
/
想要完成反射第二步、第三步,那都需要得到类的Class对象
Class<?> a
=
Zoon.
class
;
/
/
想要完成反射第二步、第三步,那都需要得到类的Class对象
Constructor[] b
=
a.getConstructors();
Constructor[] b
=
a.getConstructors();
for
(Constructor constructor : b) {
System.out.println(
"这是第一种方式获得的构造器对象:"
+
constructor.getName()
+
",这个构造器有"
+
constructor.getParameterCount()
+
"个参数!"
);
}
for
(Constructor constructor : b) {
System.out.println(
"这是第一种方式获得的构造器对象:"
+
constructor.getName()
+
",这个构造器有"
+
constructor.getParameterCount()
+
"个参数!"
);
}
Class<?> a
=
Zoon.
class
;
/
/
想要完成反射第二步、第三步,那都需要得到类的Class对象
Class<?> a
=
Zoon.
class
;
/
/
想要完成反射第二步、第三步,那都需要得到类的Class对象
Constructor[] c
=
a.getDeclaredConstructors();
Constructor[] c
=
a.getDeclaredConstructors();
for
(Constructor constructor : c) {
System.out.println(
"这是第二种方式获得的构造器对象:"
+
constructor.getName()
+
",这个构造器有"
+
constructor.getParameterCount()
+
"个参数!"
);
}
for
(Constructor constructor : c) {
System.out.println(
"这是第二种方式获得的构造器对象:"
+
constructor.getName()
+
",这个构造器有"
+
constructor.getParameterCount()
+
"个参数!"
);
}
const
/
4
p5,
0x1
/
/
p5赋值
1
const
/
16
v0,
0xa
/
/
v0赋值
10
,在
16
进制里a表示
10
const
/
4
p5,
0x1
/
/
p5赋值
1
const
/
16
v0,
0xa
/
/
v0赋值
10
,在
16
进制里a表示
10
smali类型 |
java类型 |
注释 |
V |
void |
无返回值 |
Z |
boolean |
布尔值类型,返回0或1 |
B |
byte |
字节类型,返回字节 |
S |
short |
短整数类型,返回数字 |
C |
char |
字符类型,返回字符 |
I |
int |
整数类型,返回数字 |
J |
long (64位 需要2个寄存器存储) |
长整数类型,返回数字 |
F |
float |
单浮点类型,返回数字 |
D |
double (64位 需要2个寄存器存储) |
双浮点类型,返回数字 |
string |
String |
文本类型,返回字符串 |
Lxxx/xxx/xxx |
object |
对象类型,返回对象 |
...
fill
-
array
-
data v0, :array_0
/
/
将字面量数组数据填充到 v0 寄存器所代表的数组中,:array_0为字面量数组数据的偏移地址(偏移量)
...
:array_0
.array
-
data
2
/
/
表示每个数组元素占用两个字节
0x14ebs
/
/
数组元素值为
0x14eb
(short 类型)
0x15f0s
/
/
数组元素值为
0x15f0
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
...
...
fill
-
array
-
data v0, :array_0
/
/
将字面量数组数据填充到 v0 寄存器所代表的数组中,:array_0为字面量数组数据的偏移地址(偏移量)
...
:array_0
.array
-
data
2
/
/
表示每个数组元素占用两个字节
0x14ebs
/
/
数组元素值为
0x14eb
(short 类型)
0x15f0s
/
/
数组元素值为
0x15f0
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
0x15c7s
/
/
数组元素值为
0x15c7
(short 类型)
...
packed
-
switch v0, :pswitch_data_5e
.line
120
iget
-
wide v0, p0, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>PI2:D
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
v0, p1
invoke
-
static {v0, v1}, Ljava
/
lang
/
Math;
-
>sin(D)D
move
-
result
-
wide p1
return
-
wide p1
.line
132
:pswitch_17
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
p1, v1
rem
-
double
/
2addr
p1, v1
sub
-
double
/
2addr
p1, v3
invoke
-
static {p1, p2}, Ljava
/
lang
/
Math;
-
>
abs
(D)D
move
-
result
-
wide p1
sub
-
double p1, v5, p1
mul
-
double
/
2addr
p1, p1
:goto_25
sub
-
double
/
2addr
v5, p1
return
-
wide v5
.line
130
:pswitch_27
iget
-
wide v0, p0, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>PI2:D
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
v0, p1
invoke
-
static {v0, v1}, Ljava
/
lang
/
Math;
-
>cos(D)D
move
-
result
-
wide p1
return
-
wide p1
.line
128
:pswitch_33
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
p1, v3
add
-
double
/
2addr
p1, v5
rem
-
double
/
2addr
p1, v3
goto :goto_25
.line
126
:pswitch_3b
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
p1, v3
add
-
double
/
2addr
p1, v5
rem
-
double
/
2addr
p1, v3
sub
-
double
/
2addr
p1, v5
return
-
wide p1
.line
124
:pswitch_44
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
p1, v1
add
-
double
/
2addr
p1, v5
rem
-
double
/
2addr
p1, v1
sub
-
double
/
2addr
p1, v3
invoke
-
static {p1, p2}, Ljava
/
lang
/
Math;
-
>
abs
(D)D
move
-
result
-
wide p1
goto :goto_25
:pswitch_51
const
-
wide
/
high16 v0,
0x3fe0000000000000L
.line
122
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
rem
-
double
/
2addr
p1, v5
sub
-
double
/
2addr
v0, p1
invoke
-
static {v0, v1}, Ljava
/
lang
/
Math;
-
>signum(D)D
move
-
result
-
wide p1
return
-
wide p1
:pswitch_data_5e
.packed
-
switch
0x1
:pswitch_51
:pswitch_44
:pswitch_3b
:pswitch_33
:pswitch_27
:pswitch_17
.end packed
-
switch
packed
-
switch v0, :pswitch_data_5e
.line
120
iget
-
wide v0, p0, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>PI2:D
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
v0, p1
invoke
-
static {v0, v1}, Ljava
/
lang
/
Math;
-
>sin(D)D
move
-
result
-
wide p1
return
-
wide p1
.line
132
:pswitch_17
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
p1, v1
rem
-
double
/
2addr
p1, v1
sub
-
double
/
2addr
p1, v3
invoke
-
static {p1, p2}, Ljava
/
lang
/
Math;
-
>
abs
(D)D
move
-
result
-
wide p1
sub
-
double p1, v5, p1
mul
-
double
/
2addr
p1, p1
:goto_25
sub
-
double
/
2addr
v5, p1
return
-
wide v5
.line
130
:pswitch_27
iget
-
wide v0, p0, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>PI2:D
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
v0, p1
invoke
-
static {v0, v1}, Ljava
/
lang
/
Math;
-
>cos(D)D
move
-
result
-
wide p1
return
-
wide p1
.line
128
:pswitch_33
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
p1, v3
add
-
double
/
2addr
p1, v5
rem
-
double
/
2addr
p1, v3
goto :goto_25
.line
126
:pswitch_3b
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
p1, v3
add
-
double
/
2addr
p1, v5
rem
-
double
/
2addr
p1, v3
sub
-
double
/
2addr
p1, v5
return
-
wide p1
.line
124
:pswitch_44
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
mul
-
double
/
2addr
p1, v1
add
-
double
/
2addr
p1, v5
rem
-
double
/
2addr
p1, v1
sub
-
double
/
2addr
p1, v3
invoke
-
static {p1, p2}, Ljava
/
lang
/
Math;
-
>
abs
(D)D
move
-
result
-
wide p1
goto :goto_25
:pswitch_51
const
-
wide
/
high16 v0,
0x3fe0000000000000L
.line
122
invoke
-
virtual {p0, p1, p2}, Landroidx
/
constraintlayout
/
motion
/
utils
/
Oscillator;
-
>getP(D)D
move
-
result
-
wide p1
rem
-
double
/
2addr
p1, v5
sub
-
double
/
2addr
v0, p1
invoke
-
static {v0, v1}, Ljava
/
lang
/
Math;
-
>signum(D)D
move
-
result
-
wide p1
return
-
wide p1
:pswitch_data_5e
.packed
-
switch
0x1
:pswitch_51
:pswitch_44
:pswitch_3b
:pswitch_33
:pswitch_27
:pswitch_17
.end packed
-
switch
switch
(transit) {
case
4097
:
return
8194
;
case
4099
:
return
4099
;
case
8194
:
return
4097
;
default
:
return
0
;
}
switch
(transit) {
case
4097
:
return
8194
;
case
4099
:
return
4099
;
case
8194
:
return
4097
;
default
:
return
0
;
}
sparse
-
switch p0, :sswitch_data_e
.line
2025
:goto_4
return
v0
.line
2016
:sswitch_5
const
/
16
v0,
0x2002
.line
2017
goto :goto_4
.line
2019
:sswitch_8
const
/
16
v0,
0x1001
.line
2020
goto :goto_4
.line
2022
:sswitch_b
const
/
16
v0,
0x1003
goto :goto_4
.line
2014
:sswitch_data_e
.sparse
-
switch
0x1001
-
> :sswitch_5
0x1003
-
> :sswitch_b
0x2002
-
> :sswitch_8
.end sparse
-
switch
sparse
-
switch p0, :sswitch_data_e
.line
2025
:goto_4
return
v0
.line
2016
:sswitch_5
const
/
16
v0,
0x2002
.line
2017
goto :goto_4
.line
2019
:sswitch_8
const
/
16
v0,
0x1001
.line
2020
goto :goto_4
.line
2022
:sswitch_b
const
/
16
v0,
0x1003
goto :goto_4
.line
2014
:sswitch_data_e
.sparse
-
switch
0x1001
-
> :sswitch_5
0x1003
-
> :sswitch_b
0x2002
-
> :sswitch_8
.end sparse
-
switch
iput
-
object
v0, p0, Lbin
/
mt
/
apksignaturekillerplus
/
HookApplication;
-
>appPkgName:Ljava
/
lang
/
String;
iput
-
object
v0, p0, Lbin
/
mt
/
apksignaturekillerplus
/
HookApplication;
-
>appPkgName:Ljava
/
lang
/
String;
iput
-
boolean p1, p0, Lbin
/
mt
/
plugin
/
api
/
translation
/
TranslationEngine$Configuration;
-
>acceptTranslated:Z
iput
-
boolean p1, p0, Lbin
/
mt
/
plugin
/
api
/
translation
/
TranslationEngine$Configuration;
-
>acceptTranslated:Z
iput
-
wide v1, p0, Lcom
/
alipay
/
android
/
phone
/
mrpc
/
core
/
l;
-
>e:J
iput
-
wide v1, p0, Lcom
/
alipay
/
android
/
phone
/
mrpc
/
core
/
l;
-
>e:J
iput v0, p0, Lcom
/
alipay
/
android
/
phone
/
mrpc
/
core
/
e;
-
>a:I
iput v0, p0, Lcom
/
alipay
/
android
/
phone
/
mrpc
/
core
/
e;
-
>a:I
iget
-
object
v4, p0, Lbin
/
mt
/
apksignaturekillerplus
/
HookApplication;
-
>appPkgName:Ljava
/
lang
/
String;
iget
-
object
v4, p0, Lbin
/
mt
/
apksignaturekillerplus
/
HookApplication;
-
>appPkgName:Ljava
/
lang
/
String;
iget
-
boolean v1, p0, Lbin
/
mt
/
plugin
/
api
/
translation
/
TranslationEngine$ConfigurationBuilder;
-
>acceptTranslated:Z
iget
-
boolean v1, p0, Lbin
/
mt
/
plugin
/
api
/
translation
/
TranslationEngine$ConfigurationBuilder;
-
>acceptTranslated:Z
iget
-
wide v3, p0, Lcom
/
alipay
/
android
/
phone
/
mrpc
/
core
/
l;
-
>g:J
iget
-
wide v3, p0, Lcom
/
alipay
/
android
/
phone
/
mrpc
/
core
/
l;
-
>g:J
iget v1, v8, Landroid
/
util
/
TypedValue;
-
>data:I
iget v1, v8, Landroid
/
util
/
TypedValue;
-
>data:I
sput
-
object
v0, Lbin
/
tools
/
inject
/
InjectedLog;
-
>TIME_FORMAT1:Ljava
/
text
/
SimpleDateFormat;
sput
-
object
v0, Lbin
/
tools
/
inject
/
InjectedLog;
-
>TIME_FORMAT1:Ljava
/
text
/
SimpleDateFormat;
sput
-
boolean v6, Lcom
/
alipay
/
sdk
/
cons
/
a;
-
>r:Z
sput
-
boolean v6, Lcom
/
alipay
/
sdk
/
cons
/
a;
-
>r:Z
sput
-
wide v0, Lcom
/
alipay
/
sdk
/
app
/
PayTask;
-
>i:J
sput
-
wide v0, Lcom
/
alipay
/
sdk
/
app
/
PayTask;
-
>i:J
sput v0, Lcom
/
google
/
android
/
material
/
appbar
/
AppBarLayout;
-
>DEF_STYLE_RES:I
sput v0, Lcom
/
google
/
android
/
material
/
appbar
/
AppBarLayout;
-
>DEF_STYLE_RES:I
sget
-
object
v17, Ljava
/
lang
/
System;
-
>out:Ljava
/
io
/
PrintStream;
sget
-
object
v17, Ljava
/
lang
/
System;
-
>out:Ljava
/
io
/
PrintStream;
sget
-
boolean v1, Lbin
/
mt
/
plus
/
Features;
-
>ۘf:Z
sget
-
boolean v1, Lbin
/
mt
/
plus
/
Features;
-
>ۘf:Z
sget
-
wide v2, Lcom
/
alipay
/
android
/
phone
/
mrpc
/
core
/
b;
-
>a:J
sget
-
wide v2, Lcom
/
alipay
/
android
/
phone
/
mrpc
/
core
/
b;
-
>a:J
sget v0, Landroid
/
os
/
Build$VERSION;
-
>SDK_INT:I
sget v0, Landroid
/
os
/
Build$VERSION;
-
>SDK_INT:I
sget
-
object
v17, Ljava
/
lang
/
System;
-
>out:Ljava
/
io
/
PrintStream;
const
-
string
/
jumbo v18,
"PmsHook success."
invoke
-
virtual
/
range
{v17 .. v18}, Ljava
/
io
/
PrintStream;
-
>println(Ljava
/
lang
/
String;)V
sget
-
object
v17, Ljava
/
lang
/
System;
-
>out:Ljava
/
io
/
PrintStream;
const
-
string
/
jumbo v18,
"PmsHook success."
invoke
-
virtual
/
range
{v17 .. v18}, Ljava
/
io
/
PrintStream;
-
>println(Ljava
/
lang
/
String;)V
.
class
public interface abstract Landroid
/
support
/
v4
/
os
/
IResultReceiver;
/
/
这个类是android.support.v4.os这个包下名为IResultReceiver的接口类
.
super
Ljava
/
lang
/
Object
;
/
/
该类继承了父类java.lang.
Object
.source
"IResultReceiver.java"
/
/
这个smali文件在还是Java文件时,文件名为IResultReceiver.java
.
class
public interface abstract Landroid
/
support
/
v4
/
os
/
IResultReceiver;
/
/
这个类是android.support.v4.os这个包下名为IResultReceiver的接口类
.
super
Ljava
/
lang
/
Object
;
/
/
该类继承了父类java.lang.
Object
.source
"IResultReceiver.java"
/
/
这个smali文件在还是Java文件时,文件名为IResultReceiver.java
*
*
.field <访问权限> static [修饰关键字] <字段名>:<字段类型>
*
*
*
*
.field <访问权限> static [修饰关键字] <字段名>:<字段类型>
*
*
.field private static final PARAMETER_BUFFER:Ljava
/
lang
/
ThreadLocal;
.field private static final PARAMETER_BUFFER:Ljava
/
lang
/
ThreadLocal;
.field <访问权限> [修饰关键字] <字段名>:<字段类型>
.field <访问权限> [修饰关键字] <字段名>:<字段类型>
.field public final mConstructorArgs:[Ljava
/
lang
/
Object
;
.field public final mConstructorArgs:[Ljava
/
lang
/
Object
;
.method <访问权限> [修饰关键字] <方法名称、参数还有返回值>
<.registers>
/
/
指定了方法中寄存器的总数,这个数量是参数寄存器和本地寄存器的总和。
<.local>
/
/
这个指令表明方法中非参数寄存器(本地寄存器)的个数
[.param]
/
/
表明了方法的参数,每个.param指令表示一个参数,方法使用了几个参数就有几个.param指令。
[.prologue]
/
/
表明了方法中代码的开始处
[.line]
/
/
表名了该处代码在源代码中的行号
<代码体>
.end method
.method <访问权限> [修饰关键字] <方法名称、参数还有返回值>
<.registers>
/
/
指定了方法中寄存器的总数,这个数量是参数寄存器和本地寄存器的总和。
<.local>
/
/
这个指令表明方法中非参数寄存器(本地寄存器)的个数
[.param]
/
/
表明了方法的参数,每个.param指令表示一个参数,方法使用了几个参数就有几个.param指令。
[.prologue]
/
/
表明了方法中代码的开始处
[.line]
/
/
表名了该处代码在源代码中的行号
<代码体>
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2024-3-31 22:44
被黎明与黄昏编辑
,原因: