JNI 基于命令行的清屏实现

在使用 Java 编写命令行程序时 会遇到一个很常见的问题: 无法清屏。

这个问题非常简单,但是在 Java 里面实现起来可不容易。

for Windows

cls

for Linux

clear

几个想象中可行的办法

在此之前,大家应该在脑海中想到了一些办法 但是不知道能不能行 ,现在我给大家分析下:

  • 使用 Runtime 类

    在 java.lang 包下 还提供了一个 Runtime 类,这个类可以调用 JVM 相关的功能,如:

Process process =  Runtime.getRuntime().exec("clear");

这个是不能实现清屏的,使用 exec 方法的确可以执行一些命令行的程序,这个 Runtime 类是全局的一个对象,也只有一个实例 是一个单例模式,不过当你执行 exec 方法之后 ,系统会单开一个子线程去执行你的命令,这也就是为什么不能够实现清屏功能。

  • 换行实现
System.out.println("\r\n")

这个是最粗暴的办法,虽然能够在完成功能 不过要通过计数的方式到底换多少行才能把之前的输出顶上去 而且已经偏离了这个主题 不建议使用。

来看看 JNI 的实现

Java 是基于 JVM 来运行,不和操作系统直接打交道,像操作这种系统本地的方法比较麻烦 除非 JVM 已经给了接口,比如像 Object 对象的 clone 方法。

不过 Java 语言已经为我们考虑到这些问题,所以 Java 提供了一套自定义的本地调用接口,叫做 JNI (Java Native Interface)

我们先来看一看 Object clone 的源码:

protected native Object clone() throws CloneNotSupportedException;

我们能看见这个方法并没有实现,并且添加了 natvie 关键字 代表了这个接口是本地实现的。

JNI 实现过程

最后 只需要把对应平台需要的文件放在 Java 的系统加载路径中。

查看 Java 系统加载路径

不知道 Java 系统加载路径? 可以通过下面的方法查看

String libraryDirs = System.getProperty("java.library.path");  
System.out.println(libraryDirs);  

输出:

/Users/yumemor/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.

最后把对应系统的文件放在所在路径即可。

各个平台编译动态库文件

Mac

gcc -dynamiclib -o ./libHelloJni.jnilib HelloJni.c -framework JavaVM -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin

注意 在 Mac/Linux 上面编译需要带上前缀 lib,如:HelloJni.c –> libHelloJni.jnilib

Windows

需要先安装 Visual Studio,然后使用命令行工具进行编译。

cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD HelloJni.c -HelloJni.dll   

Linux

gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -shared HelloJni.c -o libHelloWorld.so 

需要添加 lib 前缀。

动手实验

在了解 JNI 之后,我们开始进入主题:使用 JNI 完成一个清屏的实现。

Step 1 编写 HelloJni.java 文件

public class HelloJni{

    static{
        System.loadLibrary("HelloJni");
    }

    public static native void clear();

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

        clear();    
        System.out.println("清屏");
    }
}

Step 2 编译 HelloJni.java 文件

javac HelloJni.java

Step 3 生成 .h 头文件

javah -jni HelloJni

Step 4 编写头文件实现

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include <stdlib.h>
/* Header for class HelloJni */

#ifndef _Included_HelloJni
#define _Included_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJni
 * Method:    clear
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJni_clear
  (JNIEnv *evn, jclass cls){
      system("clear");
  }

#ifdef __cplusplus
}
#endif
#endif

这里注意一点,由于使用了 system 函数,所以要引入 stdlib.h 头文件。

Step 5 放入 Java 动态文件加载路径

文件路径可以通过上面的方法获取。

如果放错路径,会出现 Exception in thread "main" java.lang.UnsatisfiedLinkError 错误。

Step 6 执行程序

执行结果

从执行结果来看,确实之前的输出全部被清除了。

最后

虽然完成了功能,但是我们的程序就不能在履行 Java 的口号:一次编译,到处运行,因为需要到每一个不同的平台下面 进行一次编译才行。

往往很多时候功能难度和技术难度实现不成正比。