一起来写 Xposed 模块,让 MIUI 负一屏快递不跳阿里系

自己动手,丰衣足食。

很多时候我们并不会满足于软件自带的功能,但是我们又不是 PM,不可能让软件开发者去实现我们想要的东西。那么这个时候,我们就需要用一点其他办法了。对于 Android 软件,最直接最常用的办法就是通过 Xposed 模块。

为什么突然想写这个呢?MIUI 负一屏一直有一个查看快递的功能,但是在很久之前的一次更新之后,由于阿里的限制,查看详情必须跳转到阿里系应用(淘宝、支付宝、菜鸟裹裹之类的)。在忍受了很久之后,我决定想办法干掉这个跳转。

阅读本文需要学会 Frida 和 JADX 的使用。

找抓手,狠赋能

编写 Xposed 模块,首先需要找到 Hook 点。这个过程在以前是比较痛苦的,现在有了 Frida 就方便许多了。

首先考虑这么个情况,在点击负一屏快递的时候是会启动阿里系应用的 Activity 的,这个过程一定是通过 Context#startActivityForResult() 完成的(为什么说是 startActivityForResult() 呢?因为 startActivity() 最后也会调用它)。而 ActivityTaskManager 会在启动新的 Activity 的时候输出一条日志,包括 Intent 里面的 actdatflg 等字段。猜测负一屏会向待启动的应用传递快递单号。因此直接在 Logcat 里搜索任意一条快递单号。可以找到下面一条数据。在 dat 字段里包含了跳转阿里系应用的 URI(这里是淘宝),URI 里有一个参数就是快递单号。

2021-08-13 11:22:25.360 1344-7604/? I/ActivityTaskManager: START u0 {act=android.intent.action.VIEW dat=tbopen://m.taobao.com/tbopen/index.html?action=ali.open.nav&module=h5&h5Url=https://m.duanqu.com?_wml_code=vvslIPG4dzypz9NyrjCAq5IPalrXJLavIpXHcBKguXM%3D&_ariver_appid=11509317&query=showcard%3dtrue%26insertPackage%3dtrue%26from%3dxiaomi%26mailNo%3d43***********&showcard=true&insertPackage=true&from=xiaomi&mailNo=43*********** flg=0x20008000 cmp=com.taobao.taobao/com.taobao.tao.welcome.Welcome} from uid 10457

那么我们 Hook 掉 Context#startActivityForResult() 就好了?用 Frida 试试:

function hookStartActivity() {
    var Activity = Java.use("android.app.Activity")

    Activity.startActivityForResult.overloads[1].implementation = function (intent, requestCode, options) {
        console.log(intent, options)
    }

    Activity.startActivityForResult.overloads[0].implementation = function (intent, requestCode) {
        console.log(intent)
    }
}

我们成功拿到了输出:

Intent { act=android.intent.action.VIEW dat=tbopen://m.taobao.com/tbopen/index.html?action=ali.open.nav&module=h5&h5Url=https://m.duanqu.com?_wml_code=vvslIPG4dzypz9NyrjCAq5IPalrXJLavIpXHcBKguXM%3D&_ariver_appid=11509317&query=showcard%3dtrue%26insertPackage%3dtrue%26from%3dxiaomi%26mailNo%3d43***********&showcard=true&insertPackage=true&from=xiaomi&mailNo=43*********** flg=0x30008000 }

不过有个很诡异的问题,这个函数在从负一屏点击快递项的时候并不会触发,只会在点击智能助理里全部快递列表里的快递项才触发。这个问题我也没想出来为什么。这样的话,我们就换个函数 Hook。那么换哪个函数呢?这就需要反编译查看代码了。

但是我们需要看什么地方的源代码呢?首先可以确定的是,全部快递列表的 Activity 是 com.miui.personalassistant.express.activity.ExpressMainActivity

把智能助理拉到 JADX 里,还好,没有加壳,源代码也基本没有混淆。定位到上面提到的 Activity。其实这里没有什么有意思的东西,不过我们注意到另一个类 com.miui.personalassistant.express.ExpressIntentUtils,从名字能感觉出,这是构造快递 Intent 的工具类,点开看看。

代码贴在下面了。可以看到有很多 tryOpenXXX 的函数,看起来是用来打开不同的第三方应用的。

public class ExpressIntentUtils {
    public static final String QUERY_VALUE_FROM = "xiaomi";
    private static final String TAG = "Express:IntentUtils";
    public static final String URI_ALIPAY_DETAIL = "alipays://platformapi/startapp?appId=2021001141626787";
    public static final String URI_CAINIAO_DETAIL = "cainiao://startapp/logistic";
    public static final String URI_CAINIAO_EXPRESS = "hap://app/com.cainiao.guoguoquickapp/?__DSP__=true&page=post";
    public static final String URI_CAINIAO_H5 = "https://page.cainiao.com/guoguo/app-myexpress-taobao/ld.html";
    public static final String URI_KUAIDI100_EXPRESS = "hap://app/com.application.kuaidi100/result?channel=xiaomi_fyp";
    public static final String URI_QUERY_COME_FROM = "comefrom";
    public static final String URI_QUERY_CP_CODE = "cpCode";
    public static final String URI_QUERY_FROM = "from";
    public static final String URI_QUERY_INSERT_PACKAGE = "insertPackage";
    public static final String URI_QUERY_KUAIDI100_COMPANY = "com";
    public static final String URI_QUERY_KUAIDI100_NUMBER = "nu";
    public static final String URI_QUERY_MAIL_NO = "mailNo";
    public static final String URI_QUERY_QUERY = "query";
    public static final String URI_QUERY_SHOW_CARD = "showcard";
    public static final String URI_QUERY_URL = "h5Url";
    public static final String URI_SECRET_KEY = "secretKey";
    public static final String URI_TAOBAO_DETAIL = "tbopen://m.taobao.com/tbopen/index.html?action=ali.open.nav&module=h5";
    public static final String URI_TAOBAO_DETAIL_NEW = "tbopen://m.taobao.com/tbopen/index.html?action=ali.open.nav&module=h5&bc_fl_src=growth_dhh_2200803434088_20200910-9960-1069565676&bootImage=0";
    public static final String URI_TAOBAO_H5 = "https://m.duanqu.com?_wml_code=vvslIPG4dzypz9NyrjCAq5IPalrXJLavIpXHcBKguXM%3D&_ariver_appid=11509317";
    public static final String URI_TAOBAO_H5_NEW = "h5.m.taobao.com/awp/mtb/oper.htm";
    public static final String URI_XIAOMI_EXPRESS = "hap://app/com.mi.midelivery/home?__DSP__=true";

    private static boolean tryOpenExpressDetail(Context context, String str, Uri uri) {
        if (!Util.isInstalled(context, str)) {
            return false;
        }
        try {
            Intent intent = new Intent("android.intent.action.VIEW");
            intent.setData(uri);
            intent.addFlags(536903680);
            return Util.startActivityNewTask(context, intent);
        } catch (Exception unused) {
            return false;
        }
    }

    private static boolean tryOpenTaobaoDetail(Context context, String str, String str2) {
        Uri build = Uri.parse(URI_TAOBAO_DETAIL).buildUpon().appendQueryParameter(URI_QUERY_URL, Uri.parse(URI_TAOBAO_H5).buildUpon().appendQueryParameter("from", "xiaomi").appendQueryParameter(URI_QUERY_SHOW_CARD, String.valueOf(true)).appendQueryParameter(URI_QUERY_INSERT_PACKAGE, String.valueOf(true)).appendQueryParameter("cpCode", str).appendQueryParameter("mailNo", str2).appendQueryParameter("query", new Uri.Builder().appendQueryParameter("from", "xiaomi").appendQueryParameter(URI_QUERY_SHOW_CARD, String.valueOf(true)).appendQueryParameter(URI_QUERY_INSERT_PACKAGE, String.valueOf(true)).appendQueryParameter("cpCode", str).appendQueryParameter("mailNo", str2).build().getQuery()).toString()).build();
        PALog.i(TAG, "tryOpenTaobaoDetail: tbopen://m.taobao.com/tbopen/index.html?action=ali.open.nav&module=h5");
        PALog.d(TAG, "tryOpenTaobaoDetail: " + build);
        return tryOpenExpressDetail(context, "com.taobao.taobao", build);
    }

    private static boolean tryOpenTaobaoDetailNew(Context context, String str) {
        Uri build = Uri.parse(URI_TAOBAO_DETAIL_NEW).buildUpon().appendQueryParameter(URI_QUERY_URL, Uri.parse(URI_TAOBAO_H5_NEW).buildUpon().appendQueryParameter("mailNo", str).toString()).build();
        PALog.i(TAG, "tryOpenTaobaoDetailNew: tbopen://m.taobao.com/tbopen/index.html?action=ali.open.nav&module=h5&bc_fl_src=growth_dhh_2200803434088_20200910-9960-1069565676&bootImage=0");
        PALog.d(TAG, "tryOpenTaobaoDetailNew: " + build);
        return tryOpenExpressDetail(context, "com.taobao.taobao", build);
    }

    private static boolean tryOpenCainiaoDetail(Context context, String str, String str2) {
        Uri build = Uri.parse(URI_CAINIAO_DETAIL).buildUpon().appendQueryParameter(URI_QUERY_COME_FROM, "xiaomi").appendQueryParameter("mailNo", str2).appendQueryParameter("cpCode", str).build();
        PALog.i(TAG, "tryOpenCainiaoDetail: cainiao://startapp/logistic");
        return tryOpenExpressDetail(context, Constants.Package.CAINIAO, build);
    }

    private static boolean tryOpenAlipayDetail(Context context, String str, String str2) {
        Uri build = Uri.parse(URI_ALIPAY_DETAIL).buildUpon().appendQueryParameter("query", new Uri.Builder().appendQueryParameter("from", "xiaomi").appendQueryParameter(URI_QUERY_SHOW_CARD, String.valueOf(true)).appendQueryParameter(URI_QUERY_INSERT_PACKAGE, String.valueOf(true)).appendQueryParameter("cpCode", str).appendQueryParameter("mailNo", str2).build().getQuery()).build();
        PALog.i(TAG, "tryOpenAlipayDetail: alipays://platformapi/startapp?appId=2021001141626787");
        return tryOpenExpressDetail(context, "com.eg.android.AlipayGphone", build);
    }

    private static boolean tryOpenH5Detail(Context context, String str, String str2, String str3) {
        Uri build = Uri.parse(URI_CAINIAO_H5).buildUpon().appendQueryParameter("mailNo", str).appendQueryParameter("cpCode", str2).appendQueryParameter(URI_SECRET_KEY, str3).appendQueryParameter("from", "XIAOMI").build();
        PALog.i(TAG, "tryOpenH5Detail: https://page.cainiao.com/guoguo/app-myexpress-taobao/ld.html");
        return tryOpenExpressDetail(context, "com.miui.personalassistant", build);
    }

    public static boolean tryOpenLocalDetailPage(ExpressEntry expressEntry) {
        if (expressEntry == null) {
            return false;
        }
        String companyCode = expressEntry.getCompanyCode();
        String provider = expressEntry.getProvider();
        boolean z = Constants.Provider.MIGUO.equals(provider) || Constants.Provider.MIMALL.equals(provider);
        boolean equals = "JDKD".equals(companyCode);
        if (z || equals) {
            return true;
        }
        return false;
    }

    public static String gotoExpressDetailPage(Context context, ExpressEntry expressEntry, boolean z, boolean z2) {
        if (expressEntry == null) {
            return null;
        }
        if (expressEntry.getUris() != null && expressEntry.getUris().size() > 0) {
            String openWithServerUri = openWithServerUri(context, expressEntry);
            PALog.i(TAG, "gotoExpressDetailPage: open item with server config," + openWithServerUri);
            if (!TextUtils.isEmpty(openWithServerUri)) {
                return "server_" + openWithServerUri;
            }
        }
        if (tryOpenLocalDetailPage(expressEntry)) {
            PALog.i(TAG, "gotoExpressDetailPage: open item with local activity");
            gotoLocalDetailPage(context, expressEntry, z);
            return "native";
        }
        requestServer(context, expressEntry);
        if (tryOpenCainiaoDetail(context, expressEntry.getCompanyCode(), expressEntry.getOrderNumber())) {
            PALog.i(TAG, "gotoExpressDetailPage: open item with Cainiao App");
            return AnalysisConfig.Express.CLICK_ACTION_CAINIAO_APP;
        } else if (tryOpenTaobaoDetail(context, expressEntry.getCompanyCode(), expressEntry.getOrderNumber())) {
            PALog.i(TAG, "gotoExpressDetailPage: open item with Taobao App");
            return "taobao";
        } else if (tryOpenAlipayDetail(context, expressEntry.getCompanyCode(), expressEntry.getOrderNumber())) {
            PALog.i(TAG, "gotoExpressDetailPage: open item with Alipay App");
            return "alipay";
        } else if (z2) {
            gotoLocalDetailPage(context, expressEntry, z);
            PALog.i(TAG, "gotoExpressDetailPage: open item with local activity");
            return "native";
        } else {
            tryOpenH5Detail(context, expressEntry.getOrderNumber(), expressEntry.getCompanyCode(), expressEntry.getSecretKey());
            PALog.i(TAG, "gotoExpressDetailPage: open item with H5");
            return "h5";
        }
    }

    @Nullable
    private static String openWithServerUri(@NonNull Context context, @NonNull ExpressEntry expressEntry) {
        List<ExpressInfo.Uri> uris = expressEntry.getUris();
        Collections.sort(uris);
        for (ExpressInfo.Uri uri : uris) {
            if (tryOpenExpressDetail(context, uri.getApp(), Uri.parse(uri.getLink()))) {
                requestServer(context, expressEntry);
                return uri.getApp() + AnalysisConfig.SEPARATOR_UNDERLINE + uri.getUsage();
            }
        }
        return null;
    }

    private static void requestServer(@NonNull Context context, ExpressEntry expressEntry) {
        context.getContentResolver().call(Assistant2Contract.URI, Assistant2Contract.Method.REQUEST_EXPRESS_QUERY, JSON.toJSONString(expressEntry), (Bundle) null);
    }

    private static void gotoLocalDetailPage(@NonNull Context context, @NonNull ExpressEntry expressEntry, boolean z) {
        Intent intent = new Intent(ExpressConstants.INTENT_EXPRESS_DETAIL);
        intent.putExtra("express", JSON.toJSONString(expressEntry));
        if (z) {
            Util.startActivityNewClearTask(context, intent);
        } else {
            Util.startActivity(context, intent);
        }
    }

    public static boolean gotoExpressMainPage(Context context, boolean z) {
        Intent intent = new Intent("com.miui.personalassistant.action.EXPRESS_MAIN");
        intent.putExtra(ExpressConstants.INTENT_KEY_INVOKE_INPUT, z);
        return Util.startActivityNewClearTask(context, intent);
    }

    public static void gotoExpressSettingPage(Context context) {
        Intent intent = new Intent("android.intent.action.VIEW");
        intent.setData(Uri.parse("personalassistant://settings#express"));
        Util.startActivityNewClearTask(context, intent);
    }

    public static void gotoExpressQueryPage(Context context) {
        Util.startActivityNewClearTask(context, new Intent(ExpressConstants.INTENT_EXPRESS_SEARCH));
    }

    public static void gotoExpressSendPage(Context context, boolean z) {
        Intent intent = new Intent(ExpressConstants.INTENT_EXPRESS_SEND);
        if (z) {
            Util.startActivityNewClearTask(context, intent);
        } else {
            Util.startActivity(context, intent);
        }
    }

    public static void gotoXiaomiExpressPage(Context context) {
        Intent intent = new Intent("android.intent.action.VIEW");
        intent.setData(Uri.parse(URI_XIAOMI_EXPRESS));
        Util.startActivityNewClearTask(context, intent);
    }

    public static void gotoCainiaoExpressPage(Context context) {
        Intent intent = new Intent("android.intent.action.VIEW");
        intent.setData(Uri.parse(URI_CAINIAO_EXPRESS));
        Util.startActivityNewClearTask(context, intent);
    }

    public static void gotoKuaidi100ExpressPage(Context context, String str, String str2) {
        Uri uri;
        if (!TextUtils.isEmpty(str)) {
            uri = Uri.parse(str);
        } else {
            uri = Uri.parse(URI_KUAIDI100_EXPRESS).buildUpon().appendQueryParameter(URI_QUERY_KUAIDI100_NUMBER, str2).build();
        }
        Intent intent = new Intent("android.intent.action.VIEW");
        intent.setData(uri);
        PALog.d(TAG, "gotoKuaidi100ExpressPage: " + uri.toString());
        Util.startActivityNewClearTask(context, intent);
    }

    public static void gotoCompanySelectPage(Activity activity, String str, String str2, int i) {
        if (!TextUtils.isEmpty(str)) {
            Intent intent = new Intent(ExpressConstants.INTENT_EXPRESS_COMPANY_SELECT);
            intent.putExtra(ExpressConstants.INTENT_KEY_ORDER_NUMBER, str.toUpperCase());
            intent.putExtra("from", str2);
            Util.startActivtyForResult(activity, intent, i);
        }
    }

    public static void gotoLoginPage(Context context) {
        Util.startActivity(context, new Intent(context, LoginActivity.class));
    }
}

我们能看到很多地方都调用了 Util#startActivityXXX() 函数。随便跟一个,比如 UtilstartActivityNewClearTask()

public static boolean startActivityNewClearTask(Context context, Intent intent) {
    try {
        intent.addFlags(268468224);
        context.startActivity(intent);
        return true;
    } catch (Exception e) {
        PALog.e(TAG, "startActivityNewClearTask", e);
        return false;
    }
}

用 Frida Hook 一下试试:

function hookContext() {
    var Util = Java.use("com.miui.personalassistant.util.Util")
    Util.startActivityNewTask.implementation = function (context, intent) {
        console.log(intent)
    }
}

输出如下:

Intent { act=android.intent.action.VIEW dat=tbopen://m.taobao.com/tbopen/index.html?action=ali.open.nav&amp;module=h5&amp;h5Url=https://m.duanqu.com?_wml_code=vvslIPG4dzypz9NyrjCAq5IPalrXJLavIpXHcBKguXM%3D&amp;_ariver_appid=11509317&amp;query=showcard%3dtrue%26insertPackage%3dtrue%26from%3dxiaomi%26mailNo%3d43***********&amp;showcard=true&amp;insertPackage=true&amp;from=xiaomi&amp;mailNo=43*********** flg=0x20008000 }
Intent { act=android.intent.action.VIEW dat=tbopen://m.taobao.com/tbopen/index.html?action=ali.open.nav&amp;module=h5&amp;h5Url=h5.m.taobao.com/awp/mtb/oper.htm?mailNo=43***********&amp;bc_fl_src=growth_dhh_2200803434088_20200910-9960-1069565676&amp;bootImage=0 flg=0x20008000 }
Intent { act=android.intent.action.VIEW dat=alipays://platformapi/startapp?appId=2021001141626787&amp;query=from=xiaomi&amp;insertPackage=true&amp;showcard=true&amp;cpCode=YUNDA&amp;mailNo=43*********** flg=0x20008000 }
Intent { act=android.intent.action.VIEW dat=https://page.cainiao.com/... flg=0x20008000 }
Intent { act=android.intent.action.VIEW dat=tbopen://m.taobao.com/tbopen/index.html?action=ali.open.nav&amp;module=h5&amp;h5Url=https://m.duanqu.com?_wml_code=vvslIPG4dzypz9NyrjCAq5IPalrXJLavIpXHcBKguXM%3D&amp;_ariver_appid=11509317&amp;from=xiaomi&amp;showcard=true&amp;insertPackage=true&amp;cpCode=YUNDA&amp;mailNo=43***********&amp;query=from%3Dxiaomi%26showcard%3Dtrue%26insertPackage%3Dtrue%26cpCode%3DYUNDA%26mailNo%3D43*********** flg=0x20008000 }
Intent { act=android.intent.action.VIEW dat=alipays://platformapi/startapp?appId=2021001141626787&amp;query=from=xiaomi&amp;showcard=true&amp;insertPackage=true&amp;cpCode=YUNDA&amp;mailNo=43*********** flg=0x20008000 }
Intent { act=android.intent.action.VIEW dat=https://page.cainiao.com/... flg=0x20008000 }

这就有点麻烦了。这个函数调用了不止一次。不过别担心,这是因为这个函数的返回值我们没有给,而原始逻辑是返回 true 会停止继续调用。但是 Hook 这个函数也不太好,像京东快递和小米商城快递的详情页面是负一屏原生的,这样判断起来就比较麻烦。

回到 ExpressIntentUtils,有一个 gotoExpressDetailPage() 函数值得我们注意,它是用来调度打开不同页面的主函数。这样的话,Hook 这个函数应该是比较好的。

最后一个点,我们需要梳理一下 gotoExpressDetailPage() 的逻辑,无脑 Hook 不可取,日后隐患少不了。

如果 expressEntry.getUris() 非空的话,遍历每一个 Uri 然后尝试打开。不然的话按照顺序尝试打开菜鸟、淘宝、阿里、原生和 H5(快应用)页面。

什么情况下 getUris() 会为空呢?根据代码来看,在京东快递、小米商城和米果(这个不太确定,代码里字段名是 MIGUO)的情况下会为空,进而通过智能助理原生界面显示。

这样的话我们就需要分两种情况处理。梳理完毕,可以开始写 Xposed 模块了。

组合拳,成闭环

Xposed 模块和普通的 Android 项目在 Android Studio 里有些许差别。首先还是新建一个正常的 Android App 项目。然后引入 Xposed 模块依赖。网上大多数教程是手动将 JAR 放入工程目录,其实我们可以通过 Gradle 来依赖,虽然版本旧一点,不过也还能用。

compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'

然后在 AndroidManifest.xml 里 application 节加入 Xposed 标识:

<!-- 标识这是一个 Xposed 模块 -->
<meta-data
    android:name="xposedmodule"
    android:value="true" />
<!-- Xposed 模块的描述,模块管理器会用到 -->
<meta-data
    android:name="xposeddescription"
    android:value="Make MIUI Express jump to this app instead of TaoBao or CaiNiao." />
<!-- 所需 Xposed API 的最低版本,和引入依赖版本一致 -->
<meta-data
    android:name="xposedminversion"
    android:value="82" />

如果要兼容 LSPosed 的推荐作用域,还需要加入:

<meta-data
    android:name="xposedscope"
    android:resource="@array/xposed_scope" />

然后在 values 文件夹下创建一个 array.xml,加入:

<string-array name="xposed_scope">
    <item>com.miui.personalassistant</item>
</string-array>

新建一个类,这个类需要实现 IXposedHookLoadPackage 接口和它的方法 void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;

LoadPackageParam 包含了我们需要识别 app 的一些信息,这里主要会用到的是 packageNameclassLoader 两个字段。

后面直接贴代码了,可以结合着注释来看。

class MainHook : IXposedHookLoadPackage {

    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
        // 检查包名,只 Hook 智能助理
        if (lpparam.packageName != "com.miui.personalassistant") {
            return
        }

        // 获取 ExpressIntentUtils 类,后续我们需要去调用它的静态方法
        val expressIntentUtilsClass = XposedHelpers.findClass(
            "com.miui.personalassistant.express.ExpressIntentUtils",
            lpparam.classLoader
        )
        // 获取 ExpressEntry 类
        val expressEntryClass = XposedHelpers.findClass(
            "com.miui.personalassistant.express.bean.ExpressEntry",
            lpparam.classLoader
        )
        // Hook gotoExpressDetailPage 方法
        // public static String gotoExpressDetailPage(Context context, ExpressEntry expressEntry, boolean z, boolean z2)
        // 根据原始声明,填入对应的参数
        XposedHelpers.findAndHookMethod(
            expressIntentUtilsClass, // 方法所属的类
            "gotoExpressDetailPage", // 方法名
            Context::class.java, // 方法参数
            expressEntryClass, // 方法参数
            Boolean::class.javaPrimitiveType, // 方法参数,在 Kotlin 里注意需要使用基本类型而不是包装类型
            Boolean::class.javaPrimitiveType, // 方法参数
            object : XC_MethodReplacement() { // Hook 回调,我们需要替换掉原有方法,因此选择 XC_MethodReplacement
                /**
                 * @param  param 包含了方法的一些信息,这里我们主要用到原始方法的参数 args
                 * @return 返回 null 则标识原始方法已经经过我们的替换,不再执行原有逻辑。如果需要有调用原始方法,使用 XposedBridge.invokeOriginalMethod
                 */
                override fun replaceHookedMethod(param: MethodHookParam): Any? {
                    val context = param.args[0] as Context
                    val expressEntry = param.args[1]
                    // 判断 uris 是否为空
                    val uris =
                        expressEntry.javaClass.getMethod("getUris").invoke(expressEntry) as List<*>?
                    if (uris != null && uris.isNotEmpty()) {
                        // 执行自定义逻辑,返回 null 忽略原始方法
                        return null
                    } else { // uris 非空
                        val provider = expressEntry.javaClass.getMethod("getProvider")
                            .invoke(expressEntry) as? String
                        val isXiaomi = provider == "Miguo" || provider == "MiMall"
                        val isJingDong = companyCode == "JDKD"
                        // 判断是不是京东或者小米的快递
                        if (!isXiaomi && !isJingDong) {
                            // 执行自定义逻辑,返回 null 忽略原始方法
                            return null
                        }
                    }

                    // 其他情况(京东、小米等)走原始逻辑
                    return XposedBridge.invokeOriginalMethod(
                        param.method,
                        param.thisObject,
                        param.args
                    )
                }
            })
    }

}

大格局,创生态

还有一些其他的情况,比如说加入第三方快递 API 显示快递跟踪信息、处理手动搜索快递的情况、跳转阿里系应用显示取件码等等。甚至还可以像素级模仿一下 MIUI 的界面。完整的应用可以参考我在 GitHub 上开源的 BetterMiuiExpress,欢迎提 issue 或者 PR。编译好的二进制可以在酷安下载到。

这阿里味的标题恶心到我了。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇