記 Scrcpy 框架使用記錄


title: 記 Scrcpy 框架使用記錄
date: 2019-03-03


背景

最近使用 vysor 尼啡。發(fā)現(xiàn)直接把手機當成模擬器操作確實是方便到不行侄榴。 但是魅族 16th plus 在 vysor 失效了。同時vysor 通知太過干擾蜈漓「榱希基于以上兩點切換到開源框架 scrcpy: Display and control your Android device

原理

image.png

主要步驟如下:

  1. 通過 adb push 一個 scrcpy-server.jar 到手機上凉驻。
    注: scrcpy-server.jar 是雖然是一個 zip 文件。 但是其實是一個apk艳丛。
  2. PC 端通過 adb reverse 反向代理手機端口匣掸。用來接收手機端發(fā)送過來的數(shù)據(jù)。
  3. adb shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process /com.genymobile.scrcpy.Server com.genymobile.scrcpy.Server 0 8000000 false - false 使用 app_process 運行 scrcpy-server.jar 的代碼氮双。
    scrcpy-server.jar 主要做三件事情:
    1碰酝,開啟 LocalSocket 和PC連接。 相應 PC 端傳遞過來的操作戴差。
    2送爸,源源不斷的將屏幕畫面輸出到PC,使用Mediacodec 編碼暖释。 PC 通過ffmpeg 解碼播放袭厂。
    3,使用 adb 來提高 scrcpy-server.jar 的運行權(quán)限
    注: 模擬 input 事件使用 android.hardware.input.IInputManager.injectInputEvent 方法球匕。

安裝

mac 環(huán)境下使用 brew install scrcpy 纹磺,通過漫長的等待完成安裝。同時設置adb 環(huán)境變量亮曹。這里不具體展開橄杨。運行 scrcpy 命令

scrcpy

問題

魅族16 th 出現(xiàn)了錯誤:

image.png

通過scrcpy 的issue 發(fā)現(xiàn)這是一個已知的問題 #365 startsWith() on null object at ScreenEncoder.java:158
image.png

同樣發(fā)生在魅族 16th 的機型上。 可以確定是因為魔改 Android 源碼導致照卦。 可以相信這個 BUG 將會存在很長一段時間式矫。 那么我們嘗試自己動手一下。

分析

這是一個空指針異常窄瘟, 空指針是最常見也是最簡單的一個bug衷佃。 我們需要拿到系統(tǒng)的代碼進行分析。

獲取 android.media.MediaCodec 源碼

獲取系統(tǒng)的代碼

adb pull /system/framework/arm/boot-framework.oat

使用 baksmali 反編譯oat:

java -jar baksmali-2.2.6.jar deodex  /boot-framework.oat

得到系統(tǒng)代碼的 smali 蹄葱。 找到 MediaCodec 的 smali 的1918行
注:反編譯方式 在 8.1 上 baksmali 會失敗氏义, 可以嘗試 vdexExtractor 從 vdex 獲取 dex 文件。在對dex 進行反解得到代碼图云。

.method private configure(Landroid/media/MediaFormat;Landroid/view/Surface;Landroid/media/MediaCrypto;Landroid/os/IHwBinder;I)V
    .registers 19
    .param p1, "format"    # Landroid/media/MediaFormat;
    .param p2, "surface"    # Landroid/view/Surface;
    .param p3, "crypto"    # Landroid/media/MediaCrypto;
    .param p4, "descramblerBinder"    # Landroid/os/IHwBinder;
    .param p5, "flags"    # I
    ...
    .line 1918
    .local v2, "values":[Ljava/lang/Object;
    invoke-static {}, Landroid/app/ActivityThread;->currentPackageName()Ljava/lang/String;

    move-result-object v0

    const-string/jumbo v3, "com.tencent.mm"

    invoke-virtual {v0, v3}, Ljava/lang/String;->startsWith(Ljava/lang/String;)Z

    move-result v0

    if-eqz v0, :cond_23
   ...
}

這里使用 ActivityThread.currentPackageName() 獲取包名與 微信的包名做比較惯悠。
通過分析可以發(fā)現(xiàn) ActivityThread.currentPackageName() 返回為 null 導致的空指針異常。

問題原因

ActivityThread.currentPackageName() 為空竣况?
正確情況 APP 啟動的流程如下:AMS 調(diào)用
Process.start("android.app.ActivityThread", app.processName, uid, uid...) 通知 zygote 進程 fork 出一個新的進程同時執(zhí)行 android.app.ActivityThread.main(String[] args) 方法克婶。
main方法會初始化 ActivityThread 和初始化主線程Looper。同時調(diào)用 ActivityThread.attach()方法,會將 binder 類型 ApplicationThread 對象傳遞給 ActivityMangerService 情萤。ASM 獲取到 ApplicationThread 以后會查詢對應的信息(包括 PackageName)鸭蛙, 會通過 ApplicationThread 以 IPC 的方式將信息回調(diào)給 ActivityThread,bindApplication 方法. app bindApplication 方法以后才知道自己的包名。在至此 ActivityThread.currentPackageName() 不為空筋岛。
App 運行在 fork zygote 的子進程中娶视。
而 Scrcpy 是通過 app_process 啟動非 zygote 的 Runtime 進程中。

解決方案:

 public static String currentPackageName() {
        ActivityThread am = currentActivityThread();
        return (am != null && am.mBoundApplication != null)
            ? am.mBoundApplication.appInfo.packageName : null;
    }

最初方式是使用 xposed hook 該方法睁宰。 對空值進行防護肪获。這樣就不用管 ActivityThread 后續(xù)可能的魔改。
但是Xposed 默認不對非 zygote 進行進行攔截柒傻。
isXposedLoaded = xposed::initialize(zygote, startSystemServer, className, argc, argv)


/** Initialize Xposed (unless it is disabled). */
bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) {
#if !defined(XPOSED_ENABLE_FOR_TOOLS)
    if (!zygote)
        return false;
#endif

    if (isMinimalFramework()) {
        ALOGI("Not loading Xposed for minimal framework (encrypted device)");
        return false;
    }

求其次使用最簡單的方案: 反射

只要 currentActivityThread 孝赫,mBoundApplication ,appInfo红符,packageName 不為空青柄,ActivityThread.currentPackageName() 返回不為空。

 try {
            Class<?> ActivityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentPackageName = ActivityThreadClass.getMethod("currentPackageName");
            currentPackageName.setAccessible(true);
            Field sCurrentActivityThread = ActivityThreadClass.getDeclaredField("sCurrentActivityThread");
            Field mBoundApplication = ActivityThreadClass.getDeclaredField("mBoundApplication");

            sCurrentActivityThread.setAccessible(true);
            mBoundApplication.setAccessible(true);

            Constructor<?> constructor = ActivityThreadClass.getDeclaredConstructor();
            constructor.setAccessible(true);
//            sCurrentActivityThread.set(null, UnsafeAllocator.create().newInstance(ActivityThreadClass));
            sCurrentActivityThread.set(null, constructor.newInstance());
            Object sCurrentActivityThreadObject = sCurrentActivityThread.get(null);

            Class<?> AppBindDataClass = Class.forName("android.app.ActivityThread$AppBindData");
            Field appInfo = AppBindDataClass.getDeclaredField("appInfo");
            appInfo.setAccessible(true);
            Constructor<?> constructor1 = AppBindDataClass.getDeclaredConstructor();
            constructor1.setAccessible(true);
            Object AppBindDataObject = constructor1.newInstance();
            ApplicationInfo applicationInfo = new ApplicationInfo();
            applicationInfo.packageName = "com.dim";
            appInfo.set(AppBindDataObject, applicationInfo);
            mBoundApplication.setAccessible(true);
            mBoundApplication.set(sCurrentActivityThreadObject, AppBindDataObject);
        } catch (Throwable throwable) {
        }

我們需要將代碼插入到 com.genymobile.scrcpy.Server
main 方法中违孝。 將編譯好的 apk 重新命令 scrcpy-server.jar 替換目錄 /usr/local/Cellar/scrcpy/1.7/share/scrcpy/scrcpy-server.jar: 這是mac 下的地方刹前。其他可能會有不同。
同時還需要做的事情是
初始化 Looper 因為 ActivityThread 中的 H 是個Handler雌桑。 Handler 的初始化需要Looper 環(huán)境喇喉。
至此對于 魅族16 th 在 Scrcpy 的改造完成。

image.png

總結(jié)

這篇主要介紹了 Scrcpy 框架的主要原理校坑。 以及如何反編譯系統(tǒng)的代碼拣技。

相關(guān)

改造后 scrcpy-server.jar

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市耍目,隨后出現(xiàn)的幾起案子膏斤,更是在濱河造成了極大的恐慌,老刑警劉巖邪驮,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莫辨,死亡現(xiàn)場離奇詭異,居然都是意外死亡毅访,警方通過查閱死者的電腦和手機沮榜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喻粹,“玉大人蟆融,你說我怎么就攤上這事∈匚兀” “怎么了型酥?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵山憨,是天一觀的道長。 經(jīng)常有香客問我弥喉,道長郁竟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任由境,我火速辦了婚禮枪孩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘藻肄。我一直安慰自己,他們只是感情好拒担,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布嘹屯。 她就那樣靜靜地躺著,像睡著了一般从撼。 火紅的嫁衣襯著肌膚如雪州弟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天低零,我揣著相機與錄音婆翔,去河邊找鬼。 笑死掏婶,一個胖子當著我的面吹牛啃奴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雄妥,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼最蕾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了老厌?” 一聲冷哼從身側(cè)響起瘟则,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枝秤,沒想到半個月后醋拧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡淀弹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年丹壕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垦页。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡雀费,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出痊焊,到底是詐尸還是另有隱情盏袄,我是刑警寧澤忿峻,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站辕羽,受9級特大地震影響逛尚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刁愿,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一绰寞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧铣口,春花似錦滤钱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至叔遂,卻和暖如春他炊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背已艰。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工痊末, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哩掺。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓凿叠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嚼吞。 傳聞我的和親對象是個殘疾皇子幔嫂,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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