@_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.
- package/README.md +112 -18
- package/bin/flutter-ota-linux-x64 +0 -0
- package/bin/flutter-ota.js +12 -11
- package/dart-src/packages/cli-tools/README.md +146 -2
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +46 -13
- package/dart-src/packages/cli-tools/lib/src/backend.dart +6 -6
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +2 -0
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +7 -7
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +4 -1
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +36 -8
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +1 -1
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +1 -1
- package/dart-src/packages/cli-tools/lib/src/config.dart +5 -5
- package/dart-src/packages/cli-tools/lib/src/operations.dart +2 -2
- package/dart-src/packages/cli-tools/pubspec.yaml +8 -21
- package/dart-src/packages/cli-tools/test/backends_test.dart +3 -3
- package/dart-src/packages/cli-tools/test/cli_test.dart +1 -1
- package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +2 -2
- package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +2 -2
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +4 -1
- package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +2 -2
- package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +1 -1
- package/dart-src/root/README.md +334 -0
- package/dart-src/root/analysis_options.yaml +21 -0
- package/dart-src/root/android/build.gradle +116 -0
- package/dart-src/root/android/consumer-rules.pro +23 -0
- package/dart-src/root/android/settings.gradle +1 -0
- package/dart-src/root/android/src/main/AndroidManifest.xml +21 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/ApplyResult.kt +52 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BlacklistStore.kt +154 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BootDiagnosticStore.kt +150 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/CrashGuard.kt +271 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherApplication.kt +201 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherAutoInitProvider.kt +93 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherPlugin.kt +245 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/LoaderHook.kt +298 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatchManager.kt +1579 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt +173 -0
- package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/SignatureVerifier.kt +177 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/CrashGuardTest.kt +61 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/LoaderHookTest.kt +25 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatchManagerTest.kt +401 -0
- package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfigTest.kt +30 -0
- package/dart-src/root/bin/pack.dart +182 -0
- package/dart-src/root/lib/flutter_ota_kit.dart +1170 -0
- package/dart-src/root/lib/src/app_version_resolver.dart +36 -0
- package/dart-src/root/lib/src/aws_update_source.dart +97 -0
- package/dart-src/root/lib/src/blacklist.dart +54 -0
- package/dart-src/root/lib/src/boot_diagnostic.dart +138 -0
- package/dart-src/root/lib/src/cloudflare_update_source.dart +97 -0
- package/dart-src/root/lib/src/flutter_ota_app.dart +69 -0
- package/dart-src/root/lib/src/io.dart +48 -0
- package/dart-src/root/lib/src/io_stub.dart +19 -0
- package/dart-src/root/lib/src/ota_progress_overlay.dart +1186 -0
- package/dart-src/root/lib/src/pack/pack.dart +434 -0
- package/dart-src/root/lib/src/patch_info.dart +12 -0
- package/dart-src/root/lib/src/patcher_channel.dart +160 -0
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_database.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_s3_client.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage_profile.dart +1 -1
- package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/with_cloudfront_signed_url.dart +1 -1
- package/dart-src/root/lib/src/pkg/client/flutter_ota_kit_client.dart +1 -0
- package/dart-src/root/lib/src/pkg/client/src/server_update_source.dart +46 -0
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/cloudflare_worker_database.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_build_where.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_bundle_mapper.dart +3 -3
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database_plugin.dart +2 -2
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_s3_client.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage.dart +1 -1
- package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage_profile.dart +1 -1
- package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/status.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/flutter_ota_kit_plugin_core.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_blob_database_plugin.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/filter_compatible_app_versions.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/get_update_info.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/request_update_bundle_state.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/resolve_update_info_from_bundles.dart +1 -1
- package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/types.dart +2 -2
- package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_bundle_mapper.dart +2 -2
- package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_database.dart +26 -15
- package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_storage.dart +1 -1
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_bundle_mapper.dart +2 -2
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_database.dart +2 -2
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_get_update_info.dart +2 -2
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_storage.dart +1 -1
- package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_types.dart +1 -1
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_bundle_mapper.dart +2 -2
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_database.dart +2 -2
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_database.dart +1 -1
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_storage.dart +1 -1
- package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_storage.dart +1 -1
- package/dart-src/root/lib/src/pocketbase_update_source.dart +107 -0
- package/dart-src/root/lib/src/postgres_update_source.dart +95 -0
- package/dart-src/root/lib/src/shared_update_check.dart +131 -0
- package/dart-src/root/lib/src/supabase_update_source.dart +93 -0
- package/dart-src/root/pubspec.yaml +54 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +17 -13
- package/scripts/vendor.sh +22 -7
- package/dart-src/packages/core/CHANGELOG.md +0 -21
- package/dart-src/packages/core/README.md +0 -23
- package/dart-src/packages/core/example/lib/main.dart +0 -69
- package/dart-src/packages/core/pubspec.yaml +0 -13
- package/dart-src/packages/core/test/rollout_test.dart +0 -135
- package/dart-src/packages/core/test/semver_test.dart +0 -55
- package/dart-src/packages/core/test/semver_vectors.json +0 -1
- package/dart-src/packages/core/test/types_test.dart +0 -131
- package/dart-src/packages/core/test/uuid_test.dart +0 -40
- package/dart-src/packages/core/tool/dbg.dart +0 -7
- package/dart-src/plugins/aws/.gitkeep +0 -0
- package/dart-src/plugins/aws/CHANGELOG.md +0 -23
- package/dart-src/plugins/aws/LICENSE +0 -21
- package/dart-src/plugins/aws/README.md +0 -28
- package/dart-src/plugins/aws/example/lib/main.dart +0 -60
- package/dart-src/plugins/aws/pubspec.yaml +0 -30
- package/dart-src/plugins/aws/test/aws_database_test.dart +0 -161
- package/dart-src/plugins/aws/test/aws_storage_test.dart +0 -73
- package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +0 -78
- package/dart-src/plugins/cloudflare/.gitkeep +0 -0
- package/dart-src/plugins/cloudflare/CHANGELOG.md +0 -23
- package/dart-src/plugins/cloudflare/LICENSE +0 -21
- package/dart-src/plugins/cloudflare/README.md +0 -25
- package/dart-src/plugins/cloudflare/example/lib/main.dart +0 -62
- package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +0 -16
- package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +0 -8
- package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +0 -38
- package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +0 -11
- package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +0 -24
- package/dart-src/plugins/cloudflare/pubspec.yaml +0 -28
- package/dart-src/plugins/cloudflare/sql/bundles.sql +0 -42
- package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +0 -126
- package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +0 -106
- package/dart-src/plugins/cloudflare/test/d1_database_test.dart +0 -200
- package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +0 -261
- package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +0 -78
- package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +0 -74
- package/dart-src/plugins/plugin-core/CHANGELOG.md +0 -20
- package/dart-src/plugins/plugin-core/LICENSE +0 -21
- package/dart-src/plugins/plugin-core/README.md +0 -21
- package/dart-src/plugins/plugin-core/example/lib/main.dart +0 -61
- package/dart-src/plugins/plugin-core/pubspec.yaml +0 -23
- package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +0 -170
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +0 -74
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +0 -345
- package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +0 -843
- package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +0 -298
- package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +0 -195
- package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +0 -56
- package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +0 -1589
- package/dart-src/plugins/plugin-core/test/helpers_test.dart +0 -283
- package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +0 -202
- package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +0 -86
- package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +0 -88
- package/dart-src/plugins/pocketbase/CHANGELOG.md +0 -23
- package/dart-src/plugins/pocketbase/LICENSE +0 -21
- package/dart-src/plugins/pocketbase/README.md +0 -54
- package/dart-src/plugins/pocketbase/example/lib/main.dart +0 -62
- package/dart-src/plugins/pocketbase/pubspec.yaml +0 -26
- package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +0 -12
- package/dart-src/plugins/postgres/.gitkeep +0 -0
- package/dart-src/plugins/postgres/CHANGELOG.md +0 -23
- package/dart-src/plugins/postgres/LICENSE +0 -21
- package/dart-src/plugins/postgres/README.md +0 -29
- package/dart-src/plugins/postgres/example/lib/main.dart +0 -67
- package/dart-src/plugins/postgres/pubspec.yaml +0 -26
- package/dart-src/plugins/postgres/sql/bundles.sql +0 -45
- package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +0 -21
- package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +0 -171
- package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +0 -126
- package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +0 -125
- package/dart-src/plugins/postgres/sql/hash_user_id.sql +0 -30
- package/dart-src/plugins/postgres/sql/is_device_eligible.sql +0 -237
- package/dart-src/plugins/postgres/sql/prepareSql.ts +0 -11
- package/dart-src/plugins/postgres/test/mock_postgres_client.dart +0 -320
- package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +0 -139
- package/dart-src/plugins/postgres/test/postgres_database_test.dart +0 -210
- package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +0 -60
- package/dart-src/plugins/supabase/CHANGELOG.md +0 -24
- package/dart-src/plugins/supabase/LICENSE +0 -21
- package/dart-src/plugins/supabase/README.md +0 -53
- package/dart-src/plugins/supabase/example/lib/main.dart +0 -63
- package/dart-src/plugins/supabase/pubspec.yaml +0 -27
- package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +0 -44
- package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +0 -968
- package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +0 -232
- package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -137
- package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -206
- package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -186
- package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -505
- package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
- package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
- package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -75
- package/dart-src/plugins/supabase/test/mock_supabase_client.dart +0 -654
- package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +0 -115
- package/dart-src/plugins/supabase/test/supabase_database_test.dart +0 -198
- package/dart-src/plugins/supabase/test/supabase_storage_test.dart +0 -168
- /package/dart-src/{packages/core → root}/LICENSE +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/flutter_ota_kit_aws.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_client.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_signer.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_config.dart +0 -0
- /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_ssm_client.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/flutter_ota_kit_cloudflare.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_client.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_config.dart +0 -0
- /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_config.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/flutter_ota_kit_core.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/app_update_info.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_artifacts.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_patch_artifact.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/changed_asset.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/get_bundles_args.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/metadata.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/patch_info.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/platform.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/rollout.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_range.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_version.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/strategy.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/update_bundle_params.dart +0 -0
- /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/uuid.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/asset_storage_layout.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_helpers.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_storage_layout.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work_store.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/calculate_pagination.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/compression_format.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/content_addressed_assets.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_database_plugin.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_key_builder.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_plugin.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/generate_min_bundle_id.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/is_object.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/legacy_asset_storage_layout.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/paginate_bundles.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/parse_storage_uri.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/query_bundles.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_helpers.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_profile.dart +0 -0
- /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/uuidv7.dart +0 -0
- /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/flutter_ota_kit_pocketbase.dart +0 -0
- /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_client.dart +0 -0
- /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_config.dart +0 -0
- /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/flutter_ota_kit_postgres.dart +0 -0
- /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_client.dart +0 -0
- /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_config.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/edge.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/flutter_ota_kit_supabase.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/error_message.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_adapter.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_http.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_config.dart +0 -0
- /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_signed_url_batcher.dart +0 -0
- /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
|
+
}
|
package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/LoaderHook.kt
ADDED
|
@@ -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
|
+
}
|