# 第四十二周ARTS总结

# Algorithm

6ms | 99.49% Run time
45.6MB | 34.50% Memory

public List<List<String>> groupAnagrams(String[] strs) {
    Map<String, List<String>> map = new HashMap<>();

    for (String str : strs) {
        String sortedStr = sort(str);
        List<String> value = map.get(sortedStr);

        if (value == null) {
            value = new ArrayList<>();
            map.put(sortedStr, value);
        }

        value.add(str);
    }

    return new ArrayList<>(map.values());
}

/**
 * 对字符串按照字符进行排序
 *
 * @param str 原始字符串
 * @return 排序完之后的字符串
 */
private String sort(String str) {
    char[] chars = str.toCharArray();
    Arrays.sort(chars);
    return new String(chars);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# Review

# Tip

  • 优雅实现保活 [1] (opens new window)
    • 申请白名单
      1. 配置权限
      <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
      
      1
      1. 判断是否在白名单中
      @RequiresApi(api = Build.VERSION_CODES.M)
      private boolean isIgnoringBatteryOptimizations() {
          boolean isIgnoring = false;
          PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
          if (powerManager != null) {
              isIgnoring = powerManager.isIgnoringBatteryOptimizations(getPackageName());
          }
          return isIgnoring;
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      1. 申请白名单
      @RequiresApi(api = Build.VERSION_CODES.M)
      public void requestIgnoreBatteryOptimizations() {
          try {
              Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
              intent.setData(Uri.parse("package:" + getPackageName()));
              startActivity(intent);
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
    • 加入厂商后台管理白名单(需针对不同厂商进行适配)
  • 如何优雅的打印一个Java对象 [2] (opens new window)
    • 对象:实现toString()方法
    • 数组:使用Arrays.toString()
  • 防止二次打包最普遍方式 [3] (opens new window):签名校验
      private boolean doNormalSignCheck() {
          String trueSignMD5 = "d0add9987c7c84aeb7198c3ff26ca152";
          String nowSignMD5 = "";
          try {
              // 得到签名的MD5
              PackageInfo packageInfo = getPackageManager().getPackageInfo(
                      getPackageName(),
                      PackageManager.GET_SIGNATURES);
              Signature[] signs = packageInfo.signatures;
              String signBase64 = Base64Util.encodeToString(signs[0].toByteArray());
              nowSignMD5 = MD5Utils.MD5(signBase64);
          } catch (PackageManager.NameNotFoundException e) {
              e.printStackTrace();
          }
          return trueSignMD5.equals(nowSignMD5);
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  • 去除签名校验方法 [4] (opens new window)
    1. 替换原先的Application
    2. attachBaseContext里初始化hook
    3. 动态代理IPackageManager
    4. hook替换掉signatures的值
    public class HookApplication extends Application implements InvocationHandler {
        private static final int GET_SIGNATURES = 64;
        private String appPkgName = BuildConfig.FLAVOR;
        private Object base;
        private byte[][] sign;
    
        private void hook(Context context) {
            try {
                DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(Base64.decode("省略很长的签名 base64", 0)));
                byte[][] bArr = new byte[(dataInputStream.read() & 255)][];
                for (int i = 0; i < bArr.length; i++) {
                    bArr[i] = new byte[dataInputStream.readInt()];
                    dataInputStream.readFully(bArr[i]);
                }
                Class cls = Class.forName("android.app.ActivityThread");
                Object invoke = cls.getDeclaredMethod("currentActivityThread", new Class[0]).invoke(null, new Object[0]);
                Field declaredField = cls.getDeclaredField("sPackageManager");
                declaredField.setAccessible(true);
                Object obj = declaredField.get(invoke);
                Class cls2 = Class.forName("android.content.pm.IPackageManager");
                this.base = obj;
                this.sign = bArr;
                this.appPkgName = context.getPackageName();
                Object newProxyInstance = Proxy.newProxyInstance(cls2.getClassLoader(), new Class[]{cls2}, this);
                declaredField.set(invoke, newProxyInstance);
                PackageManager packageManager = context.getPackageManager();
                Field declaredField2 = packageManager.getClass().getDeclaredField("mPM");
                declaredField2.setAccessible(true);
                declaredField2.set(packageManager, newProxyInstance);
                System.out.println("PmsHook success.");
            } catch (Exception e) {
                System.err.println("PmsHook failed.");
                e.printStackTrace();
            }
        }
    
        /* access modifiers changed from: protected */
        public void attachBaseContext(Context context) {
            hook(context);
            super.attachBaseContext(context);
        }
    
        public Object invoke(Object obj, Method method, Object[] objArr) throws Throwable {
            if ("getPackageInfo".equals(method.getName())) {
                String str = objArr[0];
                if ((objArr[1].intValue() & 64) != 0 && this.appPkgName.equals(str)) {
                    PackageInfo packageInfo = (PackageInfo) method.invoke(this.base, objArr);
                    packageInfo.signatures = new Signature[this.sign.length];
                    for (int i = 0; i < packageInfo.signatures.length; i++) {
                        packageInfo.signatures[i] = new Signature(this.sign[i]);
                    }
                    return packageInfo;
                }
            }
            return method.invoke(this.base, objArr);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
  • 去除签名校验的应对措施 [5] (opens new window)
    • 检查Application
    • 在调用attachBaseContext之前检测签名
    • 检查IPackageManager有没有被动态代理
    • 使用别的API获取Package信息

# Share

暂无内容

更新时间: 10/20/2022, 7:04:01 AM