1. 程式人生 > >一步步手動實現熱修復(一)-dex檔案的生成與載入

一步步手動實現熱修復(一)-dex檔案的生成與載入

*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出

熱修復技術自從QQ空間團隊搞出來之後便漸漸趨於成熟。

我們這個系列主要介紹如何一步步手動實現基本的熱修復功能,無需使用第三方框架。

在開始學習之前,需要對基本的熱修復技術有些瞭解,以下文章可以幫助到你:

本節課程主要分為3塊:

dex檔案的生成與載入

我們在這部分主要做的流程有:

  • 1.編寫基本的Java檔案並編譯為.class檔案。
  • 2.將.class檔案轉為.dex檔案。
  • 3.將轉好的dex檔案放入建立好的Android工程內並在啟動時將其寫入本地。
  • 4.載入解壓後的.dex檔案中的類,並呼叫其方法進行測試。

Note: 在閱讀本節之前最好先了解一下類載入器的雙親委派原則、DexClassLoader的使用以及反射的知識點。

編寫基本的Java檔案並編譯為.class檔案

首先我們在一個工程目錄下開始建立並編寫我們的Java檔案,你可能會選擇各種IDE來做這件事,但我在這裡勸你不要這麼做,因為有坑在等你。等把基本流程搞清楚可以再選擇更進階的方法。這裡我們可以選擇文字編輯器比如EditPlus來對Java檔案進行編輯。

新建一個Java檔案,並命名為:ClassStudent.java,並在java檔案內鍵入以下程式碼:

public class ClassStudent
{
private String name; public ClassStudent() { } public void setName(String name) { this.name = name; } public String getName(){ return this.name + ".Mr"; } }

Note: 這裡要注意,不要對類新增包名,因為在後期對class檔案處理時會遇到問題,具體問題會稍後說明。上面的getName方法在返回時對this.name屬性添加了一段字串,這裡請注意,後面會用到。

在檔案建立好之後,對Java檔案進行編譯:
這裡寫圖片描述

將.class檔案轉為.dex檔案

好,現在我們使用class檔案生成對應的dex檔案。生成dex檔案所需要的工具為dx,dx工具位於sdk的build-tools資料夾內,如下圖所示:
這裡寫圖片描述

Tips: 為了方便使用,建議將dx的路徑新增到環境變數中。如果對dx工具不熟悉的,可以在終端中輸入dx –help以獲取幫助。

dx工具的基本用法是:

dx --dex [--output=<file>] [<file>.class | <file>.{zip,jar,apk} | <directory>]

Tips: 剛開始自己摸索的時候,就沒有仔細看命令,導致後面兩個引數的順序顛倒了,搞出了一些讓人疑惑難解的問題,最後又不得不去找dx工具的原始碼除錯,最後才發現自己的問題在哪。如果有對dx工具感興趣的,可以對dx的包進行反編譯或者獲取dx的相關原始碼進行了解。dx.lib檔案位於dx.bat的下級目錄lib資料夾中,可以使用JD-GUI工具對其進行檢視或匯出。如果需要獲取原始碼的,請使用以下命令進行克隆:

我們使用以下命令生成dex檔案:

dx --dex --output=user.dex ClassStudent.class

這裡我為了防止出錯,提前在當前目錄下新建好了user.dex檔案。上述命令依賴編譯.class檔案的JDK版本,如果使用的是JDK8編譯的class會提示以下問題:

PARSE ERROR:
unsupported class file version 52.0
...while parsing ClassStudent.class
1 error; aborting

這裡的52.0意味著class檔案不被支援,需要使用JDK8以下的版本進行編譯,但是dx所需的環境還是需要為JDK8的,這裡我編譯class檔案使用的是JDK7,請注意。

上面我們提到了為什麼先不要在ClassStudent中使用包名,因為在執行dx的時候會報以下異常,這是因為以下第二項條件沒有通過,該程式碼位於com.android.dx.cf.direct.DirectClassFile檔案內:

    String thisClassName = thisClass.getClassType().getClassName();
    if(!(filePath.endsWith(".class") && filePath.startsWith(thisClassName) && (filePath.length()==(thisClassName.length()+6)))){
        throw new ParseException("class name (" + thisClassName + ") does not match path (" + filePath + ")");
    }

執行截圖如下所示:
這裡寫圖片描述

好了,到此為止我們的目錄應該如下:
這裡寫圖片描述

寫入dex到本地磁碟

接下來將生成好的user.dex檔案放入Android工程的res\raw資料夾下:
這裡寫圖片描述

在系統啟動時將其寫入到磁碟,這裡不再貼出具體的寫入程式碼,專案的MainActivity中包含了此部分程式碼。

載入dex中的類並測試

在寫入完畢之後使用DexClassLoader對其進行載入。DexClassLoader的構造方法需要4個引數,這裡對這4個引數進行簡要說明:

  • String dexPath:dex檔案的絕對路徑。在這裡我將其放入了應用的cache資料夾下。
  • String optimizedDirectory:優化後的dex檔案存放路徑。DexClassLoader在構造完畢之後會對原有的dex檔案優化並生成一個新的dex檔案,在這裡我選擇的是…/cache/optimizedDirectory/目錄。此外,API文件對該目錄有嚴格的說明:Do not cache optimized classes on external storage.出於安全考慮,請不要將優化後的dex檔案放入外部儲存器中。
  • String libraryPath:dex檔案所需要的庫檔案路徑。這裡沒有依賴,使用空字串代替。
  • ClassLoader parent:雙親委派原則中提到的父類載入器。這裡我們使用預設的載入器,通過getClassLoader()方法獲得。

在解釋完畢DexClassLoader的構造引數之後,我們開始對剛剛的dex檔案進行載入:

DexClassLoader dexClassLoader = new DexClassLoader(apkPath, file.getParent() + "/optimizedDirectory/", "", classLoader);

接來下開始load我們剛剛寫入在dex檔案中的ClassStudent類:

Class<?> aClass = dexClassLoader.loadClass("ClassStudent");

然後我們對其進行初始化,並呼叫相關的get/set方法對其進行驗證,在這裡我傳給ClassStudent物件一個字串,然後呼叫它的get方法獲取在方法內合併後的字串:

    Object instance = aClass.newInstance();
    Method method = aClass.getMethod("setName", String.class);
    method.invoke(instance, "Sahadev");

    Method getNameMethod = aClass.getMethod("getName");
    Object invoke = getNameMethod.invoke(instance););

最後我們實現的程式碼可能是這樣的:

    /**
     * 載入指定路徑的dex
     *
     * @param apkPath
     */
    private void loadClass(String apkPath) {
        ClassLoader classLoader = getClassLoader();

        File file = new File(apkPath);

        try {

            DexClassLoader dexClassLoader = new DexClassLoader(apkPath, file.getParent() + "/optimizedDirectory/", "", classLoader);
            Class<?> aClass = dexClassLoader.loadClass("ClassStudent");
            mLog.i(TAG, "ClassStudent = " + aClass);

            Object instance = aClass.newInstance();
            Method method = aClass.getMethod("setName", String.class);
            method.invoke(instance, "Sahadev");

            Method getNameMethod = aClass.getMethod("getName");
            Object invoke = getNameMethod.invoke(instance);

            mLog.i(TAG, "invoke result = " + invoke);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

最後附上我們的執行截圖:
這裡寫圖片描述

如果在實現過程中遇到問題的,請在下方留言。

相關推薦

步步手動實現修復()-dex檔案生成載入

*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出 熱修復技術自從QQ空間團隊搞出來之後便漸漸趨於成熟。 我們這個系列主要介紹如何一步步手動實現基本的熱修復功能,無需使用第三方框架。 在開始學習之前,需要對基本的熱修復技術有些瞭解,以下

步步動手實現高併發的Reactor模型 —— Kafka底層如何充分利用多執行緒優勢去處理網路I/O業務分發

一、從《Apeche Kafka原始碼剖析》上搬來的概念和圖 Kafka網路採用的是Reactor模式,是一種基於事件驅動的模式。熟悉Java程式設計的讀者應該瞭解Java NIO提供了Reactor模式的API。常見的單執行緒Java NIO程式設計模式如圖所示。 熟悉NIO程式設計都應該知道這個Sele

Rxjava2.x 原始碼分析,以及手動實現Rxjava(

這兩年Rxjava火的一塌糊塗,不會點Rxjava+Okhttp+Retrofit+MVP+Dagger2架構都不好意思說自己混Android的。Rxjava 到底是什麼和Rxjava到底怎麼用,這裡就不講了,網上太多了,具體可以參考 這位大佬 和扔物線的。  Rxjava

React Native用CodePush實現更新()

本文介紹用微軟預設的CodePush Cloud和將code-push-server放在本地伺服器上,以local作為storageType實現熱更新。1. 安裝 CodePush CLI:npm in

步步動手實現簡單的執行緒池 —— 生動有趣解析 Java 執行緒池原始碼

零、引子 某天小奈與小夥伴肥宅埋的日常技(cai)術(ji)研(hu)討(zhuo)中聊起了執行緒池。 自詡十分熟悉併發程式設計

【SSH之旅】步步學習Hibernate框架():關於持久化

stc localhost 對象 schema hbm.xml java let pass [] 在不引用不論什麽框架下,我們會通過平庸的代碼不停的對數據庫進行操作,產生了非常多冗余的可是又有規律的底層代碼,這樣頻繁的操作數據庫和大量的底層代碼的反復

Android 手動實現更新

前言 在上篇Android ClassLoader淺析中我們分析了安卓ClassLoader和熱更新的原理,這篇我們在上篇熱更新分析的基礎上寫個簡單的demo實踐一下。 概述 我們先回顧下熱更新的原理 PathClassLoader是安卓中預設的類載入器,載入類是通過fi

步步完善rootfs:2.建立配置檔案

建立配置檔案 核心啟動到最後啟動的第一個使用者程序是init程序,它根據檔案系統下的配置檔案決定啟動哪些程式,init程序是後續所有程序的發起者。 /etc 目錄用於存放系統中的配置檔案,基本上所有的配置檔案都可以在這裡找到,這些檔案一般都以XXX.conf的形式命名,通過編輯這些檔案可

iOS實現修復的幾種方案

最近,在調研熱修復技術,也稱作熱更新技術。由於蘋果稽核週期有時候比較長,這是公司無法忍受的,所以熱修復技術應運而生。經過查閱多方面的資料,進行如下總結,希望對大家有所幫助。現在比較流行的熱修復技術:一、使用JSPatch進行熱修復。      JSPatch能做到通過JS呼叫

spring boot 實現部署,部署java檔案和靜態資源

自己學習了spring boot發現很方便使用,加上熱部署功能,不需要改個樣式就重啟服務,浪費時間了.修改完檔案之後,spring boot 自動給你更新資源,很方便開發人員除錯. 接下來讓我們一步步來實現這個功能. 首先我們需要在gradle 裡面新增依賴 runt

200行Go代碼實現自己的區塊鏈——區塊生成網絡通信

type hash lazy avi present lte () cti 裏的 在第一篇文章[1]中,我們向大家展示了如何通過精煉的Go代碼實現一個簡單的區塊鏈。如何計算每個塊的 Hash 值,如何驗證塊數據,如何讓塊鏈接起來等等,但是所有這些都是跑在一個節點上的。文章

C++中的位移操作以實現檔案的壓縮(實現哈夫曼對檔案壓縮解壓時做的一個小測試)

因為以前基本上沒用過位移操作,所以這裡做了一個小測試,加深了一下對位移的理解 相關概念:        因為C++中對檔案的操作常用的就是按位元組來進行讀取。下面對檔案的讀寫進行舉例(這是我常用的方式,大家也可以用其它方法讀取):   首先包含相關標頭檔案:     

步教你實現阿里巴巴的Sophix修復

1.0 整合準備 gradle遠端倉庫依賴, 開啟專案找到app的build.gradle檔案,新增如下配置: 新增maven倉庫地址: repositories { maven { url "http://maven.ali

android:使用small步步實現外掛化更新

由於外掛化開發與熱更新最近貌似越來越火,新開的專案準備也使用外掛化進行開發!其中遇到不少坑,在這裡寫了一個小的例子,記錄一下開發流程,有助於自己,同時希望能夠幫助大家理解,並且對於自身專案接入外掛化有所幫助! 外掛化 效果: 外掛化開發的含義:

步步實現redis+sentinel雙機

前言 前些天一直在忙線上環境部署的事情,初步想的是,nginx(keepalive雙機熱備)+3(tomcat)+2redis(雙機熱備),但是後來由於阿里雲伺服器經典網路不提供虛擬IP,無法使用keepalive,nginx雙機熱備只能暫時先放棄,退而求其次,採用ngin

步步深入學習webpack(入門困惑express和dev-server區別及分別使用dev-server和webpack-hot-middleware實現載入區別)

最近需要對webpack詳細學習後,給大家分享學習。於是不得不對每一個點進行學習,結果發現webpack涉及到的知識內容好多,自己學習也是一知半解,很多時候腦細胞都死得一片一片的。 注:本文是參考網上多方資料學習後記錄的,如有雷同,請聯絡我。 學習資料:入門

步步實現 Redis 搜索引擎

行集 準備 exp sta 發的 ast 註意 自己 內容 來源:jasonGeng88 github.com/jasonGeng88/blog/blob/master/201706/redis-search.md 如有好文章投稿,請點擊 → 這裏了解詳情 場景 大家如

WPF步步實現完全無邊框自定義Window(附源碼)

nbsp interop -c 思路 pan cit 最終 auto pre 在我們設計一個軟件的時候,有很多時候我們需要按照美工的設計來重新設計整個版面,這當然包括主窗體,因為WPF為我們提供了強大的模板的特性,這就為我們自定義各種空間提供了可能性,這篇博客主要用來

Android修復 Dex注入實現靜默消滅bug

      當app上線後發現緊急bug,如果重新發布版本週期比較長,並且對使用者體驗不好,此時熱修復就派上用場了。熱修復就是為緊急bug而生,能夠快速修復bug,並且使用者無感知。針對熱修復,阿里系先後推出AndFix、HotFix、SophFix,騰訊系也推出QQ空間超級補丁

步步實現windows版ijkplayer系列文章之五——使用automake生成makefile

一步步實現windows版ijkplayer系列文章之一——Windows10平臺編譯ffmpeg 4.0.2,生成ffplay 一步步實現windows版ijkplayer系列文章之二——Ijkplayer播放器原始碼分析之音視訊輸出——視訊篇 一步步實現windows版ijkplayer系列文章之三——I