Repository: Tencent/Shadow Branch: master Commit: adafe8e31825 Files: 948 Total size: 2.0 MB Directory structure: gitextract_9gwt80rw/ ├── .commitlintrc.yml ├── .github/ │ ├── actions/ │ │ ├── post-build/ │ │ │ └── action.yml │ │ └── pre-build/ │ │ └── action.yml │ └── workflows/ │ ├── check-build-test.yml │ └── release.yml ├── .gitignore ├── .idea/ │ └── codeStyles/ │ ├── Project.xml │ └── codeStyleConfig.xml ├── CONTRIBUTING.md ├── LICENSE.txt ├── PRIVACY.md ├── README.md ├── build.gradle ├── buildScripts/ │ └── gradle/ │ ├── common.gradle │ ├── fix_issue_1263.gradle │ ├── maven.gradle │ └── versions.properties ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── projects/ │ ├── sample/ │ │ ├── README.md │ │ ├── dynamic-apk/ │ │ │ ├── sample-hello-api/ │ │ │ │ ├── .gitignore │ │ │ │ ├── README.md │ │ │ │ ├── build.gradle │ │ │ │ ├── proguard-rules.pro │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── sample/ │ │ │ │ └── api/ │ │ │ │ └── hello/ │ │ │ │ ├── HelloFactory.java │ │ │ │ ├── IHelloWorld.java │ │ │ │ └── IHelloWorldImpl.java │ │ │ ├── sample-hello-api-holder/ │ │ │ │ ├── .gitignore │ │ │ │ ├── README.md │ │ │ │ ├── build.gradle │ │ │ │ ├── proguard-rules.pro │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── sample/ │ │ │ │ └── apk/ │ │ │ │ └── hello/ │ │ │ │ ├── DynamicHello.java │ │ │ │ ├── HelloImplLoader.java │ │ │ │ └── HelloWorldUpdater.java │ │ │ ├── sample-hello-apk/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ ├── proguard-rules.pro │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ ├── dynamic/ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ ├── HelloFactoryImpl.java │ │ │ │ │ │ └── WhiteList.java │ │ │ │ │ └── sample/ │ │ │ │ │ └── api/ │ │ │ │ │ └── hello/ │ │ │ │ │ └── SampleHelloWorld.java │ │ │ │ └── res/ │ │ │ │ └── layout/ │ │ │ │ └── activity_load_plugin.xml │ │ │ └── sample-hello-host/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── sample/ │ │ │ └── host/ │ │ │ ├── AndroidLogLoggerFactory.java │ │ │ ├── HostApplication.java │ │ │ ├── MainActivity.java │ │ │ ├── PluginHelper.java │ │ │ └── api/ │ │ │ ├── FixedPathPmUpdater.java │ │ │ └── HelloWorldApiHolder.java │ │ ├── maven/ │ │ │ ├── host-project/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ ├── gradle/ │ │ │ │ │ └── wrapper/ │ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ ├── gradle.properties │ │ │ │ ├── gradlew │ │ │ │ ├── gradlew.bat │ │ │ │ ├── introduce-shadow-lib/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── proguard-rules.pro │ │ │ │ │ └── src/ │ │ │ │ │ └── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ ├── java/ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ └── tencent/ │ │ │ │ │ │ └── shadow/ │ │ │ │ │ │ └── sample/ │ │ │ │ │ │ └── introduce_shadow_lib/ │ │ │ │ │ │ ├── AndroidLoggerFactory.java │ │ │ │ │ │ ├── FixedPathPmUpdater.java │ │ │ │ │ │ ├── InitApplication.java │ │ │ │ │ │ └── MainPluginProcessService.java │ │ │ │ │ └── res/ │ │ │ │ │ └── values/ │ │ │ │ │ └── themes.xml │ │ │ │ ├── sample-host/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── proguard-rules.pro │ │ │ │ │ └── src/ │ │ │ │ │ └── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ └── sample/ │ │ │ │ │ └── host/ │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── MyApplication.java │ │ │ │ └── settings.gradle │ │ │ ├── manager-project/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ ├── gradle/ │ │ │ │ │ └── wrapper/ │ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ ├── gradle.properties │ │ │ │ ├── gradlew │ │ │ │ ├── gradlew.bat │ │ │ │ ├── sample-manager/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── proguard-rules.pro │ │ │ │ │ └── src/ │ │ │ │ │ └── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ ├── aidl/ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ └── tencent/ │ │ │ │ │ │ └── shadow/ │ │ │ │ │ │ └── sample/ │ │ │ │ │ │ └── plugin/ │ │ │ │ │ │ └── IMyAidlInterface.aidl │ │ │ │ │ ├── java/ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ └── tencent/ │ │ │ │ │ │ └── shadow/ │ │ │ │ │ │ ├── dynamic/ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ ├── ManagerFactoryImpl.java │ │ │ │ │ │ │ └── WhiteList.java │ │ │ │ │ │ └── sample/ │ │ │ │ │ │ └── manager/ │ │ │ │ │ │ ├── Constant.java │ │ │ │ │ │ ├── FastPluginManager.java │ │ │ │ │ │ └── SamplePluginManager.java │ │ │ │ │ └── res/ │ │ │ │ │ └── layout/ │ │ │ │ │ └── activity_load_plugin.xml │ │ │ │ └── settings.gradle │ │ │ └── plugin-project/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── gradle/ │ │ │ │ └── wrapper/ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ │ ├── gradle.properties │ │ │ ├── gradlew │ │ │ ├── gradlew.bat │ │ │ ├── plugin-app/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ ├── proguard-rules.pro │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── aidl/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ └── sample/ │ │ │ │ │ └── plugin/ │ │ │ │ │ └── IMyAidlInterface.aidl │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ └── sample/ │ │ │ │ │ └── plugin/ │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── MyService.java │ │ │ │ └── res/ │ │ │ │ ├── layout/ │ │ │ │ │ └── activity_main.xml │ │ │ │ └── values/ │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ ├── sample-loader/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ ├── proguard-rules.pro │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ ├── dynamic/ │ │ │ │ │ └── loader/ │ │ │ │ │ └── impl/ │ │ │ │ │ └── CoreLoaderFactoryImpl.java │ │ │ │ └── sample/ │ │ │ │ └── loader/ │ │ │ │ ├── SampleComponentManager.java │ │ │ │ └── SamplePluginLoader.java │ │ │ ├── sample-runtime/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ ├── proguard-rules.pro │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── sample/ │ │ │ │ └── runtime/ │ │ │ │ ├── PluginDefaultProxyActivity.java │ │ │ │ ├── PluginSingleInstance1ProxyActivity.java │ │ │ │ └── PluginSingleTask1ProxyActivity.java │ │ │ └── settings.gradle │ │ └── source/ │ │ ├── sample-constant/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── sample/ │ │ │ └── constant/ │ │ │ └── Constant.java │ │ ├── sample-host/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── sample/ │ │ │ │ └── host/ │ │ │ │ ├── AndroidLogLoggerFactory.java │ │ │ │ ├── HostApplication.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── Plugin2ProcessPPS.java │ │ │ │ ├── PluginHelper.java │ │ │ │ ├── PluginLoadActivity.java │ │ │ │ ├── PluginProcessPPS.java │ │ │ │ ├── manager/ │ │ │ │ │ ├── FixedPathPmUpdater.java │ │ │ │ │ └── Shadow.java │ │ │ │ └── plugin_view/ │ │ │ │ ├── HostAddPluginViewActivity.java │ │ │ │ └── MainProcessManagerReceiver.java │ │ │ └── res/ │ │ │ ├── layout/ │ │ │ │ ├── activity_jump_to_plugin.xml │ │ │ │ ├── activity_load.xml │ │ │ │ └── part_key_adapter.xml │ │ │ └── values/ │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ ├── sample-host-lib/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ ├── sample-host-lib.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── sample/ │ │ │ │ └── host/ │ │ │ │ └── lib/ │ │ │ │ ├── HostAddPluginViewContainer.java │ │ │ │ ├── HostAddPluginViewContainerHolder.java │ │ │ │ ├── HostUiLayerProvider.java │ │ │ │ └── LoadPluginCallback.java │ │ │ └── res/ │ │ │ └── layout/ │ │ │ └── host_ui_layer_layout.xml │ │ ├── sample-manager/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ ├── dynamic/ │ │ │ │ │ └── impl/ │ │ │ │ │ ├── ManagerFactoryImpl.java │ │ │ │ │ └── WhiteList.java │ │ │ │ └── sample/ │ │ │ │ └── manager/ │ │ │ │ ├── FastPluginManager.java │ │ │ │ └── SamplePluginManager.java │ │ │ └── res/ │ │ │ └── layout/ │ │ │ └── activity_load_plugin.xml │ │ └── sample-plugin/ │ │ ├── sample-app/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets/ │ │ │ │ └── web/ │ │ │ │ └── test.html │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── sample/ │ │ │ │ └── plugin/ │ │ │ │ └── app/ │ │ │ │ └── lib/ │ │ │ │ ├── UseCaseApplication.java │ │ │ │ └── usecases/ │ │ │ │ ├── activity/ │ │ │ │ │ ├── TestActivityOnCreate.java │ │ │ │ │ ├── TestActivityOptionMenu.java │ │ │ │ │ ├── TestActivityOrientation.java │ │ │ │ │ ├── TestActivityReCreate.java │ │ │ │ │ ├── TestActivityReCreateBySystem.java │ │ │ │ │ ├── TestActivitySetTheme.java │ │ │ │ │ └── TestActivityWindowSoftMode.java │ │ │ │ ├── application/ │ │ │ │ │ └── TestApplicationActivity.java │ │ │ │ ├── context/ │ │ │ │ │ ├── ActivityContextSubDirTestActivity.java │ │ │ │ │ ├── ApplicationContextSubDirTestActivity.java │ │ │ │ │ └── SubDirContextThemeWrapperTestActivity.java │ │ │ │ ├── dialog/ │ │ │ │ │ ├── TestDialog.java │ │ │ │ │ └── TestDialogActivity.java │ │ │ │ ├── fragment/ │ │ │ │ │ ├── TestDialogFragment.java │ │ │ │ │ ├── TestDialogFragmentActivity.java │ │ │ │ │ ├── TestDynamicFragmentActivity.java │ │ │ │ │ ├── TestFragment.java │ │ │ │ │ └── TestXmlFragmentActivity.java │ │ │ │ ├── host_communication/ │ │ │ │ │ └── PluginUseHostClassActivity.java │ │ │ │ ├── packagemanager/ │ │ │ │ │ └── TestPackageManagerActivity.java │ │ │ │ ├── provider/ │ │ │ │ │ ├── TestDBContentProviderActivity.java │ │ │ │ │ ├── TestDBHelper.java │ │ │ │ │ ├── TestFileProviderActivity.java │ │ │ │ │ ├── TestProvider.java │ │ │ │ │ └── TestProviderInfo.java │ │ │ │ ├── receiver/ │ │ │ │ │ ├── MyReceiver.java │ │ │ │ │ ├── TestDynamicReceiverActivity.java │ │ │ │ │ └── TestReceiverActivity.java │ │ │ │ ├── service/ │ │ │ │ │ └── HostAddPluginViewService.java │ │ │ │ └── webview/ │ │ │ │ └── WebViewActivity.java │ │ │ └── res/ │ │ │ ├── anim/ │ │ │ │ └── dialog_exit_fade_out.xml │ │ │ ├── drawable/ │ │ │ │ ├── selector_group.xml │ │ │ │ └── selector_item.xml │ │ │ ├── layout/ │ │ │ │ ├── activity_test_file_provider.xml │ │ │ │ ├── activity_test_re_create_by_system.xml │ │ │ │ ├── layout_activity_lifecycle.xml │ │ │ │ ├── layout_activity_settheme.xml │ │ │ │ ├── layout_common.xml │ │ │ │ ├── layout_dialog.xml │ │ │ │ ├── layout_dialog_activity.xml │ │ │ │ ├── layout_fragment_activity.xml │ │ │ │ ├── layout_fragment_test.xml │ │ │ │ ├── layout_fragment_xml_activity.xml │ │ │ │ ├── layout_host_add_plugin_view.xml │ │ │ │ ├── layout_orientation.xml │ │ │ │ ├── layout_packagemanager.xml │ │ │ │ ├── layout_provider_db.xml │ │ │ │ ├── layout_receiver.xml │ │ │ │ ├── layout_recreate.xml │ │ │ │ ├── layout_result.xml │ │ │ │ ├── layout_service.xml │ │ │ │ ├── layout_softmode.xml │ │ │ │ └── layout_test_view_cons_cache.xml │ │ │ ├── menu/ │ │ │ │ └── case_test_activity_option_menu.xml │ │ │ ├── values/ │ │ │ │ ├── strings.xml │ │ │ │ ├── styles.xml │ │ │ │ └── themes.xml │ │ │ └── values-v21/ │ │ │ └── themes.xml │ │ ├── sample-base/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── AndroidManifest.xml │ │ ├── sample-base-lib/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── sample/ │ │ │ │ └── plugin/ │ │ │ │ └── app/ │ │ │ │ └── lib/ │ │ │ │ └── gallery/ │ │ │ │ ├── BaseActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── TestApplication.java │ │ │ │ ├── cases/ │ │ │ │ │ ├── UseCaseManager.java │ │ │ │ │ ├── UseCaseSummaryFragment.java │ │ │ │ │ └── entity/ │ │ │ │ │ ├── UseCase.java │ │ │ │ │ └── UseCaseCategory.java │ │ │ │ ├── splash/ │ │ │ │ │ ├── ISplashAnimation.java │ │ │ │ │ ├── SplashActivity.java │ │ │ │ │ └── SplashAnimation.java │ │ │ │ └── util/ │ │ │ │ ├── PluginChecker.java │ │ │ │ ├── ToastUtil.java │ │ │ │ └── UiUtil.java │ │ │ └── res/ │ │ │ ├── drawable/ │ │ │ │ └── child_bg.xml │ │ │ ├── layout/ │ │ │ │ ├── layout_case_category_item.xml │ │ │ │ ├── layout_case_item.xml │ │ │ │ ├── layout_fragment_case_summary.xml │ │ │ │ ├── layout_main.xml │ │ │ │ ├── layout_main_above.xml │ │ │ │ ├── layout_main_behind.xml │ │ │ │ └── layout_splash.xml │ │ │ ├── values/ │ │ │ │ ├── dimens.xml │ │ │ │ ├── strings.xml │ │ │ │ └── themes.xml │ │ │ └── xml/ │ │ │ └── filepaths.xml │ │ ├── sample-loader/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ ├── dynamic/ │ │ │ │ ├── impl/ │ │ │ │ │ └── WhiteList.java │ │ │ │ └── loader/ │ │ │ │ └── impl/ │ │ │ │ └── CoreLoaderFactoryImpl.java │ │ │ └── sample/ │ │ │ └── plugin/ │ │ │ └── loader/ │ │ │ ├── SampleComponentManager.java │ │ │ └── SamplePluginLoader.java │ │ ├── sample-runtime/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── sample/ │ │ │ └── plugin/ │ │ │ └── runtime/ │ │ │ ├── PluginDefaultProxyActivity.java │ │ │ ├── PluginSingleInstance1ProxyActivity.java │ │ │ └── PluginSingleTask1ProxyActivity.java │ │ └── third-party/ │ │ ├── pinnedheaderexpandablelistview/ │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── ryg/ │ │ │ └── expandable/ │ │ │ └── ui/ │ │ │ ├── PinnedHeaderExpandableListView.java │ │ │ └── StickyLayout.java │ │ └── slidingmenu/ │ │ ├── .gitignore │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── jeremyfeinstein/ │ │ │ └── slidingmenu/ │ │ │ └── lib/ │ │ │ ├── CanvasTransformerBuilder.java │ │ │ ├── CustomViewAbove.java │ │ │ ├── CustomViewBehind.java │ │ │ └── SlidingMenu.java │ │ └── res/ │ │ └── values/ │ │ ├── attrs.xml │ │ └── ids.xml │ ├── sdk/ │ │ ├── coding/ │ │ │ ├── .gitignore │ │ │ ├── aar-to-jar-plugin/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── coding/ │ │ │ │ └── aar_to_jar_plugin/ │ │ │ │ └── AarToJarPlugin.kt │ │ │ ├── android-jar/ │ │ │ │ ├── .gitignore │ │ │ │ └── build.gradle │ │ │ ├── build.gradle │ │ │ ├── code-generator/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ ├── coding/ │ │ │ │ │ │ └── code_generator/ │ │ │ │ │ │ └── ActivityCodeGenerator.kt │ │ │ │ │ └── core/ │ │ │ │ │ └── runtime/ │ │ │ │ │ └── NeighborClass.kt │ │ │ │ └── test/ │ │ │ │ └── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── coding/ │ │ │ │ └── code_generator/ │ │ │ │ └── ActivityCodeGeneratorTest.kt │ │ │ ├── common-jar-settings/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── coding/ │ │ │ │ └── common_jar_settings/ │ │ │ │ └── CommonJarSettingsPlugin.kt │ │ │ ├── get-android-jar/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── gradle/ │ │ │ │ └── wrapper/ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ │ ├── gradlew │ │ │ ├── gradlew.bat │ │ │ ├── java-build-config/ │ │ │ │ ├── .gitignore │ │ │ │ └── build.gradle │ │ │ └── settings.gradle │ │ ├── core/ │ │ │ ├── .gitignore │ │ │ ├── activity-container/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── core/ │ │ │ │ └── runtime/ │ │ │ │ └── container/ │ │ │ │ ├── DelegateProvider.java │ │ │ │ ├── DelegateProviderHolder.java │ │ │ │ ├── HostActivity.java │ │ │ │ ├── HostActivityDelegate.java │ │ │ │ ├── HostActivityDelegator.java │ │ │ │ ├── HostNativeActivityDelegate.java │ │ │ │ ├── HostNativeActivityDelegator.java │ │ │ │ ├── NativePluginContainerActivity.java │ │ │ │ └── PluginContainerActivity.java │ │ │ ├── build.gradle │ │ │ ├── common/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── core/ │ │ │ │ ├── common/ │ │ │ │ │ ├── ILoggerFactory.java │ │ │ │ │ ├── InstalledApk.java │ │ │ │ │ ├── Logger.java │ │ │ │ │ └── LoggerFactory.java │ │ │ │ └── runtime/ │ │ │ │ └── container/ │ │ │ │ ├── ContentProviderDelegateProvider.java │ │ │ │ ├── ContentProviderDelegateProviderHolder.java │ │ │ │ ├── HostContentProviderDelegate.java │ │ │ │ └── PluginContainerContentProvider.java │ │ │ ├── gradle/ │ │ │ │ └── wrapper/ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ │ ├── gradle-plugin/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ └── core/ │ │ │ │ │ └── gradle/ │ │ │ │ │ ├── AGPCompat.kt │ │ │ │ │ ├── AGPCompatImpl.kt │ │ │ │ │ ├── CreatePackagePluginTask.kt │ │ │ │ │ ├── ShadowPlugin.kt │ │ │ │ │ ├── ShadowPluginHelper.kt │ │ │ │ │ └── extensions/ │ │ │ │ │ ├── PackagePluginExtension.kt │ │ │ │ │ ├── PluginApkConfig.kt │ │ │ │ │ └── PluginBuildType.kt │ │ │ │ └── test/ │ │ │ │ ├── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ └── core/ │ │ │ │ │ └── gradle/ │ │ │ │ │ ├── PackageMultiPluginTest.kt │ │ │ │ │ ├── PackageOnlyPluginTest.kt │ │ │ │ │ └── PackagePluginTaskTest.kt │ │ │ │ └── testProjects/ │ │ │ │ └── case1/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ ├── loader/ │ │ │ │ │ ├── build.gradle │ │ │ │ │ └── src/ │ │ │ │ │ └── main/ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── plugin1/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ └── src/ │ │ │ │ │ └── main/ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── plugin2/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ └── src/ │ │ │ │ │ └── main/ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── runtime/ │ │ │ │ │ ├── build.gradle │ │ │ │ │ └── src/ │ │ │ │ │ └── main/ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── gradle.properties │ │ │ ├── gradlew │ │ │ ├── gradlew.bat │ │ │ ├── load-parameters/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── core/ │ │ │ │ └── load_parameters/ │ │ │ │ └── LoadParameters.java │ │ │ ├── loader/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ └── core/ │ │ │ │ │ └── loader/ │ │ │ │ │ ├── ShadowPluginLoader.kt │ │ │ │ │ ├── blocs/ │ │ │ │ │ │ ├── CheckPackageNameBloc.kt │ │ │ │ │ │ ├── CreateApplicationBloc.kt │ │ │ │ │ │ ├── CreatePluginApplicationInfoBloc.kt │ │ │ │ │ │ ├── CreateResourceBloc.kt │ │ │ │ │ │ ├── LoadApkBloc.kt │ │ │ │ │ │ └── LoadPluginBloc.kt │ │ │ │ │ ├── classloaders/ │ │ │ │ │ │ ├── CombineClassLoader.kt │ │ │ │ │ │ └── PluginClassLoader.kt │ │ │ │ │ ├── delegates/ │ │ │ │ │ │ ├── DI.kt │ │ │ │ │ │ ├── PackageManagerWrapper.java │ │ │ │ │ │ ├── ShadowActivityDelegate.kt │ │ │ │ │ │ ├── ShadowContentProviderDelegate.kt │ │ │ │ │ │ ├── ShadowDelegate.kt │ │ │ │ │ │ └── ShadowNativeActivityDelegate.kt │ │ │ │ │ ├── exceptions/ │ │ │ │ │ │ ├── CreateApplicationException.kt │ │ │ │ │ │ ├── LoadApkException.kt │ │ │ │ │ │ ├── LoadPluginException.kt │ │ │ │ │ │ └── ParsePluginApkException.kt │ │ │ │ │ ├── infos/ │ │ │ │ │ │ ├── ContainerProviderInfo.kt │ │ │ │ │ │ └── PluginParts.kt │ │ │ │ │ └── managers/ │ │ │ │ │ ├── ComponentManager.kt │ │ │ │ │ ├── PluginContentProviderManager.kt │ │ │ │ │ ├── PluginPackageManagerImpl.kt │ │ │ │ │ └── PluginServiceManager.kt │ │ │ │ └── test/ │ │ │ │ └── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── core/ │ │ │ │ └── loader/ │ │ │ │ └── classloaders/ │ │ │ │ └── PluginClassLoaderTest.kt │ │ │ ├── manager/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ └── core/ │ │ │ │ │ └── manager/ │ │ │ │ │ ├── BasePluginManager.java │ │ │ │ │ └── installplugin/ │ │ │ │ │ ├── AppCacheFolderManager.java │ │ │ │ │ ├── CopySoBloc.java │ │ │ │ │ ├── InstallPluginException.java │ │ │ │ │ ├── InstalledDao.java │ │ │ │ │ ├── InstalledPlugin.java │ │ │ │ │ ├── InstalledPluginDBHelper.java │ │ │ │ │ ├── InstalledRow.java │ │ │ │ │ ├── InstalledType.java │ │ │ │ │ ├── MinFileUtils.java │ │ │ │ │ ├── ODexBloc.java │ │ │ │ │ ├── PluginConfig.java │ │ │ │ │ ├── SafeZipFile.java │ │ │ │ │ └── UnpackManager.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── core/ │ │ │ │ └── manager/ │ │ │ │ └── installplugin/ │ │ │ │ └── SafeZipFileTest.java │ │ │ ├── manager-db-test/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ ├── androidTest/ │ │ │ │ │ ├── java/ │ │ │ │ │ │ ├── com/ │ │ │ │ │ │ │ └── tencent/ │ │ │ │ │ │ │ └── shadow/ │ │ │ │ │ │ │ └── core/ │ │ │ │ │ │ │ ├── manager/ │ │ │ │ │ │ │ │ └── installplugin/ │ │ │ │ │ │ │ │ └── DbCompatibilityTest.java │ │ │ │ │ │ │ └── pluginmanager/ │ │ │ │ │ │ │ └── CustomAndroidJUnitRunner.java │ │ │ │ │ │ └── common/ │ │ │ │ │ │ └── AndroidLogLoggerFactory.java │ │ │ │ │ └── res/ │ │ │ │ │ └── raw/ │ │ │ │ │ ├── expect_sql_version1.sql │ │ │ │ │ ├── expect_sql_version2.sql │ │ │ │ │ ├── expect_sql_version3.sql │ │ │ │ │ ├── expect_sql_version4.sql │ │ │ │ │ ├── init_sql_version1.sql │ │ │ │ │ ├── init_sql_version2.sql │ │ │ │ │ ├── init_sql_version3.sql │ │ │ │ │ ├── init_sql_version4.sql │ │ │ │ │ └── plugin1.json │ │ │ │ └── main/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── manifest-parser/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ └── core/ │ │ │ │ │ └── manifest_parser/ │ │ │ │ │ ├── AndroidManifestKeys.kt │ │ │ │ │ ├── AndroidManifestReader.kt │ │ │ │ │ ├── ManifestParser.kt │ │ │ │ │ └── PluginManifestGenerator.kt │ │ │ │ └── test/ │ │ │ │ ├── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ └── core/ │ │ │ │ │ └── manifest_parser/ │ │ │ │ │ ├── AndroidManifestReaderTest.kt │ │ │ │ │ └── PluginManifestGeneratorTest.kt │ │ │ │ └── resources/ │ │ │ │ ├── case_as_little_as_possible.xml │ │ │ │ ├── noAppComponentFactory.xml │ │ │ │ └── sample-app.xml │ │ │ ├── runtime/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── core/ │ │ │ │ └── runtime/ │ │ │ │ ├── ActivityOptionsSupport.java │ │ │ │ ├── FixedContextLayoutInflater.java │ │ │ │ ├── PackageManagerInvokeRedirect.java │ │ │ │ ├── PluginActivity.java │ │ │ │ ├── PluginManifest.java │ │ │ │ ├── PluginPackageManager.java │ │ │ │ ├── PluginPartInfo.java │ │ │ │ ├── PluginPartInfoManager.java │ │ │ │ ├── ResolverHook.java │ │ │ │ ├── ShadowActivity.java │ │ │ │ ├── ShadowActivityLifecycleCallbacks.java │ │ │ │ ├── ShadowAppComponentFactory.java │ │ │ │ ├── ShadowApplication.java │ │ │ │ ├── ShadowContext.java │ │ │ │ ├── ShadowDialogSupport.java │ │ │ │ ├── ShadowFactory2.java │ │ │ │ ├── ShadowFragmentSupport.java │ │ │ │ ├── ShadowInstrumentation.java │ │ │ │ ├── ShadowIntentService.java │ │ │ │ ├── ShadowLayoutInflater.java │ │ │ │ ├── ShadowNativeActivity.java │ │ │ │ ├── ShadowPackageItemInfo.java │ │ │ │ ├── ShadowPendingIntent.java │ │ │ │ ├── ShadowService.java │ │ │ │ ├── ShadowWebView.java │ │ │ │ ├── ShadowWebViewLayoutInflater.java │ │ │ │ ├── SubDirContextThemeWrapper.java │ │ │ │ ├── UriConverter.java │ │ │ │ ├── XmlPullParserUtil.java │ │ │ │ └── package-info.java │ │ │ ├── settings.gradle │ │ │ ├── transform/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ └── core/ │ │ │ │ │ └── transform/ │ │ │ │ │ ├── DeprecatedTransformWrapper.kt │ │ │ │ │ ├── GradleTransformWrapper.kt │ │ │ │ │ ├── ShadowTransform.kt │ │ │ │ │ ├── TransformManager.kt │ │ │ │ │ └── specific/ │ │ │ │ │ ├── ActivityOptionsSupportTransform.kt │ │ │ │ │ ├── ActivityTransform.kt │ │ │ │ │ ├── AppComponentFactoryTransform.kt │ │ │ │ │ ├── ApplicationTransform.kt │ │ │ │ │ ├── ContentProviderTransform.kt │ │ │ │ │ ├── DialogSupportTransform.kt │ │ │ │ │ ├── FragmentSupportTransform.kt │ │ │ │ │ ├── InstrumentationTransform.kt │ │ │ │ │ ├── IntentServiceTransform.kt │ │ │ │ │ ├── KeepHostContextTransform.kt │ │ │ │ │ ├── LayoutInflaterTransform.kt │ │ │ │ │ ├── PackageItemInfoTransform.kt │ │ │ │ │ ├── PackageManagerTransform.kt │ │ │ │ │ ├── PendingIntentTransform.kt │ │ │ │ │ ├── ReceiverSupportTransform.kt │ │ │ │ │ ├── ServiceTransform.kt │ │ │ │ │ ├── SimpleRenameTransform.kt │ │ │ │ │ └── WebViewTransform.kt │ │ │ │ └── test/ │ │ │ │ ├── java/ │ │ │ │ │ ├── android/ │ │ │ │ │ │ ├── app/ │ │ │ │ │ │ │ ├── Activity.java │ │ │ │ │ │ │ ├── Application.java │ │ │ │ │ │ │ ├── Fragment.java │ │ │ │ │ │ │ ├── Instrumentation.java │ │ │ │ │ │ │ └── Service.java │ │ │ │ │ │ ├── content/ │ │ │ │ │ │ │ ├── BroadcastReceiver.java │ │ │ │ │ │ │ ├── ComponentName.java │ │ │ │ │ │ │ ├── Context.java │ │ │ │ │ │ │ ├── Intent.java │ │ │ │ │ │ │ ├── pm/ │ │ │ │ │ │ │ │ ├── ActivityInfo.java │ │ │ │ │ │ │ │ ├── ApplicationInfo.java │ │ │ │ │ │ │ │ ├── PackageInfo.java │ │ │ │ │ │ │ │ ├── PackageItemInfo.java │ │ │ │ │ │ │ │ ├── PackageManager.java │ │ │ │ │ │ │ │ ├── ProviderInfo.java │ │ │ │ │ │ │ │ └── ServiceInfo.java │ │ │ │ │ │ │ └── res/ │ │ │ │ │ │ │ └── XmlResourceParser.java │ │ │ │ │ │ ├── os/ │ │ │ │ │ │ │ ├── Bundle.java │ │ │ │ │ │ │ └── IBinder.java │ │ │ │ │ │ ├── util/ │ │ │ │ │ │ │ └── AttributeSet.java │ │ │ │ │ │ └── webkit/ │ │ │ │ │ │ └── WebView.java │ │ │ │ │ ├── com/ │ │ │ │ │ │ └── tencent/ │ │ │ │ │ │ └── shadow/ │ │ │ │ │ │ └── core/ │ │ │ │ │ │ └── runtime/ │ │ │ │ │ │ ├── PackageManagerInvokeRedirect.java │ │ │ │ │ │ ├── PluginPartInfo.java │ │ │ │ │ │ ├── PluginPartInfoManager.java │ │ │ │ │ │ ├── ShadowActivity.java │ │ │ │ │ │ ├── ShadowApplication.java │ │ │ │ │ │ ├── ShadowContext.java │ │ │ │ │ │ ├── ShadowFragmentSupport.java │ │ │ │ │ │ ├── ShadowInstrumentation.java │ │ │ │ │ │ └── ShadowWebView.java │ │ │ │ │ └── test/ │ │ │ │ │ ├── EggReceiver.java │ │ │ │ │ ├── TestActivity.java │ │ │ │ │ ├── TestActivityLifecycleCallbacks.java │ │ │ │ │ ├── TestApplication.java │ │ │ │ │ ├── TestInstrumentation.java │ │ │ │ │ ├── TestPackageManager.java │ │ │ │ │ ├── TestReceiver.java │ │ │ │ │ ├── TestService.java │ │ │ │ │ ├── TestWebView.java │ │ │ │ │ └── fragment/ │ │ │ │ │ ├── TestFragment.java │ │ │ │ │ ├── UseGetActivityFragment.java │ │ │ │ │ ├── UseStartActivityForResultFragment.java │ │ │ │ │ └── UseStartActivityFragment.java │ │ │ │ └── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── core/ │ │ │ │ └── transform/ │ │ │ │ └── specific/ │ │ │ │ ├── ActivityLifecycleCallbacksTransformTest.kt │ │ │ │ ├── ActivityTransformTest.kt │ │ │ │ ├── ApplicationTransformTest.kt │ │ │ │ ├── FragmentSupportTransformTest.kt │ │ │ │ ├── InstrumentationTransformTest.kt │ │ │ │ ├── PackageManagerTransformTest.kt │ │ │ │ ├── ReceiverSupportTransformTest.kt │ │ │ │ ├── ServiceTransformTest.kt │ │ │ │ ├── SimpleRenameTransformTest.kt │ │ │ │ └── WebViewTransformTest.kt │ │ │ ├── transform-kit/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── java/ │ │ │ │ │ │ └── javassist/ │ │ │ │ │ │ ├── EnhancedCodeConverter.java │ │ │ │ │ │ └── convert/ │ │ │ │ │ │ ├── TransformCallExceptSuperCallToStatic.java │ │ │ │ │ │ └── TransformNewClassFix.java │ │ │ │ │ └── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── tencent/ │ │ │ │ │ └── shadow/ │ │ │ │ │ └── core/ │ │ │ │ │ └── transform_kit/ │ │ │ │ │ ├── AbstractTransform.kt │ │ │ │ │ ├── AbstractTransformManager.kt │ │ │ │ │ ├── AndroidClassPoolBuilder.kt │ │ │ │ │ ├── AutoMakeMissingClassPool.kt │ │ │ │ │ ├── ClassTransform.kt │ │ │ │ │ ├── JavassistTransform.kt │ │ │ │ │ ├── OverrideCheck.kt │ │ │ │ │ ├── ReplaceClassName.kt │ │ │ │ │ ├── SpecificTransform.kt │ │ │ │ │ └── TransformStep.kt │ │ │ │ └── test/ │ │ │ │ ├── java/ │ │ │ │ │ └── test/ │ │ │ │ │ ├── MethodRedirectToStatic.java │ │ │ │ │ └── override/ │ │ │ │ │ └── Foo.java │ │ │ │ └── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── core/ │ │ │ │ └── transform_kit/ │ │ │ │ ├── AbstractTransformTest.kt │ │ │ │ ├── FilterRefClassesTest.kt │ │ │ │ ├── OverrideCheckTest.kt │ │ │ │ └── RedirectMethodCallToStaticTest.kt │ │ │ └── utils/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── core/ │ │ │ │ └── utils/ │ │ │ │ └── Md5.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── core/ │ │ │ └── utils/ │ │ │ └── Md5Test.java │ │ └── dynamic/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── dynamic-apk/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── dynamic/ │ │ │ └── apk/ │ │ │ ├── ApkClassLoader.java │ │ │ ├── ChangeApkContextWrapper.java │ │ │ └── ImplLoader.java │ │ ├── dynamic-host/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── dynamic/ │ │ │ └── host/ │ │ │ ├── BasePluginProcessService.java │ │ │ ├── BinderUuidManager.java │ │ │ ├── DynamicPluginManager.java │ │ │ ├── DynamicRuntime.java │ │ │ ├── EnterCallback.java │ │ │ ├── FailedException.java │ │ │ ├── LoaderFactory.java │ │ │ ├── LoaderImplLoader.java │ │ │ ├── ManagerFactory.java │ │ │ ├── ManagerImplLoader.java │ │ │ ├── NotFoundException.java │ │ │ ├── PluginLoaderImpl.java │ │ │ ├── PluginManager.java │ │ │ ├── PluginManagerImpl.java │ │ │ ├── PluginManagerUpdater.java │ │ │ ├── PluginProcessService.java │ │ │ ├── PpsBinder.java │ │ │ ├── PpsController.java │ │ │ ├── PpsStatus.java │ │ │ └── UuidManager.java │ │ ├── dynamic-host-multi-loader-ext/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── dynamic/ │ │ │ └── host/ │ │ │ ├── MultiDynamicContainer.java │ │ │ ├── MultiLoaderPluginProcessService.java │ │ │ ├── MultiLoaderPpsBinder.java │ │ │ └── MultiLoaderPpsController.java │ │ ├── dynamic-loader/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── dynamic/ │ │ │ └── loader/ │ │ │ ├── PluginLoader.java │ │ │ └── PluginServiceConnection.java │ │ ├── dynamic-loader-impl/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── kotlin/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── dynamic/ │ │ │ ├── impl/ │ │ │ │ └── LoaderFactoryImpl.kt │ │ │ └── loader/ │ │ │ └── impl/ │ │ │ ├── BinderPluginServiceConnection.kt │ │ │ ├── CoreLoaderFactory.kt │ │ │ ├── DynamicPluginLoader.kt │ │ │ ├── LoaderFactoryImpl.kt │ │ │ └── PluginLoaderBinder.kt │ │ ├── dynamic-manager/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── dynamic/ │ │ │ └── manager/ │ │ │ ├── BaseDynamicPluginManager.java │ │ │ ├── BinderPluginLoader.java │ │ │ ├── PluginManagerThatUseDynamicLoader.java │ │ │ ├── PluginServiceConnectionBinder.java │ │ │ ├── UuidManagerBinder.java │ │ │ └── UuidManagerImpl.java │ │ ├── dynamic-manager-multi-loader-ext/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── dynamic/ │ │ │ └── manager/ │ │ │ └── PluginManagerThatSupportMultiLoader.java │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ └── test/ │ ├── common-jar-settings-test/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── Test.java │ ├── dynamic/ │ │ ├── host/ │ │ │ └── test-dynamic-host/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ ├── androidTest/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java/ │ │ │ │ └── com/ │ │ │ │ ├── google/ │ │ │ │ │ └── devtools/ │ │ │ │ │ └── build/ │ │ │ │ │ └── android/ │ │ │ │ │ └── desugar/ │ │ │ │ │ └── runtime/ │ │ │ │ │ └── ThrowableExtension.java │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── test/ │ │ │ │ ├── CreateResourceTest.java │ │ │ │ ├── CustomAndroidJUnitRunner.java │ │ │ │ ├── PluginTest.java │ │ │ │ ├── cases/ │ │ │ │ │ ├── plugin_androidx/ │ │ │ │ │ │ ├── AppCompatActivityTest.java │ │ │ │ │ │ ├── ComponentFactoryTest.java │ │ │ │ │ │ ├── LiveDataWithActivityTest.java │ │ │ │ │ │ └── PluginAndroidxAppTest.java │ │ │ │ │ ├── plugin_main/ │ │ │ │ │ │ ├── ActivityContextSubDirTest.java │ │ │ │ │ │ ├── ActivityLifecycleCallbacksTest.java │ │ │ │ │ │ ├── ActivityReCreateTest.java │ │ │ │ │ │ ├── ActivityWindowSoftModeTest.java │ │ │ │ │ │ ├── ApplicationContextInActivityTest.java │ │ │ │ │ │ ├── ApplicationContextSubDirTest.java │ │ │ │ │ │ ├── ApplicationTest.java │ │ │ │ │ │ ├── BasicTest.java │ │ │ │ │ │ ├── BootClassloaderTest.java │ │ │ │ │ │ ├── ContextGetPackageCodePathTest.java │ │ │ │ │ │ ├── GetCallingActivityTest.java │ │ │ │ │ │ ├── HostInterfaceTest.java │ │ │ │ │ │ ├── InstrumentationTest.java │ │ │ │ │ │ ├── LayoutInflaterTest.java │ │ │ │ │ │ ├── NullRefInXmlTest.java │ │ │ │ │ │ ├── PackageManagerTest.java │ │ │ │ │ │ ├── PluginBroadcastReceiverTest.java │ │ │ │ │ │ ├── PluginMainAppTest.java │ │ │ │ │ │ ├── PluginServiceTest.java │ │ │ │ │ │ ├── RegisterNullReceiverTest.java │ │ │ │ │ │ ├── ReinstallPluginTest.java │ │ │ │ │ │ ├── ServiceContextSubDirTest.java │ │ │ │ │ │ ├── SubDirContextThemeWrapperTest.java │ │ │ │ │ │ ├── TargetFragmentTest.java │ │ │ │ │ │ ├── ThemeTest.java │ │ │ │ │ │ ├── ViewIdTest.java │ │ │ │ │ │ ├── application_info/ │ │ │ │ │ │ │ ├── ApplicationInfoTest.java │ │ │ │ │ │ │ ├── CommonApplicationInfoTest.java │ │ │ │ │ │ │ ├── ContextGetApplicationInfoTest.java │ │ │ │ │ │ │ ├── PackageManagerGetOtherInstalledApplicationInfoTest.java │ │ │ │ │ │ │ └── PackageManagerGetSelfApplicationInfoTest.java │ │ │ │ │ │ ├── dialog_support/ │ │ │ │ │ │ │ ├── AlertDialogOwnerActivityTest.java │ │ │ │ │ │ │ └── DialogOwnerActivityTest.java │ │ │ │ │ │ └── fragment_support/ │ │ │ │ │ │ ├── CommonFragmentSupportTest.java │ │ │ │ │ │ ├── FragmentSupportTest.java │ │ │ │ │ │ ├── ProgrammaticallyAddBaseFragmentTest.java │ │ │ │ │ │ ├── ProgrammaticallyAddDialogFragmentTest.java │ │ │ │ │ │ ├── ProgrammaticallyAddFragmentTest.java │ │ │ │ │ │ ├── ProgrammaticallyAddNormalFragmentTest.java │ │ │ │ │ │ ├── ProgrammaticallyAddOnlyOverrideOnAttachActivityFragmentTest.java │ │ │ │ │ │ ├── ProgrammaticallyAddOnlyOverrideOnAttachContextFragmentTest.java │ │ │ │ │ │ ├── ProgrammaticallyAddSubFragmentTest.java │ │ │ │ │ │ ├── XmlAddBaseFragmentTest.java │ │ │ │ │ │ ├── XmlAddFragmentTest.java │ │ │ │ │ │ ├── XmlAddNormalFragmentTest.java │ │ │ │ │ │ └── XmlAddSubFragmentTest.java │ │ │ │ │ └── plugin_service/ │ │ │ │ │ ├── PluginIntentServiceConnectionTest.java │ │ │ │ │ ├── PluginServiceAppTest.java │ │ │ │ │ └── PluginServiceConnectionTest.java │ │ │ │ └── repeat/ │ │ │ │ ├── Repeat.java │ │ │ │ └── RepeatRule.java │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── test/ │ │ │ │ └── dynamic/ │ │ │ │ └── host/ │ │ │ │ ├── AndroidLogLoggerFactory.java │ │ │ │ ├── BindPluginServiceActivity.java │ │ │ │ ├── HostApplication.java │ │ │ │ ├── JumpToPluginActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── PluginHelper.java │ │ │ │ ├── PluginLoadActivity.java │ │ │ │ ├── PluginProcessPPS.java │ │ │ │ ├── PluginServiceProcessPPS.java │ │ │ │ ├── SimpleIdlingResourceImpl.java │ │ │ │ └── manager/ │ │ │ │ ├── FixedPathPmUpdater.java │ │ │ │ └── Shadow.java │ │ │ └── res/ │ │ │ ├── layout/ │ │ │ │ ├── activity_jump_to_plugin.xml │ │ │ │ ├── activity_load.xml │ │ │ │ └── part_key_adapter.xml │ │ │ └── values/ │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ ├── manager/ │ │ │ └── test-dynamic-manager/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ ├── dynamic/ │ │ │ │ │ └── impl/ │ │ │ │ │ ├── ManagerFactoryImpl.java │ │ │ │ │ └── WhiteList.java │ │ │ │ └── test/ │ │ │ │ ├── UiUtil.java │ │ │ │ ├── cases/ │ │ │ │ │ ├── PluginIntentServiceConnectionTestCase.java │ │ │ │ │ └── PluginServiceConnectionTestCase.java │ │ │ │ └── dynamic/ │ │ │ │ └── manager/ │ │ │ │ ├── ActivityTestDynamicPluginManager.java │ │ │ │ ├── FastPluginManager.java │ │ │ │ ├── ReinstallPluginTestDynamicPluginManager.java │ │ │ │ ├── ServiceTestDynamicPluginManager.java │ │ │ │ └── TestDynamicPluginManager.java │ │ │ └── res/ │ │ │ └── layout/ │ │ │ └── activity_load_plugin.xml │ │ └── plugin/ │ │ ├── test-dynamic-loader/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ ├── dynamic/ │ │ │ │ └── loader/ │ │ │ │ └── impl/ │ │ │ │ └── CoreLoaderFactoryImpl.java │ │ │ └── test/ │ │ │ └── dynamic/ │ │ │ └── loader/ │ │ │ ├── TestComponentManager.java │ │ │ └── TestPluginLoader.java │ │ └── test-dynamic-runtime/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── shadow/ │ │ └── test/ │ │ └── dynamic/ │ │ └── runtime/ │ │ └── container/ │ │ ├── PluginDefaultProxyActivity.java │ │ ├── PluginSingleInstance1ProxyActivity.java │ │ └── PluginSingleTask1ProxyActivity.java │ ├── gradle-plugin-agp-compat-test/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle │ │ ├── stub-project/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── PluginManifestIncludeTest.java │ │ │ └── res/ │ │ │ └── values/ │ │ │ └── themes.xml │ │ ├── test_JDK11.sh │ │ ├── test_JDK17.sh │ │ ├── test_clean.sh │ │ └── test_prepare.sh │ ├── lib/ │ │ ├── constant/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── test/ │ │ │ └── lib/ │ │ │ └── constant/ │ │ │ └── Constant.java │ │ ├── custom-view/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── test/ │ │ │ └── lib/ │ │ │ └── custom_view/ │ │ │ └── TestViewConstructorCacheView.java │ │ ├── plugin-use-host-code-lib/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── test/ │ │ │ └── lib/ │ │ │ └── plugin_use_host_code_lib/ │ │ │ ├── interfaces/ │ │ │ │ ├── HostTestInterface.java │ │ │ │ └── subpackage/ │ │ │ │ └── Foo.java │ │ │ └── other/ │ │ │ └── HostOtherInterface.java │ │ └── test-manager/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── shadow/ │ │ └── test/ │ │ └── lib/ │ │ └── test_manager/ │ │ ├── SimpleIdlingResource.java │ │ └── TestManager.java │ ├── none-dynamic/ │ │ └── host/ │ │ └── test-none-dynamic-host/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── test/ │ │ │ └── none_dynamic/ │ │ │ └── host/ │ │ │ ├── DefaultContainerActivity.java │ │ │ ├── HostApplication.java │ │ │ ├── MainActivity.java │ │ │ ├── PreparePluginApkBloc.java │ │ │ ├── SLoggerFactory.java │ │ │ ├── SingleTaskContainerActivity.java │ │ │ ├── TestComponentManager.java │ │ │ ├── TestLoadingActivity.java │ │ │ └── TestPluginLoader.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ └── qq_nearby_now_loading.xml │ │ └── values/ │ │ ├── strings.xml │ │ └── themes.xml │ └── plugin/ │ ├── androidx-cases/ │ │ └── test-plugin-androidx-cases/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── shadow/ │ │ └── test/ │ │ └── plugin/ │ │ └── androidx_cases/ │ │ └── lib/ │ │ ├── AppCompatTestActivity.java │ │ ├── AppComponentFactoryTestActivity.java │ │ ├── LiveDataWithActivityTestActivity.java │ │ ├── TestComponentFactory.java │ │ └── UiUtil.java │ ├── general-cases/ │ │ └── test-plugin-general-cases/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ ├── androidTest/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── shadow/ │ │ │ └── test/ │ │ │ └── plugin/ │ │ │ └── general_cases/ │ │ │ └── app/ │ │ │ ├── ActivityLifecycleCallbacksTest.java │ │ │ ├── CustomAndroidJUnitRunner.java │ │ │ ├── NormalAppTest.java │ │ │ └── PackageManagerTest.java │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ ├── com/ │ │ │ │ └── tencent/ │ │ │ │ └── shadow/ │ │ │ │ └── test/ │ │ │ │ └── plugin/ │ │ │ │ └── general_cases/ │ │ │ │ └── lib/ │ │ │ │ ├── gallery/ │ │ │ │ │ ├── TestApplication.java │ │ │ │ │ └── util/ │ │ │ │ │ ├── ToastUtil.java │ │ │ │ │ └── UiUtil.java │ │ │ │ └── usecases/ │ │ │ │ ├── SimpleIdlingResource.java │ │ │ │ ├── WithIdlingResourceActivity.java │ │ │ │ ├── activity/ │ │ │ │ │ ├── ApplicationContextActivity.java │ │ │ │ │ ├── PrintActivityResultActivity.java │ │ │ │ │ ├── TestActivityOnCreate.java │ │ │ │ │ ├── TestActivityOrientation.java │ │ │ │ │ ├── TestActivityReCreate.java │ │ │ │ │ ├── TestActivityReCreateBySystem.java │ │ │ │ │ ├── TestActivityWindowSoftMode.java │ │ │ │ │ ├── TestCallingActivity.java │ │ │ │ │ ├── TestListActivity.java │ │ │ │ │ └── WindowSoftModeJumpActivity.java │ │ │ │ ├── application/ │ │ │ │ │ ├── ActivityLifecycleCallbacksTestActivity.java │ │ │ │ │ ├── TestApplicationActivity.java │ │ │ │ │ └── TestGetApplicationInfoActivity.java │ │ │ │ ├── classloader/ │ │ │ │ │ └── TestBootClassloaderActivity.java │ │ │ │ ├── context/ │ │ │ │ │ ├── ActivityContextSubDirTestActivity.java │ │ │ │ │ ├── ApplicationContextSubDirTestActivity.java │ │ │ │ │ ├── ContextGetPackageCodePathTestActivity.java │ │ │ │ │ ├── RegisterNullReceiverActivity.java │ │ │ │ │ ├── ServiceContextSubDirTestActivity.java │ │ │ │ │ ├── SubDirContextThemeWrapperTestActivity.java │ │ │ │ │ ├── TestLayoutInflaterActivity.java │ │ │ │ │ └── TestThemeActivity.java │ │ │ │ ├── dialog/ │ │ │ │ │ ├── TestAlertDialogActivity.java │ │ │ │ │ ├── TestDialog.java │ │ │ │ │ └── TestDialogActivity.java │ │ │ │ ├── fragment/ │ │ │ │ │ ├── BaseFragment.java │ │ │ │ │ ├── FragmentStartedActivity.java │ │ │ │ │ ├── OnlyOverrideActivityMethodBaseFragment.java │ │ │ │ │ ├── OnlyOverrideContextMethodBaseFragment.java │ │ │ │ │ ├── ProgrammaticallyAddFragmentActivity.java │ │ │ │ │ ├── SubTestBaseFragment.java │ │ │ │ │ ├── TargetFragmentTestActivity.java │ │ │ │ │ ├── TestBaseFragment.java │ │ │ │ │ ├── TestDialogFragment.java │ │ │ │ │ ├── TestFragment.java │ │ │ │ │ ├── TestFragmentCommonLogic.java │ │ │ │ │ ├── TestNormalFragment.java │ │ │ │ │ ├── TestSubFragment.java │ │ │ │ │ ├── TestSubOnlyOverrideOnAttachActivityFragment.java │ │ │ │ │ ├── TestSubOnlyOverrideOnAttachContextFragment.java │ │ │ │ │ └── XmlAddFragmentActivity.java │ │ │ │ ├── instrumentation/ │ │ │ │ │ ├── MyInstrumentation.java │ │ │ │ │ └── TestInstrumentationActivity.java │ │ │ │ ├── interfaces/ │ │ │ │ │ └── TestHostInterfaceActivity.java │ │ │ │ ├── packagemanager/ │ │ │ │ │ └── TestPackageManagerActivity.java │ │ │ │ ├── provider/ │ │ │ │ │ ├── TestDBContentProviderActivity.java │ │ │ │ │ ├── TestDBHelper.java │ │ │ │ │ ├── TestFileProviderActivity.java │ │ │ │ │ ├── TestProvider.java │ │ │ │ │ └── TestProviderInfo.java │ │ │ │ ├── receiver/ │ │ │ │ │ ├── BroadCastHelper.java │ │ │ │ │ ├── MyReceiver.java │ │ │ │ │ ├── ReceiverWithoutAction.java │ │ │ │ │ └── TestReceiverActivity.java │ │ │ │ ├── service/ │ │ │ │ │ ├── TestService.java │ │ │ │ │ └── TestStartServiceActivity.java │ │ │ │ └── view/ │ │ │ │ ├── TestNullRefInXmlActivity.java │ │ │ │ ├── TestViewConstructorCache.java │ │ │ │ └── TestViewIdActivity.java │ │ │ └── org/ │ │ │ └── xmlpull/ │ │ │ └── v1/ │ │ │ └── XmlPullParser.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_list.xml │ │ │ ├── activity_test_file_provider.xml │ │ │ ├── activity_test_re_create_by_system.xml │ │ │ ├── layout_activity_lifecycle.xml │ │ │ ├── layout_common.xml │ │ │ ├── layout_fragment_activity.xml │ │ │ ├── layout_fragment_test.xml │ │ │ ├── layout_list_item.xml │ │ │ ├── layout_orientation.xml │ │ │ ├── layout_packagemanager.xml │ │ │ ├── layout_provider_db.xml │ │ │ ├── layout_receiver.xml │ │ │ ├── layout_recreate.xml │ │ │ ├── layout_result.xml │ │ │ ├── layout_service.xml │ │ │ ├── layout_softmode.xml │ │ │ ├── layout_test_null_ref.xml │ │ │ ├── layout_test_view_cons_cache.xml │ │ │ ├── layout_test_view_id.xml │ │ │ ├── layout_xml_add_base_fragment_activity.xml │ │ │ ├── layout_xml_add_normal_fragment_activity.xml │ │ │ └── layout_xml_add_sub_fragment_activity.xml │ │ ├── values/ │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ └── xml/ │ │ └── filepaths.xml │ └── particular-cases/ │ └── plugin-service-for-host/ │ ├── .gitignore │ ├── build.gradle │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── com/ │ └── tencent/ │ └── shadow/ │ └── test/ │ └── plugin/ │ └── particular_cases/ │ └── plugin_service_for_host/ │ ├── SystemExitIntentService.java │ └── SystemExitService.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .commitlintrc.yml ================================================ extends: - '@commitlint/config-conventional' defaultIgnores: false rules: #允许中文 subject-case: [ 0 ] footer-max-line-length: [ 0 ] body-max-line-length: [ 0 ] helpUrl: https://www.conventionalcommits.org/zh-hans/v1.0.0/ ================================================ FILE: .github/actions/post-build/action.yml ================================================ name: post-build description: '清理构建环境' runs: using: "composite" steps: - name: stop gradle deamon for actions/cache shell: bash run: ./gradlew --stop ================================================ FILE: .github/actions/pre-build/action.yml ================================================ name: pre-build description: '准备构建环境' runs: using: "composite" steps: - name: revert gradle distributionUrl in every gradle-wrapper.properties shell: bash run: git grep -l 'mirrors.tencent.com/gradle' -- gradle-wrapper.properties '**/gradle-wrapper.properties' | xargs sed -i 's/mirrors.tencent.com\/gradle/services.gradle.org\/distributions/g' - name: Inject slug/short variables uses: rlespinasse/github-slug-action@v3.x - name: revert gradle wrapper mirror setting shell: bash run: echo "DISABLE_TENCENT_MAVEN_MIRROR=true" >> $GITHUB_ENV - name: Add cmdline-tools to PATH shell: bash run: echo "$ANDROID_HOME/cmdline-tools/latest/bin" >> "$GITHUB_PATH" - name: Install specific Android SDK platforms shell: bash run: sdkmanager "platforms;android-33" - uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '17' cache: 'gradle' ================================================ FILE: .github/workflows/check-build-test.yml ================================================ name: Check & Build & Test on: workflow_call: push: branches: - master pull_request: branches: [ master ] jobs: check-commit-message: name: 提交日志格式化检查 runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: wagoid/commitlint-github-action@v6 with: configFile: ./.commitlintrc.yml check-code-format: name: 代码格式化检查 runs-on: ubuntu-22.04 env: AndroidStudioVersion: 2021.1.1.20 steps: - name: checkout uses: actions/checkout@v4 - name: Cache android-studio id: cache-android-studio uses: actions/cache@v4 with: path: android-studio key: ${{ runner.os }}-android-studio--${{ env.AndroidStudioVersion }} - name: download android-studio if: steps.cache-android-studio.outputs.cache-hit != 'true' run: | wget "https://redirector.gvt1.com/edgedl/android/studio/ide-zips/$AndroidStudioVersion/android-studio-$AndroidStudioVersion-linux.tar.gz" tar -xvzf "android-studio-$AndroidStudioVersion-linux.tar.gz" rm -rf "android-studio-$AndroidStudioVersion-linux.tar.gz" - name: use android-studio format all files run: ./android-studio/bin/format.sh -s .idea/codeStyles/Project.xml -r -m \*.java,\*.kt,\*.xml . - name: show diff for files not formated run: | if ! git diff --quiet; then git diff --exit-code fi build-sdk: needs: [ check-commit-message, check-code-format ] name: 构建SDK runs-on: ubuntu-22.04 steps: - name: checkout uses: actions/checkout@v4 - name: pre-build uses: ./.github/actions/pre-build - name: buildSdk run: ./gradlew buildSdk -S - name: post-build uses: ./.github/actions/post-build build-sample-maven: needs: [ check-commit-message, check-code-format ] name: 构建maven依赖SDK的sample runs-on: ubuntu-22.04 steps: - name: checkout uses: actions/checkout@v4 - name: pre-build uses: ./.github/actions/pre-build - uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '11' cache: 'gradle' - name: build sample/maven/host-project working-directory: projects/sample/maven/host-project run: ./gradlew assemble - name: build sample/maven/manager-project working-directory: projects/sample/maven/manager-project run: ./gradlew assemble - name: build sample/maven/plugin-project working-directory: projects/sample/maven/plugin-project run: ./gradlew assemble - name: post-build uses: ./.github/actions/post-build build-all: needs: build-sdk name: 构建所有源码 runs-on: ubuntu-22.04 steps: - name: checkout uses: actions/checkout@v4 - name: pre-build uses: ./.github/actions/pre-build - name: buildSdk run: ./gradlew build - name: post-build uses: ./.github/actions/post-build test-agp-compatibility: needs: build-sdk name: AGP兼容性自动化测试 runs-on: ubuntu-22.04 steps: - name: checkout uses: actions/checkout@v4 - name: pre-build uses: ./.github/actions/pre-build - uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '17' cache: 'gradle' - name: JDK17环境下AGP测试 working-directory: projects/test/gradle-plugin-agp-compat-test run: ./test_JDK17.sh - uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '11' cache: 'gradle' - name: JDK11环境下AGP测试 working-directory: projects/test/gradle-plugin-agp-compat-test run: ./test_JDK11.sh - name: post-build uses: ./.github/actions/post-build test-sdk-jvm: needs: build-sdk name: 自动化测试-JVM部分 runs-on: ubuntu-22.04 steps: - name: checkout uses: actions/checkout@v4 - name: pre-build uses: ./.github/actions/pre-build - name: jvmTestSdk run: ./gradlew jvmTestSdk -S - name: post-build uses: ./.github/actions/post-build test-sdk-avd: needs: build-sdk name: 自动化测试-AVD部分 runs-on: ubuntu-22.04 strategy: matrix: include: - api-level: 16 #16是最低支持的API arch: x86 target: default - api-level: 28 #28是项目长期使用的测试API arch: x86 target: default - api-level: 34 arch: x86_64 target: google_apis steps: - name: checkout uses: actions/checkout@v4 - name: pre-build uses: ./.github/actions/pre-build - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - name: run AVD tests uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} target: ${{ matrix.target }} arch: ${{ matrix.arch }} profile: pixel_xl force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true script: ./gradlew androidTestSdk - name: post-build uses: ./.github/actions/post-build test-sdk-avd-target34: needs: build-sdk name: 自动化测试-target 34的冒烟测试 runs-on: ubuntu-22.04 steps: - name: checkout uses: actions/checkout@v4 - name: pre-build uses: ./.github/actions/pre-build - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - name: run AVD tests uses: reactivecircus/android-emulator-runner@v2 with: api-level: 34 target: google_apis arch: x86_64 profile: pixel_xl force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true script: ./gradlew :test-dynamic-host:connectedTarget34DebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.tencent.shadow.test.cases.plugin_main.ApplicationContextSubDirTest#testGetDatabasePath - name: post-build uses: ./.github/actions/post-build ================================================ FILE: .github/workflows/release.yml ================================================ name: Publish release on: release: types: - published #https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release jobs: publish: needs: check-build-test runs-on: ubuntu-22.04 env: DISABLE_TENCENT_MAVEN_MIRROR: true PUBLISH_RELEASE: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: checkout uses: actions/checkout@v4 - name: pre-build uses: ./.github/actions/pre-build - name: publish run: ./gradlew publish - name: post-build uses: ./.github/actions/post-build ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties !.idea .idea/* !.idea/codeStyles .DS_Store /build /captures .externalNativeBuild .gradletasknamecache ================================================ FILE: .idea/codeStyles/Project.xml ================================================ ================================================ FILE: .idea/codeStyles/codeStyleConfig.xml ================================================ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing 我们非常欢迎您向Tencent Shadow提交Issue或Pull Request。 # Issue 在Tencent Shadow开源的初期,我们会密切关注所有Issue反馈。晚些时候再根据反馈的情况制定Issue模板。 反馈问题时,请Fork Shadow的代码库到自己的名下。新建分支,添加可以复现问题的最小改动,提交后push到Github上。然后在Issue单中附上你的代码库地址和分支名即可。 ```sh git clone https://github.com/Tencent/Shadow.git //大概之前你已经这样clone过Shadow的代码库了 cd shadow //切换到你clone的shadow目录 git remote add your_name https://github.com//Shadow.git //把你fork的版本库添加成一个远端 git fetch --all //更新所有远端的代码 git checkout -b new_branch_name origin/dev // 基于Shadow代码库的dev分支新建一个分支 //加上你复现问题的修改 git commit git push -u your_name //推送new_branch_name分支到你fork的版本库 ``` 然后你的分支地址应该类似:`https://github.com//Shadow/tree/new_branch_name` 其他人可以用这样的命令获取到你的分支,看到你的提交做了哪些改动,运行并Debug。 ```sh cd shadow //切换到shadow目录 git fetch https://github.com//Shadow.git new_branch_name git checkout -b new_branch_name FETCH_HEAD ``` # Pull Request 由于PR会修改代码,因此即便是在开源初期,我们也会对PR谨慎处理。 请注意以下问题: 1. 不要提交无意义改动。 1. 除非是提交复现问题的测试用例,请确保`gradlew testSdk`构建成功(需要连接Android设备) 1. 测试机需要至少有API 28,API 19两种机器,以保证ART和Dalvik虚拟机都能正常工作。 1. 尽量原子化的提交,配有较为清晰的提交信息。 我们会根据大家的PR再调整PR的要求的。 # 开发指引 ## Debug编译期代码(Gradle插件、Transform等) Shadow的`coding`和`core.gradle-plugin`、`core.manifest-parser`、`core.transform`,`core.transform-kit` 等模块都是在插件工程的编译期执行的。如果需要Debug它们,需要额外的配置。 1. 添加`Remote JVM Debug` Configuration。在Android Studio的`Run`菜单中找到`Edit Configuration`。 点"+"(Add New Configuration),选择`Remote JVM Debug`,配置参数一般采用默认值不用修改。 `Name`可以任意修改成方便识别的名字,稍后在工具栏执行时选择。 复制`Command line arguments for remote JVM`。 一般的默认值是:`-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005`。 将这行复制到`gradle.properties`中的`org.gradle.jvmargs=-Xmx4096m`后面作为更多的参数,注意加上空格。 然后将其中的`suspend=n` 改为`suspend=y`,表示让JVM启动Gradle时等待Debugger连接,再继续执行。 2. 终止正在运行的Gradle Daemon。在命令行执行`./gradlew --stop`,终止掉没有采用新参数的JVM进程。 3. Debug编译期代码。通过`./gradlew`或者Android Studio的Gradle sync,或运行`sample-host`等任务, 都会在一启动时因为前面的`suspend=y` 卡住。这时再选择刚刚添加的`Remote JVM Debug` Configuration, 点击`Debug`执行按钮,即可连接上Gradle JVM。如果在`ShadowPlugin` 或者某个Transform代码中设置了断点, 就会正常在断点处暂停。 注意选择`Remote JVM Debug` Configuration的位置同选择`sample-host` 等模块在同一个菜单中。 并且Android Studio可以同时执行多个Configuration,先运行`sample-host`, 再Debug `Remote JVM Debug` Configuration是没有问题的。 4. 在其他不是Shadow源码工程中,也可以同样设置`gradle.properties`参数,在其中执行Gradle任务。 然后切换到Shadow源码工程中执行Debug `Remote JVM Debug` Configuration, 也可以Debug Shadow在其他工程中的编译期代码执行情况。 5. 还原。回退对`gradle.properties`的修改,然后执行`./gradlew --stop`。以上所有改动的作用即可恢复。 ## 虚拟机 启动虚拟机 ```shell ~/Library/Android/sdk/emulator/emulator @Pixel_XL_API_28 -noaudio -no-boot-anim -wipe-data -no-snapstorage ``` 其中`Pixel_XL_API_28`来自: ```shell ~/Library/Android/sdk/emulator/emulator -list-avds ``` `-noaudio`可以避免耳机切换到通话模式。 `-wipe-data -no-snapstorage`使得虚拟机完全恢复到新建状态。 `-no-boot-anim`稍微加快点启动。 ## 启动指定自动化测试用例 随着Android Studio更新,在#1263 解决之前,只能通过命令行执行自动化测试。 如下命令,传入类名#方法可以指定单个方法。只传入类名可以测试整个类。 如果测试方法是抽象类中的,需要传入一个具体的实现类。 ```shell ./gradlew :test-dynamic-host:connectedTarget28DebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.tencent.shadow.test.cases.plugin_main.ApplicationContextSubDirTest#testGetDatabasePath ``` ## 清理工作区 由于复合构建的存在,Gradle clean任务不能总是很好的完成清理工作区的目的。 ```shell git clean -fdx -e .idea -e local.properties ``` ================================================ FILE: LICENSE.txt ================================================ Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of THL A29 Limited nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: PRIVACY.md ================================================ # Tencent Shadow SDK个人信息保护规则 _生效日期:2022年4月6日_ ## 引言 Tencent Shadow SDK (以下简称“SDK产品”)由深圳市腾讯计算机系统有限公司(以下简称“我们”)开发, 公司注册地为深圳市南山区粤海街道麻岭社区科技中一路腾讯大厦35层。 **我们在此特别声明:** **1. 本SDK产品功能的实现将不需要收集、获取、传输、分享或者使用终端用户的任何个人信息。** **2. 我们不会因为开发者适配、集成和装载本SDK产品而向其提供、传输或共享任何的个人信息。** **3. 如果开发者因提供其产品或服务而需要处理终端用户的个人信息, 由开发者独自承担相应的法律责任。** **4. 请终端用户注意, 在开发者将本SDK产品适配、集成或装载到开发者产品或服务前, 我们已经要求相关开发者仔细阅读我们在官网公示的相关服务协议、本规则及开发者合规指南(或具有同样性质的相关法律文件), 并已经要求开发者依据开发者的产品收集使用个人信息的情况进行合规自查。** **5. 如果我们更新、改进或修改了本SDK产品, 并因此导致我们需要处理终端用户的个人信息的, 我们将会依据适用法律的要求对本规则进行修订, 并将修订后的内容及时告知开发者和终端用户, 我们将要求开发者适时更新其隐私政策,并以弹框形式通知终端用户并且获得其同意。** **6. 对于本规则的任何内容存在疑问的, 可以通过如下的方式与我们取得联系:** (1) 通过 https://kf.qq.com/ 与我们联系进行在线咨询; (2) 发送邮件至 Dataprivacy@tencent.com ; (3) 邮寄信件至:中国广东省深圳市南山区海天二路33号腾讯滨海大厦 数据隐私保护部(收)邮编:518054。 ================================================ FILE: README.md ================================================ # Shadow ![Android CI](https://github.com/Tencent/Shadow/workflows/Android%20CI/badge.svg?event=push) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) ## 介绍 Shadow是一个腾讯自主研发的Android插件框架,经过线上亿级用户量检验。 Shadow不仅开源分享了插件技术的关键代码,还完整的分享了上线部署所需要的所有设计。 与市面上其他插件框架相比,Shadow主要具有以下特点: * **复用独立安装App的源码**:插件App的源码原本就是可以正常安装运行的。 * **零反射无Hack实现插件技术**:从理论上就已经确定无需对任何系统做兼容开发,更无任何隐藏API调用,和Google限制非公开SDK接口访问的策略完全不冲突。 * **全动态插件框架**:一次性实现完美的插件框架很难,但Shadow将这些实现全部动态化起来,使插件框架的代码成为了插件的一部分。插件的迭代不再受宿主打包了旧版本插件框架所限制。 * **宿主增量极小**:得益于全动态实现,真正合入宿主程序的代码量极小(15KB,160方法数左右)。 * **Kotlin实现**:core.loader,core.transform核心代码完全用Kotlin实现,代码简洁易维护。 ### 支持特性 * 四大组件 * Fragment(代码添加和Xml添加) * DataBinding(无需特别支持,但已验证可正常工作) * 跨进程使用插件Service * 自定义Theme * 插件访问宿主类 * So加载 * 分段加载插件(多Apk分别加载或多Apk以此依赖加载) * 一个Activity中加载多个Apk中的View * 等等…… ## 编译与开发环境 ### 环境准备 建议直接用最新的稳定版本Android Studio打开工程。目前项目已适配`Android Studio Arctic Fox | 2020.3.1`, 低版本的Android Studio可能因为Gradle版本过高而无法正常打开项目。 然后在IDE中选择`sample-app`或`sample-host`模块直接运行,分别体验同一份代码在正常安装情况下和插件情况下的运行情况。 ![选择sample-host直接运行](pics/run-sample-host-in-ide.png) Shadow的所有代码都位于`projects`目录下的3个目录,分别是: * `sdk`包含SDK的所有代码 * `test`包含SDK的自动化测试代码 * `sample`包含演示代码 其中`sample`应该是大家体验Shadow的最佳环境。 详见`sample`目录中的[README](projects/sample/README.md)介绍。 ### 兼容性 Shadow项目有较为完善的自动化测试,因此最新代码对外部环境的版本兼容性可以参考自动化测试的配置。 * [pr-check.yml](.github/workflows/pr-check.yml) 虚拟机自动化测试,包含Android测试机版本和编译环境JDK等版本。 * [pr-check-gradle-plugin.yml](.github/workflows/pr-check-gradle-plugin.yml) AGP兼容性测试。 其中指向的[test_JDK17.sh](projects/test/gradle-plugin-agp-compat-test/test_JDK17.sh)和 [test_JDK11.sh](projects/test/gradle-plugin-agp-compat-test/test_JDK11.sh)中定义了被测试的AGP版本。 ## 自己写的测试代码出错? 以我们多年的插件环境下业务开发经验,插件框架是不可能一步到位实现完美的。 因此,我们相信大部分业务在接入时都是需要一定的二次开发工作。 Shadow现有的代码满足的是我们自己的业务现在的需求。得益于全动态的设计, 插件框架和插件本身都是动态发布的,插件包里既有插件代码也有插件框架代码, 所以可以根据新版本插件的需要同时开发插件框架。 例如,ShadowActivity没有实现全所有Activity方法,你写的测试代码可能用到了, 就会出现Method Not Found错误,只需要在ShadowActivity中实现对应方法就可以了。 大部分方法的实现都只是需要简单的转调就能工作正常。 如果遇到不会实现的功能,可以提Issue。最好附上测试代码。 ## 后续开发 * 原理与设计说明文档 * 多插件支持的演示工程 * 自动化测试用例补充 * 开源包含下载能力的manager实现 ## 贡献代码 详见[CONTRIBUTING.md](CONTRIBUTING.md) ## 许可协议 Tencent Shadow采用`BSD 3-Clause License`,详见[LICENSE](LICENSE.txt)。 ## 个人信息保护规则声明 详见[PRIVACY.md](PRIVACY.md) ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. //buildscript不能从其他gradle文件中apply,所以这段buildscript脚本存在于多个子构建中。 //请更新buildscript时同步更新。 buildscript { loadVersions: {// 读取versions.properties到ext中,供项目中直接用变量引用版本号 def versions_properties_path = 'buildScripts/gradle/versions.properties' def versions = new Properties() versions.load(file(versions_properties_path).newReader()) versions.forEach { key, stringValue -> def value = stringValue?.isInteger() ? stringValue as Integer : stringValue ext.set(key, value) } } repositories { if (!System.getenv().containsKey("DISABLE_TENCENT_MAVEN_MIRROR")) { maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' } } else { google() mavenCentral() } } dependencies { classpath "com.android.tools.build:gradle:$build_gradle_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.tencent.shadow.coding:aar-to-jar-plugin' classpath 'com.tencent.shadow.coding:common-jar-settings' } } apply from: 'buildScripts/gradle/common.gradle' apply from: "buildScripts/gradle/maven.gradle" apply from: "buildScripts/gradle/fix_issue_1263.gradle" ================================================ FILE: buildScripts/gradle/common.gradle ================================================ def gitShortRev() { def gitCommit = "" def proc = "git rev-parse --short HEAD".execute() proc.in.eachLine { line -> gitCommit = line } proc.err.eachLine { line -> println line } proc.waitFor() return gitCommit } allprojects { def versionName, versionSuffix if ("${System.env.CI}".equalsIgnoreCase("true")) { versionName = System.getenv("GITHUB_REF_SLUG") } else { versionName = project.VERSION_NAME } if ("${System.env.PUBLISH_RELEASE}".equalsIgnoreCase("true")) { versionSuffix = "" } else if ("${System.env.CI}".equalsIgnoreCase("true")) { versionSuffix = "-${System.env.GITHUB_SHA_SHORT}-SNAPSHOT" } else { versionSuffix = "-${gitShortRev()}-SNAPSHOT" } ext.ARTIFACT_VERSION = versionName + versionSuffix ext.TEST_HOST_APP_APPLICATION_ID = 'com.tencent.shadow.test.hostapp' ext.SAMPLE_HOST_APP_APPLICATION_ID = 'com.tencent.shadow.sample.host' repositories { if (!System.getenv().containsKey("DISABLE_TENCENT_MAVEN_MIRROR")) { maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' } } else { google() mavenCentral() } } } ================================================ FILE: buildScripts/gradle/fix_issue_1263.gradle ================================================ /** * 这个脚本通过hook create*ApkListingFileRedirect任务, * 在它执行完成后,即生成了apk_ide_redirect_file,也应该生成了apk之后, * 补充一个复制build/intermediates/apk到build/outputs/apk的操作。 * * 采用这个修复方式是因为Shadow的打包代码设计不是很合理,难以通过少量改动, * 保证引用项目不引入任何兼容性问题。 * * 详见issue #1263 */ buildscript { dependencies { classpath files(rootProject.buildscript.configurations.classpath) } } def taskList = [ ":sample-loader:createDebugApkListingFileRedirect", ":sample-loader:createReleaseApkListingFileRedirect", ":sample-runtime:createDebugApkListingFileRedirect", ":sample-runtime:createReleaseApkListingFileRedirect", ":sample-manager:createDebugApkListingFileRedirect", ":sample-manager:createReleaseApkListingFileRedirect", ":sample-app:createPluginDebugApkListingFileRedirect", ":sample-app:createPluginReleaseApkListingFileRedirect", ":sample-base:createPluginDebugApkListingFileRedirect", ":sample-base:createPluginReleaseApkListingFileRedirect", ":test-dynamic-loader:createDebugApkListingFileRedirect", ":test-dynamic-loader:createReleaseApkListingFileRedirect", ":test-dynamic-runtime:createDebugApkListingFileRedirect", ":test-dynamic-runtime:createReleaseApkListingFileRedirect", ":test-dynamic-manager:createDebugApkListingFileRedirect", ":test-dynamic-manager:createReleaseApkListingFileRedirect", ":plugin-service-for-host:createPluginDebugApkListingFileRedirect", ":plugin-service-for-host:createPluginReleaseApkListingFileRedirect", ":test-plugin-androidx-cases:createPluginDebugApkListingFileRedirect", ":test-plugin-androidx-cases:createPluginReleaseApkListingFileRedirect", ":test-plugin-general-cases:createPluginDebugApkListingFileRedirect", ":test-plugin-general-cases:createPluginReleaseApkListingFileRedirect", ":sample-hello-apk:createDebugApkListingFileRedirect", ":sample-hello-apk:createReleaseApkListingFileRedirect", ] afterEvaluate { taskList.forEach { def t = tasks.findByPath(it) copyApkAfterTask(t) } } def copyApkAfterTask(t) { t.doLast { def redirectFile = t.getOutputs().getFiles().singleFile def listingFile = redirectFile.readLines().get(1).replaceFirst("listingFile=", "") def metadataFile = new File(redirectFile.parentFile, listingFile) def metadata = new org.json.JSONObject(metadataFile.text) def outputFile = metadata.getJSONArray("elements").getJSONObject(0).getString("outputFile") def apkFile = new File(metadataFile.parentFile, outputFile) def testRelativePath = redirectFile.relativePath(apkFile) def needCopy = !testRelativePath.matches("^(\\.\\.${File.separatorChar})+outputs${File.separatorChar}.+") if (needCopy) { def matchPath = new File("/build/intermediates").toPath().toString() def intermediatesDir = new File(apkFile.toPath().normalize().toString().find("^.+?$matchPath")) def outputsDir = new File(intermediatesDir.parentFile, "outputs") def r = copy { from intermediatesDir into outputsDir include 'apk/**' } if (r.didWork) { getLogger().info("copy apk from ${intermediatesDir.path} to ${outputsDir.path}") } } } } ================================================ FILE: buildScripts/gradle/maven.gradle ================================================ task buildSdk() { dependsOn gradle.includedBuild('core').task(':gradle-plugin:assemble') dependsOn gradle.includedBuild('core').task(':manifest-parser:assemble') dependsOn gradle.includedBuild('core').task(':common:assemble') dependsOn gradle.includedBuild('core').task(':loader:assemble') dependsOn gradle.includedBuild('core').task(':manager:assemble') dependsOn gradle.includedBuild('core').task(':runtime:assemble') dependsOn gradle.includedBuild('core').task(':activity-container:assemble') dependsOn gradle.includedBuild('core').task(':transform-kit:assemble') dependsOn gradle.includedBuild('core').task(':transform-kit:testJar') dependsOn gradle.includedBuild('core').task(':transform:assemble') dependsOn gradle.includedBuild('core').task(':load-parameters:assemble') dependsOn gradle.includedBuild('core').task(':utils:assemble') dependsOn gradle.includedBuild('dynamic').task(':dynamic-apk:assemble') dependsOn gradle.includedBuild('dynamic').task(':dynamic-host:assemble') dependsOn gradle.includedBuild('dynamic').task(':dynamic-loader:assemble') dependsOn gradle.includedBuild('dynamic').task(':dynamic-loader-impl:assemble') dependsOn gradle.includedBuild('dynamic').task(':dynamic-manager:assemble') dependsOn gradle.includedBuild('dynamic').task(':dynamic-host-multi-loader-ext:assemble') dependsOn gradle.includedBuild('dynamic').task(':dynamic-manager-multi-loader-ext:assemble') } task jvmTestSdk() { dependsOn gradle.includedBuild('coding').task(':test') dependsOn gradle.includedBuild('core').task(':test') dependsOn gradle.includedBuild('dynamic').task(':test') dependsOn ':common-jar-settings-test:testJavacBootclasspath' } task androidTestSdk() { dependsOn gradle.includedBuild('core').task(':manager-db-test:connectedDebugAndroidTest') dependsOn ':test-dynamic-host:connectedTarget28DebugAndroidTest' } task testSdk() { dependsOn jvmTestSdk dependsOn androidTestSdk } apply plugin: 'maven-publish' static def getDependencyNode(scope, groupId, artifactId, version) { Node node = new Node(null, 'dependency') node.appendNode('groupId', groupId) node.appendNode('artifactId', artifactId) node.appendNode('version', version) node.appendNode('scope', scope) return node } def gitShortRev() { def gitCommit = "" def proc = "git rev-parse --short HEAD".execute() proc.in.eachLine { line -> gitCommit = line } proc.err.eachLine { line -> println line } proc.waitFor() return gitCommit } def setScm(scm) { scm.appendNode('connection', "https://github.com/${System.getenv("GITHUB_ACTOR")}/Shadow.git") def commit if ("${System.env.CI}".equalsIgnoreCase("true")) { commit = System.getenv("GITHUB_SHA") } else { commit = gitShortRev() } scm.appendNode('url', "https://github.com/${System.getenv("GITHUB_ACTOR")}/Shadow/commit/$commit") } def setGeneratePomFileAndDepends(publicationName) { model { tasks."generatePomFileFor${publicationName.capitalize()}Publication" { destination = file("$buildDir/pom/$publicationName-pom.xml") dependsOn(buildSdk) } } } def sourceJar(String name, String path) { return tasks.create("source${name.capitalize()}Jar", Jar) { group = "publishing" description = "package ${name} source to jar" from "$path/src/main/java" from "$path/src/main/kotlin" destinationDir = file("$path/build/libs/") classifier = 'sources' } } def publicationVersion = project.ARTIFACT_VERSION def coreGroupId = 'com.tencent.shadow.core' def corePath = 'projects/sdk/core' def dynamicGroupId = 'com.tencent.shadow.dynamic' def dynamicPath = 'projects/sdk/dynamic' task getPublicationVersion() { doLast { println "publicationVersion:$publicationVersion" } } publishing { publications { gradlePlugin(MavenPublication) { groupId coreGroupId artifactId 'gradle-plugin' version publicationVersion artifact("$corePath/gradle-plugin/build/libs/gradle-plugin.jar") artifact sourceJar("gradlePlugin", "$corePath/gradle-plugin") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', 'org.jetbrains.kotlin', 'kotlin-stdlib-jdk7', kotlin_version)) dependencies.append(getDependencyNode('compile', 'com.googlecode.json-simple', 'json-simple', json_simple_version)) dependencies.append(getDependencyNode('compile', coreGroupId, 'transform-kit', publicationVersion)) dependencies.append(getDependencyNode('compile', coreGroupId, 'transform', publicationVersion)) dependencies.append(getDependencyNode('compile', coreGroupId, 'runtime', publicationVersion)) dependencies.append(getDependencyNode('compile', coreGroupId, 'activity-container', publicationVersion)) dependencies.append(getDependencyNode('compile', coreGroupId, 'manifest-parser', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } // Plugin Marker Artifacts // https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers pluginMarker(MavenPublication) { def pluginId = 'com.tencent.shadow.plugin' groupId pluginId artifactId pluginId + '.gradle.plugin' version publicationVersion pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', coreGroupId, 'gradle-plugin', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } manifestParser(MavenPublication) { groupId coreGroupId artifactId 'manifest-parser' version publicationVersion artifact("$corePath/manifest-parser/build/libs/manifest-parser.jar") artifact sourceJar("manifestParser", "$corePath/manifest-parser") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', 'org.jetbrains.kotlin', 'kotlin-stdlib-jdk7', kotlin_version)) dependencies.append(getDependencyNode('compile', 'com.squareup', 'javapoet', javapoet_version)) dependencies.append(getDependencyNode('compile', coreGroupId, 'runtime', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } common(MavenPublication) { groupId coreGroupId artifactId 'common' version publicationVersion artifact("$corePath/common/build/libs/common.jar") artifact sourceJar("common", "$corePath/common") pom.withXml { def root = asNode() def scm = root.appendNode('scm') setScm(scm) } } loadParameters(MavenPublication) { groupId coreGroupId artifactId 'load-parameters' version publicationVersion artifact("$corePath/load-parameters/build/libs/load-parameters.jar") artifact sourceJar("loadParameters", "$corePath/load-parameters") pom.withXml { def root = asNode() def scm = root.appendNode('scm') setScm(scm) } } coreLoader(MavenPublication) { groupId coreGroupId artifactId 'loader' version publicationVersion artifact("$corePath/loader/build/libs/loader.jar") artifact sourceJar("loader", "$corePath/loader") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', 'org.jetbrains.kotlin', 'kotlin-stdlib-jdk7', kotlin_version)) dependencies.append(getDependencyNode('compile', coreGroupId, 'runtime', publicationVersion)) dependencies.append(getDependencyNode('provided', coreGroupId, 'activity-container', publicationVersion)) dependencies.append(getDependencyNode('provided', coreGroupId, 'common', publicationVersion)) dependencies.append(getDependencyNode('compile', coreGroupId, 'load-parameters', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } coreManager(MavenPublication) { groupId coreGroupId artifactId 'manager' version publicationVersion artifact("$corePath/manager/build/libs/manager.jar") artifact sourceJar("manager", "$corePath/manager") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('provided', coreGroupId, 'common', publicationVersion)) dependencies.append(getDependencyNode('compile', coreGroupId, 'load-parameters', publicationVersion)) dependencies.append(getDependencyNode('compile', coreGroupId, 'utils', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } runtime(MavenPublication) { groupId coreGroupId artifactId 'runtime' version publicationVersion artifact("$corePath/runtime/build/libs/runtime.jar") artifact sourceJar("runtime", "$corePath/runtime") pom.withXml { def root = asNode() def scm = root.appendNode('scm') setScm(scm) } } activityContainer(MavenPublication) { groupId coreGroupId artifactId 'activity-container' version publicationVersion artifact("$corePath/activity-container/build/libs/activity-container.jar") artifact sourceJar("activity-container", "$corePath/activity-container") pom.withXml { def root = asNode() def scm = root.appendNode('scm') setScm(scm) } } transformKit(MavenPublication) { groupId coreGroupId artifactId 'transform-kit' version publicationVersion artifact("$corePath/transform-kit/build/libs/transform-kit.jar") artifact sourceJar("transformKit", "$corePath/transform-kit") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', 'org.jetbrains.kotlin', 'kotlin-stdlib-jdk7', kotlin_version)) dependencies.append(getDependencyNode('compile', 'org.javassist', 'javassist', javassist_version)) def scm = root.appendNode('scm') setScm(scm) } } transformKitTest(MavenPublication) { groupId coreGroupId artifactId 'transform-kit-test' version publicationVersion artifact("$corePath/transform-kit/build/libs/test-transform-kit.jar") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', 'junit', 'junit', junit_version)) def scm = root.appendNode('scm') setScm(scm) } } transform(MavenPublication) { groupId coreGroupId artifactId 'transform' version publicationVersion artifact("$corePath/transform/build/libs/transform.jar") artifact sourceJar("transform", "$corePath/transform") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', coreGroupId, 'transform-kit', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } dynamicApk(MavenPublication) { groupId dynamicGroupId artifactId 'apk' version publicationVersion artifact("$dynamicPath/dynamic-apk/build/libs/dynamic-apk.jar") artifact sourceJar("dynamicApk", "$dynamicPath/dynamic-apk") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', coreGroupId, 'common', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } dynamicHost(MavenPublication) { groupId dynamicGroupId artifactId 'host' version publicationVersion artifact("$dynamicPath/dynamic-host/build/libs/dynamic-host.jar") artifact sourceJar("dynamicHost", "$dynamicPath/dynamic-host") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', dynamicGroupId, 'apk', publicationVersion)) dependencies.append(getDependencyNode('compile', coreGroupId, 'common', publicationVersion)) dependencies.append(getDependencyNode('compile', coreGroupId, 'utils', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } dynamicHostMultiLoaderExt(MavenPublication) { groupId dynamicGroupId artifactId 'host-multi-loader-ext' version publicationVersion artifact("$dynamicPath/dynamic-host-multi-loader-ext/build/libs/dynamic-host-multi-loader-ext.jar") artifact sourceJar("dynamicHostMultiLoaderExt", "$dynamicPath/dynamic-host-multi-loader-ext") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', coreGroupId, 'common', publicationVersion)) dependencies.append(getDependencyNode('compile', dynamicGroupId, 'host', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } dynamicLoader(MavenPublication) { groupId dynamicGroupId artifactId 'loader' version publicationVersion artifact("$dynamicPath/dynamic-loader/build/libs/dynamic-loader.jar") artifact sourceJar("dynamicLoader", "$dynamicPath/dynamic-loader") pom.withXml { def root = asNode() def scm = root.appendNode('scm') setScm(scm) } } dynamicLoaderImpl(MavenPublication) { groupId dynamicGroupId artifactId 'loader-impl' version publicationVersion artifact("$dynamicPath/dynamic-loader-impl/build/libs/dynamic-loader-impl.jar") artifact sourceJar("dynamicLoaderImpl", "$dynamicPath/dynamic-loader-impl") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', 'org.jetbrains.kotlin', 'kotlin-stdlib-jdk7', kotlin_version)) dependencies.append(getDependencyNode('compile', coreGroupId, 'loader', publicationVersion)) dependencies.append(getDependencyNode('provided', coreGroupId, 'activity-container', publicationVersion)) dependencies.append(getDependencyNode('provided', coreGroupId, 'common', publicationVersion)) dependencies.append(getDependencyNode('provided', dynamicGroupId, 'host', publicationVersion)) dependencies.append(getDependencyNode('compile', dynamicGroupId, 'loader', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } dynamicManager(MavenPublication) { groupId dynamicGroupId artifactId 'manager' version publicationVersion artifact("$dynamicPath/dynamic-manager/build/libs/dynamic-manager.jar") artifact sourceJar("dynamicManager", "$dynamicPath/dynamic-manager") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', coreGroupId, 'manager', publicationVersion)) dependencies.append(getDependencyNode('compile', dynamicGroupId, 'loader', publicationVersion)) dependencies.append(getDependencyNode('provided', coreGroupId, 'common', publicationVersion)) dependencies.append(getDependencyNode('provided', dynamicGroupId, 'host', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } dynamicManagerMultiLoaderExt(MavenPublication) { groupId dynamicGroupId artifactId 'manager-multi-loader-ext' version publicationVersion artifact("$dynamicPath/dynamic-manager-multi-loader-ext/build/libs/dynamic-manager-multi-loader-ext.jar") artifact sourceJar("dynamicManagerMultiLoaderExt", "$dynamicPath/dynamic-manager-multi-loader-ext") pom.withXml { def root = asNode() def dependencies = root.appendNode('dependencies') dependencies.append(getDependencyNode('compile', coreGroupId, 'manager', publicationVersion)) dependencies.append(getDependencyNode('compile', dynamicGroupId, 'loader', publicationVersion)) dependencies.append(getDependencyNode('compile', dynamicGroupId, 'manager', publicationVersion)) dependencies.append(getDependencyNode('provided', coreGroupId, 'common', publicationVersion)) dependencies.append(getDependencyNode('provided', dynamicGroupId, 'host-multi-loader-ext', publicationVersion)) def scm = root.appendNode('scm') setScm(scm) } } coreUtils(MavenPublication) { groupId coreGroupId artifactId 'utils' version publicationVersion artifact("$corePath/utils/build/libs/utils.jar") artifact sourceJar("utils", "$corePath/utils") pom.withXml { def root = asNode() def scm = root.appendNode('scm') setScm(scm) } } } repositories { def useLocalCredential = false Properties properties = new Properties() def propertiesFile = project.rootProject.file('local.properties') if (propertiesFile.exists()) { properties.load(propertiesFile.newDataInputStream()) if ("${properties.getProperty('gpr.local')}".equalsIgnoreCase('true')) { def user = properties.getProperty('gpr.user') def key = properties.getProperty('gpr.key') maven { name = "GitHubPackages" credentials { username = user password = key } url "https://maven.pkg.github.com/${user}/shadow" } useLocalCredential = true } } if (!useLocalCredential && "${System.env.CI}".equalsIgnoreCase("true")) { maven { name = "GitHubPackages" credentials { username = System.getenv("GITHUB_ACTOR") password = System.getenv("GITHUB_TOKEN") } url "https://maven.pkg.github.com/" + "${System.env.GITHUB_REPOSITORY}".toLowerCase() } } else { mavenLocal() } } } setGeneratePomFileAndDepends('gradlePlugin') setGeneratePomFileAndDepends('manifestParser') setGeneratePomFileAndDepends('common') setGeneratePomFileAndDepends('loadParameters') setGeneratePomFileAndDepends('coreLoader') setGeneratePomFileAndDepends('coreManager') setGeneratePomFileAndDepends('coreUtils') setGeneratePomFileAndDepends('runtime') setGeneratePomFileAndDepends('activityContainer') setGeneratePomFileAndDepends('transformKit') setGeneratePomFileAndDepends('transformKitTest') setGeneratePomFileAndDepends('transform') setGeneratePomFileAndDepends('dynamicApk') setGeneratePomFileAndDepends('dynamicHost') setGeneratePomFileAndDepends('dynamicHostMultiLoaderExt') setGeneratePomFileAndDepends('dynamicLoader') setGeneratePomFileAndDepends('dynamicLoaderImpl') setGeneratePomFileAndDepends('dynamicManager') setGeneratePomFileAndDepends('dynamicManagerMultiLoaderExt') ================================================ FILE: buildScripts/gradle/versions.properties ================================================ COMPILE_SDK_VERSION=33 MIN_SDK_VERSION=14 TARGET_SDK_VERSION=28 VERSION_CODE=1 VERSION_NAME=local android_build_tools_version=30.0.3 android_support_annotations_version=28.0.0 android_support_version=27.1.1 androidx_activity_version=1.4.0 androidx_appcompat_version=1.4.1 androidx_test_junit_version=1.1.2 androidx_test_version=1.3.0 build_gradle_version=7.4.2 commons_io_android_version=2.5 commons_io_jvm_version=2.9.0 espresso_version=3.3.0 javapoet_version=1.11.1 javassist_version=3.28.0-GA json_simple_version=1.1 junit_version=4.12 kotlin_version=1.5.31 slf4j_version=1.7.25 ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists #distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionUrl=https\://mirrors.tencent.com/gradle/gradle-7.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx4096m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true android.useAndroidX=true org.gradle.caching=true android.nonTransitiveRClass=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: projects/sample/README.md ================================================ # Sample 在Shadow框架下,应用由几部分构成。 宿主应用打包了很简单的一些接口,并在Manifest中注册了壳子代理组件, 还打包了插件管理器(manager)的动态升级逻辑。 manager负责下载、安装插件,还带有一个动态的View表达Loading态。 而"插件"则不光包含业务App,还包含Shadow的核心实现,即loader和runtime。 "插件"中的业务App和loader、runtime是同一个版本的代码编译出的, 因此loader可以包含一些业务逻辑,针对业务进行特殊处理。 由于loader是多实例的,因此同一个宿主中可以有多种不同的loader实现。 manager在加载"插件"时,首先需要先加载"插件"中的runtime和loader, 再通过loader的Binder(插件应该处于独立进程中避免native库冲突)操作loader进而加载业务App。 在这个Sample目录下,提供了两种示例工程: ## 源码依赖SDK的Sample(`projects/sample/source`) *** 要测试这个Sample请用Android Studio直接打开clone版本库的根目录。 *** * `sample-host`是宿主应用 * `sample-manager`是插件管理器的动态实现 * `sample-plugin/sample-loader`是loader的动态实现,业务主要在这里定义插件组件和壳子代理组件的配对关系等。 * `sample-constant`是在前3者中共用的相同字符串常量。 * `sample-plugin/sample-runtime`是runtime的动态实现,业务主要在这里定义壳子代理组件的实际类。 * `sample-plugin/sample-base-lib`是业务App的一部分基础代码,是一个aar库。 * `sample-plugin/sample-base`是一个apk模块壳子,将`sample-base-lib`打包在其中。既用于正常安装运行开发`sample-base-lib` ,又用于编译`sample-base`插件。 * `sample-plugin/sample-app`是依赖`sample-base-lib`开发的更多业务代码。它编译出的插件apk没有打包`sample-base-lib` ,会在插件运行时依赖`sample-base`插件。 `sample-app`和`sample-base`构成了一个多插件示例,请注意`sample-app/build.gradle`中的`dependsOn = ['sample-base']`设置。 这些工程中对Shadow SDK的依赖完全是源码级的依赖,因此修改Shadow SDK的源码后可以直接运行生效。 使用时可以直接在Android Studio中选择运行`sample-host`模块。 `sample-host`在构建中会自动打包manager和"插件"到assets中,在运行时自动释放模拟下载过程。 ## 二进制Maven依赖SDK的Sample(`projects/sample/maven`) *** 要测试这个Sample请用Android Studio *分别* 打开`projects/sample/maven/host-project` ,`projects/sample/maven/manager-project`,`projects/sample/maven/plugin-project`三个目录。 *** 源码依赖SDK的Sample中对Shadow SDK的依赖配置不适用于正式业务接入。 Shadow实现了完整的Maven发布脚本,支持方便的Maven依赖。 `maven`目录下的3个目录分别演示了3个工程。 这3个工程在实际业务中大概率上是3个不同的代码库。 因此,在这个演示中没有试图做着3个工程间的任何依赖关系, 甚至**3个工程中依赖的Shadow版本都是独立配置的**, 使用时请注意这一点。 ### 自行发布SDK到Maven仓库方法 在`buildScripts/gradle/maven.gradle`文件中配置了Shadow的Maven发布脚本。 正式使用时,请修改其中的两个GroupID变量:`coreGroupId`、`dynamicGroupId`, 以及`setScm`方法中的两个URL到自己的版本库地址上。 然后将`mavenLocal()`改为自己发布的目标Maven仓库。 执行`./gradlew publish`即可将Shadow SDK发布到Maven仓库。 构件的版本号可以在`build/pom`目录中查看生成的pom文件中查看。 在这个Sample的3个工程的`build.gradle`文件中都有`shadow_version`定义, 将这个定义值改为刚刚发布的版本号(生成的pom中写的版本号)。 ### 运行方法 这个演示工程没有实现下载功能,而是假设下载的文件直接位于指定路径。 因此运行前需要手工用adb命令将指定内容push到指定位置。 编译插件,在`plugin-project`目录中运行: ``` ./gradlew packageDebugPlugin adb push build/plugin-debug.zip /data/local/tmp ``` 编译PluginManager,在`manager-project`目录中运行: ``` ./gradlew assembleDebug adb push sample-manager/build/outputs/apk/debug/sample-manager-debug.apk /data/local/tmp ``` 最后可以用Android Studio打开`host-project`直接运行`sample-host`模块。 `plugin-project`中的`plugin-normal-apk`模块也可以直接安装运行,演示不使用Shadow时插件的运行情况。 ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api/.gitignore ================================================ /build ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api/README.md ================================================ 演示如何将自定义接口动态化,使得宿主能够使用apk中的实现 sample-hello-api:定义宿主api接口 sample-hello-api-holder:将 api 动态化,宿主通过这个包提供的方法来获取apk中的实现 ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api/build.gradle ================================================ apply plugin: 'com.android.library' android { compileSdkVersion project.COMPILE_SDK_VERSION defaultConfig { minSdkVersion project.MIN_SDK_VERSION targetSdkVersion project.TARGET_SDK_VERSION versionCode project.VERSION_CODE versionName project.VERSION_NAME testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api/src/main/java/com/tencent/shadow/sample/api/hello/HelloFactory.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.api.hello; import android.content.Context; /** * @author 林学渊 * @email linxy59@mail2.sysu.edu.cn * @date 2021/9/6 * @description 宿主只能通过工厂模式获取 apk 中的类 * @usage null */ public interface HelloFactory { IHelloWorldImpl build(Context context); } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api/src/main/java/com/tencent/shadow/sample/api/hello/IHelloWorld.java ================================================ package com.tencent.shadow.sample.api.hello; import android.content.Context; import android.widget.TextView; /** * @author 林学渊 * @email linxy59@mail2.sysu.edu.cn * @date 2021/9/6 * @description 定义在宿主里的接口,使用插件apk中的实现 * @usage 插件打印 hello world */ public interface IHelloWorld { void sayHelloWorld(Context context, TextView textView); } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api/src/main/java/com/tencent/shadow/sample/api/hello/IHelloWorldImpl.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.api.hello; import android.os.Bundle; /** * @author 林学渊 * @email linxy59@mail2.sysu.edu.cn * @date 2021/9/6 * @description 给接口 IHelloWorld 包装一层生命周期 * 可参考 com.tencent.shadow.sample.apk.hello.DynamicHello 中管理该生命周期 * @usage hello.apk 里可以感知加载的过程 */ public interface IHelloWorldImpl extends IHelloWorld { void onCreate(Bundle bundle); void onSaveInstanceState(Bundle outState); void onDestroy(); } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api-holder/.gitignore ================================================ /build ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api-holder/README.md ================================================ 演示如何将自定义接口动态化,使得宿主能够使用apk中的实现 sample-hello-api:定义宿主api接口 sample-hello-api-holder:将 api 动态化,宿主通过这个包提供的方法来获取apk中的实现 宿主引入 apk 包,implementation project(':sample-hello-api-holder') ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api-holder/build.gradle ================================================ apply plugin: 'com.android.library' android { compileSdkVersion project.COMPILE_SDK_VERSION defaultConfig { minSdkVersion project.MIN_SDK_VERSION targetSdkVersion project.TARGET_SDK_VERSION versionCode project.VERSION_CODE versionName project.VERSION_NAME testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { // 使用 api 而不是 compileOnly:发布 aar 时会传递依赖,而不是打包进 aar api 'com.tencent.shadow.core:utils' api 'com.tencent.shadow.core:common' api 'com.tencent.shadow.dynamic:dynamic-apk' api project(':sample-hello-api') } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api-holder/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api-holder/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api-holder/src/main/java/com/tencent/shadow/sample/apk/hello/DynamicHello.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.apk.hello; import android.content.Context; import android.os.Bundle; import android.text.TextUtils; import android.widget.TextView; import com.tencent.shadow.core.common.Logger; import com.tencent.shadow.core.common.LoggerFactory; import com.tencent.shadow.sample.api.hello.IHelloWorld; import com.tencent.shadow.sample.api.hello.IHelloWorldImpl; import java.io.File; import static com.tencent.shadow.core.utils.Md5.md5File; public final class DynamicHello implements IHelloWorld { final private HelloWorldUpdater mUpdater; private IHelloWorldImpl mHelloWorldImpl; private String mCurrentImplMd5; private static final Logger mLogger = LoggerFactory.getLogger(DynamicHello.class); public DynamicHello(HelloWorldUpdater updater) { if (updater.getLatest() == null) { throw new IllegalArgumentException("构造DynamicPluginManager时传入的PluginManagerUpdater" + "必须已经已有本地文件,即getLatest()!=null"); } mUpdater = updater; } @Override public void sayHelloWorld(Context context, TextView textView) { if (mLogger.isInfoEnabled()) { mLogger.info("sayHelloWorld context:" + context); } updateImpl(context); mHelloWorldImpl.sayHelloWorld(context, textView); mUpdater.update(); } public void release() { if (mLogger.isInfoEnabled()) { mLogger.info("release"); } if (mHelloWorldImpl != null) { mHelloWorldImpl.onDestroy(); mHelloWorldImpl = null; } } private void updateImpl(Context context) { File latestImplApk = mUpdater.getLatest(); String md5 = md5File(latestImplApk); if (mLogger.isInfoEnabled()) { mLogger.info("TextUtils.equals(mCurrentImplMd5, md5) : " + (TextUtils.equals(mCurrentImplMd5, md5))); } if (!TextUtils.equals(mCurrentImplMd5, md5)) { HelloImplLoader implLoader = new HelloImplLoader(context, latestImplApk); IHelloWorldImpl newImpl = implLoader.load(); Bundle state; if (mHelloWorldImpl != null) { state = new Bundle(); mHelloWorldImpl.onSaveInstanceState(state); mHelloWorldImpl.onDestroy(); } else { state = null; } newImpl.onCreate(state); mHelloWorldImpl = newImpl; mCurrentImplMd5 = md5; } } public IHelloWorld getHelloWorkdImpl() { return mHelloWorldImpl; } } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api-holder/src/main/java/com/tencent/shadow/sample/apk/hello/HelloImplLoader.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.apk.hello; import android.content.Context; import com.tencent.shadow.core.common.InstalledApk; import com.tencent.shadow.dynamic.apk.ApkClassLoader; import com.tencent.shadow.dynamic.apk.ChangeApkContextWrapper; import com.tencent.shadow.dynamic.apk.ImplLoader; import com.tencent.shadow.sample.api.hello.HelloFactory; import com.tencent.shadow.sample.api.hello.IHelloWorldImpl; import java.io.File; final class HelloImplLoader extends ImplLoader { //指定实现类在apk中的路径 private static final String FACTORY_CLASS_NAME = "com.tencent.shadow.dynamic.impl.HelloFactoryImpl"; private static final String[] REMOTE_PLUGIN_MANAGER_INTERFACES = new String[] { "com.tencent.shadow.core.common", //注意将宿主自定义接口加入白名单 "com.tencent.shadow.sample.api.hello" }; final private Context applicationContext; final private InstalledApk installedApk; HelloImplLoader(Context context, File apk) { applicationContext = context.getApplicationContext(); File root = new File(applicationContext.getFilesDir(), "HelloImplLoader"); File odexDir = new File(root, Long.toString(apk.lastModified(), Character.MAX_RADIX)); odexDir.mkdirs(); installedApk = new InstalledApk(apk.getAbsolutePath(), odexDir.getAbsolutePath(), null); } IHelloWorldImpl load() { ApkClassLoader apkClassLoader = new ApkClassLoader( installedApk, getClass().getClassLoader(), loadWhiteList(installedApk), 1 ); Context contextForApi = new ChangeApkContextWrapper( applicationContext, installedApk.apkFilePath, apkClassLoader ); try { HelloFactory factory = apkClassLoader.getInterface( HelloFactory.class, FACTORY_CLASS_NAME ); return factory.build(contextForApi); } catch (Exception e) { throw new RuntimeException(e); } } @Override protected String[] getCustomWhiteList() { return REMOTE_PLUGIN_MANAGER_INTERFACES; } } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-api-holder/src/main/java/com/tencent/shadow/sample/apk/hello/HelloWorldUpdater.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.apk.hello; import java.io.File; import java.util.concurrent.Future; /** * apk文件升级器 *

* 注意这个类不负责什么时候该升级 实现IHelloWorld的apk文件, * 它只提供需要升级时的功能,如下载和向远端查询文件是否还可用。 */ public interface HelloWorldUpdater { /** * 更新 */ Future update(); /** * 获取本地最新可用的 * * @return null表示本地没有可用的 */ File getLatest(); } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-apk/.gitignore ================================================ /build ================================================ FILE: projects/sample/dynamic-apk/sample-hello-apk/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion project.COMPILE_SDK_VERSION defaultConfig { //region hello.apk 演示 applicationId 'com.tencent.shadow.sample.hello.host' // 必须保证和 host 的 applicationId 一致 //endregion minSdkVersion project.MIN_SDK_VERSION targetSdkVersion project.TARGET_SDK_VERSION versionCode project.VERSION_CODE versionName project.VERSION_NAME } buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.create("release") signingConfig.initWith(buildTypes.debug.signingConfig) } } lintOptions { abortOnError false } } dependencies { // 自定义接口在宿主内 // hello.apk 不必要自己打包一份,只需要通过白名单访问宿主的接口定义,所以是 compileOnly compileOnly project(":sample-hello-api") } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-apk/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keep class org.slf4j.**{*;} -keep class com.tencent.shadow.dynamic.impl.**{*;} -keep class com.tencent.shadow.dynamic.loader.**{*;} ================================================ FILE: projects/sample/dynamic-apk/sample-hello-apk/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/dynamic-apk/sample-hello-apk/src/main/java/com/tencent/shadow/dynamic/impl/HelloFactoryImpl.java ================================================ package com.tencent.shadow.dynamic.impl; import android.content.Context; import com.tencent.shadow.sample.api.hello.HelloFactory; import com.tencent.shadow.sample.api.hello.IHelloWorldImpl; import com.tencent.shadow.sample.api.hello.SampleHelloWorld; /** * @author 林学渊 * @email linxy59@mail2.sysu.edu.cn * @date 2021/9/6 * @description 此类包名及类名固定 * @usage null */ public final class HelloFactoryImpl implements HelloFactory { @Override public IHelloWorldImpl build(Context context) { return new SampleHelloWorld(); } } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-apk/src/main/java/com/tencent/shadow/dynamic/impl/WhiteList.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.dynamic.impl; /** * 此类包名及类名固定 * classLoader的白名单 * hello.apk 可以加载宿主中位于白名单内的类 */ public interface WhiteList { String[] sWhiteList = new String[] { "com.tencent.host.shadow", "com.tencent.shadow.test.lib.constant", }; } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-apk/src/main/java/com/tencent/shadow/sample/api/hello/SampleHelloWorld.java ================================================ package com.tencent.shadow.sample.api.hello; import android.content.Context; import android.os.Bundle; import android.widget.TextView; /** * @author 林学渊 * @email linxy59@mail2.sysu.edu.cn * @date 2021/9/6 * @description 实现宿主自定义接口 * @usage null */ public class SampleHelloWorld implements IHelloWorldImpl { @Override public void sayHelloWorld(Context context, TextView textView) { String text = "这是apk中的实现:" + SampleHelloWorld.class.toString(); if (textView == null) { return; } textView.setText(text); } @Override public void onCreate(Bundle bundle) { } @Override public void onSaveInstanceState(Bundle outState) { } @Override public void onDestroy() { } } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-apk/src/main/res/layout/activity_load_plugin.xml ================================================ ================================================ FILE: projects/sample/dynamic-apk/sample-hello-host/.gitignore ================================================ /build ================================================ FILE: projects/sample/dynamic-apk/sample-hello-host/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion project.COMPILE_SDK_VERSION defaultConfig { //region hello.apk 演示 applicationId 'com.tencent.shadow.sample.hello.host' //endregion minSdkVersion project.MIN_SDK_VERSION targetSdkVersion project.TARGET_SDK_VERSION versionCode project.VERSION_CODE versionName project.VERSION_NAME testInstrumentationRunner "com.tencent.shadow.test.CustomAndroidJUnitRunner" } buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.create("release") signingConfig.initWith(buildTypes.debug.signingConfig) } } //region hello.apk 演示 sourceSets { debug { assets.srcDir('build/generated/assets/sample-hello-apk/debug/') } release { assets.srcDir('build/generated/assets/sample-hello-apk/release/') } } //endregion lintOptions { checkReleaseBuilds false abortOnError false } } dependencies { implementation "commons-io:commons-io:$commons_io_android_version"//sample-hello-host从assets中复制插件用的 implementation "org.slf4j:slf4j-api:$slf4j_version" //region hello.apk 演示 implementation project(':sample-hello-api-holder') //endregion } def createCopyTask(projectName, buildType, name, apkName, inputFile, taskName) { def outputFile = file("${getBuildDir()}/generated/assets/${name}/${buildType}/${apkName}") outputFile.getParentFile().mkdirs() return tasks.create("copy${buildType.capitalize()}${name.capitalize()}Task", Copy) { group = 'build' description = "复制${name}到assets中." from(inputFile.getParent()) { include(inputFile.name) rename { outputFile.name } } into(outputFile.getParent()) }.dependsOn("${projectName}:${taskName}") } //region hello.apk 演示 def generateHelloAssets(generateAssetsTask, buildType) { def moduleName = 'sample-hello-apk' def pluginManagerApkFile = file( "${project(":sample-hello-apk").getBuildDir()}" + "/outputs/apk/${buildType}/" + "${moduleName}-${buildType}.apk" ) generateAssetsTask.dependsOn createCopyTask( ':sample-hello-apk', buildType, moduleName, 'hello.apk', pluginManagerApkFile, "assemble${buildType.capitalize()}" ) } //endregion tasks.whenTaskAdded { task -> if (task.name == "generateDebugAssets") { //region hello.apk 演示 generateHelloAssets(task, 'debug') //endregion } if (task.name == "generateReleaseAssets") { //region hello.apk 演示 generateHelloAssets(task, 'release') //endregion } } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-host/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keep class org.slf4j.**{*;} -dontwarn org.slf4j.impl.** -keep class com.tencent.shadow.dynamic.host.**{*;} -keep class com.tencent.shadow.core.common.**{*;} -keep class com.tencent.shadow.core.runtime.container.**{*;} ================================================ FILE: projects/sample/dynamic-apk/sample-hello-host/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/AndroidLogLoggerFactory.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host; import android.util.Log; import com.tencent.shadow.core.common.ILoggerFactory; import com.tencent.shadow.core.common.Logger; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class AndroidLogLoggerFactory implements ILoggerFactory { private static final int LOG_LEVEL_TRACE = 5; private static final int LOG_LEVEL_DEBUG = 4; private static final int LOG_LEVEL_INFO = 3; private static final int LOG_LEVEL_WARN = 2; private static final int LOG_LEVEL_ERROR = 1; private static AndroidLogLoggerFactory sInstance = new AndroidLogLoggerFactory(); public static ILoggerFactory getInstance() { return sInstance; } final private ConcurrentMap loggerMap = new ConcurrentHashMap(); public Logger getLogger(String name) { Logger simpleLogger = loggerMap.get(name); if (simpleLogger != null) { return simpleLogger; } else { Logger newInstance = new IVLogger(name); Logger oldInstance = loggerMap.putIfAbsent(name, newInstance); return oldInstance == null ? newInstance : oldInstance; } } class IVLogger implements Logger { private String name; IVLogger(String name) { this.name = name; } @Override public String getName() { return name; } private void log(int level, String message, Throwable t) { final String tag = String.valueOf(name); switch (level) { case LOG_LEVEL_TRACE: case LOG_LEVEL_DEBUG: if (t == null) Log.d(tag, message); else Log.d(tag, message, t); break; case LOG_LEVEL_INFO: if (t == null) Log.i(tag, message); else Log.i(tag, message, t); break; case LOG_LEVEL_WARN: if (t == null) Log.w(tag, message); else Log.w(tag, message, t); break; case LOG_LEVEL_ERROR: if (t == null) Log.e(tag, message); else Log.e(tag, message, t); break; default: break; } } @Override public boolean isTraceEnabled() { return true; } @Override public void trace(String msg) { log(LOG_LEVEL_TRACE, msg, null); } @Override public void trace(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_TRACE, tuple.getMessage(), null); } @Override public void trace(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_TRACE, tuple.getMessage(), null); } @Override public void trace(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_TRACE, tuple.getMessage(), null); } @Override public void trace(String msg, Throwable throwable) { log(LOG_LEVEL_TRACE, msg, throwable); } @Override public boolean isDebugEnabled() { return true; } @Override public void debug(String msg) { log(LOG_LEVEL_DEBUG, msg, null); } @Override public void debug(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_DEBUG, tuple.getMessage(), null); } @Override public void debug(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_DEBUG, tuple.getMessage(), null); } @Override public void debug(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_DEBUG, tuple.getMessage(), null); } @Override public void debug(String msg, Throwable throwable) { log(LOG_LEVEL_DEBUG, msg, throwable); } @Override public boolean isInfoEnabled() { return true; } @Override public void info(String msg) { log(LOG_LEVEL_INFO, msg, null); } @Override public void info(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_INFO, tuple.getMessage(), null); } @Override public void info(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_INFO, tuple.getMessage(), null); } @Override public void info(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_INFO, tuple.getMessage(), null); } @Override public void info(String msg, Throwable throwable) { log(LOG_LEVEL_INFO, msg, throwable); } @Override public boolean isWarnEnabled() { return true; } @Override public void warn(String msg) { log(LOG_LEVEL_WARN, msg, null); } @Override public void warn(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_WARN, tuple.getMessage(), null); } @Override public void warn(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_WARN, tuple.getMessage(), null); } @Override public void warn(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_WARN, tuple.getMessage(), null); } @Override public void warn(String msg, Throwable throwable) { log(LOG_LEVEL_WARN, msg, throwable); } @Override public boolean isErrorEnabled() { return true; } @Override public void error(String msg) { log(LOG_LEVEL_ERROR, msg, null); } @Override public void error(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_ERROR, tuple.getMessage(), null); } @Override public void error(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_ERROR, tuple.getMessage(), null); } @Override public void error(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_ERROR, tuple.getMessage(), null); } @Override public void error(String msg, Throwable throwable) { log(LOG_LEVEL_ERROR, msg, throwable); } } } class FormattingTuple { static public FormattingTuple NULL = new FormattingTuple(null); private String message; private Throwable throwable; private Object[] argArray; public FormattingTuple(String message) { this(message, null, null); } public FormattingTuple(String message, Object[] argArray, Throwable throwable) { this.message = message; this.throwable = throwable; this.argArray = argArray; } public String getMessage() { return message; } public Object[] getArgArray() { return argArray; } public Throwable getThrowable() { return throwable; } } final class MessageFormatter { static final char DELIM_START = '{'; static final char DELIM_STOP = '}'; static final String DELIM_STR = "{}"; private static final char ESCAPE_CHAR = '\\'; /** * Performs single argument substitution for the 'messagePattern' passed as * parameter. *

* For example, * *

     * MessageFormatter.format("Hi {}.", "there");
     * 
*

* will return the string "Hi there.". *

* * @param messagePattern The message pattern which will be parsed and formatted * @param arg The argument to be substituted in place of the formatting anchor * @return The formatted message */ final public static FormattingTuple format(String messagePattern, Object arg) { return arrayFormat(messagePattern, new Object[]{arg}); } /** * Performs a two argument substitution for the 'messagePattern' passed as * parameter. *

* For example, * *

     * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob");
     * 
*

* will return the string "Hi Alice. My name is Bob.". * * @param messagePattern The message pattern which will be parsed and formatted * @param arg1 The argument to be substituted in place of the first formatting * anchor * @param arg2 The argument to be substituted in place of the second formatting * anchor * @return The formatted message */ final public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) { return arrayFormat(messagePattern, new Object[]{arg1, arg2}); } static final Throwable getThrowableCandidate(Object[] argArray) { if (argArray == null || argArray.length == 0) { return null; } final Object lastEntry = argArray[argArray.length - 1]; if (lastEntry instanceof Throwable) { return (Throwable) lastEntry; } return null; } final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) { Throwable throwableCandidate = getThrowableCandidate(argArray); Object[] args = argArray; if (throwableCandidate != null) { args = trimmedCopy(argArray); } return arrayFormat(messagePattern, args, throwableCandidate); } private static Object[] trimmedCopy(Object[] argArray) { if (argArray == null || argArray.length == 0) { throw new IllegalStateException("non-sensical empty or null argument array"); } final int trimemdLen = argArray.length - 1; Object[] trimmed = new Object[trimemdLen]; System.arraycopy(argArray, 0, trimmed, 0, trimemdLen); return trimmed; } final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) { if (messagePattern == null) { return new FormattingTuple(null, argArray, throwable); } if (argArray == null) { return new FormattingTuple(messagePattern); } int i = 0; int j; // use string builder for better multicore performance StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); int L; for (L = 0; L < argArray.length; L++) { j = messagePattern.indexOf(DELIM_STR, i); if (j == -1) { // no more variables if (i == 0) { // this is a simple string return new FormattingTuple(messagePattern, argArray, throwable); } else { // add the tail string which contains no variables and return // the result. sbuf.append(messagePattern, i, messagePattern.length()); return new FormattingTuple(sbuf.toString(), argArray, throwable); } } else { if (isEscapedDelimeter(messagePattern, j)) { if (!isDoubleEscaped(messagePattern, j)) { L--; // DELIM_START was escaped, thus should not be incremented sbuf.append(messagePattern, i, j - 1); sbuf.append(DELIM_START); i = j + 1; } else { // The escape character preceding the delimiter start is // itself escaped: "abc x:\\{}" // we have to consume one backward slash sbuf.append(messagePattern, i, j - 1); deeplyAppendParameter(sbuf, argArray[L], new HashMap()); i = j + 2; } } else { // normal case sbuf.append(messagePattern, i, j); deeplyAppendParameter(sbuf, argArray[L], new HashMap()); i = j + 2; } } } // append the characters following the last {} pair. sbuf.append(messagePattern, i, messagePattern.length()); return new FormattingTuple(sbuf.toString(), argArray, throwable); } final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) { if (delimeterStartIndex == 0) { return false; } char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1); if (potentialEscape == ESCAPE_CHAR) { return true; } else { return false; } } final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) { if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) { return true; } else { return false; } } // special treatment of array values was suggested by 'lizongbo' private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map seenMap) { if (o == null) { sbuf.append("null"); return; } if (!o.getClass().isArray()) { safeObjectAppend(sbuf, o); } else { // check for primitive array types because they // unfortunately cannot be cast to Object[] if (o instanceof boolean[]) { booleanArrayAppend(sbuf, (boolean[]) o); } else if (o instanceof byte[]) { byteArrayAppend(sbuf, (byte[]) o); } else if (o instanceof char[]) { charArrayAppend(sbuf, (char[]) o); } else if (o instanceof short[]) { shortArrayAppend(sbuf, (short[]) o); } else if (o instanceof int[]) { intArrayAppend(sbuf, (int[]) o); } else if (o instanceof long[]) { longArrayAppend(sbuf, (long[]) o); } else if (o instanceof float[]) { floatArrayAppend(sbuf, (float[]) o); } else if (o instanceof double[]) { doubleArrayAppend(sbuf, (double[]) o); } else { objectArrayAppend(sbuf, (Object[]) o, seenMap); } } } private static void safeObjectAppend(StringBuilder sbuf, Object o) { try { String oAsString = o.toString(); sbuf.append(oAsString); } catch (Throwable t) { sbuf.append("[FAILED toString()]"); } } private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map seenMap) { sbuf.append('['); if (!seenMap.containsKey(a)) { seenMap.put(a, null); final int len = a.length; for (int i = 0; i < len; i++) { deeplyAppendParameter(sbuf, a[i], seenMap); if (i != len - 1) sbuf.append(", "); } // allow repeats in siblings seenMap.remove(a); } else { sbuf.append("..."); } sbuf.append(']'); } private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void byteArrayAppend(StringBuilder sbuf, byte[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void charArrayAppend(StringBuilder sbuf, char[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void shortArrayAppend(StringBuilder sbuf, short[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void intArrayAppend(StringBuilder sbuf, int[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void longArrayAppend(StringBuilder sbuf, long[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void floatArrayAppend(StringBuilder sbuf, float[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void doubleArrayAppend(StringBuilder sbuf, double[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/HostApplication.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host; import android.app.Application; import android.os.Build; import android.os.StrictMode; import android.webkit.WebView; import com.tencent.shadow.core.common.LoggerFactory; public class HostApplication extends Application { @Override public void onCreate() { super.onCreate(); detectNonSdkApiUsageOnAndroidP(); setWebViewDataDirectorySuffix(); LoggerFactory.setILoggerFactory(new AndroidLogLoggerFactory()); PluginHelper.getInstance().init(this); } private static void setWebViewDataDirectorySuffix() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { return; } WebView.setDataDirectorySuffix(Application.getProcessName()); } private static void detectNonSdkApiUsageOnAndroidP() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { return; } StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); builder.detectNonSdkApiUsage(); StrictMode.setVmPolicy(builder.build()); } } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/MainActivity.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import com.tencent.shadow.sample.api.hello.IHelloWorld; import com.tencent.shadow.sample.host.api.HelloWorldApiHolder; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTheme(android.R.style.Theme_NoTitleBar); LinearLayout rootView = new LinearLayout(this); rootView.setOrientation(LinearLayout.VERTICAL); rootView.addView(createTextView("演示自定义 api 的动态化,宿主 api 的实现在 hello.apk 中", null)); final TextView textView = createTextView("等待apk实现", null); rootView.addView(createButton("宿主自定义接口的动态化", new View.OnClickListener() { @Override public void onClick(View v) { PluginHelper.getInstance().singlePool.execute(new Runnable() { @Override public void run() { //hello.apk 里实现了 IHelloWorld final IHelloWorld api = HelloWorldApiHolder.getHelloWorld(PluginHelper.getInstance().helloApkFile); if (api == null) { return; } runOnUiThread(new Runnable() { @Override public void run() { api.sayHelloWorld(MainActivity.this, textView); } }); } }); } })); rootView.addView(textView); setContentView(rootView); } public Button createButton(String title, View.OnClickListener listener) { Button button = new Button(this); button.setText(title); button.setOnClickListener(listener); return button; } public TextView createTextView(String title, View.OnClickListener listener) { TextView textView = new TextView(this); textView.setText(title); textView.setOnClickListener(listener); return textView; } } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/PluginHelper.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host; import android.content.Context; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class PluginHelper { /** * 动态加载的 hello.apk */ public final static String sHelloApkName = "hello.apk"; public File helloApkFile; public ExecutorService singlePool = Executors.newSingleThreadExecutor(); private Context mContext; private static PluginHelper sInstance = new PluginHelper(); public static PluginHelper getInstance() { return sInstance; } private PluginHelper() { } public void init(Context context) { helloApkFile = new File(context.getFilesDir(), sHelloApkName); mContext = context.getApplicationContext(); singlePool.execute(new Runnable() { @Override public void run() { preparePlugin(); } }); } private void preparePlugin() { try { InputStream is = mContext.getAssets().open(sHelloApkName); FileUtils.copyInputStreamToFile(is, helloApkFile); } catch (IOException e) { throw new RuntimeException("从assets中复制apk出错", e); } } } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/api/FixedPathPmUpdater.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host.api; import com.tencent.shadow.sample.apk.hello.HelloWorldUpdater; import java.io.File; import java.util.concurrent.Future; public class FixedPathPmUpdater implements HelloWorldUpdater { final private File apk; FixedPathPmUpdater(File apk) { this.apk = apk; } @Override public Future update() { return null; } @Override public File getLatest() { return apk; } } ================================================ FILE: projects/sample/dynamic-apk/sample-hello-host/src/main/java/com/tencent/shadow/sample/host/api/HelloWorldApiHolder.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host.api; import com.tencent.shadow.sample.api.hello.IHelloWorld; import com.tencent.shadow.sample.apk.hello.DynamicHello; import java.io.File; public class HelloWorldApiHolder { public static IHelloWorld getHelloWorld(File apk) { final FixedPathPmUpdater fixedPathPmUpdater = new FixedPathPmUpdater(apk); File tempPm = fixedPathPmUpdater.getLatest(); if (tempPm != null) { return new DynamicHello(fixedPathPmUpdater); } return null; } } ================================================ FILE: projects/sample/maven/host-project/.gitignore ================================================ *.iml .gradle /local.properties .idea .DS_Store /build /captures .externalNativeBuild ================================================ FILE: projects/sample/maven/host-project/build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.shadow_version = '2.2.1' repositories { if (!System.getenv().containsKey("DISABLE_TENCENT_MAVEN_MIRROR")) { maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' } } else { google() mavenCentral() } } dependencies { classpath 'com.android.tools.build:gradle:4.0.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { if (!System.getenv().containsKey("DISABLE_TENCENT_MAVEN_MIRROR")) { maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' } } else { google() mavenCentral() } maven { name = "GitHubPackages" url "https://maven.pkg.github.com/tencent/shadow" //一个只读账号兼容Github Packages暂时不支持匿名下载 //https://github.community/t/download-from-github-package-registry-without-authentication/14407 credentials { username = 'readonlypat' password = '\u0067hp_s3VOOZnLf1bTyvHWblPfaessrVYyEU4JdNbs' } } mavenLocal() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: projects/sample/maven/host-project/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists #distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip distributionUrl=https\://mirrors.tencent.com/gradle/gradle-6.6.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: projects/sample/maven/host-project/gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx4096m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official ================================================ FILE: projects/sample/maven/host-project/gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: projects/sample/maven/host-project/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: projects/sample/maven/host-project/introduce-shadow-lib/.gitignore ================================================ /build ================================================ FILE: projects/sample/maven/host-project/introduce-shadow-lib/build.gradle ================================================ apply plugin: 'com.android.library' android { compileSdkVersion 29 defaultConfig { minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation "com.tencent.shadow.dynamic:host:$shadow_version" } ================================================ FILE: projects/sample/maven/host-project/introduce-shadow-lib/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: projects/sample/maven/host-project/introduce-shadow-lib/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/maven/host-project/introduce-shadow-lib/src/main/java/com/tencent/shadow/sample/introduce_shadow_lib/AndroidLoggerFactory.java ================================================ package com.tencent.shadow.sample.introduce_shadow_lib; import android.util.Log; import com.tencent.shadow.core.common.ILoggerFactory; import com.tencent.shadow.core.common.Logger; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class AndroidLoggerFactory implements ILoggerFactory { private static final int LOG_LEVEL_TRACE = 5; private static final int LOG_LEVEL_DEBUG = 4; private static final int LOG_LEVEL_INFO = 3; private static final int LOG_LEVEL_WARN = 2; private static final int LOG_LEVEL_ERROR = 1; private static AndroidLoggerFactory sInstance = new AndroidLoggerFactory(); public static ILoggerFactory getInstance() { return sInstance; } final private ConcurrentMap loggerMap = new ConcurrentHashMap(); public Logger getLogger(String name) { Logger simpleLogger = loggerMap.get(name); if (simpleLogger != null) { return simpleLogger; } else { Logger newInstance = new IVLogger(name); Logger oldInstance = loggerMap.putIfAbsent(name, newInstance); return oldInstance == null ? newInstance : oldInstance; } } class IVLogger implements Logger { private String name; IVLogger(String name) { this.name = name; } @Override public String getName() { return name; } private void log(int level, String message, Throwable t) { final String tag = String.valueOf(name); switch (level) { case LOG_LEVEL_TRACE: case LOG_LEVEL_DEBUG: if (t == null) Log.d(tag, message); else Log.d(tag, message, t); break; case LOG_LEVEL_INFO: if (t == null) Log.i(tag, message); else Log.i(tag, message, t); break; case LOG_LEVEL_WARN: if (t == null) Log.w(tag, message); else Log.w(tag, message, t); break; case LOG_LEVEL_ERROR: if (t == null) Log.e(tag, message); else Log.e(tag, message, t); break; default: break; } } @Override public boolean isTraceEnabled() { return true; } @Override public void trace(String msg) { log(LOG_LEVEL_TRACE, msg, null); } @Override public void trace(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_TRACE, tuple.getMessage(), null); } @Override public void trace(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_TRACE, tuple.getMessage(), null); } @Override public void trace(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_TRACE, tuple.getMessage(), null); } @Override public void trace(String msg, Throwable throwable) { log(LOG_LEVEL_TRACE, msg, throwable); } @Override public boolean isDebugEnabled() { return true; } @Override public void debug(String msg) { log(LOG_LEVEL_DEBUG, msg, null); } @Override public void debug(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_DEBUG, tuple.getMessage(), null); } @Override public void debug(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_DEBUG, tuple.getMessage(), null); } @Override public void debug(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_DEBUG, tuple.getMessage(), null); } @Override public void debug(String msg, Throwable throwable) { log(LOG_LEVEL_DEBUG, msg, throwable); } @Override public boolean isInfoEnabled() { return true; } @Override public void info(String msg) { log(LOG_LEVEL_INFO, msg, null); } @Override public void info(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_INFO, tuple.getMessage(), null); } @Override public void info(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_INFO, tuple.getMessage(), null); } @Override public void info(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_INFO, tuple.getMessage(), null); } @Override public void info(String msg, Throwable throwable) { log(LOG_LEVEL_INFO, msg, throwable); } @Override public boolean isWarnEnabled() { return true; } @Override public void warn(String msg) { log(LOG_LEVEL_WARN, msg, null); } @Override public void warn(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_WARN, tuple.getMessage(), null); } @Override public void warn(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_WARN, tuple.getMessage(), null); } @Override public void warn(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_WARN, tuple.getMessage(), null); } @Override public void warn(String msg, Throwable throwable) { log(LOG_LEVEL_WARN, msg, throwable); } @Override public boolean isErrorEnabled() { return true; } @Override public void error(String msg) { log(LOG_LEVEL_ERROR, msg, null); } @Override public void error(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_ERROR, tuple.getMessage(), null); } @Override public void error(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_ERROR, tuple.getMessage(), null); } @Override public void error(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_ERROR, tuple.getMessage(), null); } @Override public void error(String msg, Throwable throwable) { log(LOG_LEVEL_ERROR, msg, throwable); } } } class FormattingTuple { static public FormattingTuple NULL = new FormattingTuple(null); private String message; private Throwable throwable; private Object[] argArray; public FormattingTuple(String message) { this(message, null, null); } public FormattingTuple(String message, Object[] argArray, Throwable throwable) { this.message = message; this.throwable = throwable; this.argArray = argArray; } public String getMessage() { return message; } public Object[] getArgArray() { return argArray; } public Throwable getThrowable() { return throwable; } } final class MessageFormatter { static final char DELIM_START = '{'; static final char DELIM_STOP = '}'; static final String DELIM_STR = "{}"; private static final char ESCAPE_CHAR = '\\'; /** * Performs single argument substitution for the 'messagePattern' passed as * parameter. *

* For example, * *

     * MessageFormatter.format("Hi {}.", "there");
     * 
*

* will return the string "Hi there.". *

* * @param messagePattern The message pattern which will be parsed and formatted * @param arg The argument to be substituted in place of the formatting anchor * @return The formatted message */ final public static FormattingTuple format(String messagePattern, Object arg) { return arrayFormat(messagePattern, new Object[]{arg}); } /** * Performs a two argument substitution for the 'messagePattern' passed as * parameter. *

* For example, * *

     * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob");
     * 
*

* will return the string "Hi Alice. My name is Bob.". * * @param messagePattern The message pattern which will be parsed and formatted * @param arg1 The argument to be substituted in place of the first formatting * anchor * @param arg2 The argument to be substituted in place of the second formatting * anchor * @return The formatted message */ final public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) { return arrayFormat(messagePattern, new Object[]{arg1, arg2}); } static final Throwable getThrowableCandidate(Object[] argArray) { if (argArray == null || argArray.length == 0) { return null; } final Object lastEntry = argArray[argArray.length - 1]; if (lastEntry instanceof Throwable) { return (Throwable) lastEntry; } return null; } final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) { Throwable throwableCandidate = getThrowableCandidate(argArray); Object[] args = argArray; if (throwableCandidate != null) { args = trimmedCopy(argArray); } return arrayFormat(messagePattern, args, throwableCandidate); } private static Object[] trimmedCopy(Object[] argArray) { if (argArray == null || argArray.length == 0) { throw new IllegalStateException("non-sensical empty or null argument array"); } final int trimemdLen = argArray.length - 1; Object[] trimmed = new Object[trimemdLen]; System.arraycopy(argArray, 0, trimmed, 0, trimemdLen); return trimmed; } final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) { if (messagePattern == null) { return new FormattingTuple(null, argArray, throwable); } if (argArray == null) { return new FormattingTuple(messagePattern); } int i = 0; int j; // use string builder for better multicore performance StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); int L; for (L = 0; L < argArray.length; L++) { j = messagePattern.indexOf(DELIM_STR, i); if (j == -1) { // no more variables if (i == 0) { // this is a simple string return new FormattingTuple(messagePattern, argArray, throwable); } else { // add the tail string which contains no variables and return // the result. sbuf.append(messagePattern, i, messagePattern.length()); return new FormattingTuple(sbuf.toString(), argArray, throwable); } } else { if (isEscapedDelimeter(messagePattern, j)) { if (!isDoubleEscaped(messagePattern, j)) { L--; // DELIM_START was escaped, thus should not be incremented sbuf.append(messagePattern, i, j - 1); sbuf.append(DELIM_START); i = j + 1; } else { // The escape character preceding the delimiter start is // itself escaped: "abc x:\\{}" // we have to consume one backward slash sbuf.append(messagePattern, i, j - 1); deeplyAppendParameter(sbuf, argArray[L], new HashMap()); i = j + 2; } } else { // normal case sbuf.append(messagePattern, i, j); deeplyAppendParameter(sbuf, argArray[L], new HashMap()); i = j + 2; } } } // append the characters following the last {} pair. sbuf.append(messagePattern, i, messagePattern.length()); return new FormattingTuple(sbuf.toString(), argArray, throwable); } final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) { if (delimeterStartIndex == 0) { return false; } char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1); if (potentialEscape == ESCAPE_CHAR) { return true; } else { return false; } } final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) { if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) { return true; } else { return false; } } // special treatment of array values was suggested by 'lizongbo' private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map seenMap) { if (o == null) { sbuf.append("null"); return; } if (!o.getClass().isArray()) { safeObjectAppend(sbuf, o); } else { // check for primitive array types because they // unfortunately cannot be cast to Object[] if (o instanceof boolean[]) { booleanArrayAppend(sbuf, (boolean[]) o); } else if (o instanceof byte[]) { byteArrayAppend(sbuf, (byte[]) o); } else if (o instanceof char[]) { charArrayAppend(sbuf, (char[]) o); } else if (o instanceof short[]) { shortArrayAppend(sbuf, (short[]) o); } else if (o instanceof int[]) { intArrayAppend(sbuf, (int[]) o); } else if (o instanceof long[]) { longArrayAppend(sbuf, (long[]) o); } else if (o instanceof float[]) { floatArrayAppend(sbuf, (float[]) o); } else if (o instanceof double[]) { doubleArrayAppend(sbuf, (double[]) o); } else { objectArrayAppend(sbuf, (Object[]) o, seenMap); } } } private static void safeObjectAppend(StringBuilder sbuf, Object o) { try { String oAsString = o.toString(); sbuf.append(oAsString); } catch (Throwable t) { sbuf.append("[FAILED toString()]"); } } private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map seenMap) { sbuf.append('['); if (!seenMap.containsKey(a)) { seenMap.put(a, null); final int len = a.length; for (int i = 0; i < len; i++) { deeplyAppendParameter(sbuf, a[i], seenMap); if (i != len - 1) sbuf.append(", "); } // allow repeats in siblings seenMap.remove(a); } else { sbuf.append("..."); } sbuf.append(']'); } private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void byteArrayAppend(StringBuilder sbuf, byte[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void charArrayAppend(StringBuilder sbuf, char[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void shortArrayAppend(StringBuilder sbuf, short[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void intArrayAppend(StringBuilder sbuf, int[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void longArrayAppend(StringBuilder sbuf, long[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void floatArrayAppend(StringBuilder sbuf, float[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void doubleArrayAppend(StringBuilder sbuf, double[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } } ================================================ FILE: projects/sample/maven/host-project/introduce-shadow-lib/src/main/java/com/tencent/shadow/sample/introduce_shadow_lib/FixedPathPmUpdater.java ================================================ package com.tencent.shadow.sample.introduce_shadow_lib; import com.tencent.shadow.dynamic.host.PluginManagerUpdater; import java.io.File; import java.util.concurrent.Future; /** * 这个Updater没有任何升级能力。直接将指定路径作为其升级结果。 */ public class FixedPathPmUpdater implements PluginManagerUpdater { final private File apk; FixedPathPmUpdater(File apk) { this.apk = apk; } @Override public boolean wasUpdating() { return false; } @Override public Future update() { return null; } @Override public File getLatest() { return apk; } @Override public Future isAvailable(final File file) { return null; } } ================================================ FILE: projects/sample/maven/host-project/introduce-shadow-lib/src/main/java/com/tencent/shadow/sample/introduce_shadow_lib/InitApplication.java ================================================ package com.tencent.shadow.sample.introduce_shadow_lib; import android.app.ActivityManager; import android.app.Application; import android.content.Context; import com.tencent.shadow.core.common.LoggerFactory; import com.tencent.shadow.dynamic.host.DynamicPluginManager; import com.tencent.shadow.dynamic.host.DynamicRuntime; import com.tencent.shadow.dynamic.host.PluginManager; import java.io.File; import java.util.concurrent.Future; import static android.os.Process.myPid; public class InitApplication { /** * 这个PluginManager对象在Manager升级前后是不变的。它内部持有具体实现,升级时更换具体实现。 */ private static PluginManager sPluginManager; public static PluginManager getPluginManager() { return sPluginManager; } public static void onApplicationCreate(Application application) { //Log接口Manager也需要使用,所以主进程也初始化。 LoggerFactory.setILoggerFactory(new AndroidLoggerFactory()); if (isProcess(application, ":plugin")) { //在全动态架构中,Activity组件没有打包在宿主而是位于被动态加载的runtime, //为了防止插件crash后,系统自动恢复crash前的Activity组件,此时由于没有加载runtime而发生classNotFound异常,导致二次crash //因此这里恢复加载上一次的runtime DynamicRuntime.recoveryRuntime(application); } if (isProcess(application, application.getPackageName())) { FixedPathPmUpdater fixedPathPmUpdater = new FixedPathPmUpdater(new File("/data/local/tmp/sample-manager-debug.apk")); boolean needWaitingUpdate = fixedPathPmUpdater.wasUpdating()//之前正在更新中,暗示更新出错了,应该放弃之前的缓存 || fixedPathPmUpdater.getLatest() == null;//没有本地缓存 Future update = fixedPathPmUpdater.update(); if (needWaitingUpdate) { try { update.get();//这里是阻塞的,需要业务自行保证更新Manager足够快。 } catch (Exception e) { throw new RuntimeException("Sample程序不容错", e); } } sPluginManager = new DynamicPluginManager(fixedPathPmUpdater); } } private static boolean isProcess(Context context, String processName) { String currentProcName = ""; ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) { if (processInfo.pid == myPid()) { currentProcName = processInfo.processName; break; } } return currentProcName.endsWith(processName); } } ================================================ FILE: projects/sample/maven/host-project/introduce-shadow-lib/src/main/java/com/tencent/shadow/sample/introduce_shadow_lib/MainPluginProcessService.java ================================================ package com.tencent.shadow.sample.introduce_shadow_lib; import com.tencent.shadow.dynamic.host.PluginProcessService; /** * 一个PluginProcessService(简称PPS)代表一个插件进程。插件进程由PPS启动触发启动。 * 新建PPS子类允许一个宿主中有多个互不影响的插件进程。 */ public class MainPluginProcessService extends PluginProcessService { } ================================================ FILE: projects/sample/maven/host-project/introduce-shadow-lib/src/main/res/values/themes.xml ================================================ ================================================ FILE: projects/sample/maven/host-project/sample-host/.gitignore ================================================ /build ================================================ FILE: projects/sample/maven/host-project/sample-host/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 29 defaultConfig { applicationId "com.tencent.shadow.sample.host" minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.create("release") signingConfig.initWith(buildTypes.debug.signingConfig) } } } dependencies { implementation project(':introduce-shadow-lib') //如果introduce-shadow-lib发布到Maven,在pom中写明此依赖,宿主就不用写这个依赖了。 implementation "com.tencent.shadow.dynamic:host:$shadow_version" } ================================================ FILE: projects/sample/maven/host-project/sample-host/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keep class com.tencent.shadow.core.common.**{*;} -keep class com.tencent.shadow.core.runtime.**{*;} -keep class com.tencent.shadow.dynamic.host.**{*;} ================================================ FILE: projects/sample/maven/host-project/sample-host/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/maven/host-project/sample-host/src/main/java/com/tencent/shadow/sample/host/MainActivity.java ================================================ package com.tencent.shadow.sample.host; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import com.tencent.shadow.dynamic.host.EnterCallback; import com.tencent.shadow.dynamic.host.PluginManager; import com.tencent.shadow.sample.introduce_shadow_lib.InitApplication; public class MainActivity extends Activity { public static final int FROM_ID_START_ACTIVITY = 1001; public static final int FROM_ID_CALL_SERVICE = 1002; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final LinearLayout linearLayout = new LinearLayout(this); linearLayout.setOrientation(LinearLayout.VERTICAL); TextView textView = new TextView(this); textView.setText("宿主App"); Button button = new Button(this); button.setText("启动插件"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { v.setEnabled(false);//防止点击重入 PluginManager pluginManager = InitApplication.getPluginManager(); pluginManager.enter(MainActivity.this, FROM_ID_START_ACTIVITY, new Bundle(), new EnterCallback() { @Override public void onShowLoadingView(View view) { MainActivity.this.setContentView(view);//显示Manager传来的Loading页面 } @Override public void onCloseLoadingView() { MainActivity.this.setContentView(linearLayout); } @Override public void onEnterComplete() { v.setEnabled(true); } }); } }); linearLayout.addView(textView); linearLayout.addView(button); Button callServiceButton = new Button(this); callServiceButton.setText("调用插件Service,结果打印到Log"); callServiceButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { v.setEnabled(false);//防止点击重入 PluginManager pluginManager = InitApplication.getPluginManager(); pluginManager.enter(MainActivity.this, FROM_ID_CALL_SERVICE, null, null); } }); linearLayout.addView(callServiceButton); setContentView(linearLayout); } } ================================================ FILE: projects/sample/maven/host-project/sample-host/src/main/java/com/tencent/shadow/sample/host/MyApplication.java ================================================ package com.tencent.shadow.sample.host; import android.app.Application; import com.tencent.shadow.sample.introduce_shadow_lib.InitApplication; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); InitApplication.onApplicationCreate(this); } } ================================================ FILE: projects/sample/maven/host-project/settings.gradle ================================================ include ':sample-host', ':introduce-shadow-lib' ================================================ FILE: projects/sample/maven/manager-project/.gitignore ================================================ *.iml .gradle /local.properties .idea .DS_Store /build /captures .externalNativeBuild ================================================ FILE: projects/sample/maven/manager-project/build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.shadow_version = '2.2.1' repositories { if (!System.getenv().containsKey("DISABLE_TENCENT_MAVEN_MIRROR")) { maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' } } else { google() mavenCentral() } } dependencies { classpath 'com.android.tools.build:gradle:4.0.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { if (!System.getenv().containsKey("DISABLE_TENCENT_MAVEN_MIRROR")) { maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' } } else { google() mavenCentral() } maven { name = "GitHubPackages" url "https://maven.pkg.github.com/tencent/shadow" //一个只读账号兼容Github Packages暂时不支持匿名下载 //https://github.community/t/download-from-github-package-registry-without-authentication/14407 credentials { username = 'readonlypat' password = '\u0067hp_s3VOOZnLf1bTyvHWblPfaessrVYyEU4JdNbs' } } mavenLocal() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: projects/sample/maven/manager-project/gradle/wrapper/gradle-wrapper.properties ================================================ #Wed May 08 11:09:16 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists #distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip distributionUrl=https\://mirrors.tencent.com/gradle/gradle-6.6.1-bin.zip ================================================ FILE: projects/sample/maven/manager-project/gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx4096m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official ================================================ FILE: projects/sample/maven/manager-project/gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: projects/sample/maven/manager-project/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: projects/sample/maven/manager-project/sample-manager/.gitignore ================================================ /build ================================================ FILE: projects/sample/maven/manager-project/sample-manager/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 29 defaultConfig { applicationId "com.tencent.shadow.sample.manager" minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.create("release") signingConfig.initWith(buildTypes.debug.signingConfig) } } lintOptions { abortOnError false } } dependencies { implementation "com.tencent.shadow.dynamic:manager:$shadow_version" compileOnly "com.tencent.shadow.core:common:$shadow_version" compileOnly "com.tencent.shadow.dynamic:host:$shadow_version" } ================================================ FILE: projects/sample/maven/manager-project/sample-manager/proguard-rules.pro ================================================ -keep class com.tencent.shadow.dynamic.impl.**{*;} -keep class com.tencent.shadow.dynamic.loader.**{*;} ================================================ FILE: projects/sample/maven/manager-project/sample-manager/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/maven/manager-project/sample-manager/src/main/aidl/com/tencent/shadow/sample/plugin/IMyAidlInterface.aidl ================================================ // IMyAidlInterface.aidl package com.tencent.shadow.sample.plugin; // Declare any non-default types here with import statements interface IMyAidlInterface { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ String basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); } ================================================ FILE: projects/sample/maven/manager-project/sample-manager/src/main/java/com/tencent/shadow/dynamic/impl/ManagerFactoryImpl.java ================================================ package com.tencent.shadow.dynamic.impl; import android.content.Context; import com.tencent.shadow.dynamic.host.ManagerFactory; import com.tencent.shadow.dynamic.host.PluginManagerImpl; import com.tencent.shadow.sample.manager.SamplePluginManager; /** * 此类包名及类名固定 */ public final class ManagerFactoryImpl implements ManagerFactory { @Override public PluginManagerImpl buildManager(Context context) { return new SamplePluginManager(context); } } ================================================ FILE: projects/sample/maven/manager-project/sample-manager/src/main/java/com/tencent/shadow/dynamic/impl/WhiteList.java ================================================ package com.tencent.shadow.dynamic.impl; /** * 此类包名及类名固定 * classLoader的白名单 * PluginManager可以加载宿主中位于白名单内的类 */ public interface WhiteList { String[] sWhiteList = new String[] { }; } ================================================ FILE: projects/sample/maven/manager-project/sample-manager/src/main/java/com/tencent/shadow/sample/manager/Constant.java ================================================ package com.tencent.shadow.sample.manager; final public class Constant { public static final String KEY_PLUGIN_ZIP_PATH = "pluginZipPath"; public static final String KEY_ACTIVITY_CLASSNAME = "KEY_ACTIVITY_CLASSNAME"; public static final String KEY_EXTRAS = "KEY_EXTRAS"; public static final String KEY_PLUGIN_PART_KEY = "KEY_PLUGIN_PART_KEY"; public static final int FROM_ID_START_ACTIVITY = 1001; public static final int FROM_ID_CALL_SERVICE = 1002; } ================================================ FILE: projects/sample/maven/manager-project/sample-manager/src/main/java/com/tencent/shadow/sample/manager/FastPluginManager.java ================================================ package com.tencent.shadow.sample.manager; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.RemoteException; import com.tencent.shadow.core.common.Logger; import com.tencent.shadow.core.common.LoggerFactory; import com.tencent.shadow.core.manager.installplugin.InstalledPlugin; import com.tencent.shadow.core.manager.installplugin.InstalledType; import com.tencent.shadow.core.manager.installplugin.PluginConfig; import com.tencent.shadow.dynamic.host.FailedException; import com.tencent.shadow.dynamic.manager.PluginManagerThatUseDynamicLoader; import org.json.JSONException; import java.io.File; import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public abstract class FastPluginManager extends PluginManagerThatUseDynamicLoader { private static final Logger mLogger = LoggerFactory.getLogger(FastPluginManager.class); private ExecutorService mFixedPool = Executors.newFixedThreadPool(4); public FastPluginManager(Context context) { super(context); } public InstalledPlugin installPlugin(String zip, String hash, boolean odex) throws IOException, JSONException, InterruptedException, ExecutionException { final PluginConfig pluginConfig = installPluginFromZip(new File(zip), hash); final String uuid = pluginConfig.UUID; List futures = new LinkedList<>(); if (pluginConfig.runTime != null && pluginConfig.pluginLoader != null) { Future odexRuntime = mFixedPool.submit(new Callable() { @Override public Object call() throws Exception { oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_RUNTIME, pluginConfig.runTime.file); return null; } }); futures.add(odexRuntime); Future odexLoader = mFixedPool.submit(new Callable() { @Override public Object call() throws Exception { oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_LOADER, pluginConfig.pluginLoader.file); return null; } }); futures.add(odexLoader); } for (Map.Entry plugin : pluginConfig.plugins.entrySet()) { final String partKey = plugin.getKey(); final File apkFile = plugin.getValue().file; Future extractSo = mFixedPool.submit(new Callable() { @Override public Object call() throws Exception { extractSo(uuid, partKey, apkFile); return null; } }); futures.add(extractSo); if (odex) { Future odexPlugin = mFixedPool.submit(new Callable() { @Override public Object call() throws Exception { oDexPlugin(uuid, partKey, apkFile); return null; } }); futures.add(odexPlugin); } } for (Future future : futures) { future.get(); } onInstallCompleted(pluginConfig); return getInstalledPlugins(1).get(0); } public void startPluginActivity(Context context, InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException { Intent intent = convertActivityIntent(installedPlugin, partKey, pluginIntent); if (!(context instanceof Activity)) { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } context.startActivity(intent); } public Intent convertActivityIntent(InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException { loadPlugin(installedPlugin.UUID, partKey); return mPluginLoader.convertActivityIntent(pluginIntent); } private void loadPluginLoaderAndRuntime(String uuid) throws RemoteException, TimeoutException, FailedException { if (mPpsController == null) { bindPluginProcessService(getPluginProcessServiceName()); waitServiceConnected(10, TimeUnit.SECONDS); } loadRunTime(uuid); loadPluginLoader(uuid); } protected void loadPlugin(String uuid, String partKey) throws RemoteException, TimeoutException, FailedException { loadPluginLoaderAndRuntime(uuid); Map map = mPluginLoader.getLoadedPlugin(); if (!map.containsKey(partKey)) { mPluginLoader.loadPlugin(partKey); } Boolean isCall = (Boolean) map.get(partKey); if (isCall == null || !isCall) { mPluginLoader.callApplicationOnCreate(partKey); } } protected abstract String getPluginProcessServiceName(); } ================================================ FILE: projects/sample/maven/manager-project/sample-manager/src/main/java/com/tencent/shadow/sample/manager/SamplePluginManager.java ================================================ package com.tencent.shadow.sample.manager; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.*; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import com.tencent.shadow.core.manager.installplugin.InstalledPlugin; import com.tencent.shadow.dynamic.host.EnterCallback; import com.tencent.shadow.dynamic.loader.PluginServiceConnection; import com.tencent.shadow.sample.plugin.IMyAidlInterface; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SamplePluginManager extends FastPluginManager { private ExecutorService executorService = Executors.newSingleThreadExecutor(); private Context mCurrentContext; public SamplePluginManager(Context context) { super(context); mCurrentContext = context; } /** * @return PluginManager实现的别名,用于区分不同PluginManager实现的数据存储路径 */ @Override protected String getName() { return "sample-manager"; } /** * @return 宿主中注册的PluginProcessService实现的类名 */ @Override protected String getPluginProcessServiceName() { return "com.tencent.shadow.sample.introduce_shadow_lib.MainPluginProcessService"; } @Override public void enter(final Context context, long fromId, Bundle bundle, final EnterCallback callback) { if (fromId == Constant.FROM_ID_START_ACTIVITY) { bundle.putString(Constant.KEY_PLUGIN_ZIP_PATH, "/data/local/tmp/plugin-debug.zip"); bundle.putString(Constant.KEY_PLUGIN_PART_KEY, "sample-plugin"); bundle.putString(Constant.KEY_ACTIVITY_CLASSNAME, "com.tencent.shadow.sample.plugin.MainActivity"); onStartActivity(context, bundle, callback); } else if (fromId == Constant.FROM_ID_CALL_SERVICE) { callPluginService(context); } else { throw new IllegalArgumentException("不认识的fromId==" + fromId); } } private void onStartActivity(final Context context, Bundle bundle, final EnterCallback callback) { final String pluginZipPath = bundle.getString(Constant.KEY_PLUGIN_ZIP_PATH); final String partKey = bundle.getString(Constant.KEY_PLUGIN_PART_KEY); final String className = bundle.getString(Constant.KEY_ACTIVITY_CLASSNAME); if (className == null) { throw new NullPointerException("className == null"); } final Bundle extras = bundle.getBundle(Constant.KEY_EXTRAS); if (callback != null) { final View view = LayoutInflater.from(mCurrentContext).inflate(R.layout.activity_load_plugin, null); callback.onShowLoadingView(view); } executorService.execute(new Runnable() { @Override public void run() { try { InstalledPlugin installedPlugin = installPlugin(pluginZipPath, null, true);//这个调用是阻塞的 Intent pluginIntent = new Intent(); pluginIntent.setClassName( context.getPackageName(), className ); if (extras != null) { pluginIntent.replaceExtras(extras); } startPluginActivity(context, installedPlugin, partKey, pluginIntent); } catch (Exception e) { throw new RuntimeException(e); } if (callback != null) { Handler uiHandler = new Handler(Looper.getMainLooper()); uiHandler.post(new Runnable() { @Override public void run() { callback.onCloseLoadingView(); callback.onEnterComplete(); } }); } } }); } private void callPluginService(final Context context) { final String pluginZipPath = "/data/local/tmp/plugin-debug.zip"; final String partKey = "sample-plugin"; final String className = "com.tencent.shadow.sample.plugin.MyService"; Intent pluginIntent = new Intent(); pluginIntent.setClassName(context.getPackageName(), className); executorService.execute(new Runnable() { @Override public void run() { try { InstalledPlugin installedPlugin = installPlugin(pluginZipPath, null, true);//这个调用是阻塞的 loadPlugin(installedPlugin.UUID, partKey); Intent pluginIntent = new Intent(); pluginIntent.setClassName(context.getPackageName(), className); boolean callSuccess = mPluginLoader.bindPluginService(pluginIntent, new PluginServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder); try { String s = iMyAidlInterface.basicTypes(1, 2, true, 4.0f, 5.0, "6"); Log.i("SamplePluginManager", "iMyAidlInterface.basicTypes : " + s); } catch (RemoteException e) { throw new RuntimeException(e); } } @Override public void onServiceDisconnected(ComponentName componentName) { throw new RuntimeException("onServiceDisconnected"); } }, Service.BIND_AUTO_CREATE); if (!callSuccess) { throw new RuntimeException("bind service失败 className==" + className); } } catch (Exception e) { throw new RuntimeException(e); } } }); } } ================================================ FILE: projects/sample/maven/manager-project/sample-manager/src/main/res/layout/activity_load_plugin.xml ================================================ ================================================ FILE: projects/sample/maven/manager-project/settings.gradle ================================================ include ':sample-manager' ================================================ FILE: projects/sample/maven/plugin-project/.gitignore ================================================ *.iml .gradle /local.properties .idea .DS_Store /build /captures .externalNativeBuild ================================================ FILE: projects/sample/maven/plugin-project/build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.shadow_version = '2.2.1' repositories { if (!System.getenv().containsKey("DISABLE_TENCENT_MAVEN_MIRROR")) { maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' } } else { google() mavenCentral() } } dependencies { classpath 'com.android.tools.build:gradle:4.0.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { if (!System.getenv().containsKey("DISABLE_TENCENT_MAVEN_MIRROR")) { maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' } } else { google() mavenCentral() } maven { name = "GitHubPackages" url "https://maven.pkg.github.com/tencent/shadow" //一个只读账号兼容Github Packages暂时不支持匿名下载 //https://github.community/t/download-from-github-package-registry-without-authentication/14407 credentials { username = 'readonlypat' password = '\u0067hp_s3VOOZnLf1bTyvHWblPfaessrVYyEU4JdNbs' } } mavenLocal() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: projects/sample/maven/plugin-project/gradle/wrapper/gradle-wrapper.properties ================================================ #Wed May 08 11:09:16 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists #distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip distributionUrl=https\://mirrors.tencent.com/gradle/gradle-6.6.1-bin.zip ================================================ FILE: projects/sample/maven/plugin-project/gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx4096m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official ================================================ FILE: projects/sample/maven/plugin-project/gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: projects/sample/maven/plugin-project/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: projects/sample/maven/plugin-project/plugin-app/.gitignore ================================================ /build ================================================ FILE: projects/sample/maven/plugin-project/plugin-app/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'com.tencent.shadow.plugin' android { compileSdkVersion 30 defaultConfig { applicationId "com.tencent.shadow.sample.plugin" minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.create("release") signingConfig.initWith(buildTypes.debug.signingConfig) } } // 将插件applicationId设置为和宿主相同 productFlavors { plugin { applicationId "com.tencent.shadow.sample.host" } } } dependencies { //Shadow Transform后业务代码会有一部分实际引用runtime中的类 //如果不以compileOnly方式依赖,会导致其他Transform或者Proguard找不到这些类 pluginCompileOnly "com.tencent.shadow.core:runtime:$shadow_version" } //这段buildscript配置的dependencies是为了apply plugin: 'com.tencent.shadow.plugin'能找到实现 buildscript { repositories { if (!System.getenv().containsKey("DISABLE_TENCENT_MAVEN_MIRROR")) { maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' } } else { google() mavenCentral() } maven { name = "GitHubPackages" url "https://maven.pkg.github.com/tencent/shadow" //一个只读账号兼容Github Packages暂时不支持匿名下载 //https://github.community/t/download-from-github-package-registry-without-authentication/14407 credentials { username = 'readonlypat' password = '\u0067hp_s3VOOZnLf1bTyvHWblPfaessrVYyEU4JdNbs' } } mavenLocal() } dependencies { classpath "com.tencent.shadow.core:gradle-plugin:$shadow_version" } } shadow { packagePlugin { pluginTypes { debug { loaderApkConfig = new Tuple2('sample-loader-debug.apk', ':sample-loader:assembleDebug') runtimeApkConfig = new Tuple2('sample-runtime-debug.apk', ':sample-runtime:assembleDebug') pluginApks { pluginApk1 { businessName = 'sample-plugin'//businessName相同的插件,context获取的Dir是相同的。businessName留空,表示和宿主相同业务,直接使用宿主的Dir。 partKey = 'sample-plugin' buildTask = 'assemblePluginDebug' apkName = 'plugin-app-plugin-debug.apk' apkPath = 'plugin-app/build/outputs/apk/plugin/debug/plugin-app-plugin-debug.apk' } } } release { loaderApkConfig = new Tuple2('sample-loader-release.apk', ':sample-loader:assembleRelease') runtimeApkConfig = new Tuple2('sample-runtime-release.apk', ':sample-runtime:assembleRelease') pluginApks { pluginApk1 { businessName = 'demo' partKey = 'sample-plugin' buildTask = 'assemblePluginRelease' apkName = 'plugin-app-plugin-release.apk' apkPath = 'plugin-app/build/outputs/apk/plugin/release/plugin-app-plugin-release.apk' } } } } loaderApkProjectPath = 'sample-loader' runtimeApkProjectPath = 'sample-runtime' version = 4 compactVersion = [1, 2, 3] uuidNickName = "1.1.5" } } ================================================ FILE: projects/sample/maven/plugin-project/plugin-app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: projects/sample/maven/plugin-project/plugin-app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/maven/plugin-project/plugin-app/src/main/aidl/com/tencent/shadow/sample/plugin/IMyAidlInterface.aidl ================================================ // IMyAidlInterface.aidl package com.tencent.shadow.sample.plugin; // Declare any non-default types here with import statements interface IMyAidlInterface { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ String basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); } ================================================ FILE: projects/sample/maven/plugin-project/plugin-app/src/main/java/com/tencent/shadow/sample/plugin/MainActivity.java ================================================ package com.tencent.shadow.sample.plugin; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } ================================================ FILE: projects/sample/maven/plugin-project/plugin-app/src/main/java/com/tencent/shadow/sample/plugin/MyService.java ================================================ package com.tencent.shadow.sample.plugin; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; public class MyService extends Service { public MyService() { } @Override public IBinder onBind(Intent intent) { return new IMyAidlInterface.Stub() { @Override public String basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { return Integer.toString(anInt) + aLong + aBoolean + aFloat + aDouble + aString; } }; } } ================================================ FILE: projects/sample/maven/plugin-project/plugin-app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: projects/sample/maven/plugin-project/plugin-app/src/main/res/values/colors.xml ================================================ #008577 #00574B #D81B60 ================================================ FILE: projects/sample/maven/plugin-project/plugin-app/src/main/res/values/strings.xml ================================================ Shadow Sample Plugin ================================================ FILE: projects/sample/maven/plugin-project/plugin-app/src/main/res/values/styles.xml ================================================ ================================================ FILE: projects/sample/maven/plugin-project/sample-loader/.gitignore ================================================ /build ================================================ FILE: projects/sample/maven/plugin-project/sample-loader/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 29 defaultConfig { applicationId "com.tencent.shadow.sample.loader"//applicationId不重要 minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.create("release") signingConfig.initWith(buildTypes.debug.signingConfig) } } } dependencies { implementation "com.tencent.shadow.dynamic:loader-impl:$shadow_version" compileOnly "com.tencent.shadow.core:activity-container:$shadow_version" compileOnly "com.tencent.shadow.core:common:$shadow_version" //下面这行依赖是为了防止在proguard的时候找不到LoaderFactory接口 compileOnly "com.tencent.shadow.dynamic:host:$shadow_version" } ================================================ FILE: projects/sample/maven/plugin-project/sample-loader/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile #kotlin一般性配置 START -dontwarn kotlin.** -keepclassmembers class **$WhenMappings { ; } -keepclassmembers class kotlin.Metadata { public ; } #kotlin一般性配置 END #kotlin优化性能 START -assumenosideeffects class kotlin.jvm.internal.Intrinsics { static void checkParameterIsNotNull(java.lang.Object, java.lang.String); } #kotlin优化性能 END -keep class org.slf4j.**{*;} -dontwarn org.slf4j.impl.** -keep class com.tencent.shadow.dynamic.host.**{*;} -keep class com.tencent.shadow.dynamic.impl.**{*;} -keep class com.tencent.shadow.dynamic.loader.**{*;} -keep class com.tencent.shadow.core.common.**{*;} -keep class com.tencent.shadow.core.loader.**{*;} -keep class com.tencent.shadow.core.runtime.**{*;} -dontwarn com.tencent.shadow.dynamic.host.** -dontwarn com.tencent.shadow.dynamic.impl.** -dontwarn com.tencent.shadow.dynamic.loader.** -dontwarn com.tencent.shadow.core.common.** -dontwarn com.tencent.shadow.core.loader.** -dontwarn module-info ================================================ FILE: projects/sample/maven/plugin-project/sample-loader/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/maven/plugin-project/sample-loader/src/main/java/com/tencent/shadow/dynamic/loader/impl/CoreLoaderFactoryImpl.java ================================================ package com.tencent.shadow.dynamic.loader.impl; import android.content.Context; import com.tencent.shadow.core.loader.ShadowPluginLoader; import com.tencent.shadow.sample.loader.SamplePluginLoader; import org.jetbrains.annotations.NotNull; /** * 这个类的包名类名是固定的。 *

* 见com.tencent.shadow.dynamic.loader.impl.DynamicPluginLoader#CORE_LOADER_FACTORY_IMPL_NAME */ public class CoreLoaderFactoryImpl implements CoreLoaderFactory { @NotNull @Override public ShadowPluginLoader build(@NotNull Context context) { return new SamplePluginLoader(context); } } ================================================ FILE: projects/sample/maven/plugin-project/sample-loader/src/main/java/com/tencent/shadow/sample/loader/SampleComponentManager.java ================================================ package com.tencent.shadow.sample.loader; import android.content.ComponentName; import android.content.Context; import com.tencent.shadow.core.loader.infos.ContainerProviderInfo; import com.tencent.shadow.core.loader.managers.ComponentManager; import java.util.ArrayList; import java.util.List; public class SampleComponentManager extends ComponentManager { /** * sample-runtime 模块中定义的壳子Activity,需要在宿主AndroidManifest.xml注册 */ private static final String DEFAULT_ACTIVITY = "com.tencent.shadow.sample.runtime.PluginDefaultProxyActivity"; private static final String SINGLE_INSTANCE_ACTIVITY = "com.tencent.shadow.sample.runtime.PluginSingleInstance1ProxyActivity"; private static final String SINGLE_TASK_ACTIVITY = "com.tencent.shadow.sample.runtime.PluginSingleTask1ProxyActivity"; private Context context; public SampleComponentManager(Context context) { this.context = context; } /** * 配置插件Activity 到 壳子Activity的对应关系 * * @param pluginActivity 插件Activity * @return 壳子Activity */ @Override public ComponentName onBindContainerActivity(ComponentName pluginActivity) { switch (pluginActivity.getClassName()) { /** * 这里配置对应的对应关系 */ } return new ComponentName(context, DEFAULT_ACTIVITY); } /** * 配置对应宿主中预注册的壳子contentProvider的信息 */ @Override public ContainerProviderInfo onBindContainerContentProvider(ComponentName pluginContentProvider) { return new ContainerProviderInfo( "com.tencent.shadow.runtime.container.PluginContainerContentProvider", "com.tencent.shadow.contentprovider.authority.dynamic"); } } ================================================ FILE: projects/sample/maven/plugin-project/sample-loader/src/main/java/com/tencent/shadow/sample/loader/SamplePluginLoader.java ================================================ package com.tencent.shadow.sample.loader; import android.content.Context; import com.tencent.shadow.core.loader.ShadowPluginLoader; import com.tencent.shadow.core.loader.managers.ComponentManager; import com.tencent.shadow.sample.loader.SampleComponentManager; /** * 这里的类名和包名需要固定 * com.tencent.shadow.sdk.pluginloader.PluginLoaderImpl */ public class SamplePluginLoader extends ShadowPluginLoader { private final static String TAG = "shadow"; private ComponentManager componentManager; public SamplePluginLoader(Context hostAppContext) { super(hostAppContext); componentManager = new SampleComponentManager(hostAppContext); } @Override public ComponentManager getComponentManager() { return componentManager; } } ================================================ FILE: projects/sample/maven/plugin-project/sample-runtime/.gitignore ================================================ /build ================================================ FILE: projects/sample/maven/plugin-project/sample-runtime/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 29 defaultConfig { applicationId "com.tencent.shadow.sample.runtime"//applicationId不重要 minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.create("release") signingConfig.initWith(buildTypes.debug.signingConfig) } } } dependencies { implementation "com.tencent.shadow.core:activity-container:$shadow_version" } ================================================ FILE: projects/sample/maven/plugin-project/sample-runtime/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keep class org.slf4j.**{*;} -dontwarn org.slf4j.impl.** -keep class com.tencent.shadow.core.runtime.**{*;} #需要keep在宿主AndroidManifest.xml注册的壳子activity -keep class com.tencent.shadow.sample.runtime.**{*;} #GeneratedPluginContainerActivity包含新版本API的接口,可能在业务编译时使用的低版本compileSDK中找不到 -dontwarn com.tencent.shadow.core.runtime.container.GeneratedPluginContainerActivity ================================================ FILE: projects/sample/maven/plugin-project/sample-runtime/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/maven/plugin-project/sample-runtime/src/main/java/com/tencent/shadow/sample/runtime/PluginDefaultProxyActivity.java ================================================ package com.tencent.shadow.sample.runtime; import com.tencent.shadow.core.runtime.container.PluginContainerActivity; public class PluginDefaultProxyActivity extends PluginContainerActivity { } ================================================ FILE: projects/sample/maven/plugin-project/sample-runtime/src/main/java/com/tencent/shadow/sample/runtime/PluginSingleInstance1ProxyActivity.java ================================================ package com.tencent.shadow.sample.runtime; import com.tencent.shadow.core.runtime.container.PluginContainerActivity; public class PluginSingleInstance1ProxyActivity extends PluginContainerActivity { } ================================================ FILE: projects/sample/maven/plugin-project/sample-runtime/src/main/java/com/tencent/shadow/sample/runtime/PluginSingleTask1ProxyActivity.java ================================================ package com.tencent.shadow.sample.runtime; import com.tencent.shadow.core.runtime.container.PluginContainerActivity; public class PluginSingleTask1ProxyActivity extends PluginContainerActivity { } ================================================ FILE: projects/sample/maven/plugin-project/settings.gradle ================================================ include ':plugin-app' include ':sample-runtime' include ':sample-loader' ================================================ FILE: projects/sample/source/sample-constant/.gitignore ================================================ /build ================================================ FILE: projects/sample/source/sample-constant/build.gradle ================================================ apply plugin: 'com.android.library' android { compileSdkVersion project.COMPILE_SDK_VERSION defaultConfig { minSdkVersion project.MIN_SDK_VERSION targetSdkVersion project.TARGET_SDK_VERSION versionCode project.VERSION_CODE versionName project.VERSION_NAME testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } ================================================ FILE: projects/sample/source/sample-constant/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: projects/sample/source/sample-constant/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/source/sample-constant/src/main/java/com/tencent/shadow/sample/constant/Constant.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.constant; final public class Constant { public static final String KEY_PLUGIN_ZIP_PATH = "pluginZipPath"; public static final String KEY_ACTIVITY_CLASSNAME = "KEY_ACTIVITY_CLASSNAME"; public static final String KEY_EXTRAS = "KEY_EXTRAS"; public static final String KEY_PLUGIN_PART_KEY = "KEY_PLUGIN_PART_KEY"; public static final String PART_KEY_PLUGIN_MAIN_APP = "sample-plugin-app"; public static final String PART_KEY_PLUGIN_ANOTHER_APP = "sample-plugin-app2"; public static final String PART_KEY_PLUGIN_BASE = "sample-base"; public static final int FROM_ID_NOOP = 1000; public static final int FROM_ID_START_ACTIVITY = 1002; public static final int FROM_ID_CLOSE = 1003; public static final int FROM_ID_LOAD_VIEW_TO_HOST = 1004; } ================================================ FILE: projects/sample/source/sample-host/.gitignore ================================================ /build ================================================ FILE: projects/sample/source/sample-host/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion project.COMPILE_SDK_VERSION defaultConfig { applicationId project.SAMPLE_HOST_APP_APPLICATION_ID minSdkVersion project.MIN_SDK_VERSION targetSdkVersion project.TARGET_SDK_VERSION versionCode project.VERSION_CODE versionName project.VERSION_NAME testInstrumentationRunner "com.tencent.shadow.test.CustomAndroidJUnitRunner" } buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.create("release") signingConfig.initWith(buildTypes.debug.signingConfig) } } sourceSets { debug { assets.srcDir('build/generated/assets/sample-manager/debug/') assets.srcDir('build/generated/assets/plugin-zip/debug/') } release { assets.srcDir('build/generated/assets/sample-manager/release/') assets.srcDir('build/generated/assets/plugin-zip/release/') } } lintOptions { checkReleaseBuilds false abortOnError false } } dependencies { implementation "commons-io:commons-io:$commons_io_android_version"//sample-host从assets中复制插件用的 implementation "org.slf4j:slf4j-api:$slf4j_version" implementation 'com.tencent.shadow.core:common' implementation 'com.tencent.shadow.dynamic:dynamic-host' implementation project(':sample-constant') implementation project(':sample-host-lib') } def createCopyTask(projectName, buildType, name, apkName, inputFile, taskName) { def outputFile = file("${getBuildDir()}/generated/assets/${name}/${buildType}/${apkName}") outputFile.getParentFile().mkdirs() return tasks.create("copy${buildType.capitalize()}${name.capitalize()}Task", Copy) { group = 'build' description = "复制${name}到assets中." from(inputFile.getParent()) { include(inputFile.name) rename { outputFile.name } } into(outputFile.getParent()) }.dependsOn("${projectName}:${taskName}") } def generateAssets(generateAssetsTask, buildType) { def moduleName = 'sample-manager' def pluginManagerApkFile = file( "${project(":sample-manager").getBuildDir()}" + "/outputs/apk/${buildType}/" + "${moduleName}-${buildType}.apk" ) generateAssetsTask.dependsOn createCopyTask( ':sample-manager', buildType, moduleName, 'pluginmanager.apk', pluginManagerApkFile, "assemble${buildType.capitalize()}" ) def pluginZip = file("${getRootProject().getBuildDir()}/plugin-${buildType}.zip") generateAssetsTask.dependsOn createCopyTask( ':sample-app', buildType, 'plugin-zip', "plugin-${buildType}.zip", pluginZip, "package${buildType.capitalize()}Plugin" ) } tasks.whenTaskAdded { task -> if (task.name == "generateDebugAssets") { generateAssets(task, 'debug') } if (task.name == "generateReleaseAssets") { generateAssets(task, 'release') } } ================================================ FILE: projects/sample/source/sample-host/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keep class org.slf4j.**{*;} -dontwarn org.slf4j.impl.** -keep class com.tencent.shadow.dynamic.host.**{*;} -keep class com.tencent.shadow.core.common.**{*;} -keep class com.tencent.shadow.core.runtime.container.**{*;} ================================================ FILE: projects/sample/source/sample-host/src/main/AndroidManifest.xml ================================================ ================================================ FILE: projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/AndroidLogLoggerFactory.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host; import android.util.Log; import com.tencent.shadow.core.common.ILoggerFactory; import com.tencent.shadow.core.common.Logger; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class AndroidLogLoggerFactory implements ILoggerFactory { private static final int LOG_LEVEL_TRACE = 5; private static final int LOG_LEVEL_DEBUG = 4; private static final int LOG_LEVEL_INFO = 3; private static final int LOG_LEVEL_WARN = 2; private static final int LOG_LEVEL_ERROR = 1; private static AndroidLogLoggerFactory sInstance = new AndroidLogLoggerFactory(); public static ILoggerFactory getInstance() { return sInstance; } final private ConcurrentMap loggerMap = new ConcurrentHashMap(); public Logger getLogger(String name) { Logger simpleLogger = loggerMap.get(name); if (simpleLogger != null) { return simpleLogger; } else { Logger newInstance = new IVLogger(name); Logger oldInstance = loggerMap.putIfAbsent(name, newInstance); return oldInstance == null ? newInstance : oldInstance; } } class IVLogger implements Logger { private String name; IVLogger(String name) { this.name = name; } @Override public String getName() { return name; } private void log(int level, String message, Throwable t) { final String tag = String.valueOf(name); switch (level) { case LOG_LEVEL_TRACE: case LOG_LEVEL_DEBUG: if (t == null) Log.d(tag, message); else Log.d(tag, message, t); break; case LOG_LEVEL_INFO: if (t == null) Log.i(tag, message); else Log.i(tag, message, t); break; case LOG_LEVEL_WARN: if (t == null) Log.w(tag, message); else Log.w(tag, message, t); break; case LOG_LEVEL_ERROR: if (t == null) Log.e(tag, message); else Log.e(tag, message, t); break; default: break; } } @Override public boolean isTraceEnabled() { return true; } @Override public void trace(String msg) { log(LOG_LEVEL_TRACE, msg, null); } @Override public void trace(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_TRACE, tuple.getMessage(), null); } @Override public void trace(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_TRACE, tuple.getMessage(), null); } @Override public void trace(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_TRACE, tuple.getMessage(), null); } @Override public void trace(String msg, Throwable throwable) { log(LOG_LEVEL_TRACE, msg, throwable); } @Override public boolean isDebugEnabled() { return true; } @Override public void debug(String msg) { log(LOG_LEVEL_DEBUG, msg, null); } @Override public void debug(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_DEBUG, tuple.getMessage(), null); } @Override public void debug(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_DEBUG, tuple.getMessage(), null); } @Override public void debug(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_DEBUG, tuple.getMessage(), null); } @Override public void debug(String msg, Throwable throwable) { log(LOG_LEVEL_DEBUG, msg, throwable); } @Override public boolean isInfoEnabled() { return true; } @Override public void info(String msg) { log(LOG_LEVEL_INFO, msg, null); } @Override public void info(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_INFO, tuple.getMessage(), null); } @Override public void info(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_INFO, tuple.getMessage(), null); } @Override public void info(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_INFO, tuple.getMessage(), null); } @Override public void info(String msg, Throwable throwable) { log(LOG_LEVEL_INFO, msg, throwable); } @Override public boolean isWarnEnabled() { return true; } @Override public void warn(String msg) { log(LOG_LEVEL_WARN, msg, null); } @Override public void warn(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_WARN, tuple.getMessage(), null); } @Override public void warn(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_WARN, tuple.getMessage(), null); } @Override public void warn(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_WARN, tuple.getMessage(), null); } @Override public void warn(String msg, Throwable throwable) { log(LOG_LEVEL_WARN, msg, throwable); } @Override public boolean isErrorEnabled() { return true; } @Override public void error(String msg) { log(LOG_LEVEL_ERROR, msg, null); } @Override public void error(String format, Object o) { FormattingTuple tuple = MessageFormatter.format(format, o); log(LOG_LEVEL_ERROR, tuple.getMessage(), null); } @Override public void error(String format, Object o, Object o1) { FormattingTuple tuple = MessageFormatter.format(format, o, o1); log(LOG_LEVEL_ERROR, tuple.getMessage(), null); } @Override public void error(String format, Object... objects) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, objects); log(LOG_LEVEL_ERROR, tuple.getMessage(), null); } @Override public void error(String msg, Throwable throwable) { log(LOG_LEVEL_ERROR, msg, throwable); } } } class FormattingTuple { static public FormattingTuple NULL = new FormattingTuple(null); private String message; private Throwable throwable; private Object[] argArray; public FormattingTuple(String message) { this(message, null, null); } public FormattingTuple(String message, Object[] argArray, Throwable throwable) { this.message = message; this.throwable = throwable; this.argArray = argArray; } public String getMessage() { return message; } public Object[] getArgArray() { return argArray; } public Throwable getThrowable() { return throwable; } } final class MessageFormatter { static final char DELIM_START = '{'; static final char DELIM_STOP = '}'; static final String DELIM_STR = "{}"; private static final char ESCAPE_CHAR = '\\'; /** * Performs single argument substitution for the 'messagePattern' passed as * parameter. *

* For example, * *

     * MessageFormatter.format("Hi {}.", "there");
     * 
*

* will return the string "Hi there.". *

* * @param messagePattern The message pattern which will be parsed and formatted * @param arg The argument to be substituted in place of the formatting anchor * @return The formatted message */ final public static FormattingTuple format(String messagePattern, Object arg) { return arrayFormat(messagePattern, new Object[]{arg}); } /** * Performs a two argument substitution for the 'messagePattern' passed as * parameter. *

* For example, * *

     * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob");
     * 
*

* will return the string "Hi Alice. My name is Bob.". * * @param messagePattern The message pattern which will be parsed and formatted * @param arg1 The argument to be substituted in place of the first formatting * anchor * @param arg2 The argument to be substituted in place of the second formatting * anchor * @return The formatted message */ final public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) { return arrayFormat(messagePattern, new Object[]{arg1, arg2}); } static final Throwable getThrowableCandidate(Object[] argArray) { if (argArray == null || argArray.length == 0) { return null; } final Object lastEntry = argArray[argArray.length - 1]; if (lastEntry instanceof Throwable) { return (Throwable) lastEntry; } return null; } final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) { Throwable throwableCandidate = getThrowableCandidate(argArray); Object[] args = argArray; if (throwableCandidate != null) { args = trimmedCopy(argArray); } return arrayFormat(messagePattern, args, throwableCandidate); } private static Object[] trimmedCopy(Object[] argArray) { if (argArray == null || argArray.length == 0) { throw new IllegalStateException("non-sensical empty or null argument array"); } final int trimemdLen = argArray.length - 1; Object[] trimmed = new Object[trimemdLen]; System.arraycopy(argArray, 0, trimmed, 0, trimemdLen); return trimmed; } final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) { if (messagePattern == null) { return new FormattingTuple(null, argArray, throwable); } if (argArray == null) { return new FormattingTuple(messagePattern); } int i = 0; int j; // use string builder for better multicore performance StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); int L; for (L = 0; L < argArray.length; L++) { j = messagePattern.indexOf(DELIM_STR, i); if (j == -1) { // no more variables if (i == 0) { // this is a simple string return new FormattingTuple(messagePattern, argArray, throwable); } else { // add the tail string which contains no variables and return // the result. sbuf.append(messagePattern, i, messagePattern.length()); return new FormattingTuple(sbuf.toString(), argArray, throwable); } } else { if (isEscapedDelimeter(messagePattern, j)) { if (!isDoubleEscaped(messagePattern, j)) { L--; // DELIM_START was escaped, thus should not be incremented sbuf.append(messagePattern, i, j - 1); sbuf.append(DELIM_START); i = j + 1; } else { // The escape character preceding the delimiter start is // itself escaped: "abc x:\\{}" // we have to consume one backward slash sbuf.append(messagePattern, i, j - 1); deeplyAppendParameter(sbuf, argArray[L], new HashMap()); i = j + 2; } } else { // normal case sbuf.append(messagePattern, i, j); deeplyAppendParameter(sbuf, argArray[L], new HashMap()); i = j + 2; } } } // append the characters following the last {} pair. sbuf.append(messagePattern, i, messagePattern.length()); return new FormattingTuple(sbuf.toString(), argArray, throwable); } final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) { if (delimeterStartIndex == 0) { return false; } char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1); if (potentialEscape == ESCAPE_CHAR) { return true; } else { return false; } } final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) { if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) { return true; } else { return false; } } // special treatment of array values was suggested by 'lizongbo' private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map seenMap) { if (o == null) { sbuf.append("null"); return; } if (!o.getClass().isArray()) { safeObjectAppend(sbuf, o); } else { // check for primitive array types because they // unfortunately cannot be cast to Object[] if (o instanceof boolean[]) { booleanArrayAppend(sbuf, (boolean[]) o); } else if (o instanceof byte[]) { byteArrayAppend(sbuf, (byte[]) o); } else if (o instanceof char[]) { charArrayAppend(sbuf, (char[]) o); } else if (o instanceof short[]) { shortArrayAppend(sbuf, (short[]) o); } else if (o instanceof int[]) { intArrayAppend(sbuf, (int[]) o); } else if (o instanceof long[]) { longArrayAppend(sbuf, (long[]) o); } else if (o instanceof float[]) { floatArrayAppend(sbuf, (float[]) o); } else if (o instanceof double[]) { doubleArrayAppend(sbuf, (double[]) o); } else { objectArrayAppend(sbuf, (Object[]) o, seenMap); } } } private static void safeObjectAppend(StringBuilder sbuf, Object o) { try { String oAsString = o.toString(); sbuf.append(oAsString); } catch (Throwable t) { sbuf.append("[FAILED toString()]"); } } private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map seenMap) { sbuf.append('['); if (!seenMap.containsKey(a)) { seenMap.put(a, null); final int len = a.length; for (int i = 0; i < len; i++) { deeplyAppendParameter(sbuf, a[i], seenMap); if (i != len - 1) sbuf.append(", "); } // allow repeats in siblings seenMap.remove(a); } else { sbuf.append("..."); } sbuf.append(']'); } private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void byteArrayAppend(StringBuilder sbuf, byte[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void charArrayAppend(StringBuilder sbuf, char[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void shortArrayAppend(StringBuilder sbuf, short[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void intArrayAppend(StringBuilder sbuf, int[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void longArrayAppend(StringBuilder sbuf, long[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void floatArrayAppend(StringBuilder sbuf, float[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } private static void doubleArrayAppend(StringBuilder sbuf, double[] a) { sbuf.append('['); final int len = a.length; for (int i = 0; i < len; i++) { sbuf.append(a[i]); if (i != len - 1) sbuf.append(", "); } sbuf.append(']'); } } ================================================ FILE: projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/HostApplication.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host; import static android.os.Process.myPid; import android.app.ActivityManager; import android.app.Application; import android.content.Context; import android.os.Build; import android.os.StrictMode; import android.webkit.WebView; import com.tencent.shadow.core.common.LoggerFactory; import com.tencent.shadow.dynamic.host.DynamicRuntime; import com.tencent.shadow.dynamic.host.PluginManager; import com.tencent.shadow.sample.host.lib.HostUiLayerProvider; import com.tencent.shadow.sample.host.manager.Shadow; import java.io.File; public class HostApplication extends Application { private static HostApplication sApp; private PluginManager mPluginManager; @Override public void onCreate() { super.onCreate(); sApp = this; detectNonSdkApiUsageOnAndroidP(); setWebViewDataDirectorySuffix(); LoggerFactory.setILoggerFactory(new AndroidLogLoggerFactory()); if (isProcess(this, ":plugin")) { //在全动态架构中,Activity组件没有打包在宿主而是位于被动态加载的runtime, //为了防止插件crash后,系统自动恢复crash前的Activity组件,此时由于没有加载runtime而发生classNotFound异常,导致二次crash //因此这里恢复加载上一次的runtime DynamicRuntime.recoveryRuntime(this); } if (isProcess(this, getPackageName())) { PluginHelper.getInstance().init(this); } HostUiLayerProvider.init(this); } private static void setWebViewDataDirectorySuffix() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { return; } WebView.setDataDirectorySuffix(Application.getProcessName()); } private static void detectNonSdkApiUsageOnAndroidP() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { return; } StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); builder.detectNonSdkApiUsage(); StrictMode.setVmPolicy(builder.build()); } public static HostApplication getApp() { return sApp; } public void loadPluginManager(File apk) { if (mPluginManager == null) { mPluginManager = Shadow.getPluginManager(apk); } } public PluginManager getPluginManager() { return mPluginManager; } private static boolean isProcess(Context context, String processName) { String currentProcName = ""; ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) { if (processInfo.pid == myPid()) { currentProcName = processInfo.processName; break; } } return currentProcName.endsWith(processName); } } ================================================ FILE: projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/MainActivity.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host; import static com.tencent.shadow.sample.constant.Constant.PART_KEY_PLUGIN_BASE; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; import com.tencent.shadow.sample.constant.Constant; import com.tencent.shadow.sample.host.plugin_view.HostAddPluginViewActivity; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTheme(R.style.TestHostTheme); LinearLayout rootView = new LinearLayout(this); rootView.setOrientation(LinearLayout.VERTICAL); TextView infoTextView = new TextView(this); infoTextView.setText(R.string.main_activity_info); rootView.addView(infoTextView); final Spinner partKeySpinner = new Spinner(this); ArrayAdapter partKeysAdapter = new ArrayAdapter<>(this, R.layout.part_key_adapter); partKeysAdapter.addAll( Constant.PART_KEY_PLUGIN_MAIN_APP, Constant.PART_KEY_PLUGIN_ANOTHER_APP ); partKeySpinner.setAdapter(partKeysAdapter); rootView.addView(partKeySpinner); Button startPluginButton = new Button(this); startPluginButton.setText(R.string.start_plugin); startPluginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String partKey = (String) partKeySpinner.getSelectedItem(); Intent intent = new Intent(MainActivity.this, PluginLoadActivity.class); switch (partKey) { //为了演示多进程多插件,其实两个插件内容完全一样,除了所在进程 case Constant.PART_KEY_PLUGIN_MAIN_APP: intent.putExtra(Constant.KEY_PLUGIN_PART_KEY, PART_KEY_PLUGIN_BASE); break; case Constant.PART_KEY_PLUGIN_ANOTHER_APP: intent.putExtra(Constant.KEY_PLUGIN_PART_KEY, partKey); ; break; } switch (partKey) { //为了演示多进程多插件,其实两个插件内容完全一样,除了所在进程 case Constant.PART_KEY_PLUGIN_MAIN_APP: case Constant.PART_KEY_PLUGIN_ANOTHER_APP: intent.putExtra(Constant.KEY_ACTIVITY_CLASSNAME, "com.tencent.shadow.sample.plugin.app.lib.gallery.splash.SplashActivity"); break; } startActivity(intent); } }); rootView.addView(startPluginButton); Button startHostAddPluginViewActivityButton = new Button(this); startHostAddPluginViewActivityButton.setText("宿主添加插件View"); startHostAddPluginViewActivityButton.setOnClickListener(v -> { Intent intent = new Intent(this, HostAddPluginViewActivity.class); startActivity(intent); }); rootView.addView(startHostAddPluginViewActivityButton); setContentView(rootView); } } ================================================ FILE: projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/Plugin2ProcessPPS.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host; import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.util.Log; import com.tencent.shadow.dynamic.host.PluginProcessService; import com.tencent.shadow.sample.host.lib.LoadPluginCallback; public class Plugin2ProcessPPS extends PluginProcessService { public Plugin2ProcessPPS() { LoadPluginCallback.setCallback(new LoadPluginCallback.Callback() { @Override public void beforeLoadPlugin(String partKey) { Log.d("Plugin2ProcessPPS", "beforeLoadPlugin(" + partKey + ")"); } @Override public void afterLoadPlugin(String partKey, ApplicationInfo applicationInfo, ClassLoader pluginClassLoader, Resources pluginResources) { Log.d("Plugin2ProcessPPS", "afterLoadPlugin(" + partKey + "," + applicationInfo.className + "{metaData=" + applicationInfo.metaData + "}" + "," + pluginClassLoader + ")"); } }); } } ================================================ FILE: projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/PluginHelper.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host; import android.content.Context; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class PluginHelper { /** * 动态加载的插件管理apk */ public final static String sPluginManagerName = "pluginmanager.apk"; /** * 动态加载的插件包,里面包含以下几个部分,插件apk,插件框架apk(loader apk和runtime apk), apk信息配置关系json文件 */ public final static String sPluginZip = BuildConfig.DEBUG ? "plugin-debug.zip" : "plugin-release.zip"; public File pluginManagerFile; public File pluginZipFile; public ExecutorService singlePool = Executors.newSingleThreadExecutor(); private Context mContext; private static PluginHelper sInstance = new PluginHelper(); public static PluginHelper getInstance() { return sInstance; } private PluginHelper() { } public void init(Context context) { pluginManagerFile = new File(context.getFilesDir(), sPluginManagerName); pluginZipFile = new File(context.getFilesDir(), sPluginZip); mContext = context.getApplicationContext(); singlePool.execute(new Runnable() { @Override public void run() { preparePlugin(); } }); } private void preparePlugin() { try { //noinspection ResultOfMethodCallIgnored pluginManagerFile.setWritable(true); InputStream is = mContext.getAssets().open(sPluginManagerName); FileUtils.copyInputStreamToFile(is, pluginManagerFile); InputStream zip = mContext.getAssets().open(sPluginZip); FileUtils.copyInputStreamToFile(zip, pluginZipFile); } catch (IOException e) { throw new RuntimeException("从assets中复制apk出错", e); } } } ================================================ FILE: projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/PluginLoadActivity.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.view.ViewGroup; import com.tencent.shadow.dynamic.host.EnterCallback; import com.tencent.shadow.sample.constant.Constant; public class PluginLoadActivity extends Activity { private ViewGroup mViewGroup; private Handler mHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_load); mViewGroup = findViewById(R.id.container); startPlugin(); } public void startPlugin() { PluginHelper.getInstance().singlePool.execute(new Runnable() { @Override public void run() { HostApplication.getApp().loadPluginManager(PluginHelper.getInstance().pluginManagerFile); Bundle bundle = new Bundle(); bundle.putString(Constant.KEY_PLUGIN_ZIP_PATH, PluginHelper.getInstance().pluginZipFile.getAbsolutePath()); bundle.putString(Constant.KEY_PLUGIN_PART_KEY, getIntent().getStringExtra(Constant.KEY_PLUGIN_PART_KEY)); bundle.putString(Constant.KEY_ACTIVITY_CLASSNAME, getIntent().getStringExtra(Constant.KEY_ACTIVITY_CLASSNAME)); HostApplication.getApp().getPluginManager() .enter(PluginLoadActivity.this, Constant.FROM_ID_START_ACTIVITY, bundle, new EnterCallback() { @Override public void onShowLoadingView(final View view) { mHandler.post(new Runnable() { @Override public void run() { mViewGroup.addView(view); } }); } @Override public void onCloseLoadingView() { finish(); } @Override public void onEnterComplete() { } }); } }); } @Override protected void onDestroy() { super.onDestroy(); HostApplication.getApp().getPluginManager().enter(this, Constant.FROM_ID_CLOSE, null, null); mViewGroup.removeAllViews(); } } ================================================ FILE: projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/PluginProcessPPS.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host; import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.util.Log; import com.tencent.shadow.dynamic.host.PluginProcessService; import com.tencent.shadow.sample.host.lib.LoadPluginCallback; public class PluginProcessPPS extends PluginProcessService { public PluginProcessPPS() { LoadPluginCallback.setCallback(new LoadPluginCallback.Callback() { @Override public void beforeLoadPlugin(String partKey) { Log.d("PluginProcessPPS", "beforeLoadPlugin(" + partKey + ")"); } @Override public void afterLoadPlugin(String partKey, ApplicationInfo applicationInfo, ClassLoader pluginClassLoader, Resources pluginResources) { Log.d("PluginProcessPPS", "afterLoadPlugin(" + partKey + "," + applicationInfo.className + "{metaData=" + applicationInfo.metaData + "}" + "," + pluginClassLoader + ")"); } }); } } ================================================ FILE: projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/manager/FixedPathPmUpdater.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host.manager; import android.os.Build; import com.tencent.shadow.dynamic.host.PluginManagerUpdater; import java.io.File; import java.util.concurrent.Future; public class FixedPathPmUpdater implements PluginManagerUpdater { final private File apk; FixedPathPmUpdater(File apk) { this.apk = apk; //在API 33以上的系统上,禁止动态加载文件可写入,满足系统安全限制 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) { //noinspection ResultOfMethodCallIgnored apk.setWritable(false); } } @Override public boolean wasUpdating() { return false; } @Override public Future update() { return null; } @Override public File getLatest() { return apk; } @Override public Future isAvailable(final File file) { return null; } } ================================================ FILE: projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/manager/Shadow.java ================================================ /* * Tencent is pleased to support the open source community by making Tencent Shadow available. * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.tencent.shadow.sample.host.manager; import com.tencent.shadow.dynamic.host.DynamicPluginManager; import com.tencent.shadow.dynamic.host.PluginManager; import java.io.File; public class Shadow { public static PluginManager getPluginManager(File apk) { final FixedPathPmUpdater fixedPathPmUpdater = new FixedPathPmUpdater(apk); File tempPm = fixedPathPmUpdater.getLatest(); if (tempPm != null) { return new DynamicPluginManager(fixedPathPmUpdater); } return null; } } ================================================ FILE: projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/plugin_view/HostAddPluginViewActivity.java ================================================ package com.tencent.shadow.sample.host.plugin_view; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import com.tencent.shadow.sample.host.lib.HostAddPluginViewContainer; import com.tencent.shadow.sample.host.lib.HostAddPluginViewContainerHolder; public class HostAddPluginViewActivity extends Activity implements HostAddPluginViewContainer { private ViewGroup mPluginViewContainer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout activityContentView = new LinearLayout(this); activityContentView.setOrientation(LinearLayout.VERTICAL); LinearLayout.LayoutParams wrapContent = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ); TextView note = new TextView(this); note.setLayoutParams(wrapContent); note.setText("需要先启动插件sample-plugin-app后,才能点下面的加载插件View"); Button loadButton = new Button(this); loadButton.setText("加载插件View"); loadButton.setOnClickListener(this::loadPluginView); loadButton.setLayoutParams(wrapContent); ViewGroup pluginViewContainer = new LinearLayout(this); pluginViewContainer.setLayoutParams(wrapContent); mPluginViewContainer = pluginViewContainer; View[] views = { note, loadButton, pluginViewContainer }; for (View view : views) { activityContentView.addView(view); } setContentView(activityContentView); } private void loadPluginView(View view) { //简化逻辑,只允许点一次 view.setEnabled(false); //因为当前Activity和插件都在:plugin进程,不能直接操作主进程的manager对象,所以通过一个广播调用manager。 Intent intent = new Intent(); intent.setPackage(getPackageName()); intent.setAction("sample_host.manager.startPluginService"); final int id = System.identityHashCode(this); HostAddPluginViewContainerHolder.instances.put(id, this); intent.putExtra("id", id); sendBroadcast(intent); } @Override public void addView(View view) { mPluginViewContainer.addView(view); } } ================================================ FILE: projects/sample/source/sample-host/src/main/java/com/tencent/shadow/sample/host/plugin_view/MainProcessManagerReceiver.java ================================================ package com.tencent.shadow.sample.host.plugin_view; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import com.tencent.shadow.sample.constant.Constant; import com.tencent.shadow.sample.host.HostApplication; public class MainProcessManagerReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { HostApplication.getApp().getPluginManager() .enter(context, Constant.FROM_ID_LOAD_VIEW_TO_HOST, intent.getExtras(), null); } } ================================================ FILE: projects/sample/source/sample-host/src/main/res/layout/activity_jump_to_plugin.xml ================================================