天天看點

java的反射在Android中的應用

    可能,與你所期待的内容有點不一樣。今天回來的時候,叫了滴滴。上車後,由于我的距離比較近,滴滴就指揮司機先送我到站。沒錯,後面坐了一名女子。

    她突然大聲說道:“你要去哪裡?!”司機回應道:“先送他回去呀。”

    “你這都離我那邊越來越遠啦!”女子略顯着急。

    “什麼?!從這裡過橋呀!”

    “哦。。。”

    我差不多到的時候,女子顯得更加着急的樣子,“呵,我都不知道你開到哪裡了!”

    司機無奈道:“就到了,馬上就送你回去了!”仿佛吃了什麼反胃的東西,剛要吐出來又吞了回去的樣子。唯獨我一個人在前排偷笑,不禁感歎一下,社會治安真是越來越好了。

    回到正題,今天在看百度語音sdk的時候,發現并沒有開放完全離線的功能。那麼完全離線到底有多重要?如果你的客戶處于網絡極差的環境,一次語音合成要耗掉6秒的時間,他不把你家産品砸了都算客氣了。幸虧以往遇到的客戶都是比較客氣的,使用的時候都是把網絡斷開的。今天就想處理一下百度語音沒開放完全離線功能的這個問題。

    文檔中說明了線上離線語音判别的基本邏輯 

java的反射在Android中的應用

    看到這個,我想如果能讓第一步直接跳到離線就好了。百度語音類庫中肯定存在網絡連接配接判斷,我需要找出在哪個地方進行了判斷,然後通過java的方式重寫或通過反射的方式植入自己的代碼改變其判斷結果。思路就這是樣的。

    遺憾的是,在我調試程式的時候,調用speak就直接調用了libbdtts.so裡面的方法完成了邏輯判斷。于是知道核心代碼不是放在java上,而是放在封裝好的二進制類庫中。

    但是,天無絕人之路。我找了一下通過ndk判斷網絡連接配接狀态的相關資料,但沒找到。想會不會是通過ndk調用java進行判斷的呢?我先從sdk中找到了與網絡狀态相關的一個類,NetworkInfo: https://developer.android.google.cn/reference/android/net/NetworkInfo

java的反射在Android中的應用

    這兩個方法傳回的就是android的網絡連接配接狀态。換言之,我可以讓這兩個方法傳回false試試。

    猜想有了,接下來是如何實作的問題。android代碼的運作過程是先通過classloader加載編譯後的dex檔案,然後通過dalvik虛拟機運作。android5.0後就是art了。這裡我沒有使用java的反射直接實作,那需要自己寫代理類和代理方法。找了一個android的aop架構,直接拿來用。

    找了一個阿裡巴巴開源的aop架構,https://github.com/alibaba/dexposed, 沒錯,關鍵字是dex。遺憾的是還沒支援android5.1,隻好把源碼拉取下來看看他們的進度如何。果然,android5.0以上有在測試中,遂編譯了一下,試試。

    在語音初始化後,調用如下代碼進行設定:

private static final XC_MethodHook hookOfflineCallback = new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        if (hook)param.setResult(false);
        else {
            ConnectivityManager manager = (ConnectivityManager) mSpeechSynthesizer.context.getApplicationContext().getSystemService(Application.CONNECTIVITY_SERVICE);
            param.setResult(manager.getActiveNetworkInfo().getState() == NetworkInfo.State.CONNECTED);
        }
    }
};      
DexposedBridge.findAndHookMethod(NetworkInfo.class, "isConnected", hookOfflineCallback);      

    找到isConnected的方法,傳回false就OK了。(其實我一開始試的是isConnectedOrConnecting,沒有效果,以為相容性的問題。後來才試驗的isConnected這個方法,有反應)上面是最終修改的代碼,隻執行了beforeHookedMethod,然後傳回結果。如果beforeHookedMethod不做傳回,會出現程式奔潰的問題,aop架構沒有處理好,是以這裡通過另類的方式解決:

    通過一個成員變量"hook"判斷是否開啟完全離線模式,如果使用原來的模式,就通過getState()來判斷網絡狀态是否連接配接,避開經過代理的isConnected()方法。(否則會出事)

    聯網的狀态下試驗結果:

    預設是連網調用傳回結果的,聲音也會比較好聽

java的反射在Android中的應用

    hook離線後:

java的反射在Android中的應用

    可以看到hook後執行了代理方法,傳回網絡連接配接狀态為false, 然後百度語音庫驗證本地離線證書再合成語音。 看來确實是通過so調用java進行判斷呢。測試了一下,穩定性還可以。就是dexposed這個架構跟不上,後續隐患待觀測。

    結論,java是世界上最好的語言。世界晚安,惠州晚安。。。

轉載于:https://my.oschina.net/eyesos/blog/2247706