[原]鸿蒙(HarmonyOS)API的源代码哪去了?竟然全抛出异常,原来使用的是虚拟API【建议收藏】
2021-09-06 10:09:57 Author: blog.csdn.net(查看原文) 阅读量:83 收藏

1. HarmonyOS的方法为何都抛出异常

不管是鸿蒙(HarmonyOS),或是Android、还是其他系统。理解其工作原理最好的方式就是阅读源代码。不过HarmonyOS的所有系统类,跟踪进去,全部是下面的东西。

从反编译后的源代码可以看出,除了类的成员变量外,所有方法的内部实现全都抛出了RuntimeException异常,而且异常的信息是Stub。

就这个问题,一直有粉丝问我,这到底是咋回事。Android的系统类跟踪进去就会看到源代码,HarmonyOS为什么看不到源代码呢?当然,也不是一点源代码都看不到,看到的都是抛出异常的方法。既然这些方法都抛出异常,那么HarmonyOS App为什么可以正常运行呢?

以前我就直接回答:HarmonyOS的系统类还没开源呢,所以看不到源代码。不过这个回答并没有解释反编译后源代码都是一些抛出异常的方法的原因。所以借本文将背后的细节深入解释一下。

2. 追根溯源

既然在DevEco Studio中在对象后面输入一个点(.),可以显示该对象包含的成员,那么说明,在本地一定存在这些对象对应的.class文件。因为对于Java来说,成员列表中的数据全部通过反射技术从.class文件中获取,所以HarmonyOS种所有公开的系统类对应的.class文件在本地一定存在。所以我们先在本地寻找这些.class文件。

通常.class文件是以jar文件形式发布的,一般这些jar文件会在HarmonyOS SDK目录中,所以现在打开HarmonyOS SDK目录,再进入下面的子目录:

/java/2.2.0.1/api

其中2.2.0.1是sdk的版本号,在读者的机器上可能是其他目录,在java目录中可能会有多个类似2.2.0.1的目录,随便进入一个即可。

在api目录中,会找到一个名为ohos.jar的文件,这个文件包含了HarmonyOS中所有的系统类。如果将该文件解压,进入ohos目录,会看到如下图所示的很多目录,这些都是包目录。

读者可以随便进入一个包目录看个究竟。如进入ohos\aafwk\ability目录,这是与Ability相关的包。在该目录中会找到一个Ability.class文件,这就是包含Ability类的.class文件。

读者可以直接将Ability.class文件拖动到EcoDev Studio的代码区域,EcoDev Studio会自动反编译Ability.class文件,得到对应的Java代码。得到的内容与直接跟踪进Ability类完全一样。

难道这是EcoDev Studio搞的鬼吗?

再看看Ability.class文件的尺寸有多大,只有19KB,然后看看其他.class文件,绝大多数都在10KB以内。再看看ohos.jar文件,只有3393KB,也就是说,包含整个HarmonyOS API的jar文件,只有不到4MB。这也太小了吧!

 

3. 为什么要提供空实现

现在的问题是,ohos.jar为何要提供一套空实现API呢?其实主要原因有如下3个:

(1)HarmonyOS的系统类目前还没未开源

(2)DevEco Studio需要提供智能提示,也就是输入一个点(.),会弹出一个成员列表

(3)需要编译生成HAP文件

第1点非常好理解,ohos.jar文件本来应该包含完整的实现代码,不过由于HarmonyOS未开源,所以就只能包含空实现代码了。第2点前面也已经提到,DevEco Studio要想提供智能提示,就需要.class文件,不过只需要.class文件中方法的名称、返回值类型、参数等接口信息,并不需要知道方法的具体实现。所以只需要提供空实现就可以蒙混过关。

第3点是为了编译的需要。因为如果A.java调用了B.java中的API,那么在编译A.java时,至少需要提供B.java对应的B.class文件(B.java可以不需要)。只不过Java在编译时,只会考虑B的接口,不会考虑B的具体实现,也就是说,只要B的方法在名称、返回值类型、参数个数、参数类型等方面符合A的要求,A.java就可以编译通过,至于B的具体实现是否符合实际的业务要求,在编译A时并不关心,所以ohos.jar只要提供一个空实现即可完成编译工作。

所以从这几点可以看出,ohos.jar其实就是一个Stub(庄),用过Web Services以及其他远程调用技术的同学,应该非常清楚Stub是什么。例如,Web Services有一种接口描述语言WSDL。通过WSDL,可以生成Stub类。WSDL与具体的编程语言无关,可以根据WSDL生成任何编程语言的Stub类。Stub本身其实就是Web Services的一个调用接口,里面包含了Web Services中所有方法的空实现(方法中包含如何访问Web Services的代码,并不包含具体的业务代码)。

ohos.jar与Stub其实同出一辙,也是调用接口,这就是为何ohos.jar中的方法抛出的异常信息都是Stub的原因,当然,你抛出其他异常信息,或者干脆不抛出任何异常,就是一个纯粹的空实现也没任何问题。

4. 为何抛出异常而不出错

可能很多同学会问,既然ohos.jar中所有的方法都抛出了异常,那么调用这些方法时为何可以正常运行,而不会让程序挂起呢?

其实这是一个错觉,在运行HarmonyOS App时,其实并不是调用本机只有不到4MB的ohos.jar中的API,而调用的是真机或模拟器中的同名的API。这些真实的API与ohos.jar中的API无论在包名、还是方法名,返回值类型、参数等方面完全相同。

换句话说,就是HarmonyOS App在开发阶段和运行阶段不是在同一个环境中完成的。开发阶段,使用本机的ohos.jar中的.class文件获取智能提示列表中的数据,以及编译生成HAP文件。运行时,会将HAP文件上传到真机或模拟器上运行,这时会调用真正的API。所以本机的ohos.jar文件只负责获取智能提示列表和编译生成HAP,并不负责运行。所以并不会抛出异常,因为在运行时压根就没调用ohos.jar中的API。

到这里,相信大家已经清楚了ohos.jar文件到底是怎么回事,以及跟踪源代码,为何所有的系统方法都抛出异常。其实ohos.jar就是一个空实现,或称为虚拟实现。当然,里面的API也可以称为虚拟API。ohos.jar也是虚拟API的用处之一。那么虚拟API还有什么用处呢?继续看下面的内容吧。

PS:尽管ohos.jar的.class文件都是虚拟实现,但成员变量都是真实的,而且与手机中对应的类中的成员变量完全一样,因为这些变量也同样需要访问,而且无法虚拟实现,所以成员变量必须是真实的。

5. 虚拟API的妙用

在前面的部分已经分析了ohos.jar的原理以及系统方法抛出异常的原因。核心思想就是虚拟API。所谓虚拟API,其实就是没有具体实现的API,其实就相当于接口。那么为什么需要虚拟API呢?虚拟API到底有什么用呢?

虚拟API用处很多,不过主要有如下两种情况:

(1)由于某些原因,暂时无法获得真实的API,所以用虚拟API编译程序,在发布时再使用真实的API运行;

(2)由于使用真实的API需要某些特殊的条件,如需要企业身份的账号、需要某些官方的认证、需要某些暂时无法获得的资质等。例如,支付API,通常需要进行企业身份认证,个人一般是无法调用像支付宝、微信支付API的;

ohos.jar毫无疑问,属于第一种情况。由于HarmonyOS未开源,所以发布给用户的就只能用虚拟API的形式,以便生成.class文件和hap文件。在运行时,其实是在真实环境中运行的,这时就已经有真实的API了(二进制形式,部署在手机上)。

而第2种情况属于使用暂时无法使用真实API,或使用真实API比较费劲,所以使用虚拟API进行开发,然后在发布时再打包真实的API。也就是说,开发时和发布、运行时其实使用的不是一套API,只不过这两套API,在接口上100%相同。

现在举个例子:

这里有一个基础算法类(BasicAlgorithm),里面有一个用于计算阶乘的方法factorial。

package com.unitymarvel.algorithm;

public class BasicAlgorithm {
    public long factorial(long n) throws Exception{
        if(n == 0){
            return 1;
        } else if(n > 0){
            return n * factorial(n - 1);
        } else {
            throw new Exception("n不能小于0");
        }
    }

}

下面的Working类调用了factorial方法:

import com.unitymarvel.algorithm.*;
public class Working {
    public static void main(String[] args) throws Exception {
        BasicAlgorithm basicAlgorithm = new BasicAlgorithm();
        System.out.println(basicAlgorithm.factorial(10));
    }
}

在这个例子中,Working类调用了BasicAlgorithm类中的factorial方法计算阶乘,并输出计算结果。

假设由于某些原因,无法获取BasicAlgorithm类的源代码以及.class文件,而又想先编写Working类的代码,并且将Working.java编译成Working.class该怎么办呢?

PS:这里假设,除了BasicAlgorithm类的接口(方法和成员变量的相关信息)外,无法获得与BasicAlgorithm类相关的任何信息和资源。

有的同学可能会说,反正factorial方法又不复杂,再实现一个不就是了。其实这里只是用factorial方法举例,可能Working调用的API库非常复杂,实现的代码成千上万,甚至实现的逻辑和算法我们完全不清楚,根本无法再现整个实现过程。

如果遇到这种情况,就可以使用虚拟API,先做一个与BasicAlgorithm.java一模一样的接口类(Stub类),利用这个虚拟实现的BasicAlgorithm类,可以先编写Working类,然后部署时,再与真正的BasicAlgorithm类一起发布。

所以可以先实现下面的虚拟BasicAlgorithm类:

package com.unitymarvel.algorithm;

public class BasicAlgorithm {
    public long factorial(long n) throws Exception{
        throw new Exception("Stub");
    }
}

我们可以看到,虚拟BasicAlgorithm类的factorial方法与真实的factorial方法,在方法名、返回值类型、参数个数和类型,甚至抛出的异常完全相同,但内部除了抛出一个异常外,什么都没有。

其实利用这个虚拟的BasicAlgorithm类,完全可以将Working.java编译成Working.class文件,当然,运行会抛出异常。不过如果运行时可以获得真实的BasicAlgorithm.class文件,那么将一切正常。

当然,如果想用虚拟的BasicAlgorithm类来测试Working类,可以进一步改进BasicAlgorithm类:

package com.unitymarvel.algorithm;

public class BasicAlgorithm {
    public long factorial(long n) throws Exception{
        if(n < 0) {
            throw new Exception("Stub");
        } else if(n == 1) {
            return 1;
        } else if(n == 2) {
            return 2;
        } else {
            return 1234;
        }
    }
}

这个改进的虚拟factorial方法只针对特定的n返回具体的值,当然,这里面的代码需要根据实际的业务来调整,总之,factorial方法的返回值需要满足Working类的测试要求。这样不仅仅可以编译Working.java文件,还可以进行测试。等一切搞定后,就可以将Working.class迁移到真实的环境中(包含真实的BasicAlgorithm.class文件的环境),一切都是那么完美!


文章来源: https://blog.csdn.net/nokiaguy/article/details/120126147
如有侵权请联系:admin#unsafe.sh