Flutter 混淆打包以及一些注意事项

混淆Dart代码

Flutter 1.16.2 以上默认支持混淆,不需要特殊设置,只需要在构建命令后面加上

--obfuscate --split-debug-info=/<project-name>/<directory>

具体可看官方文档:混淆Dart代码

构建发布包

具体配置请看官方文档(只有很少的配置,很简单):生成并发布Android应用生成并发布iOS应用

构建命令:

Android:

flutter build apk --obfuscate --split-debug-info=./symbols

Google目前建议Android使用 App Bundle 的发布格式,构建命令如下:

flutter build appbundle --target-platform android-arm,android-arm64,android-x64

有不知道 App Bundle 发布格式的同学可以查看官方文档(中文的): Android App Bundle 简介

iOS:

flutter build ios --obfuscate --split-debug-info=./symbols

注意:

  1. 如果不想混淆代码的话,iOS只需要去掉 “--obfuscate --split-debug-info=./symbols” 进行构建就可以了,Android 的话单独去掉 “--obfuscate --split-debug-info=./symbols” 是没有用的,需要在构建命令后面加上 “--no-shrink” 表示不混淆代码:
    flutter build apk --no-shrink
    
  2. 如果你的程序入口不是“main.dart”,而是像我一样改成“MyApp.dart”的话,需要在构建命令后面加上 “--target=lib/MyApp.dart” 指定程序入口:
    flutter build apk --target=lib/MyApp.dart
    或者
    flutter build ios --target=lib/MyApp.dart
    
  3. 在Android上如果你觉得生成出来的apk包太大,或者觉得只需要适配单独一种架构的手机就可以了,你也可以单独构建一种架构的apk包:
    flutter build apk --target=lib/MyApp.dart --no-shrink --split-per-abi
    或者
    flutter build apk --target=lib/MyApp.dart --no-shrink --target-platform android-arm,android-arm64,android-x64 --split-per-abi
    
    此命令会生成三种架构单独的apk包:app-armeabi-v7a-release.apk、app-arm64-v8a-release.apk、app-x86_64-release.apk,大大减少apk包的大小。

问题

  1. 报错如下:

    Execution failed for task ':app:lintVitalRelease'.
    > Could not resolve all artifacts for configuration ':app:profileRuntimeClasspath'.
       > Failed to transform libs.jar to match attributes {artifactType=processed-jar, org.gradle.libraryelements=jar, org.gradle.usage=java-runtime}.
          > Execution failed for JetifyTransform: 项目\build\app\intermediates\flutter\profile\libs.jar.
             > Transform's input file does not exist: 项目\build\app\intermediates\flutter\profile\libs.jar. (See https://issuetracker.google.com/issues/158753935)
    

    解决办法是先运行一次 debug 或者 profile 模式:

    // debug
    flutter run 
    和
    // profile
    flutter run --profile 
    

    像上面的报错的意思是找不到 profile 模式下的 libs.jar 文件,我只需要命令行运行一次 profile 模式就可以了。

  2. 目前发现且未解决的问题是在混淆之后,应用所依赖的第三方pub插件(sqflite、dio等)无法正常使用,具体还需进一步测试确认。

    • 2021年1月12日更新:
      使用一个空包对dio和sqflite进行混淆测试,build.gradle 和 proguard-rules.pro 配置如下:
      android {
          ...
          buildTypes {
              release {
                  signingConfig signingConfigs.debug
      
                  minifyEnabled true
                  useProguard true
      
                  proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
              }
          }
      }
      
      #默认的proguard-android.txt已经增加了Annotation、native、view的setget方法、Activity参数为view的  方法、Enum枚举、Parcelable、R,此处不再写
      #------------------------------------------通用区域----------------------------------------------------
      #----------------------基本指令------------------------
      -optimizationpasses 5
      -dontusemixedcaseclassnames
      -dontskipnonpubliclibraryclasses
      -dontskipnonpubliclibraryclassmembers
      -dontpreverify
      -verbose
      -printmapping proguardMapping.txt
      -optimizations !code/simplification/cast,!field/*,!class/merging/*
      -keepattributes *Annotation*,InnerClasses
      -keepattributes Signature
      -keepattributes SourceFile,LineNumberTable
      
      #如果引用了v4或者v7包
      -dontwarn android.support.**
      -keep class android.support.** { *; }
      -keep interface android.support.** { *; }
      -keep public class * extends android.support.**
      -dontwarn android.support.**
      
      #如果引用了androidx包
      -keep class com.google.android.material.** {*;}
      -keep class androidx.** {*;}
      -keep public class * extends androidx.**
      -keep interface androidx.** {*;}
      -dontwarn com.google.android.material.**
      -dontnote com.google.android.material.**
      -dontwarn androidx.**
      
      #---------------------默认保留-------------------------
      ## 基础保留 ##
      -keep public class * extends android.app.Fragment
      -keep public class * extends android.app.Activity
      -keep public class * extends android.app.Application
      -keep public class * extends android.app.Service
      -keep public class * extends android.content.BroadcastReceiver
      -keep public class * extends android.content.ContentProvider
      -keep public class * extends android.app.backup.BackupAgentHelper
      -keep public class * extends android.preference.Preference
      -keep public class com.android.vending.licensing.ILicensingService
      
      -keep public class * extends android.view.View {
          public <init>(android.content.Context);
          public <init>(android.content.Context, android.util.AttributeSet);
          public <init>(android.content.Context, android.util.AttributeSet, int);
          public void set*(...);
      }
      # 保持自定义控件类不被混淆
      -keepclasseswithmembers class * {
          public <init>(android.content.Context, android.util.AttributeSet);
          public <init>(android.content.Context, android.util.AttributeSet, int);
      }
      #保持自定义控件类不被混淆
      -keepclassmembers class * extends android.app.Activity {
          public void *(android.view.View);
      }
      -keepclassmembers enum * {     # 保持枚举 enum 类不被混淆
          public static **[] values();
          public static ** valueOf(java.lang.String);
      }
      -keep class * implements android.os.Parcelable { # 保持 Parcelable 不被混淆
          public static final android.os.Parcelable$Creator *;
      }
      -keep class * implements java.io.Serializable # 保持 Serializable 不被混淆
      #保持 Serializable 不被混淆并且enum 类也不被混淆
      -keepclassmembers class * implements java.io.Serializable {
          static final long serialVersionUID;
          private static final java.io.ObjectStreamField[] serialPersistentFields;
          !static !transient <fields>;
          !private <fields>;
          !private <methods>;
          private void writeObject(java.io.ObjectOutputStream);
          private void readObject(java.io.ObjectInputStream);
          java.lang.Object writeReplace();
          java.lang.Object readResolve();
      }
      #不混淆资源类
      -keepclassmembers class **.R$* {
          public static <fields>;
      }
      # 保持 native 方法不被混淆
      -keepclasseswithmembernames class * {
          native <methods>;
      }
      #EventBus的注解
      -keepclassmembers class * {
          @org.greenrobot.eventbus.Subscribe <methods>;
      }
      #WebView
      -keepclassmembers class * extends android.webkit.WebView {*;}
      -keepclassmembers class * extends android.webkit.WebViewClient {*;}
      -keepclassmembers class * extends android.webkit.WebChromeClient {*;}
      -keepclassmembers class * {
          @android.webkit.JavascriptInterface <methods>;
      }
      
      #-------------------------------------------项目定义区-------------------------------------------------
      #sqflite
      -keep class com.tekartik.sqflite.** { *; }
      #Flutter Wrapper
      #-dontwarn io.flutter.**
      #-keep class io.flutter.app.** { *; }
      #-keep class io.flutter.plugin.**  { *; }
      #-keep class io.flutter.util.**  { *; }
      #-keep class io.flutter.view.**  { *; }
      #-keep class io.flutter.**  { *; }
      #-keep class io.flutter.plugins.**  { *; }
      
      测试结果正常,但是发现混淆对泛型十分不友好,很可能因为泛型导致混淆之后APP不能正常使用,比如我在 Flutter Dio二次封装 这篇文章里面的那个json转换工厂 EntityFactory:
      class EntityFactory {
        static T generateOBJ<T>(json) {
          if (json == null) {
            return null;
          }
          else if (T.toString() == "LoginEntity") {
            return LoginEntity.fromJson(json) as T;
          }
          else {
            return json as T;
          }
        }
      }
      
      这里需要使用 T.toString() == "LoginEntity" 对泛型进行具体的判断,但是混淆之后泛型 T 传进来的是混淆之后的符号,所以导致 T.toString() == "LoginEntity" 判断失败无法使用具体的实体去解析 json,我想下一步需要找办法配置对某些文件进行混淆排除。。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容