牙叔教程 简单易懂
为什么要合并
上一个教程是 autojs输入法 , 我们要使用的话, 需要两个app:
- 脚本打包后的app
- 输入法app
有的人觉得安装两个app麻烦, 我觉得还好, 毕竟输入法装一次就可以永久使用了.
但是有的人就不想安装两个app, 那么我们就把输入法合并到app里面吧;
这里的合并的两个app指的是
- 你写的脚本打包后的app
- 你自己写的输入法app
你能合并别人写的输入法吗?
比如搜狗输入法, qq输入法,等等?
答: 不能
因为别人的输入法有太多太多的资源文件和代码, 各种代码之间互相依赖,
根本无从下手的好吗;
况且, 人家肯定加密混淆了, 估计还得反编译, 一般人没那个技术;
只有你自己写的输入法, 剔肉去骨, 只添加必要的文件, 做一个非常非常简单的安卓输入法工程:
- 一个输入法界面
- 一个 继承 InputMethodService 的类
- 几个 xml 文件
使用到的软件
- mt管理器 版本: 2.10.4
- 输入法app
- autojs 版本: 8.8.22
- autojs打包后的app
合并步骤
- 打包一个app用来调试脚本
我们只有一个单文件, 打包也用单文件,
用项目方式打包也可以, 基本没区别
"ui"; ui.layout( <vertical> <text gravity="center" textStyle="bold" textSize="30sp"> 牙叔出品 </text> <input id="代码内容" w="*"></input> <button id="执行代码">执行代码</button> <input id="脚本文件路径" w="*"></input> <button id="执行脚本文件">执行脚本文件</button> <input id="项目入口文件路径" w="*"></input> <button id="执行项目">执行项目</button> <button id="日志">日志</button> <button id="停止脚本" text="停止脚本"></button> </vertical> ); ui.代码内容.setText('toastLog("hello");'); ui.脚本文件路径.setText("/sdcard/脚本/main.js"); ui.项目入口文件路径.setText("/sdcard/脚本/测试/main.js"); ui.执行代码.click(function () { eval(ui.代码内容.text()); }); ui.执行脚本文件.click(function () { engines.execScriptFile(ui.脚本文件路径.text().trim()); }); ui.执行项目.click(function () { let entryFilePath = ui.项目入口文件路径.text().trim(); engines.execScriptFile(entryFilePath, { path: entryFilePath.replace(/\/[\w.]+?$/, "") }); }); ui.日志.click(function () { app.startActivity("console"); }); ui.停止脚本.click(function () { engines.all().map((ScriptEngine) => { if (engines.myEngine().toString() !== ScriptEngine.toString()) { ScriptEngine.forceStop(); } }); });
- 使用mt管理器打开输入法app的应用清单 AndroidManifest.xml, 添加组件
<service android:label="@string/yashu_ime_label" android:name="com.yashu66.input.YashuIME" android:permission="android.permission.BIND_INPUT_METHOD" android:exported="true"> <intent-filter> <action android:name="android.view.InputMethod" /> </intent-filter> <meta-data android:name="android.view.im" android:resource="@xml/method" /> </service>
标签在 标签内部
<application> <service></service> </application>
注意从这里开始, 就有了引用, 比如
android:label="@string/yashu_ime_label"
我们要尽量简化我们的操作, 能不引用的尽量不引用;
这个labe的值, 指向的就是一个字符串, 我们直接改为
android:label="牙叔教程"
还有一个引用是
android:resource="@xml/method"
这个没辙, 对应的method.xml文件代码太多, 必须添加到打包后的app里面
method.xml文件内容
上图左侧是autojs打包后的app的res文件夹
右侧是输入法的res文件夹
在app的res文件夹中, 并没有xml文件,
输入法的res文件夹中, 有xml文件,
因此我们在把右侧的xml文件夹, 添加到左侧的res文件夹中;
到此为止, 左侧就有了 method.xml 这个文件实体,
有了实体, 还不够,, 还要在对应的位置添加正确的id,
我们先把所有的输入法依赖的实体都添加进去, id稍后再说.
- 右侧res/layout/key_layout.xml添加到左侧res/
- 右侧res/drawable/test_selector.xml添加到左侧res/
- 添加classes2.dex改成classes3.dex, 添加到左侧
- 打开输入法的安卓工程, 查看 YashuIME这个类的第118行, 这里引用了键盘界面
View myKeyboardView = (ViewGroup) LayoutInflater.from(this).inflate(R.layout.key_layout, null);
用mt查看classes3.dex中对应的代码, 是这样的
View myKeyboardView = (ViewGroup) LayoutInflater.from(this).inflate(0x7f0b002e, (ViewGroup) null);
设置0x7f0b002e
- 我们在输入法app的 resources.arsc 中搜索, 都有哪些地方包含了 0x7f0b002e
搜索资源ID
第一个
<path name="key_layout">res/layout/key_layout.xml</path>
第二个
<entry id="0x7f0b002e" name="key_layout" />
我们可以认为这个资源的调用流程是这样的
View myKeyboardView = (ViewGroup) LayoutInflater.from(this).inflate(R.layout.key_layout, null); -> <entry id="0x7f0b002e" name="key_layout" /> -> <path name="key_layout">res/layout/key_layout.xml</path>
现在我们去左侧的输入法, 按照这个流程添加ID, 倒着来,
先添加path, 再添加entry, 再把修改classes.dex中的id
为防止id太小引发冲突, 我们一律增加id,
意思就是比如左侧最后一个id是10, 我们就新增id 11
添加path
<path name="key_layout">res/key_layout.xml</path>
再添加entry
<entry id="0x7f0c00a2" name="key_layout" />
修改classes.dex中的id
改为
const v1, 0x7f0c00a2
改完了吗?
做梦呢你, 清醒点, 还有好几步呢!
key_layout.xml是输入法界面, 它里面有两个id
style="@7f0f02a0" style="@7f0f029f"
同理, 我们去输入法的resources.arsc搜索资源ID
先处理 7f0f02a0
style
<style name="layout_input_amount_style"> <item name="android:gravity"> center </item> ... </style>
type-info
<entry id="0x7f0f02a0" name="layout_input_amount_style" />
左侧对应的文件中, 添加style和type-info
修改原则, 一律在文件底部添加.
style就直接复制黏贴, 注意不要格式化, 原样复制即可, 否则可能报错;
type-info需要修改id
此处最后一个id是ea, 新增+1, 就是eb
同理, 处理 7f0f029f
添加style保存并退出的时候会报错, 说找不到test_selector
<item name="android:background">@drawable/test_selector</item>
此时我们随便写个颜色#ffff00保存退出,
去右侧搜索 test_selector
又得添加一个ID, 这次是给drawable里面添加
<path name="test_selector">res/test_selector.xml</path>
test_selector添加完成后, 把上面的颜色改回去
<item name="android:background">@drawable/test_selector</item>
这次保存退出的时候, 就不会报刚才那个错误了: 找不到test_selector
安装合并了输入法的app, 看看效果
很不幸, 没有我们的输入法, 修改失败
应该是遗漏了哪里
先看看清单文件, 结果发现, 刚才嘴上说要添加service, 结果并没有添加,
现在把service添加进去
<service android:label="牙叔教程" android:name="com.yashu66.input.YashuIME" android:permission="android.permission.BIND_INPUT_METHOD" android:exported="true"> <intent-filter> <action android:name="android.view.InputMethod" /> </intent-filter> <meta-data android:name="android.view.im" android:resource="@xml/method" /> </service>
保存的时候, 报错了
去右侧查询一下资源ID
搜一下7F110000
<entry id="0x7f110000" name="method" />
对应的path
如法炮制, 左侧添加该资源
添加完之后, 再去清单中添加service,, 这次就不会报错了
卸载刚才安装的app, 安装刚修改好的app, 看看效果
棒啊, 终于出现我们输入法的名字了
设置为默认输入法, 测试输入效果
报错说必须提供宽度属性, 我们打开文件看看
确实没有宽度, 这个abc_seekbar_track_material也不知道从哪里冒出来的,
我们重新把右侧的key_layout.xml文件添加到左侧, 结果还是和原来一样
看看这个 abc_seekbar_track_material 是什么
<path name="abc_seekbar_track_material">res/J71.xml</path>
啥也不是, 完全没关系
key_layout.xml是输入法界面, 每个按键都有id, 因此我们要批量添加按键的ID
要添加许多ID, 26个字母+数字+特殊键,
必须写个脚本自动生成, 不然手敲累死了,
十六进制递增
var template = '<entry id="0x_hex" name="btn_name" />'; let btnsList = ["0123456789", "qwertyuiop", "asdfghjkl", "zxcvbnm"]; var len = btnsList.length; let xmls = []; let _hex = "7f0901df"; for (var i = 0; i < len; i++) { let btns = btnsList[i]; for (var j = 0; j < btns.length; j++) { _hex = incrementHex(_hex); let btnXml = generateBtnXml(btns[j], _hex); xmls.push(btnXml); } } log(xmls.join("\n")); function generateBtnXml(_name, _hex) { let btnXml = template.replace(/_name/, _name).replace(/_hex/, _hex); return btnXml; } function incrementHex(hex) { let value = java.lang.Integer.parseInt(hex, 16); value++; return java.lang.Integer.toHexString(value); }
批量添加按键资源以后, 安装app测试
我们看看125行的代码
int resID = getResources().getIdentifier(btnId, "id", "com.yashu66.input");
我猜是这个包名应该换成脚本打包的包名
com.example.script1649662595863
再次安装测试
漂亮
终于搞好了, 打字没问题, 特殊按键也没问题,
下面来测试广播调用输入法
intent = new Intent(); intent.setAction("com.yashu66.input"); for (var i = 0; i < 2; i++) { intent.putExtra("value", "公众号: 牙叔教程"); context.sendBroadcast(intent); sleep(666); } for (var i = 0; i < 10; i++) { intent.putExtra("value", "del"); context.sendBroadcast(intent); sleep(111); } intent.putExtra("value", "clear"); context.sendBroadcast(intent); sleep(1000); intent.putExtra("value", "公众号: 牙叔教程"); context.sendBroadcast(intent); intent.putExtra("value", "enter"); context.sendBroadcast(intent);
完美
我宣布, 合并成功
备注
不同的autojs版本打包的app, 略有差异,
合并步骤类似
名人名言
思路是最重要的, 其他的百度, bing, stackoverflow, github, 安卓文档, autojs文档, 最后才是群里问问 --- 牙叔教程
声明
部分内容来自网络 本教程仅用于学习, 禁止用于其他用途