从API version 10开始,开启混淆后代码中的方法、属性或路径将被混淆。但在运行时,通过混淆前的原始名称访问已被混淆的方法、属性或路径,可能会导致功能失效。因此需要根据不同的场景配置相应的保留选项。
排查场景和配置字段时,推荐使用混淆助手配置保留选项,快速识别需要配置的保留选项和白名单字段。
| 功能 | 选项 | 起始API版本 |
|---|---|---|
| 指定保留属性名称 | -keep-property-name | 10 |
| 指定保留顶层作用域或导入导出元素名称 | -keep-global-name | 10 |
| 指定保留文件/文件夹名称 | -keep-file-name | 10 |
| 指定保留注释 | -keep-comments | 12 |
| 指定保留声明文件中的所有名称 | -keep-dts | 12 |
| 指定保留源码文件中的所有名称 | -keep | 12 |
| 名称类和路径类的保留选项支持通配符 | 保留选项支持的通配符 | 12 |
| 在代码压缩时排除指定路径的文件 | -keep-uncompact | 26.0.0 |
指定想保留的属性名,支持使用名称类通配符。按如下方式进行配置,表示保留名称为firstName和lastName的属性:
-keep-property-name firstName lastName
使用该选项时,需要注意以下事项:
需要手动配置白名单的属性名:
如果代码中通过字符串拼接、变量访问或使用defineProperty方法定义对象属性,则这些属性名应被保留。
// ArkGuardAbility.js
var obj = {x0: '0', x1: '1', x2: '2'};
for (var i = 0; i <= 2; i++) {
console.info(obj['x' + i]); // x0, x1, x2应该被保留
}
Object.defineProperty(obj, 'y', {}); // y应该被保留
Object.getOwnPropertyDescriptor(obj, 'y'); // y应该被保留
console.info(obj.y);
obj.s1 = 'a';
let key = 's1';
console.info(obj[key]); // key对应的变量值s1应该被保留
obj.t1 = 'b';
console.info(obj['t' + '1']); // t1应该被保留 对于如下的字符串常量形式的属性调用,可以选择性保留:
// 混淆配置:
// -enable-property-obfuscation
// -enable-string-property-obfuscation
// ArkGuardAbility.ts
var obj2 = {t:'1', m:'2'};
obj2.t = 'a';
console.info(obj2['t']); // 此时,'t'会被正确混淆,t可以选择性保留
obj2['m'] = 'b';
console.info(obj2['m']); // 此时,'m'会被正确混淆,m可以选择性保留 对于间接或直接导出的类或对象的属性名的场景,如果混淆后出现问题,可以使用-keep-property-name来保留这些属性名。
// 间接导出MyClass07
class MyClass07 {
greet() {}
}
let alias = new MyClass07();
export { alias };
// 直接导出MyClass08
export class MyClass08 {
exampleName: 'jack'
exampleAge: 100
} 在ArkTS/TS/JS文件中使用so库的API(如示例中的addNum)时,需手动保留API名称。
// src/main/cpp/types/libentry/Index.d.ts export const addNum: (a: number, b: number) => number;
// ArkGuardAbility.ets import testNapi from 'libentry.so'; // ... testNapi.addNum(2, 3); // addNum需要保留,示例如:-keep-property-name addNum
JSON数据解析和对象序列化时,需要保留使用到的字段。
{
"jsonProperty": "value",
"otherProperty": "value2"
} import jsonData from './ImportJson.json';
// ...
let jsonProp = jsonData.jsonProperty; // jsonProperty应该被保留
class JsonTest {
prop1: string = '';
prop2: number = 0
}
let obj = new JsonTest();
const jsonStr = JSON.stringify(obj); // prop1 和 prop2 会被混淆,应该被保留 使用到的数据库相关的字段,需要手动保留。例如,数据库键值对类型(ValuesBucket)中的属性:
import { ValuesBucket } from '@kit.ArkData';
// ...
const valueBucket: ValuesBucket = {
ID1: 'ID1', // ID1应该被保留
NAME1: 'jack', // NAME1应该被保留
AGE1: 20, // AGE1应该被保留
SALARY1: 100 // SALARY1应该被保留
} 源码中自定义装饰器修饰了成员变量、成员方法、参数,同时其源码编译的中间产物为js文件时(如编译release源码HAR或者源码包含@ts-ignore、@ts-nocheck),这些装饰器所在的成员变量/成员方法名称需要被保留。这是由于ts高级语法特性转换为js标准语法时,将上述装饰器所在的成员变量/成员方法名称硬编码为字符串常量。
function CustomDecorator(target: Object, propertyKey: string) {}
function MethodDecorator(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {}
function ParamDecorator(target: Object, propertyKey: string, parameterIndex: number) {}
class A {
// 1.成员变量装饰器
@CustomDecorator
propertyName1: string = "" // propertyName1 需要被保留
// 2.成员方法装饰器
@MethodDecorator
methodName1() {} // methodName1 需要被保留
// 3.方法参数装饰器
methodName2(@ParamDecorator param: string): void {} // methodName2 需要被保留
} 使用到的数据请求相关的字段需要手动保留,例如,传递给数据请求方的字段需要手动保留:
// ArkGuardAbility.ets
import { UIAbility } from '@kit.AbilityKit';
import { http } from '@kit.NetworkKit';
// ...
export default class EntryAbility extends UIAbility {
onForeground(): void {
let httpRequest = http.createHttp();
httpRequest.request('https://www.example/Login',
{
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
extraData: { usernameTest: 'test1', passwordTest: 'test2'}, // usernameTest 和 passwordTest 需要被保留
})
}
} 使用到的数字字面量属性需要手动保留。
class MyClass09 {
123 = 'numeric-prop'; // 数字字面量属性
[456] = 'computed'; // 计算属性中的数字
method() {
console.info(this[123]); // 123和456需要被保留
console.info(this[456]);
}
} 指定要保留的顶层作用域及导入和导出元素的名称,支持使用名称类通配符。配置方式如下:
-keep-global-name Person printPersonName
namespace中导出的名称也可以通过-keep-global-name选项保留。
// ArkGuardAbility.ts
export namespace Ns {
export const myAge = 18 // -keep-global-name myAge 保留变量myAge
export function myFunc() {} // -keep-global-name myFunc 保留函数myFunc
} 使用该选项时,需要注意以下事项:
需要手动配置白名单的顶层作用域名称:
当以命名导入的方式导入so库的API时,如果同时开启-enable-toplevel-obfuscation和-enable-export-obfuscation选项,需要手动保留API的名称。
// src/main/cpp/types/libentry/Index.d.ts declare function testNapi2(): void; declare function testNapi3(): void;
// ArkGuardAbility.ets
import { testNapi2, testNapi3 as myNapi } from 'libentry.so'; // testNapi2 和 testNapi3 应该被保留
// ...
testNapi2();
myNapi(); 指定要保留的文件或文件夹名称(不需要写文件后缀),支持使用名称类通配符。
以文件路径"utils/file.ets"为例,配置白名单的方法如下:
-keep-file-name utils file
使用该选项时,需要注意以下事项:
# 这种写法仅保留该条路径,pages目录下的文件和文件夹名称依旧会被混淆 -keep-file-name ./src/main/ets/components/pages/**
需要手动配置白名单的文件名:
在使用require引入文件路径时,由于ArkTS不支持CommonJS模块语法,因此这种情况下require引入的文件路径应该被保留。
// ArkGuardAbility.js
const module1 = require('./RequireFile'); // RequireFile 应该被保留 对于动态导入的路径名,由于无法识别import函数中的参数是否为路径,因此在这种情况下应保留动态导入的路径名。
// DynamicImportFile.ts
export function foo () {} // ArkGuardAbility.ts
const moduleName = './DynamicImportFile'; // moduleName对应的路径名DynamicImportFile应该被保留
async function func2() {
const modules = await import(moduleName);
const result = modules.foo();
} 对于API version 19及之前版本,使用Navigation跨包路由进行路由跳转时,传递给动态路由的路径应被保留。动态路由提供系统路由表和自定义路由表两种方式:
若采用自定义路由表进行跳转,配置白名单的方式与第二种动态引用场景一致。
若采用系统路由表进行跳转,则需将模块下resources/base/profile/route_map.json文件中pageSourceFile字段对应的路径添加到白名单中。
对于API version 20及之后版本,不再需要手动配置白名单。
{
"routerMap": [
{
"name": "PageOne",
"pageSourceFile": "src/main/ets/pages/directory/PageOne.ets",
"buildFunction": "PageOneBuilder",
"data": {
"description" : "this is PageOne"
}
}
]
} 对于API version 19及之前版本,使用应用启动框架AppStartup时,启动参数配置文件和启动任务文件的路径应保留。这些路径配置在本模块的resources/base/profile/startup_config.json文件中,分别对应configEntry字段和startupTasks对象的srcEntry字段。
对于API version 20及之后版本,不再需要手动配置白名单。
startup_config.json文件示例如下:
{
"startupTasks": [
{
"name": "StartupTask_001",
"srcEntry": "./ets/startup/StartupTask_001.ets",
"dependencies": [
"StartupTask_002"
],
"runOnThread": "taskPool",
"waitOnMainThread": false
},
{
"name": "StartupTask_002",
"srcEntry": "./ets/startup/StartupTask_002.ets",
"runOnThread": "taskPool",
"waitOnMainThread": false
}
],
"configEntry": "./ets/startup/StartupConfig.ets"
} 配置白名单方式如下:
-keep-file-name # 启动任务文件路径为:"./ets/startup/StartupTask_001.ets" 和 "./ets/startup/StartupTask_002.ets"。 startup StartupTask_001 StartupTask_002 # 启动参数配置文件路径为:"./ets/startup/StartupConfig.ets"。 StartupConfig
使用三方库提供的路由跳转方法时开启文件名混淆规则,文件路径将被混淆,从而导致跳转失败。因此需要将路由跳转的路径都配置到-keep-file-name下,防止文件路径被混淆。
保留编译生成的声明文件中class、function、namespace、enum、struct、interface、module、type及属性上方的JsDoc注释,支持使用名称类通配符。例如想保留声明文件中Human类上方的JsDoc注释,可进行以下配置:
-keep-comments Human
使用该选项时,需要注意以下事项:
该选项在开启-remove-comments时生效。
当编译生成的声明文件中class、function、namespace、enum、struct、interface、module、type及属性的名称被混淆时,该元素上方的JsDoc注释无法通过-keep-comments保留。例如,当在-keep-comments中配置了exportClass时,如果exportClass类名被混淆,其JsDoc注释无法被保留。
/**
* @class exportClass
*/
export class exportClass {} 指定路径filepath的.d.ts文件中的名称(如变量名、类名、属性名等)将被添加到-keep-global-name和-keep-property-name白名单中。请确保filepath为绝对路径,也可以指定为一个目录。如果指定为目录,则该目录下所有.d.ts文件中的名称都将被保留。
保留指定相对路径filepath中的所有名称(例如变量名、类名、属性名等)不被混淆。filepath可以是文件或文件夹,若是文件夹,则文件夹下的文件及子文件夹中文件都不混淆。
filepath仅支持相对路径,./和../为相对于混淆配置文件所在目录,支持使用路径类通配符。
-keep ./src/main/ets/fileName.ts // fileName.ts中的名称不混淆 ../folder // folder目录下文件及子文件夹中的名称都不混淆 ../oh_modules/json5 // 引用的三方库json5里所有文件中的名称都不混淆
如何在模块中保留远程HAR包
方式一:指定远程HAR包在模块级oh_modules中的具体路径(该路径为软链接路径,真实路径为工程级oh_modules中的文件路径)。因为在配置模块级oh_modules中的路径作为白名单时,需要具体到包名或之后的目录才能正确地软链接到真实的目录路径,所以不能仅配置HAR包的上级目录名称。
// 正例 -keep ./oh_modules/harName1 // harName1目录下所有文件及子文件夹中的名称都不混淆 ./oh_modules/harName1/src // src目录下所有文件及子文件夹中的名称都不混淆 ./oh_modules/folder/harName2 // harName2目录下所有文件及子文件夹中的名称都不混淆 // 反例 -keep ./oh_modules // 保留模块级oh_modules里HAR包时,不支持配置HAR包的上级目录名称
方式二:指定远程HAR包在工程级oh_modules中的具体路径。工程级oh_modules中的文件路径均为真实路径,可直接配置。
-keep ../oh_modules // 工程级oh_modules目录下所有文件及子文件夹中的名称都不混淆 ../oh_modules/harName3 // harName3目录下所有文件及子文件夹中的名称都不混淆
模块级oh_modules和工程级oh_modules在DevEco Studio中的目录结构如下图所示:

使用该选项时,需要注意以下事项:
从API版本26.0.0开始,可通过-keep-uncompact指定相对路径下的源码不参与代码压缩。
使用该选项时,需要注意以下事项:
-compact -keep-uncompact ./src/main/ets/example/FileA.ets ./src/main/ets/example/folder ../oh_modules/somePackage/src
名称类通配符使用方式如下:
| 通配符 | 含义 | 示例 |
|---|---|---|
| ? | 匹配任意单个字符 | "AB?"能匹配"ABC"等,但不能匹配"AB"。 |
| * | 匹配任意数量的任意字符 | "*AB*"能匹配"AB"、"aABb"、"cAB"、"ABc"等。 |
使用示例:
保留所有以a开头的属性名称:
-keep-property-name a*
保留所有单个字符的属性名称:
-keep-property-name ?
保留所有属性名称:
-keep-property-name *
路径类通配符使用方式如下:
| 通配符 | 含义 | 示例 |
|---|---|---|
| ? | 匹配任意单个字符,除了路径分隔符/。 | "../a?"能匹配"../ab"等,但不能匹配"../a/"。 |
| * | 匹配任意数量的任意字符,但不包括路径分隔符/。 | "../a*/c"能匹配"../ab/c",但不能匹配"../ab/d/s/c"。 |
| ** | 匹配任意数量的任意字符。 | "../a**/c"能匹配"../ab/c",也能匹配"../ab/d/s/c"。 |
| ! | 表示非,只能写在某个路径最前端,用来排除用户配置的白名单中已有的某种情况。 | "!../a/b/c.ets"表示匹配除了"../a/b/c.ets"以外的路径。 |
使用示例:
表示路径../a/b/中所有文件夹(不包含子文件夹)中的c.ets文件不会被混淆:
-keep ../a/b/*/c.ets
表示路径../a/b/中所有文件夹(包含子文件夹)中的c.ets文件不会被混淆:
-keep ../a/b/**/c.ets
表示路径../a/b/中,除了c.ets文件以外的其它文件都不会被混淆。其中,!不可单独使用,只能用来排除白名单中已有的情况:
-keep ../a/b/ !../a/b/c.ets
表示路径../a/中的所有文件(不包含子文件夹)不会被混淆:
-keep ../a/*
表示路径../a/下的所有文件夹(包含子文件夹)中的所有文件不会被混淆:
-keep ../a/**
表示模块内的所有文件不会被混淆:
-keep ./**
使用通配符时,需要注意以下事项:
以上选项不支持将通配符*、?、!用作其他含义。
class A {
'*'= 1
}
-keep-property-name
* 此时*表示匹配任意数量的任意字符,配置效果为所有属性名称都不会被混淆,而不是只有*属性不被混淆。
-keep选项中只允许使用/路径格式,不支持\或\\。