@_nazmiforreal/flutter-ota 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/README.md +112 -18
  2. package/bin/flutter-ota-linux-x64 +0 -0
  3. package/bin/flutter-ota.js +12 -11
  4. package/dart-src/packages/cli-tools/README.md +146 -2
  5. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +46 -13
  6. package/dart-src/packages/cli-tools/lib/src/backend.dart +6 -6
  7. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +2 -0
  8. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +7 -7
  9. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +4 -1
  10. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +36 -8
  11. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +1 -1
  12. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +1 -1
  13. package/dart-src/packages/cli-tools/lib/src/config.dart +5 -5
  14. package/dart-src/packages/cli-tools/lib/src/operations.dart +2 -2
  15. package/dart-src/packages/cli-tools/pubspec.yaml +8 -21
  16. package/dart-src/packages/cli-tools/test/backends_test.dart +3 -3
  17. package/dart-src/packages/cli-tools/test/cli_test.dart +1 -1
  18. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +2 -2
  19. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +2 -2
  20. package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +4 -1
  21. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +2 -2
  22. package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +1 -1
  23. package/dart-src/root/README.md +334 -0
  24. package/dart-src/root/analysis_options.yaml +21 -0
  25. package/dart-src/root/android/build.gradle +116 -0
  26. package/dart-src/root/android/consumer-rules.pro +23 -0
  27. package/dart-src/root/android/settings.gradle +1 -0
  28. package/dart-src/root/android/src/main/AndroidManifest.xml +21 -0
  29. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/ApplyResult.kt +52 -0
  30. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BlacklistStore.kt +154 -0
  31. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BootDiagnosticStore.kt +150 -0
  32. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/CrashGuard.kt +271 -0
  33. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherApplication.kt +201 -0
  34. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherAutoInitProvider.kt +93 -0
  35. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherPlugin.kt +245 -0
  36. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/LoaderHook.kt +298 -0
  37. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatchManager.kt +1579 -0
  38. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt +173 -0
  39. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/SignatureVerifier.kt +177 -0
  40. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/CrashGuardTest.kt +61 -0
  41. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/LoaderHookTest.kt +25 -0
  42. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatchManagerTest.kt +401 -0
  43. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfigTest.kt +30 -0
  44. package/dart-src/root/bin/pack.dart +182 -0
  45. package/dart-src/root/lib/flutter_ota_kit.dart +1170 -0
  46. package/dart-src/root/lib/src/app_version_resolver.dart +36 -0
  47. package/dart-src/root/lib/src/aws_update_source.dart +97 -0
  48. package/dart-src/root/lib/src/blacklist.dart +54 -0
  49. package/dart-src/root/lib/src/boot_diagnostic.dart +138 -0
  50. package/dart-src/root/lib/src/cloudflare_update_source.dart +97 -0
  51. package/dart-src/root/lib/src/flutter_ota_app.dart +69 -0
  52. package/dart-src/root/lib/src/io.dart +48 -0
  53. package/dart-src/root/lib/src/io_stub.dart +19 -0
  54. package/dart-src/root/lib/src/ota_progress_overlay.dart +1186 -0
  55. package/dart-src/root/lib/src/pack/pack.dart +434 -0
  56. package/dart-src/root/lib/src/patch_info.dart +12 -0
  57. package/dart-src/root/lib/src/patcher_channel.dart +160 -0
  58. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_database.dart +1 -1
  59. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_s3_client.dart +1 -1
  60. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage.dart +1 -1
  61. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage_profile.dart +1 -1
  62. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/with_cloudfront_signed_url.dart +1 -1
  63. package/dart-src/root/lib/src/pkg/client/flutter_ota_kit_client.dart +1 -0
  64. package/dart-src/root/lib/src/pkg/client/src/server_update_source.dart +46 -0
  65. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/cloudflare_worker_database.dart +1 -1
  66. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_build_where.dart +1 -1
  67. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_bundle_mapper.dart +3 -3
  68. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database.dart +1 -1
  69. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database_plugin.dart +2 -2
  70. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_s3_client.dart +1 -1
  71. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage.dart +1 -1
  72. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage_profile.dart +1 -1
  73. package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/status.dart +1 -1
  74. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/flutter_ota_kit_plugin_core.dart +1 -1
  75. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_blob_database_plugin.dart +1 -1
  76. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/filter_compatible_app_versions.dart +1 -1
  77. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/get_update_info.dart +1 -1
  78. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/request_update_bundle_state.dart +1 -1
  79. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/resolve_update_info_from_bundles.dart +1 -1
  80. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/types.dart +2 -2
  81. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_bundle_mapper.dart +2 -2
  82. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_database.dart +26 -15
  83. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_storage.dart +1 -1
  84. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_bundle_mapper.dart +2 -2
  85. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_database.dart +2 -2
  86. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_get_update_info.dart +2 -2
  87. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_storage.dart +1 -1
  88. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_types.dart +1 -1
  89. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_bundle_mapper.dart +2 -2
  90. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_database.dart +2 -2
  91. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_database.dart +1 -1
  92. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_storage.dart +1 -1
  93. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_storage.dart +1 -1
  94. package/dart-src/root/lib/src/pocketbase_update_source.dart +107 -0
  95. package/dart-src/root/lib/src/postgres_update_source.dart +95 -0
  96. package/dart-src/root/lib/src/shared_update_check.dart +131 -0
  97. package/dart-src/root/lib/src/supabase_update_source.dart +93 -0
  98. package/dart-src/root/pubspec.yaml +54 -0
  99. package/package.json +1 -1
  100. package/scripts/postinstall.js +17 -13
  101. package/scripts/vendor.sh +22 -7
  102. package/dart-src/packages/core/CHANGELOG.md +0 -21
  103. package/dart-src/packages/core/README.md +0 -23
  104. package/dart-src/packages/core/example/lib/main.dart +0 -69
  105. package/dart-src/packages/core/pubspec.yaml +0 -13
  106. package/dart-src/packages/core/test/rollout_test.dart +0 -135
  107. package/dart-src/packages/core/test/semver_test.dart +0 -55
  108. package/dart-src/packages/core/test/semver_vectors.json +0 -1
  109. package/dart-src/packages/core/test/types_test.dart +0 -131
  110. package/dart-src/packages/core/test/uuid_test.dart +0 -40
  111. package/dart-src/packages/core/tool/dbg.dart +0 -7
  112. package/dart-src/plugins/aws/.gitkeep +0 -0
  113. package/dart-src/plugins/aws/CHANGELOG.md +0 -23
  114. package/dart-src/plugins/aws/LICENSE +0 -21
  115. package/dart-src/plugins/aws/README.md +0 -28
  116. package/dart-src/plugins/aws/example/lib/main.dart +0 -60
  117. package/dart-src/plugins/aws/pubspec.yaml +0 -30
  118. package/dart-src/plugins/aws/test/aws_database_test.dart +0 -161
  119. package/dart-src/plugins/aws/test/aws_storage_test.dart +0 -73
  120. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +0 -78
  121. package/dart-src/plugins/cloudflare/.gitkeep +0 -0
  122. package/dart-src/plugins/cloudflare/CHANGELOG.md +0 -23
  123. package/dart-src/plugins/cloudflare/LICENSE +0 -21
  124. package/dart-src/plugins/cloudflare/README.md +0 -25
  125. package/dart-src/plugins/cloudflare/example/lib/main.dart +0 -62
  126. package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +0 -16
  127. package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +0 -8
  128. package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +0 -38
  129. package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +0 -11
  130. package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +0 -24
  131. package/dart-src/plugins/cloudflare/pubspec.yaml +0 -28
  132. package/dart-src/plugins/cloudflare/sql/bundles.sql +0 -42
  133. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +0 -126
  134. package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +0 -106
  135. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +0 -200
  136. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +0 -261
  137. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +0 -78
  138. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +0 -74
  139. package/dart-src/plugins/plugin-core/CHANGELOG.md +0 -20
  140. package/dart-src/plugins/plugin-core/LICENSE +0 -21
  141. package/dart-src/plugins/plugin-core/README.md +0 -21
  142. package/dart-src/plugins/plugin-core/example/lib/main.dart +0 -61
  143. package/dart-src/plugins/plugin-core/pubspec.yaml +0 -23
  144. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +0 -170
  145. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +0 -74
  146. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +0 -345
  147. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +0 -843
  148. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +0 -298
  149. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +0 -195
  150. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +0 -56
  151. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +0 -1589
  152. package/dart-src/plugins/plugin-core/test/helpers_test.dart +0 -283
  153. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +0 -202
  154. package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +0 -86
  155. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +0 -88
  156. package/dart-src/plugins/pocketbase/CHANGELOG.md +0 -23
  157. package/dart-src/plugins/pocketbase/LICENSE +0 -21
  158. package/dart-src/plugins/pocketbase/README.md +0 -54
  159. package/dart-src/plugins/pocketbase/example/lib/main.dart +0 -62
  160. package/dart-src/plugins/pocketbase/pubspec.yaml +0 -26
  161. package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +0 -12
  162. package/dart-src/plugins/postgres/.gitkeep +0 -0
  163. package/dart-src/plugins/postgres/CHANGELOG.md +0 -23
  164. package/dart-src/plugins/postgres/LICENSE +0 -21
  165. package/dart-src/plugins/postgres/README.md +0 -29
  166. package/dart-src/plugins/postgres/example/lib/main.dart +0 -67
  167. package/dart-src/plugins/postgres/pubspec.yaml +0 -26
  168. package/dart-src/plugins/postgres/sql/bundles.sql +0 -45
  169. package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +0 -21
  170. package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +0 -171
  171. package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +0 -126
  172. package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +0 -125
  173. package/dart-src/plugins/postgres/sql/hash_user_id.sql +0 -30
  174. package/dart-src/plugins/postgres/sql/is_device_eligible.sql +0 -237
  175. package/dart-src/plugins/postgres/sql/prepareSql.ts +0 -11
  176. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +0 -320
  177. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +0 -139
  178. package/dart-src/plugins/postgres/test/postgres_database_test.dart +0 -210
  179. package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +0 -60
  180. package/dart-src/plugins/supabase/CHANGELOG.md +0 -24
  181. package/dart-src/plugins/supabase/LICENSE +0 -21
  182. package/dart-src/plugins/supabase/README.md +0 -53
  183. package/dart-src/plugins/supabase/example/lib/main.dart +0 -63
  184. package/dart-src/plugins/supabase/pubspec.yaml +0 -27
  185. package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +0 -44
  186. package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +0 -968
  187. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +0 -232
  188. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -137
  189. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -206
  190. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -186
  191. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -505
  192. package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
  193. package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
  194. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -75
  195. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +0 -654
  196. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +0 -115
  197. package/dart-src/plugins/supabase/test/supabase_database_test.dart +0 -198
  198. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +0 -168
  199. /package/dart-src/{packages/core → root}/LICENSE +0 -0
  200. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/flutter_ota_kit_aws.dart +0 -0
  201. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_client.dart +0 -0
  202. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_signer.dart +0 -0
  203. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_config.dart +0 -0
  204. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_ssm_client.dart +0 -0
  205. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/flutter_ota_kit_cloudflare.dart +0 -0
  206. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_client.dart +0 -0
  207. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_config.dart +0 -0
  208. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_config.dart +0 -0
  209. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/flutter_ota_kit_core.dart +0 -0
  210. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/app_update_info.dart +0 -0
  211. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle.dart +0 -0
  212. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_artifacts.dart +0 -0
  213. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_patch_artifact.dart +0 -0
  214. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/changed_asset.dart +0 -0
  215. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/get_bundles_args.dart +0 -0
  216. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/metadata.dart +0 -0
  217. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/patch_info.dart +0 -0
  218. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/platform.dart +0 -0
  219. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/rollout.dart +0 -0
  220. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver.dart +0 -0
  221. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_range.dart +0 -0
  222. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_version.dart +0 -0
  223. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/strategy.dart +0 -0
  224. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/update_bundle_params.dart +0 -0
  225. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/uuid.dart +0 -0
  226. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/asset_storage_layout.dart +0 -0
  227. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_helpers.dart +0 -0
  228. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_storage_layout.dart +0 -0
  229. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work.dart +0 -0
  230. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work_store.dart +0 -0
  231. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/calculate_pagination.dart +0 -0
  232. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/compression_format.dart +0 -0
  233. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/content_addressed_assets.dart +0 -0
  234. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_database_plugin.dart +0 -0
  235. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_key_builder.dart +0 -0
  236. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_plugin.dart +0 -0
  237. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/generate_min_bundle_id.dart +0 -0
  238. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/is_object.dart +0 -0
  239. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/legacy_asset_storage_layout.dart +0 -0
  240. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/paginate_bundles.dart +0 -0
  241. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/parse_storage_uri.dart +0 -0
  242. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/query_bundles.dart +0 -0
  243. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_helpers.dart +0 -0
  244. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_profile.dart +0 -0
  245. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/uuidv7.dart +0 -0
  246. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/flutter_ota_kit_pocketbase.dart +0 -0
  247. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_client.dart +0 -0
  248. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_config.dart +0 -0
  249. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/flutter_ota_kit_postgres.dart +0 -0
  250. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_client.dart +0 -0
  251. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_config.dart +0 -0
  252. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/edge.dart +0 -0
  253. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/flutter_ota_kit_supabase.dart +0 -0
  254. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/error_message.dart +0 -0
  255. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_adapter.dart +0 -0
  256. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_http.dart +0 -0
  257. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_config.dart +0 -0
  258. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_signed_url_batcher.dart +0 -0
  259. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/types.dart +0 -0
@@ -0,0 +1,245 @@
1
+ package com.flutter_patcher.flutter_patcher
2
+
3
+ import android.content.Context
4
+ import android.os.Build
5
+ import android.os.Handler
6
+ import android.os.Looper
7
+ import android.util.Log
8
+ import io.flutter.embedding.engine.plugins.FlutterPlugin
9
+ import io.flutter.plugin.common.EventChannel
10
+ import io.flutter.plugin.common.MethodCall
11
+ import io.flutter.plugin.common.MethodChannel
12
+ import io.flutter.plugin.common.MethodChannel.MethodCallHandler
13
+ import io.flutter.plugin.common.MethodChannel.Result
14
+
15
+ /**
16
+ * Dart ↔ Android 的 MethodChannel 处理器。
17
+ *
18
+ * Channel 名:
19
+ * - MethodChannel `flutter_ota_kit` —— RPC(saveConfig / applyPatch / ...)
20
+ * - EventChannel `flutter_ota_kit/events` —— applyPatch 过程的阶段 / 进度事件
21
+ *
22
+ * MethodChannel 方法:
23
+ * - saveConfig(publicKeyBase64, maxCrashCount, strictSignature,
24
+ * loaderFieldCandidates, loaderFallbackHeuristic)
25
+ * - markBooting() — Dart init 最开头调用,补写一次「启动中」
26
+ * - reportBootSuccess() — Dart 首帧渲染时调用,清熔断
27
+ * - reportDartBootError(Map) — 引导阶段 Dart 未捕获异常上报,等同一次真崩溃
28
+ * - applyPatch(Map) -> Map{ok, error, message}
29
+ * - rollback()
30
+ * - currentVersion() -> String?
31
+ * - lastBootDiagnostic() -> Map? — 上次冷启动补丁加载诊断(见 BootDiagnosticStore)
32
+ * - cacheDir() -> String — 插件可写的临时目录,供 Dart applyPatchBytes 内部 staging 使用
33
+ * - appVersionCode() -> Long — 当前宿主 APK versionCode,A3 便捷查询
34
+ * - deviceAbi() -> String — 当前设备首选 ABI,用于服务端按 ABI 分发补丁
35
+ * - blacklist() -> List<Map> — 已知"装上就出事"的补丁本地黑名单
36
+ * - clearBlacklist() — 清空黑名单(仅调试 / 显式恢复用)
37
+ */
38
+ class FlutterPatcherPlugin :
39
+ FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler {
40
+
41
+ companion object {
42
+ private const val TAG = "FlutterPatcher/Plugin"
43
+ private const val CHANNEL = "flutter_ota_kit"
44
+ private const val EVENT_CHANNEL = "flutter_ota_kit/events"
45
+ }
46
+
47
+ private lateinit var channel: MethodChannel
48
+ private lateinit var eventChannel: EventChannel
49
+ private lateinit var appContext: Context
50
+ private val main = Handler(Looper.getMainLooper())
51
+
52
+ @Volatile
53
+ private var eventSink: EventChannel.EventSink? = null
54
+
55
+ override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
56
+ appContext = binding.applicationContext
57
+ channel = MethodChannel(binding.binaryMessenger, CHANNEL)
58
+ channel.setMethodCallHandler(this)
59
+ eventChannel = EventChannel(binding.binaryMessenger, EVENT_CHANNEL)
60
+ eventChannel.setStreamHandler(this)
61
+ }
62
+
63
+ // ==================== EventChannel ====================
64
+
65
+ override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
66
+ eventSink = events
67
+ }
68
+
69
+ override fun onCancel(arguments: Any?) {
70
+ eventSink = null
71
+ }
72
+
73
+ private fun emitProgress(phase: String, received: Long, total: Long) {
74
+ val sink = eventSink ?: return
75
+ val event = mapOf(
76
+ "phase" to phase,
77
+ "received" to received,
78
+ "total" to total
79
+ )
80
+ main.post { sink.success(event) }
81
+ }
82
+
83
+ override fun onMethodCall(call: MethodCall, result: Result) {
84
+ when (call.method) {
85
+ "saveConfig" -> handleSaveConfig(call, result)
86
+ "markBooting" -> {
87
+ CrashGuard(appContext).markBooting()
88
+ result.success(null)
89
+ }
90
+ "reportBootSuccess" -> {
91
+ CrashGuard(appContext).markBootSuccess()
92
+ result.success(null)
93
+ }
94
+ "reportDartBootError" -> handleReportDartBootError(call, result)
95
+ "applyPatch" -> handleApplyPatch(call, result)
96
+ "rollback" -> handleRollback(result)
97
+ "rollbackToPrevious" -> handleRollbackToPrevious(result)
98
+ "currentVersion" -> handleCurrentVersion(result)
99
+ "lastBootDiagnostic" -> handleLastBootDiagnostic(result)
100
+ "cacheDir" -> result.success(appContext.cacheDir.absolutePath)
101
+ "appVersionCode" -> result.success(PatcherConfig.currentVersionCode(appContext))
102
+ "deviceAbi" -> result.success(Build.SUPPORTED_ABIS.firstOrNull().orEmpty())
103
+ "blacklist" -> result.success(BlacklistStore.entries(appContext))
104
+ "clearBlacklist" -> {
105
+ BlacklistStore.clear(appContext)
106
+ result.success(null)
107
+ }
108
+ "reportApplyFailure" -> handleReportApplyFailure(call, result)
109
+ "isVersionBlacklisted" -> handleIsVersionBlacklisted(call, result)
110
+ else -> result.notImplemented()
111
+ }
112
+ }
113
+
114
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
115
+ channel.setMethodCallHandler(null)
116
+ eventChannel.setStreamHandler(null)
117
+ eventSink = null
118
+ }
119
+
120
+ // ==================== handlers ====================
121
+
122
+ private fun handleSaveConfig(call: MethodCall, result: Result) {
123
+ val pk = call.argument<String>("publicKeyBase64") ?: ""
124
+ val max = call.argument<Int>("maxCrashCount") ?: PatcherConfig.DEFAULT_MAX_CRASH
125
+ val strict = call.argument<Boolean>("strictSignature") ?: PatcherConfig.DEFAULT_STRICT_SIG
126
+ val fields = call.argument<List<String>>("loaderFieldCandidates") ?: emptyList()
127
+ val heuristic = call.argument<Boolean>("loaderFallbackHeuristic")
128
+ ?: PatcherConfig.DEFAULT_LOADER_HEURISTIC
129
+ val maxPatchHistory = call.argument<Int>("maxPatchHistory") ?: PatcherConfig.MAX_PATCH_HISTORY
130
+ val maxAssetHistory = call.argument<Int>("maxAssetHistory") ?: PatcherConfig.MAX_ASSET_HISTORY
131
+ PatcherConfig.saveConfig(appContext, pk, max, strict, fields, heuristic, maxPatchHistory, maxAssetHistory)
132
+ result.success(null)
133
+ }
134
+
135
+ private fun handleApplyPatch(call: MethodCall, result: Result) {
136
+ @Suppress("UNCHECKED_CAST")
137
+ val args = call.arguments as? Map<String, Any?>
138
+ if (args == null) {
139
+ result.error("INVALID_ARGS", "applyPatch expects a Map", null)
140
+ return
141
+ }
142
+ Thread {
143
+ val progress: ProgressCallback = { phase, received, total ->
144
+ emitProgress(phase, received, total)
145
+ }
146
+ val applyResult = try {
147
+ PatchManager(appContext, progress).applyPatch(args)
148
+ } catch (e: Exception) {
149
+ Log.e(TAG, "applyPatch error", e)
150
+ ApplyResult.failure(ApplyErrorCode.UNKNOWN, e.message ?: e.javaClass.simpleName)
151
+ }
152
+ main.post { result.success(applyResult.toMap()) }
153
+ }.start()
154
+ }
155
+
156
+ private fun handleRollback(result: Result) {
157
+ Thread {
158
+ try {
159
+ PatchManager(appContext).rollback()
160
+ } catch (e: Exception) {
161
+ Log.e(TAG, "rollback error", e)
162
+ }
163
+ main.post { result.success(null) }
164
+ }.start()
165
+ }
166
+
167
+ private fun handleRollbackToPrevious(result: Result) {
168
+ Thread {
169
+ try {
170
+ val outcome = PatchManager(appContext).rollbackToPrevious()
171
+ main.post { result.success(outcome.name) }
172
+ } catch (e: Exception) {
173
+ Log.e(TAG, "rollbackToPrevious error", e)
174
+ main.post { result.success(RollbackResult.FALLBACK_TO_BASE.name) }
175
+ }
176
+ }.start()
177
+ }
178
+
179
+ private fun handleCurrentVersion(result: Result) {
180
+ val v = PatchManager(appContext).currentVersion()
181
+ result.success(if (v.isEmpty()) null else v)
182
+ }
183
+
184
+ private fun handleLastBootDiagnostic(result: Result) {
185
+ // null 表示从未 record 过(首次安装 / pm clear 后第一次启动且尚未到首帧)
186
+ result.success(BootDiagnosticStore.read(appContext))
187
+ }
188
+
189
+ /**
190
+ * Dart 侧 PlatformDispatcher.onError / FlutterError.onError 在「verifyAfter 窗口内」
191
+ * 抓到的未捕获异常 ⇒ 等同 ApplicationExitInfo.REASON_CRASH 处理:crash_count += 1,
192
+ * 达阈值则 onTrip 回调里走完整的"加黑名单 + 记诊断 + 删补丁"链路。
193
+ *
194
+ * 与 attachPatcher 里 shouldLoadPatch 的 onTrip 逻辑保持一致,让用户在 DiagCard
195
+ * 上看到 status=DROPPED_CIRCUIT_BREAKER 而不是上一次的 status=PATCHED 残影。
196
+ */
197
+ private fun handleReportDartBootError(call: MethodCall, result: Result) {
198
+ val message = call.argument<String>("message")
199
+ val patchManager = PatchManager(appContext)
200
+ val crashedMeta = patchManager.currentMeta()
201
+ val appVc = PatcherConfig.currentVersionCode(appContext)
202
+
203
+ CrashGuard(appContext).reportDartBootError(message) { crashCount ->
204
+ if (crashedMeta != null) {
205
+ BlacklistStore.add(
206
+ appContext,
207
+ crashedMeta.first,
208
+ crashedMeta.second,
209
+ BlacklistStore.REASON_BOOT_CRASH,
210
+ )
211
+ }
212
+ BootDiagnosticStore.record(
213
+ context = appContext,
214
+ status = BootDiagnosticStore.DROPPED_CIRCUIT_BREAKER,
215
+ patchVersion = crashedMeta?.first,
216
+ appVersionCode = appVc,
217
+ crashCount = crashCount,
218
+ message = if (crashedMeta != null)
219
+ "$crashCount Dart boot failure(s); blacklisted (version=${crashedMeta.first}); ${message ?: ""}"
220
+ else
221
+ "$crashCount Dart boot failure(s); ${message ?: ""}",
222
+ )
223
+ }
224
+ result.success(null)
225
+ }
226
+
227
+ private fun handleReportApplyFailure(call: MethodCall, result: Result) {
228
+ val version = call.argument<String>("version") ?: ""
229
+ val md5 = call.argument<String>("md5") ?: ""
230
+ val reason = call.argument<String>("reason") ?: "APPLY_FAILED"
231
+ BlacklistStore.add(appContext, version, md5, reason)
232
+ result.success(null)
233
+ }
234
+
235
+ private fun handleIsVersionBlacklisted(call: MethodCall, result: Result) {
236
+ val version = call.argument<String>("version") ?: ""
237
+ val md5 = call.argument<String>("md5") ?: ""
238
+ val found = if (md5.isNotEmpty()) {
239
+ BlacklistStore.contains(appContext, version, md5)
240
+ } else {
241
+ BlacklistStore.containsByVersion(appContext, version)
242
+ }
243
+ result.success(found)
244
+ }
245
+ }
@@ -0,0 +1,298 @@
1
+ package com.flutter_patcher.flutter_patcher
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import io.flutter.FlutterInjector
6
+ import io.flutter.embedding.engine.FlutterJNI
7
+ import io.flutter.embedding.engine.loader.FlutterLoader
8
+ import java.lang.reflect.Field
9
+ import java.lang.reflect.Modifier
10
+ import java.util.concurrent.ExecutorService
11
+
12
+ /**
13
+ * 通过反射把 FlutterInjector 内部持有的 FlutterLoader 替换为自定义实现。
14
+ *
15
+ * # 为什么是最脆弱的部分
16
+ * Flutter Engine 每个大版本都可能改内部字段名 / 改 FlutterLoader 的方法签名。
17
+ * 本类采用「候选字段名 + 类型启发式 + Dart 侧可覆盖」三层保护:
18
+ *
19
+ * ## 已验证字段名(按 Flutter 版本)
20
+ * | Flutter 版本 | FlutterInjector 字段 | ensureInitializationComplete 签名 |
21
+ * |-------------|----------------------|-------------------------------------------|
22
+ * | 3.19.x | `flutterLoader` | (Context, @Nullable String[]) |
23
+ * | 3.22.x | `flutterLoader` | (Context, @Nullable String[]) |
24
+ * | 3.24.x | `flutterLoader` | (Context, @Nullable String[]) |
25
+ * | 3.27.x | `flutterLoader` | (Context, @Nullable String[]) |
26
+ * | 3.29.x | `flutterLoader` | (Context, @Nullable String[]) |
27
+ * | 3.32.x | `flutterLoader` | (Context, @Nullable String[]) |
28
+ * | 3.35.x | `flutterLoader` | (Context, @Nullable String[]) |
29
+ * | 3.38.x | `flutterLoader` | (Context, @Nullable String[]) |
30
+ *
31
+ * 超出此范围(比如未来 Flutter 4.x)请先通过 `FlutterPatcher.init(loaderFieldCandidates: [...])`
32
+ * 下发新的字段名,再升级本库。
33
+ *
34
+ * 必须 **早于** 任何 Flutter 引擎初始化(即 FlutterActivity/FlutterFragment 创建
35
+ * 之前)调用,最佳时机是 Application.attachBaseContext。
36
+ */
37
+ internal object LoaderHook {
38
+
39
+ private const val TAG = "FlutterPatcher/Hook"
40
+
41
+ /** 默认候选字段名。多字段是为了抵御 Flutter 未来改名。 */
42
+ private val DEFAULT_CANDIDATES = listOf("flutterLoader")
43
+
44
+ /**
45
+ * 用带补丁路径的自定义 FlutterLoader 替换默认实现。
46
+ *
47
+ * @param context 用于读 PatcherConfig 中的 Dart 侧覆盖配置
48
+ * @param patchSoPath 补丁 libapp.so 的绝对路径
49
+ * @param attemptedFields 可选 out 参数:调用前传入空 MutableList,方法返回后
50
+ * 会包含本次按顺序尝试过的字段候选名(无论成功失败)。供
51
+ * [BootDiagnosticStore.HOOK_INSTALL_FAILED] 上报使用。
52
+ * @return 是否成功注入
53
+ */
54
+ fun install(
55
+ context: Context,
56
+ patchSoPath: String,
57
+ patchAssetsPath: String? = null,
58
+ patchAssetsArchivePath: String? = null,
59
+ attemptedFields: MutableList<String>? = null,
60
+ ): Boolean {
61
+ return try {
62
+ val injector = FlutterInjector.instance()
63
+ if (!patchAssetsArchivePath.isNullOrEmpty() && !patchAssetsPath.isNullOrEmpty()) {
64
+ if (!verifyAssetArchiveReadable(context, patchAssetsArchivePath, patchAssetsPath)) {
65
+ throw IllegalStateException(
66
+ "AssetManager.addAssetPath failed: $patchAssetsArchivePath"
67
+ )
68
+ }
69
+ installPatchedFlutterJniFactory(
70
+ injector,
71
+ context,
72
+ patchAssetsArchivePath,
73
+ patchAssetsPath
74
+ )
75
+ }
76
+ val candidates = buildCandidates(context)
77
+ attemptedFields?.addAll(candidates)
78
+ val heuristic = PatcherConfig.loaderFallbackHeuristic(context)
79
+ val field = findLoaderField(injector.javaClass, candidates, heuristic)
80
+ ?: throw IllegalStateException(
81
+ "FlutterInjector has no FlutterLoader field (tried: $candidates, " +
82
+ "heuristic=$heuristic). Flutter API may have changed; pass " +
83
+ "loaderFieldCandidates to FlutterPatcher.init."
84
+ )
85
+ field.isAccessible = true
86
+
87
+ val patched = PatchedFlutterLoader(patchSoPath, patchAssetsPath)
88
+ field.set(injector, patched)
89
+
90
+ Log.d(TAG, "FlutterLoader patched via field '${field.name}' -> $patchSoPath")
91
+ true
92
+ } catch (e: Throwable) {
93
+ Log.e(TAG, "install failed", e)
94
+ false
95
+ }
96
+ }
97
+
98
+ private fun verifyAssetArchiveReadable(
99
+ context: Context,
100
+ path: String,
101
+ bundlePath: String
102
+ ): Boolean {
103
+ return try {
104
+ val packageContext = context.createPackageContext(context.packageName, 0)
105
+ addAssetPathTo(packageContext, path, bundlePath, verifyManifest = true)
106
+ } catch (e: Throwable) {
107
+ Log.e(TAG, "createPackageContext asset patch failed", e)
108
+ false
109
+ }
110
+ }
111
+
112
+ private fun installPatchedFlutterJniFactory(
113
+ injector: FlutterInjector,
114
+ context: Context,
115
+ archivePath: String,
116
+ bundlePath: String
117
+ ) {
118
+ val field = injector.javaClass.getDeclaredField("flutterJniFactory")
119
+ field.isAccessible = true
120
+ field.set(
121
+ injector,
122
+ PatchedFlutterJniFactory(context.applicationContext, archivePath, bundlePath)
123
+ )
124
+ Log.d(TAG, "FlutterJNI factory patched for asset bundle=$bundlePath")
125
+ }
126
+
127
+ private class PatchedFlutterJniFactory(
128
+ private val context: Context,
129
+ private val archivePath: String,
130
+ private val bundlePath: String
131
+ ) : FlutterJNI.Factory() {
132
+ override fun provideFlutterJNI(): FlutterJNI {
133
+ return PatchedFlutterJNI(context, archivePath, bundlePath)
134
+ }
135
+ }
136
+
137
+ private class PatchedFlutterJNI(
138
+ private val context: Context,
139
+ private val archivePath: String,
140
+ private val bundlePath: String
141
+ ) : FlutterJNI() {
142
+ override fun runBundleAndSnapshotFromLibrary(
143
+ bundlePath: String,
144
+ entrypointFunctionName: String?,
145
+ pathToEntrypointFunction: String?,
146
+ assetManager: android.content.res.AssetManager,
147
+ entrypointArgs: MutableList<String>?,
148
+ engineId: Long
149
+ ) {
150
+ try {
151
+ val packageContext = context.createPackageContext(context.packageName, 0)
152
+ if (addAssetPathTo(packageContext, archivePath, this.bundlePath)) {
153
+ super.runBundleAndSnapshotFromLibrary(
154
+ this.bundlePath,
155
+ entrypointFunctionName,
156
+ pathToEntrypointFunction,
157
+ packageContext.assets,
158
+ entrypointArgs,
159
+ engineId
160
+ )
161
+ return
162
+ }
163
+ } catch (e: Throwable) {
164
+ Log.e(TAG, "patched FlutterJNI asset setup failed", e)
165
+ }
166
+ super.runBundleAndSnapshotFromLibrary(
167
+ bundlePath,
168
+ entrypointFunctionName,
169
+ pathToEntrypointFunction,
170
+ assetManager,
171
+ entrypointArgs,
172
+ engineId
173
+ )
174
+ }
175
+ }
176
+
177
+ private fun addAssetPathTo(
178
+ context: Context,
179
+ path: String,
180
+ bundlePath: String?,
181
+ verifyManifest: Boolean = false
182
+ ): Boolean {
183
+ val method = context.assets.javaClass.getMethod("addAssetPath", String::class.java)
184
+ val cookie = method.invoke(context.assets, path) as? Int ?: 0
185
+ if (cookie == 0) {
186
+ Log.e(TAG, "AssetManager rejected asset archive: $path")
187
+ return false
188
+ }
189
+ if (verifyManifest && !bundlePath.isNullOrEmpty()) {
190
+ val manifestPath = "${bundlePath.trimEnd('/')}/AssetManifest.bin"
191
+ try {
192
+ context.assets.open(manifestPath).use { input ->
193
+ input.read()
194
+ }
195
+ } catch (e: Throwable) {
196
+ Log.e(TAG, "AssetManager self-check failed for $manifestPath", e)
197
+ return false
198
+ }
199
+ }
200
+ return true
201
+ }
202
+
203
+ private fun buildCandidates(context: Context): List<String> {
204
+ val override = PatcherConfig.loaderFieldCandidates(context)
205
+ return if (override.isNotEmpty()) override + DEFAULT_CANDIDATES.filter { it !in override }
206
+ else DEFAULT_CANDIDATES
207
+ }
208
+
209
+ /**
210
+ * 定位 FlutterLoader 字段:
211
+ * 1. 按候选名精确匹配(安全)
212
+ * 2. 按类型精确匹配:字段类型是 FlutterLoader 或其子类(安全)
213
+ * 3. 启发式回退:首个非 static、非 ExecutorService 的实例字段(**可能命错字段**)
214
+ *
215
+ * Layer 3 默认关闭:宁可退回内置 .so,也不要瞎设字段导致不可预测的崩溃。
216
+ * 通过 FlutterPatcher.init(loaderFallbackHeuristic: true) 显式启用。
217
+ */
218
+ private fun findLoaderField(
219
+ clazz: Class<*>,
220
+ candidates: List<String>,
221
+ heuristic: Boolean
222
+ ): Field? {
223
+ for (name in candidates) {
224
+ try {
225
+ return clazz.getDeclaredField(name)
226
+ } catch (_: NoSuchFieldException) {
227
+ // continue
228
+ }
229
+ }
230
+ Log.w(TAG, "no exact-name match, falling back to type-based detection")
231
+
232
+ clazz.declaredFields
233
+ .firstOrNull { FlutterLoader::class.java.isAssignableFrom(it.type) }
234
+ ?.let { return it }
235
+
236
+ if (!heuristic) {
237
+ Log.w(TAG, "type-based match failed; heuristic disabled, giving up")
238
+ return null
239
+ }
240
+ Log.w(TAG, "type-based match failed; using heuristic (may pick wrong field)")
241
+ return clazz.declaredFields.firstOrNull {
242
+ !Modifier.isStatic(it.modifiers) &&
243
+ it.type != ExecutorService::class.java
244
+ }
245
+ }
246
+ }
247
+
248
+ /**
249
+ * 自定义 FlutterLoader:通过 `--aot-shared-library-name` 参数让 Flutter Engine
250
+ * 从补丁路径加载 libapp.so,替代 APK 内置版本。
251
+ *
252
+ * `--aot-shared-library-name=<path>` 从 Flutter 1.x 开始一直稳定存在,跨大版本兼容。
253
+ */
254
+ internal fun patchedAssetLookupKey(patchAssetsPath: String, asset: String): String {
255
+ return "${patchAssetsPath.trimEnd('/')}/${asset.trimStart('/')}"
256
+ }
257
+
258
+ internal fun patchedPackageAssetLookupKey(
259
+ patchAssetsPath: String,
260
+ asset: String,
261
+ packageName: String
262
+ ): String {
263
+ return patchedAssetLookupKey(patchAssetsPath, "packages/$packageName/$asset")
264
+ }
265
+
266
+ internal class PatchedFlutterLoader(
267
+ private val patchSoPath: String,
268
+ private val patchAssetsPath: String? = null,
269
+ ) : FlutterLoader() {
270
+
271
+ companion object {
272
+ private const val TAG = "FlutterPatcher/Loader"
273
+ }
274
+
275
+ override fun ensureInitializationComplete(context: Context, args: Array<String>?) {
276
+ val patched = (args ?: emptyArray()).toMutableList()
277
+ patched.add("--aot-shared-library-name=$patchSoPath")
278
+ if (!patchAssetsPath.isNullOrEmpty()) {
279
+ patched.add("--flutter-assets-dir=$patchAssetsPath")
280
+ }
281
+ Log.d(TAG, "load patch: so=$patchSoPath assets=${patchAssetsPath ?: "<built-in>"}")
282
+ super.ensureInitializationComplete(context, patched.toTypedArray())
283
+ }
284
+
285
+ override fun findAppBundlePath(): String {
286
+ return patchAssetsPath ?: super.findAppBundlePath()
287
+ }
288
+
289
+ override fun getLookupKeyForAsset(asset: String): String {
290
+ return patchAssetsPath?.let { patchedAssetLookupKey(it, asset) }
291
+ ?: super.getLookupKeyForAsset(asset)
292
+ }
293
+
294
+ override fun getLookupKeyForAsset(asset: String, packageName: String): String {
295
+ return patchAssetsPath?.let { patchedPackageAssetLookupKey(it, asset, packageName) }
296
+ ?: super.getLookupKeyForAsset(asset, packageName)
297
+ }
298
+ }