Android加載Class的思考

?????? ?做的一個項目需要用到串口通訊,而android恰恰有現(xiàn)成的串口通訊服務,但是相關的接口類都被隱藏起來了,不能被用戶APP直接使用。那么如何使用呢?

??????? 首先假設相關接口沒有被隱藏,使用起來很簡單

1.得到SerialManager實例

SerialManager serialManager = (SerialManager) context.getSystemService("serial");

2.取到可用的端口

String[] ports = serialManager.getSerialPorts();

if (ports != null && ports.length > 0) {
String portName = ports[0];
}

3.打開

SerialPort serialPort = serialManager.openSerialPort(portName, SPEED);

4.讀寫

ByteBuffer inputBuffer;
int ret = serialPort.read(inputBuffer);
private void writeBuffer(byte[] sendData) throws IOException {
outputBuffer.clear();
outputBuffer.put(sendData);
serialPort.write(outputBuffer, sendData.length);
}

(注:要使用串口,還需要定制系統(tǒng),修改系統(tǒng)源碼,將串口服務的權限打開,否則一般的app無權使用。)

上面的使用步驟非常簡單,也符合我們的需求,但是 SerialManager、SerialPort這兩個類在android.jar里面沒有,因為它們是隱藏類。

如果我們想使用隱藏類,怎么辦?

兩種方法:

1.打開系統(tǒng)源碼,將SerialManager.java、SerialPort.java及它們引用到的其它隱藏類的源碼拷到我們的項目工程中,包括包名。

2.使用反射機制(簡單例舉一下)

//得到系統(tǒng)的SerialManager實例
Object serialManager = context.getSystemService("serial");
try {
//反射得到SerialManager實例的getSerialPorts方法
Method methodGetSerialPorts = serialManager.getClass().getMethod("getSerialPorts");
//執(zhí)行SerialManager實例的getSerialPorts方法
String[] ports = (String[])methodGetSerialPorts.invoke(serialManager);

if (ports != null && ports.length > 0) {
String portName = ports[0];
//反射得到SerialManager實例的openSerialPort方法
Method methodOpenSerialPort = serialManager.getClass().getMethod("openSerialPort",String.class,int.class);

//執(zhí)行SerialManager實例的openSerialPort方法,得到SerialPort對象實例
Object serialPort = methodOpenSerialPort.invoke(serialManager,portName,115200);

//反射得到SerialPort實例的write方法
Method methodWrite = serialPort.getClass().getMethod("write",ByteBuffer.class,int.class);

//發(fā)送數(shù)據(jù),收數(shù)據(jù)與此類似
ByteBuffer outputBuffer = ByteBuffer.allocate(1024);
byte[] sendData = new byte[]{0x00};
outputBuffer.put(sendData);
methodWrite.invoke(serialPort,outputBuffer,sendData.length);
}


} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}


我們使用的是第一種方法,因為能直接使用類,效率高且比反射方便。

??????? 將系統(tǒng)已有的類直接拷過來使用,肯定會有人疑問:系統(tǒng)里已經(jīng)存在這樣的類,app中再創(chuàng)建相同的類,會不會有問題。APP運行時到底調(diào)用的是哪個class,是APP中拷貝的這個還是系統(tǒng)本來有的那個?

??????? 這樣就引入了本篇的主題:android如何加載class的。

?????? 其實憑直覺,這樣的方式是安全的,沒有問題,APP運行時應該調(diào)用的是系統(tǒng)里的class。原因很簡單,如果運行的是APP里這份class,那么APP誰敢用,我們把源碼里的一些類拿過來改改,加些小動作,加到APP里,替代系統(tǒng)的class,這樣不是把系統(tǒng)的邏輯都改變了,想想不可能。

?????? 究其因,需要了解Android Dalvik/ART虛擬機的類加載機制,而加載Class的是ClassLoader。

java代碼被編譯成class文件,然后程序運行在虛擬機上時,虛擬機會把需要的Class加載進來。

??????? Android系統(tǒng)一啟動,會創(chuàng)建一個Boot類型的ClassLoader實例,用于加載系統(tǒng)Framework層級需要的類,APP啟動的時把這個Boot類型的ClassLoader傳進來,使APP可以使用Framework里的類,這也是為什么打包成apk時,不需要將android.jar包打進來的原因。

??????? APP里自定義的類以Class形式保存在apk里的dex文件中,當APP啟動的時候,會創(chuàng)建一個PathClassLoader實例,用于加載dex文件中的Class。同時ClassLoader是一種雙親代理模型,即創(chuàng)建一個ClassLoader實例的時候,需要使用一個現(xiàn)有的ClassLoader實例作為新創(chuàng)建的實例的Parent,所以加載dex的PathClassLoader實例其Parent是系統(tǒng)創(chuàng)建的Boot類型的ClassLoader實例。

?????? 一般至少有這兩個ClassLoader,我們還可以自定義ClassLoader。

??????? ClassLoader實例加載Class時遵循的規(guī)則是:

??????? 1.如果當前ClassLoader實例加載過此類,返回當前加載的Class,沒有則執(zhí)行第2步;

??????? 2.如果Parent加載過此類,有直接返回Parent加載的Class,沒有則往上找,繼續(xù)執(zhí)行第2步,直到?jīng)]有Parent,再執(zhí)行第3步;

??????? 3.如果繼承路線上的ClassLoader都沒有加載,由Child執(zhí)行類的加載工作。

了解了這個加載規(guī)則,前面問題答案就非常清楚了。我們拷貝過來的系統(tǒng)類,在我們的IDE中只是幫助進行順利編譯工程而已,真正APP執(zhí)行時,是調(diào)用系統(tǒng)已經(jīng)加載好了的Class。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容