Tasker 永久试用修改思路

首先贴出 官网 和 Google Play 商店链接 。建议购买正版支持原作者,不贵,当前 4.49 USD。制作此改版是由于我有台不带 Google 框架的设备无法通过验证。

成品下载: OneDrive | 123 云盘 | 蓝奏云 。

目前网上很多所谓“破解版”下载后校验就是官方签名,说明根本没改过,真搞笑。此应用 7 天试用期内无任何提示,许多人在各论坛分享假“破解版”,骗取回帖经验值。

由于目前最新版 6.6.20 体积较大,这里选择相对旧的 6.2.22 (Target API SDK 33 T),功能够用且不包含 Google Gemini 等无聊组件。经尝试相同修改思路对新版也有效。

java -jar apktool.jar d origin.apk -o apk_decoded -f # 解开 apk
rm -rf apk_decoded/lib # 删除原生库

试用机制分析(感谢 MiMo-V2.5 模型 提供的支持):

最简单的思路就是改安装时间戳,改到下辈子去。正好 Main.aa() 中有判断 idt 是否为异常值的逻辑,每次启动都会执行。我们就改它,免得改别处又被它覆盖。所以打开 Main.smali 找到:

invoke-static {}, Ljava/lang/System;->currentTimeMillis()J
# ...
cmp-long v1, v4, v8
if-gtz v1, :cond_2
cmp-long v1, v4, v2
if-nez v1, :cond_1
:cond_1
# ...
invoke-interface {v1, v4, v8, v9}, Landroid/content/SharedPreferences$Editor;->putLong(Ljava/lang/String;J)Landroid/content/SharedPreferences$Editor;

把第一个 if-gtz v1, :cond_2 改成 goto :cond_1,永远当成异常值。写入逻辑用了 v8, v9 凑成的 64 位,所以在前面加上 const-wide v8, 0x223344556677L 完事。

为了看着开心,还可以改下 Licence.smali。找到这样的逻辑:

invoke-virtual {v4, v5}, Landroid/widget/TextView;->setTextIsSelectable(Z)V
const/4 v4, 0x0
if-eqz v2, :cond_2
const v0, 0x7f100818
iget-object v2, p0, Lnet/dinglisch/android/taskerm/Licence;->v:Landroid/widget/EditText;

把 if-eqz v2, :cond_2 删除即可。

另外,重新打包会破坏签名造成 Google Sign In 不可用,分享 As Link 自然需要联网验证,而用 Data URI 功能分享单个 task/profile 理应不依赖 Google 登录,却被故意限制,这里单独解除,文件 smali_classes2/com/joaomgcd/taskerm/datashare/export/d$c.smali:

move-result-object v4
sget-object v9, Lcom/joaomgcd/taskerm/signin/a;->a:Lcom/joaomgcd/taskerm/signin/a$a;
# ...
:cond_5
:try_start_2
iget-object v5, p0, Lcom/joaomgcd/taskerm/datashare/export/d$c;->p:Landroid/app/Activity;

在 move-result-object v4 之后加上 goto :cond_5 即可。

打包后发现 State 类型的 profile(如 State: %Temp1 = 9)永不触发,而 Event 类型(如监听通知)正常。即使对官方版不做修改 unzip → zip → align → sign 也会出现,说明有签名验证。

经探索,mn.smali 中的 mn.v(Context) 通过 getPackageInfo(..., 0x40) 取到当前 APK 签名,与硬编码的 0x15a25eb8 比较。所以,我们在 mn.smali:12150 找到:

invoke-virtual {p0}, Landroid/content/pm/Signature;->hashCode()I
move-result p0
if-eq p0, v0, :cond_0

把 if-eq xxx 改成 goto :cond_0。此处修改也同时移除了 ExecuteService.f4 对旧版动作(Variable Set/Flash 等)的拦截。

顺带在 res/values-zh/strings.xml 加几条缺失的翻译。最后重新打包、压缩、签名。这里过程中还使用了 ect(优化过的 zopfli)。

java -jar apktool.jar build apk_decoded -o raw.apk
aapt2 optimize -o optimized.apk --shorten-resource-paths --deduplicate-entry-values --enable-compact-entries raw.apk
rm -rf /tmp/apk_extreme
mkdir /tmp/apk_extreme
d=$(pwd)
pushd /tmp/apk_extreme
unzip -q $d/optimized.apk
zip -q -1 -r -x resources.arsc -X $d/a.apk .
$d/zcodecs12 ect -9 --strict -zip $d/a.apk # extreme compress
zip -q -0 -X $d/a.apk resources.arsc # arsc as store
popd
zipalign -f -p 16 a.apk a.aligned.apk # 16k align
apksigner sign \
  --ks "password=123456.keystore" \
  --ks-pass pass:123456 \
  --key-pass pass:123456 \
  --ks-key-alias tasker-crack \
  --v1-signing-enabled true \
  --v2-signing-enabled true \
  a.aligned.apk
apksigner verify a.aligned.apk
# C:\Users\klc2u1\AppData\Local\Android\Sdk\build-tools\36.1.0\d8 classes.dex classes2.dex --min-api 21 --lib C:\Users\klc2u1\AppData\Local\Android\Sdk\platforms\android-35\android.jar --release --output .\dex_output\

使用 apktool(或 mt 管理器)解开 dex 到 smali 后重新生成会增大体积,因此建议在验证可行性后,只编辑对应的 dex,避免触碰其他无关 dex。对于已被修改且体积膨胀的 dex,可以这样挽救一部分:

curl -o smali.jar -L https://github.com/baksmali/smali/releases/download/3.0.9/smali-3.0.9-fat-release.jar -o baksmali.jar -L https://github.com/baksmali/smali/releases/download/3.0.9/baksmali-3.0.9-fat-release.jar
# 已经修改且打包过的 dex 文件,使用 baksmali 再一次反编译并安全剥离调试信息,然后再 smali 回去
java -jar baksmali.jar disassemble --debug-info false -o smali_out classes2.dex
java -jar smali.jar assemble smali_out -o classes2_next.dex

不要尝试使用 facebook redex 来优化。对于这种没有调试信息的 dex 来说风险太大。很容易炸飞自己。

(AI 生成内容) Tasker 添加单点执行按钮

附加功能目标:TASKS 列表每个条目末尾增加一个 ▷ 按钮,直接执行该条目,不再需要长按选中后点顶栏 Play。

调用链:

  • TASKS 列表适配器由 Main.db() 创建,类是 net.dinglisch.android.taskerm.el。
  • 条目布局由 el.getView() 加载,文件是 res/layout/task_list_item.xml。
  • 单个任务对象是 net.dinglisch.android.taskerm.cl。
  • Tasker 已有现成的直接执行入口:
invoke-virtual {v0, v1}, Lnet/dinglisch/android/taskerm/cl;->v0(Landroid/content/Context;)V

它内部调用 ExecuteService.h7(Context, cl)。因此不用追顶栏 Play 的 ActionMode 逻辑,只需要让行内按钮拿到当前 cl 并调用这个入口。

修改布局:

修改 apk_decoded/res/layout/task_list_item.xml。在 drag_margin 后面加一个 TextView;同时在 LinearLayout 的末尾加一小段占位空间,避免原来的右侧图标被按钮压住:

<LinearLayout ...>
    <!-- 原有 name、lock_icon、icon、problem_icon 保持不动 -->
    <View android:layout_width="42.0dp" android:layout_height="match_parent" />
</LinearLayout>

<View android:id="@id/drag_margin"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<TextView
    android:id="@id/playpause"
    android:layout_alignParentEnd="true"
    android:layout_centerVertical="true"
    android:layout_width="wrap_content"
    android:layout_height="40.0dp"
    android:gravity="center"
    android:text="  ▷ "
    android:textColor="?android:textColorPrimary"
    android:textSize="16.0sp" />

这里复用了 APK 里已经存在的 playpause ID,不需要分配新资源 ID。按钮文本是四个字符:左侧两个全角空格,右侧一个全角空格,中间是 U+25B7。

给 Holder 加字段:

修改 apk_decoded/smali_classes2/net/dinglisch/android/taskerm/el$a.smali,在实例字段里加:

.field h:Landroid/widget/TextView;

完整字段顺序大致保持原有字段不动即可。

让适配器处理点击:

修改 el.smali 头部,让它实现 OnClickListener:

.class public Lnet/dinglisch/android/taskerm/el;
.super Lnet/dinglisch/android/taskerm/ff;
.implements Landroid/view/View$OnClickListener;
.source "SourceFile"

在 getView() 创建新行的分支中,原代码把 drag_margin 放进 holder 字段 f 之后,继续初始化新按钮:

const v2, 0x7f090278
invoke-virtual {p2, v2}, Landroid/view/View;->findViewById(I)Landroid/view/View;
move-result-object v2
check-cast v2, Landroid/widget/TextView;
iput-object v2, v1, Lnet/dinglisch/android/taskerm/el$a;->h:Landroid/widget/TextView;
invoke-virtual {v2, p0}, Landroid/view/View;->setOnClickListener(Landroid/view/View$OnClickListener;)V

0x7f090278 就是现有资源里的 playpause。注意这里 receptor 是 root view p2,holder 是 v1。

然后找到每次绑定当前位置的位置。这段附近会先从 e:ArrayList 取出当前项并转成 cl:

move-result-object p1
check-cast p1, Lnet/dinglisch/android/taskerm/cl;

在紧接着的绑定区域加两行,把当前任务对象写进按钮 tag:

iget-object v3, v1, Lnet/dinglisch/android/taskerm/el$a;->h:Landroid/widget/TextView;
invoke-virtual {v3, p1}, Landroid/view/View;->setTag(Ljava/lang/Object;)V

最后给 el.smali 追加点击方法:

.method public onClick(Landroid/view/View;)V
    .locals 2
    invoke-virtual {p1}, Landroid/view/View;->getTag()Ljava/lang/Object;
    move-result-object v0
    check-cast v0, Lnet/dinglisch/android/taskerm/cl;
    iget-object v1, p0, Lnet/dinglisch/android/taskerm/ff;->a:Landroid/content/Context;
    invoke-virtual {v0, v1}, Lnet/dinglisch/android/taskerm/cl;->v0(Landroid/content/Context;)V
    return-void
.end method

ff.a 是 public 的 Context 字段。

已踩过的小坑:

  • RelativeLayout 里的子控件必须显式写 layout_width 和 layout_height,否则 AAPT 能编译通过,运行 inflate 时 crash。
  • 同一个 holder 字段名必须全程使用完全相同的类型描述符;写成声明 TextView 但读取 ImageView 会报 NoSuchFieldError。

更新日志

20260827_v5

20260811_v4

20260805_v3

20260725_v2