Android中的“沙雕”操作之hook Toast

版權(quán)聲明:本文為博主原創(chuàng)文章痊臭,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議踩萎,轉(zhuǎn)載請附上原文出處鏈接和本聲明停局。
本文鏈接:http://www.reibang.com/p/a47bcf62109c

一,背景

這是個(gè)沙雕操作香府,原因是:在小米手機(jī)的部分機(jī)型上董栽,彈Toast時(shí)會在吐司內(nèi)容前面帶上app名稱,如下:


1.gif

此時(shí)產(chǎn)品經(jīng)理發(fā)話了:為了統(tǒng)一風(fēng)格企孩,在小米手機(jī)上去掉Toast前的應(yīng)用名锭碳。

網(wǎng)上有以下解決方案,比如:先給toastmessage設(shè)置為空勿璃,然后再設(shè)置需要提示的message擒抛,如下:

Toast toast = Toast.makeText(context, “”, Toast.LENGTH_LONG);
toast.setText(message);
toast.show();

但這些都不能從根本上解決問題,于是Hook Toast的方案誕生了补疑。

二闻葵,分析

首先分析一下Toast的創(chuàng)建過程.

Toast的簡單使用如下:

Toast.makeText(this,"abc",Toast.LENGTH_LONG).show();

1,構(gòu)造toast

通過makeText()構(gòu)造一個(gè)Toast癣丧,具體代碼如下:

public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
        @NonNull CharSequence text, @Duration int duration) {
    if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
        Toast result = new Toast(context, looper);
        result.mText = text;
        result.mDuration = duration;
        return result;
    } else {
        Toast result = new Toast(context, looper);
        View v = ToastPresenter.getTextToastView(context, text);
        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }
}

makeText()中也就是設(shè)置了時(shí)長以及要顯示的文本或自定義布局,對Hook沒有什么幫助栈妆。

2胁编,展示toast

接著看下Toast的show():

public void show() {
    ...

    INotificationManager service = getService();
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;
    final int displayId = mContext.getDisplayId();

    try {
        if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
            if (mNextView != null) {
                // It's a custom toast
                service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
            } else {
                // It's a text toast
                ITransientNotificationCallback callback =
                        new CallbackBinder(mCallbacks, mHandler);
                service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
            }
        } else {
            // 展示toast
            service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
        }
    } catch (RemoteException e) {
        // Empty
    }
}

代碼很簡單厢钧,主要是通過serviceenqueueToast()enqueueTextToast()兩種方式顯示toast。

service是一個(gè)INotificationManager類型的對象嬉橙,INotificationManager是一個(gè)接口早直,這就為動態(tài)代理提供了可能。

service是在每次show()時(shí)通過getService()獲取市框,那就來看看getService():

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private static INotificationManager sService;

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
static private INotificationManager getService() {
    if (sService != null) {
        return sService;
    }
    sService = INotificationManager.Stub.asInterface(
            ServiceManager.getService(Context.NOTIFICATION_SERVICE));
    return sService;
}

getService()最終返回的是sService霞扬,是一個(gè)懶漢式單例,因此可以通過反射獲取到其實(shí)例枫振。

3喻圃,小結(jié)

sService是一個(gè)單例,可以反射獲取到其實(shí)例粪滤。

sService實(shí)現(xiàn)了INotificationManager接口斧拍,因此可以動態(tài)代理。

因此可以通過Hook來干預(yù)Toast的展示杖小。

三肆汹,擼碼

理清了上面的過程,實(shí)現(xiàn)就很簡單了予权,直接擼碼:

1昂勉,獲取sService的Field

Class<Toast> toastClass = Toast.class;

Field sServiceField = toastClass.getDeclaredField("sService");
sServiceField.setAccessible(true);

2,動態(tài)代理替換

Object proxy = Proxy.newProxyInstance(Thread.class.getClassLoader(), new Class[]{INotificationManager.class}, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        return null;
    }
});
// 用代理對象給sService賦值
sServiceField.set(null, proxy);

3扫腺,獲取sService原始對象

因?yàn)閯討B(tài)代理不能影響被代理對象的原有流程岗照,因此需要在第二步的InvocationHandler()invoke()中需要執(zhí)行原有的邏輯,這就需要獲取sService的原始對象斧账。

前面已經(jīng)獲取到了sService的Field谴返,它是靜態(tài)的,那直接通過sServiceField.get(null)獲取不就可以了咧织?然而并不能獲取到嗓袱,這是因?yàn)檎麄€(gè)Hook操作是在應(yīng)用初始化時(shí),整個(gè)應(yīng)用還沒有執(zhí)行過Toast.show()的操作习绢,因此sService還沒有初始化(因?yàn)樗且粋€(gè)懶漢單例)渠抹。

既然不能直接獲取,那就通過反射調(diào)用一下:

Method getServiceMethod = toastClass.getDeclaredMethod("getService", null);
getServiceMethod.setAccessible(true);
Object service = getServiceMethod.invoke(null);

接著完善一下第二步代碼:

Object proxy = Proxy.newProxyInstance(Thread.class.getClassLoader(), new Class[]{INotificationManager.class}, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        return method.invoke(service, args);
    }
});

到此闪萄,已經(jīng)實(shí)現(xiàn)了對Toast的代理梧却,Toast可以按照原始邏輯正常執(zhí)行,但還沒有加入額外邏輯败去。

4放航,添加Hook邏輯

InvocationHandlerinvoke()方法中添加額外邏輯:

Object proxy = Proxy.newProxyInstance(Thread.class.getClassLoader(), new Class[]{INotificationManager.class}, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 判斷enqueueToast()方法時(shí)執(zhí)行操作
        if (method.getName().equals("enqueueToast")) {
            Log.e("hook", method.getName());
            getContent(args[1]);
        }
        return method.invoke(service, args);
    }
});

args數(shù)組的第二個(gè)是TN類型的對象,其中有一個(gè)LinearLayout類型的mNextView對象圆裕,mNextView中有一個(gè)TextView類型的childView广鳍,這個(gè)childView就是展示toast文本的那個(gè)TextView荆几,可以直接獲取其文本內(nèi)容,也可以對其賦值赊时,因此代碼如下:

private static void getContent(Object arg) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    // 獲取TN的class
    Class<?> tnClass = Class.forName(Toast.class.getName() + "$TN");
    // 獲取mNextView的Field
    Field mNextViewField = tnClass.getDeclaredField("mNextView");
    mNextViewField.setAccessible(true);
    // 獲取mNextView實(shí)例
    LinearLayout mNextView = (LinearLayout) mNextViewField.get(arg);
    // 獲取textview
    TextView childView = (TextView) mNextView.getChildAt(0);
    // 獲取文本內(nèi)容
    CharSequence text = childView.getText();
    // 替換文本并賦值
    childView.setText(text.toString().replace("HookToast:", ""));
    Log.e("hook", "content: " + childView.getText());
}

最后看一下效果:


2.gif

四吨铸,總結(jié)

這個(gè)一個(gè)沙雕操作,實(shí)際應(yīng)用中這種需求也比較少見祖秒。通過Hook的方式可以統(tǒng)一控制诞吱,而且沒有侵入性。大佬勿噴=叻臁7课!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末歌馍,一起剝皮案震驚了整個(gè)濱河市握巢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌松却,老刑警劉巖暴浦,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異晓锻,居然都是意外死亡歌焦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門砚哆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來独撇,“玉大人,你說我怎么就攤上這事躁锁》紫常” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵战转,是天一觀的道長搜立。 經(jīng)常有香客問我,道長槐秧,這世上最難降的妖魔是什么啄踊? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮刁标,結(jié)果婚禮上颠通,老公的妹妹穿的比我還像新娘。我一直安慰自己膀懈,他們只是感情好顿锰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般撵儿。 火紅的嫁衣襯著肌膚如雪乘客。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天淀歇,我揣著相機(jī)與錄音,去河邊找鬼匈织。 笑死浪默,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缀匕。 我是一名探鬼主播纳决,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乡小!你這毒婦竟也來了阔加?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤满钟,失蹤者是張志新(化名)和其女友劉穎胜榔,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體湃番,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡夭织,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吠撮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尊惰。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖泥兰,靈堂內(nèi)的尸體忽然破棺而出弄屡,到底是詐尸還是另有隱情,我是刑警寧澤鞋诗,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布膀捷,位于F島的核電站,受9級特大地震影響师脂,放射性物質(zhì)發(fā)生泄漏担孔。R本人自食惡果不足惜蟆技,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一箭昵、第九天 我趴在偏房一處隱蔽的房頂上張望谭羔。 院中可真熱鬧确镊,春花似錦袭厂、人聲如沸玖翅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至墩崩,卻和暖如春氓英,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鹦筹。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工铝阐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铐拐。 一個(gè)月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓徘键,卻偏偏與公主長得像,于是被迫代替她去往敵國和親遍蟋。 傳聞我的和親對象是個(gè)殘疾皇子吹害,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

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

  • 最近發(fā)現(xiàn)在小米高系統(tǒng)版本的手機(jī)上,Toast的內(nèi)容會自帶應(yīng)用名稱的前綴虚青;百度一下它呀,發(fā)現(xiàn)的確不少這些反饋(萬惡的小米...
    zl_adams閱讀 565評論 1 4
  • 最近在研究插件化開發(fā),插件化開發(fā)的基礎(chǔ)就是hook技術(shù)棒厘,現(xiàn)在市面上存在的各種插件化框架纵穿,其基礎(chǔ)原理都是使用hook...
    在路上的_軟件菜鳥閱讀 2,424評論 0 1
  • 什么是土司(Toast)? Toast是Android系統(tǒng)提供的一種非常好的提示方式,在程序中可以使用它將一些短小...
    一航j(luò)ason閱讀 1,060評論 0 1
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭绊谭,有人歡樂有人憂愁政恍,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,536評論 28 53
  • 信任包括信任自己和信任他人 很多時(shí)候达传,很多事情篙耗,失敗、遺憾宪赶、錯過宗弯,源于不自信,不信任他人 覺得自己做不成搂妻,別人做不...
    吳氵晃閱讀 6,189評論 4 8