Repository: Qihoo360/RePlugin Branch: dev Commit: 36db020bf190 Files: 560 Total size: 2.1 MB Directory structure: gitextract_twe1voez/ ├── .github/ │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README_CN.md ├── deploy.sh ├── replugin-host-gradle/ │ ├── README.md │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ └── src/ │ └── main/ │ ├── groovy/ │ │ └── com/ │ │ └── qihoo360/ │ │ └── replugin/ │ │ └── gradle/ │ │ ├── compat/ │ │ │ ├── ScopeCompat.groovy │ │ │ └── VariantCompat.groovy │ │ └── host/ │ │ ├── AppConstant.groovy │ │ ├── RePlugin.groovy │ │ ├── creator/ │ │ │ ├── FileCreators.groovy │ │ │ ├── IFileCreator.groovy │ │ │ └── impl/ │ │ │ ├── java/ │ │ │ │ └── RePluginHostConfigCreator.groovy │ │ │ └── json/ │ │ │ ├── PluginBuiltinJsonCreator.groovy │ │ │ ├── PluginInfo.groovy │ │ │ └── PluginInfoParser.groovy │ │ └── handlemanifest/ │ │ └── ComponentsGenerator.groovy │ └── resources/ │ └── META-INF/ │ └── gradle-plugins/ │ └── replugin-host-gradle.properties ├── replugin-host-library/ │ ├── README.md │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── replugin-host-lib/ │ │ ├── bintray.gradle │ │ ├── build.gradle │ │ ├── replugin-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── aidl/ │ │ │ └── com/ │ │ │ └── qihoo360/ │ │ │ ├── loader2/ │ │ │ │ ├── IPlugin.aidl │ │ │ │ ├── IPluginClient.aidl │ │ │ │ ├── IPluginHost.aidl │ │ │ │ ├── PluginBinderInfo.aidl │ │ │ │ ├── mgr/ │ │ │ │ │ └── IServiceConnection.aidl │ │ │ │ └── sp/ │ │ │ │ └── IPref.aidl │ │ │ ├── mobilesafe/ │ │ │ │ └── svcmanager/ │ │ │ │ └── IServiceChannel.aidl │ │ │ └── replugin/ │ │ │ ├── IBinderGetter.aidl │ │ │ ├── component/ │ │ │ │ └── service/ │ │ │ │ └── server/ │ │ │ │ └── IPluginServiceServer.aidl │ │ │ ├── model/ │ │ │ │ └── PluginInfo.aidl │ │ │ └── packages/ │ │ │ ├── IPluginManagerServer.aidl │ │ │ └── PluginRunningList.aidl │ │ └── java/ │ │ └── com/ │ │ └── qihoo360/ │ │ ├── i/ │ │ │ ├── Factory.java │ │ │ ├── Factory2.java │ │ │ ├── IModule.java │ │ │ ├── IPlugin.java │ │ │ └── IPluginManager.java │ │ ├── loader/ │ │ │ ├── utils/ │ │ │ │ ├── LocalBroadcastManager.java │ │ │ │ ├── PackageUtils.java │ │ │ │ ├── PatchClassLoaderUtils.java │ │ │ │ ├── ProcessLocker.java │ │ │ │ ├── StringUtils.java │ │ │ │ └── SysUtils.java │ │ │ └── utils2/ │ │ │ └── FilePermissionUtils.java │ │ ├── loader2/ │ │ │ ├── BinderCursor.java │ │ │ ├── BuildCompat.java │ │ │ ├── Builder.java │ │ │ ├── CertUtils.java │ │ │ ├── Constant.java │ │ │ ├── DumpUtils.java │ │ │ ├── Finder.java │ │ │ ├── FinderBuiltin.java │ │ │ ├── LaunchModeStates.java │ │ │ ├── Loader.java │ │ │ ├── MP.java │ │ │ ├── PMF.java │ │ │ ├── Plugin.java │ │ │ ├── PluginBinderInfo.java │ │ │ ├── PluginCommImpl.java │ │ │ ├── PluginContainers.java │ │ │ ├── PluginContext.java │ │ │ ├── PluginDesc.java │ │ │ ├── PluginIntent.java │ │ │ ├── PluginLibraryInternalProxy.java │ │ │ ├── PluginManager.java │ │ │ ├── PluginNativeLibsHelper.java │ │ │ ├── PluginProcessMain.java │ │ │ ├── PluginProcessPer.java │ │ │ ├── PluginProviderStub.java │ │ │ ├── PluginStatusController.java │ │ │ ├── PluginTable.java │ │ │ ├── PmBase.java │ │ │ ├── PmHostSvc.java │ │ │ ├── ProcessStates.java │ │ │ ├── StubProcessManager.java │ │ │ ├── TaskAffinityStates.java │ │ │ ├── V5FileInfo.java │ │ │ ├── V5Finder.java │ │ │ ├── VMRuntimeCompat.java │ │ │ ├── alc/ │ │ │ │ ├── ActivityController.java │ │ │ │ └── IActivityWatcher.java │ │ │ ├── mgr/ │ │ │ │ ├── PluginProviderClient.java │ │ │ │ └── PluginServiceClient.java │ │ │ └── sp/ │ │ │ └── PrefImpl.java │ │ ├── mobilesafe/ │ │ │ ├── api/ │ │ │ │ ├── AppVar.java │ │ │ │ ├── IPC.java │ │ │ │ ├── Intents.java │ │ │ │ ├── Pref.java │ │ │ │ └── Tasks.java │ │ │ ├── loader/ │ │ │ │ ├── a/ │ │ │ │ │ └── DummyActivity.java │ │ │ │ ├── p/ │ │ │ │ │ └── DummyProvider.java │ │ │ │ └── s/ │ │ │ │ └── DummyService.java │ │ │ ├── parser/ │ │ │ │ └── manifest/ │ │ │ │ ├── ManifestParser.java │ │ │ │ ├── XmlHandler.java │ │ │ │ └── bean/ │ │ │ │ ├── ComponentBean.java │ │ │ │ └── DataBean.java │ │ │ └── svcmanager/ │ │ │ ├── ParcelBinder.java │ │ │ ├── PluginServiceManager.java │ │ │ ├── PluginServiceRecord.java │ │ │ ├── PluginServiceReferenceManager.java │ │ │ ├── QihooServiceManager.java │ │ │ ├── ServiceChannelCursor.java │ │ │ ├── ServiceChannelImpl.java │ │ │ ├── ServiceProvider.java │ │ │ └── ServiceWrapper.java │ │ └── replugin/ │ │ ├── ContextInjector.java │ │ ├── DefaultRePluginCallbacks.java │ │ ├── DefaultRePluginEventCallbacks.java │ │ ├── IHostBinderFetcher.java │ │ ├── PluginDexClassLoader.java │ │ ├── PluginDexClassLoaderPatch.java │ │ ├── RePlugin.java │ │ ├── RePluginApplication.java │ │ ├── RePluginCallbacks.java │ │ ├── RePluginClassLoader.java │ │ ├── RePluginConfig.java │ │ ├── RePluginConstants.java │ │ ├── RePluginEventCallbacks.java │ │ ├── RePluginInternal.java │ │ ├── base/ │ │ │ ├── AMSUtils.java │ │ │ ├── IPC.java │ │ │ ├── LocalBroadcastHelper.java │ │ │ └── ThreadUtils.java │ │ ├── component/ │ │ │ ├── ComponentList.java │ │ │ ├── activity/ │ │ │ │ ├── ActivityInjector.java │ │ │ │ └── DynamicClassProxyActivity.java │ │ │ ├── app/ │ │ │ │ └── PluginApplicationClient.java │ │ │ ├── dummy/ │ │ │ │ ├── DummyActivity.java │ │ │ │ ├── DummyProvider.java │ │ │ │ ├── DummyReceiver.java │ │ │ │ ├── DummyService.java │ │ │ │ └── ForwardActivity.java │ │ │ ├── process/ │ │ │ │ ├── PluginProcessHost.java │ │ │ │ ├── ProcessPitProviderBase.java │ │ │ │ ├── ProcessPitProviderLoader0.java │ │ │ │ ├── ProcessPitProviderLoader1.java │ │ │ │ ├── ProcessPitProviderP0.java │ │ │ │ ├── ProcessPitProviderP1.java │ │ │ │ ├── ProcessPitProviderP2.java │ │ │ │ ├── ProcessPitProviderPersist.java │ │ │ │ └── ProcessPitProviderUI.java │ │ │ ├── provider/ │ │ │ │ ├── PluginPitProviderBase.java │ │ │ │ ├── PluginPitProviderP0.java │ │ │ │ ├── PluginPitProviderP1.java │ │ │ │ ├── PluginPitProviderP2.java │ │ │ │ ├── PluginPitProviderPersist.java │ │ │ │ ├── PluginPitProviderUI.java │ │ │ │ ├── PluginProviderClient.java │ │ │ │ ├── PluginProviderClient2.java │ │ │ │ └── PluginProviderHelper.java │ │ │ ├── receiver/ │ │ │ │ ├── PluginReceiverHelper.java │ │ │ │ └── PluginReceiverProxy.java │ │ │ ├── service/ │ │ │ │ ├── PluginServiceClient.java │ │ │ │ ├── PluginServiceDispatcherManager.java │ │ │ │ ├── PluginServiceServerFetcher.java │ │ │ │ ├── ServiceDispatcher.java │ │ │ │ └── server/ │ │ │ │ ├── ConnectionBindRecord.java │ │ │ │ ├── IntentBindRecord.java │ │ │ │ ├── PluginPitService.java │ │ │ │ ├── PluginServiceServer.java │ │ │ │ ├── ProcessBindRecord.java │ │ │ │ ├── ProcessRecord.java │ │ │ │ └── ServiceRecord.java │ │ │ └── utils/ │ │ │ ├── ApkCommentReader.java │ │ │ ├── IntentMatcherHelper.java │ │ │ └── PluginClientHelper.java │ │ ├── debugger/ │ │ │ └── DebuggerReceivers.java │ │ ├── ext/ │ │ │ └── parser/ │ │ │ ├── AbstractApkParser.java │ │ │ ├── ApkParser.java │ │ │ ├── exception/ │ │ │ │ └── ParserException.java │ │ │ ├── parser/ │ │ │ │ ├── BinaryXmlParser.java │ │ │ │ ├── StringPoolEntry.java │ │ │ │ ├── XmlNamespaces.java │ │ │ │ ├── XmlStreamer.java │ │ │ │ └── XmlTranslator.java │ │ │ ├── struct/ │ │ │ │ ├── ChunkHeader.java │ │ │ │ ├── ChunkType.java │ │ │ │ ├── ResourceValue.java │ │ │ │ ├── StringPool.java │ │ │ │ ├── StringPoolHeader.java │ │ │ │ └── xml/ │ │ │ │ ├── Attribute.java │ │ │ │ ├── Attributes.java │ │ │ │ ├── NullHeader.java │ │ │ │ ├── XmlHeader.java │ │ │ │ ├── XmlNamespaceEndTag.java │ │ │ │ ├── XmlNamespaceStartTag.java │ │ │ │ ├── XmlNodeEndTag.java │ │ │ │ ├── XmlNodeHeader.java │ │ │ │ ├── XmlNodeStartTag.java │ │ │ │ └── XmlResourceMapHeader.java │ │ │ └── utils/ │ │ │ ├── Buffers.java │ │ │ ├── ParseUtils.java │ │ │ └── xml/ │ │ │ ├── AggregateTranslator.java │ │ │ ├── CharSequenceTranslator.java │ │ │ ├── CodePointTranslator.java │ │ │ ├── EntityArrays.java │ │ │ ├── LookupTranslator.java │ │ │ ├── NumericEntityEscaper.java │ │ │ ├── UnicodeUnpairedSurrogateRemover.java │ │ │ └── XmlEscaper.java │ │ ├── helper/ │ │ │ ├── HostConfigHelper.java │ │ │ ├── JSONHelper.java │ │ │ ├── LogDebug.java │ │ │ └── LogRelease.java │ │ ├── model/ │ │ │ ├── PluginInfo.java │ │ │ └── PluginInfoList.java │ │ ├── packages/ │ │ │ ├── PluginFastInstallProvider.java │ │ │ ├── PluginFastInstallProviderProxy.java │ │ │ ├── PluginInfoUpdater.java │ │ │ ├── PluginManagerProxy.java │ │ │ ├── PluginManagerServer.java │ │ │ ├── PluginPublishFileGenerator.java │ │ │ ├── PluginRunningList.java │ │ │ └── RePluginInstaller.java │ │ └── utils/ │ │ ├── AssetsUtils.java │ │ ├── Charsets.java │ │ ├── CloseableUtils.java │ │ ├── Dex2OatUtils.java │ │ ├── FileUtils.java │ │ ├── FixOTranslucentOrientation.java │ │ ├── IOUtils.java │ │ ├── InterpretDex2OatHelper.java │ │ ├── ReflectUtils.java │ │ ├── StringBuilderWriter.java │ │ ├── Validate.java │ │ ├── basic/ │ │ │ ├── ArrayMap.java │ │ │ ├── ArraySet.java │ │ │ ├── ByteConvertor.java │ │ │ ├── ContainerHelpers.java │ │ │ ├── MapCollections.java │ │ │ ├── SecurityUtil.java │ │ │ └── SimpleArrayMap.java │ │ └── pkg/ │ │ └── PackageFilesUtil.java │ └── settings.gradle ├── replugin-plugin-gradle/ │ ├── README.md │ ├── bintray.gradle │ ├── build.gradle │ ├── config.gradle │ ├── config.properties │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ └── src/ │ └── main/ │ ├── groovy/ │ │ └── com/ │ │ └── qihoo360/ │ │ └── replugin/ │ │ └── gradle/ │ │ └── plugin/ │ │ ├── AppConstant.groovy │ │ ├── ReClassPlugin.groovy │ │ ├── debugger/ │ │ │ └── PluginDebugger.groovy │ │ ├── injector/ │ │ │ ├── BaseInjector.groovy │ │ │ ├── IClassInjector.groovy │ │ │ ├── Injectors.groovy │ │ │ ├── identifier/ │ │ │ │ ├── GetIdentifierExprEditor.groovy │ │ │ │ └── GetIdentifierInjector.groovy │ │ │ ├── loaderactivity/ │ │ │ │ └── LoaderActivityInjector.groovy │ │ │ ├── localbroadcast/ │ │ │ │ ├── LocalBroadcastExprEditor.groovy │ │ │ │ └── LocalBroadcastInjector.groovy │ │ │ └── provider/ │ │ │ ├── ProviderExprEditor.groovy │ │ │ ├── ProviderExprEditor2.groovy │ │ │ ├── ProviderInjector.groovy │ │ │ └── ProviderInjector2.groovy │ │ ├── inner/ │ │ │ ├── ClassFileVisitor.groovy │ │ │ ├── CommonData.groovy │ │ │ ├── ReClassTransform.groovy │ │ │ └── Util.groovy │ │ ├── manifest/ │ │ │ ├── IManifest.groovy │ │ │ ├── ManifestAPI.groovy │ │ │ └── ManifestReader.groovy │ │ └── util/ │ │ └── CmdUtil.groovy │ └── resources/ │ └── META-INF/ │ └── gradle-plugins/ │ └── replugin-plugin-gradle.properties ├── replugin-plugin-library/ │ ├── README.md │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── replugin-plugin-lib/ │ │ ├── bintray.gradle │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ ├── replugin-library-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── aidl/ │ │ │ └── com/ │ │ │ └── qihoo360/ │ │ │ ├── loader2/ │ │ │ │ └── IPlugin.aidl │ │ │ └── replugin/ │ │ │ └── IBinderGetter.aidl │ │ └── java/ │ │ └── com/ │ │ └── qihoo360/ │ │ └── replugin/ │ │ ├── Entry.java │ │ ├── MethodInvoker.java │ │ ├── RePlugin.java │ │ ├── RePluginCompat.java │ │ ├── RePluginEnv.java │ │ ├── RePluginFramework.java │ │ ├── RePluginInternal.java │ │ ├── RePluginServiceManager.java │ │ ├── base/ │ │ │ └── IPC.java │ │ ├── helper/ │ │ │ ├── JSONHelper.java │ │ │ ├── LogDebug.java │ │ │ └── LogRelease.java │ │ ├── i/ │ │ │ └── IPluginManager.java │ │ ├── loader/ │ │ │ ├── PluginResource.java │ │ │ ├── a/ │ │ │ │ ├── PluginActivity.java │ │ │ │ ├── PluginActivityGroup.java │ │ │ │ ├── PluginAppCompatActivity.java │ │ │ │ ├── PluginAppCompatXActivity.java │ │ │ │ ├── PluginExpandableListActivity.java │ │ │ │ ├── PluginFragmentActivity.java │ │ │ │ ├── PluginFragmentXActivity.java │ │ │ │ ├── PluginListActivity.java │ │ │ │ ├── PluginPreferenceActivity.java │ │ │ │ └── PluginTabActivity.java │ │ │ ├── b/ │ │ │ │ └── PluginLocalBroadcastManager.java │ │ │ ├── p/ │ │ │ │ └── PluginProviderClient.java │ │ │ └── s/ │ │ │ └── PluginServiceClient.java │ │ ├── model/ │ │ │ └── PluginInfo.java │ │ ├── packages/ │ │ │ └── PluginRunningList.java │ │ └── utils/ │ │ ├── ParcelUtils.java │ │ └── ReflectUtils.java │ └── settings.gradle ├── replugin-sample/ │ ├── README.md │ ├── host/ │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets/ │ │ │ │ ├── external/ │ │ │ │ │ └── README │ │ │ │ └── plugins/ │ │ │ │ ├── demo1.jar │ │ │ │ ├── demo2.jar │ │ │ │ └── webview.jar │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── qihoo360/ │ │ │ │ └── replugin/ │ │ │ │ └── sample/ │ │ │ │ └── host/ │ │ │ │ ├── FileProvider.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── PluginFragmentActivity.java │ │ │ │ ├── SampleApplication.java │ │ │ │ └── TimeUtils.java │ │ │ └── res/ │ │ │ ├── layout/ │ │ │ │ ├── activity_main.xml │ │ │ │ └── activity_plugin_fragment.xml │ │ │ ├── values/ │ │ │ │ ├── colors.xml │ │ │ │ ├── public.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ └── xml/ │ │ │ └── fileprovider_path.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ └── plugin/ │ ├── plugin-demo1/ │ │ ├── README.md │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ ├── case.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── aidl/ │ │ │ │ └── com/ │ │ │ │ └── qihoo360/ │ │ │ │ └── replugin/ │ │ │ │ └── sample/ │ │ │ │ └── demo2/ │ │ │ │ └── IDemo2.aidl │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── qihoo360/ │ │ │ │ └── replugin/ │ │ │ │ └── sample/ │ │ │ │ └── demo1/ │ │ │ │ ├── BaseActivity.java │ │ │ │ ├── LibMainActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MainApp.java │ │ │ │ ├── TestItem.java │ │ │ │ ├── TimeUtils.java │ │ │ │ ├── activity/ │ │ │ │ │ ├── file_provider/ │ │ │ │ │ │ ├── BitmapUtils.java │ │ │ │ │ │ └── FileProviderActivity.java │ │ │ │ │ ├── for_result/ │ │ │ │ │ │ └── ForResultActivity.java │ │ │ │ │ ├── intent_filter/ │ │ │ │ │ │ └── IntentFilterDemoActivity1.java │ │ │ │ │ ├── notify_test/ │ │ │ │ │ │ └── NotifyActivity.java │ │ │ │ │ ├── preference/ │ │ │ │ │ │ ├── PrefActivity1.java │ │ │ │ │ │ └── PrefActivity2.java │ │ │ │ │ ├── single_instance/ │ │ │ │ │ │ └── TIActivity1.java │ │ │ │ │ ├── single_top/ │ │ │ │ │ │ └── SingleTopActivity1.java │ │ │ │ │ ├── standard/ │ │ │ │ │ │ └── StandardActivity.java │ │ │ │ │ ├── task_affinity/ │ │ │ │ │ │ ├── TAActivity1.java │ │ │ │ │ │ ├── TAActivity2.java │ │ │ │ │ │ ├── TAActivity3.java │ │ │ │ │ │ ├── TAActivity4.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── task_affinity_2/ │ │ │ │ │ │ ├── TA2Activity1.java │ │ │ │ │ │ ├── TA2Activity2.java │ │ │ │ │ │ ├── TA2Activity3.java │ │ │ │ │ │ ├── TA2Activity4.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── theme/ │ │ │ │ │ │ ├── ThemeBlackNoTitleBarActivity.java │ │ │ │ │ │ ├── ThemeBlackNoTitleBarFullscreenActivity.java │ │ │ │ │ │ └── ThemeDialogActivity.java │ │ │ │ │ └── webview/ │ │ │ │ │ └── WebViewActivity.java │ │ │ │ ├── fragment/ │ │ │ │ │ ├── DemoCodeFragment.java │ │ │ │ │ └── DemoFragment.java │ │ │ │ ├── provider/ │ │ │ │ │ ├── FileProvider.java │ │ │ │ │ └── Provider2.java │ │ │ │ ├── receivers/ │ │ │ │ │ └── PluginDemo1Receiver.java │ │ │ │ ├── service/ │ │ │ │ │ ├── PluginDemoAppService.java │ │ │ │ │ ├── PluginDemoService1.java │ │ │ │ │ └── PluginDemoService2.java │ │ │ │ ├── support/ │ │ │ │ │ ├── LogX.java │ │ │ │ │ └── NotifyUtils.java │ │ │ │ └── webview/ │ │ │ │ ├── IWebPage.java │ │ │ │ ├── ViewProxy.java │ │ │ │ └── WebPageProxy.java │ │ │ └── res/ │ │ │ ├── layout/ │ │ │ │ ├── content_lib_main.xml │ │ │ │ ├── layout_notify.xml │ │ │ │ ├── lib_activity_main.xml │ │ │ │ ├── main.xml │ │ │ │ ├── main_fragment.xml │ │ │ │ ├── simple.xml │ │ │ │ ├── simple_2.xml │ │ │ │ └── simple_4.xml │ │ │ ├── values/ │ │ │ │ └── strings.xml │ │ │ └── xml/ │ │ │ ├── fileprovider_path.xml │ │ │ └── pref_headers.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ ├── plugin-demo2/ │ │ ├── README.md │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ ├── case.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── aidl/ │ │ │ │ └── com/ │ │ │ │ └── qihoo360/ │ │ │ │ └── replugin/ │ │ │ │ └── sample/ │ │ │ │ └── demo2/ │ │ │ │ └── IDemo2.aidl │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── qihoo360/ │ │ │ │ └── replugin/ │ │ │ │ └── sample/ │ │ │ │ └── demo2/ │ │ │ │ ├── Demo2Impl.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MainApp.java │ │ │ │ ├── activity/ │ │ │ │ │ ├── appcompat/ │ │ │ │ │ │ └── AppCompatActivityDemo.java │ │ │ │ │ └── for_result/ │ │ │ │ │ └── ForResultActivity.java │ │ │ │ ├── databinding/ │ │ │ │ │ ├── DataBindingActivity.java │ │ │ │ │ └── Entry.java │ │ │ │ ├── receivers/ │ │ │ │ │ └── PluginDemo2Receiver.java │ │ │ │ ├── service/ │ │ │ │ │ └── PluginDemo2Service.java │ │ │ │ ├── support/ │ │ │ │ │ └── LogX.java │ │ │ │ └── testcase/ │ │ │ │ └── TestMultiDex.java │ │ │ └── res/ │ │ │ ├── layout/ │ │ │ │ ├── databinding_test.xml │ │ │ │ └── from_demo1.xml │ │ │ └── values/ │ │ │ └── strings.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ ├── plugin-demo3-kotlin/ │ │ ├── README.md │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── aidl/ │ │ │ │ └── com/ │ │ │ │ └── qihoo360/ │ │ │ │ └── replugin/ │ │ │ │ └── sample/ │ │ │ │ └── demo2/ │ │ │ │ └── IDemo2.aidl │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── qihoo360/ │ │ │ │ └── replugin/ │ │ │ │ └── sample/ │ │ │ │ └── demo3/ │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MainApp.kt │ │ │ │ ├── TestItem.kt │ │ │ │ ├── activity/ │ │ │ │ │ └── theme/ │ │ │ │ │ └── ThemeBlackNoTitleBarActivity.kt │ │ │ │ ├── receivers/ │ │ │ │ │ └── PluginDemo3Receiver.kt │ │ │ │ ├── service/ │ │ │ │ │ └── PluginDemoService1.kt │ │ │ │ └── support/ │ │ │ │ └── LogX.kt │ │ │ └── res/ │ │ │ ├── layout/ │ │ │ │ ├── main.xml │ │ │ │ ├── main_fragment.xml │ │ │ │ └── simple.xml │ │ │ └── values/ │ │ │ └── strings.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ └── plugin-webview/ │ ├── README.md │ ├── app/ │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── qihoo360/ │ │ │ └── replugin/ │ │ │ └── sample/ │ │ │ └── webview/ │ │ │ ├── MainActivity.java │ │ │ ├── MainApp.java │ │ │ ├── common/ │ │ │ │ ├── CommonWebChromeClient.java │ │ │ │ ├── CommonWebView.java │ │ │ │ └── CommonWebViewClient.java │ │ │ ├── env/ │ │ │ │ └── Env.java │ │ │ ├── utils/ │ │ │ │ ├── ReflectUtil.java │ │ │ │ └── WebViewResourceHelper.java │ │ │ └── views/ │ │ │ ├── SimpleWebPage.java │ │ │ └── SimpleWebView.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── web_page.xml │ │ │ └── webview.xml │ │ └── values/ │ │ ├── colors.xml │ │ └── strings.xml │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── replugin-sample-extra/ │ ├── README.md │ └── fresco/ │ ├── FrescoHost/ │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── libs/ │ │ │ │ ├── drawee-modified-1.7.1.jar │ │ │ │ ├── fbcore-1.7.1.jar │ │ │ │ ├── fresco-1.7.1.jar │ │ │ │ ├── imagepipeline-1.7.1.jar │ │ │ │ └── imagepipeline-base-1.7.1.jar │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets/ │ │ │ │ └── plugins/ │ │ │ │ └── plugin1.jar │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ ├── facebook/ │ │ │ │ │ └── fresco/ │ │ │ │ │ └── patch/ │ │ │ │ │ ├── DraweeStyleableCallbackImpl.java │ │ │ │ │ └── FrescoPatch.java │ │ │ │ └── qihoo360/ │ │ │ │ └── replugin/ │ │ │ │ └── fresco/ │ │ │ │ └── host/ │ │ │ │ ├── HostApp.java │ │ │ │ ├── HostFrescoActivity.java │ │ │ │ └── MainActivity.java │ │ │ └── res/ │ │ │ ├── layout/ │ │ │ │ ├── activity_host_fresco.xml │ │ │ │ └── activity_main.xml │ │ │ └── values/ │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ └── FrescoPlugin/ │ ├── .gitignore │ ├── app/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── libs/ │ │ │ ├── drawee-modified-1.7.1.jar │ │ │ ├── fbcore-1.7.1.jar │ │ │ ├── fresco-1.7.1.jar │ │ │ ├── imagepipeline-1.7.1.jar │ │ │ └── imagepipeline-base-1.7.1.jar │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ ├── facebook/ │ │ │ │ └── fresco/ │ │ │ │ └── patch/ │ │ │ │ ├── DraweeStyleableCallbackImpl.java │ │ │ │ └── FrescoPatch.java │ │ │ └── qihoo360/ │ │ │ └── replugin/ │ │ │ └── fresco/ │ │ │ └── plugin/ │ │ │ ├── MainActivity.java │ │ │ └── PluginApp.java │ │ └── res/ │ │ ├── layout/ │ │ │ └── activity_main.xml │ │ └── values/ │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── rp-config.gradle └── rp-publish.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ #### 问题详细描述 Detailed description of the problem #### 复现问题步骤 Steps to reproduce the problem 1. 2. #### 其它重要信息 Other important information replugin-host-lib/gradle Version: rePlugin-plugin-lib/gradle Version: Android API Version: Android 手机型号&ROM(Phone model & ROM): #### Logcat上下文 Logcat context ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ #### 要解决的问题 Describe the problem to be solved 1. 2. #### 要解决的Issue编号(可多个) Associated issue number (multiple) # ================================================ FILE: .gitignore ================================================ # -------- # Git Global Ignores # Power By github/gitignore (License: CC0 1.0) # Collect by RePlugin Team # -------- # -------- # Android # https://github.com/github/gitignore/blob/master/Android.gitignore # Date: 17 Feb 2017 # -------- # Built application files *.apk *.ap_ # Files for the ART/Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ out/ # Gradle files .gradle/ build/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Log Files *.log # Android Studio Navigation editor temp files .navigation/ # Android Studio captures folder captures/ # Intellij *.iml .idea # Keystore files *.jks # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild # Google Services (e.g. APIs or Firebase) google-services.json # Freeline freeline.py freeline/ freeline_project_description.json # -------- # Gradle # https://raw.githubusercontent.com/github/gitignore/master/Gradle.gitignore # Date: 25 Jun 2016 # -------- .gradle /build/ # Ignore Gradle GUI config gradle-app.setting # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar # Cache of project .gradletasknamecache # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 # gradle/wrapper/gradle-wrapper.properties # -------- # Eclipse # https://github.com/github/gitignore/blob/master/Global/Eclipse.gitignore # Date: 28 Mar 2017 # -------- .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .settings/ .loadpath .recommenders # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # PyDev specific (Python IDE for Eclipse) *.pydevproject # CDT-specific (C/C++ Development Tooling) .cproject # Java annotation processor (APT) .factorypath # PDT-specific (PHP Development Tools) .buildpath # sbteclipse plugin .target # Tern plugin .tern-project # TeXlipse plugin .texlipse # STS (Spring Tool Suite) .springBeans # Code Recommenders .recommenders/ # Scala IDE specific (Scala & Java development for Eclipse) .cache-main .scala_dependencies .worksheet # -------- # Windows # https://github.com/github/gitignore/blob/master/Global/Windows.gitignore # Date: 10 May 2017 # -------- # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk # -------- # Linux # https://github.com/github/gitignore/blob/master/Global/Linux.gitignore # -------- *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* # -------- # Mac OS # https://github.com/github/gitignore/blob/master/Global/macOS.gitignore # Date: 01 Apr 2017 # -------- # General *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # -------- # Tortoise Git # Author: RePlugin Team # Date: 20 Dec 2016 # -------- # Project-level settings /.tgitconfig ================================================ FILE: CONTRIBUTING.md ================================================ 欢迎关注和使用RePlugin。为了让解决问题、贡献代码的效率更高,请务必按照下面流程来做。 ## 有关Issue 如果遇到接入上的问题,请随时提出**【有效的】**Issue。 #### 如果是问题反馈 > 出于效率考虑,请**务必按照 Issue模板 所列来完整的填写反馈**(我们已自动帮您建立模板,您在写新Issue时会有,您也可以[点击此处查看模板内容](https://github.com/Qihoo360/RePlugin/blob/dev/.github/ISSUE_TEMPLATE.md)) > > 注意:**未按照模板填写,且不能帮助我们定位问题的,我们将其标记为“Invalid”,并直接关闭,不会进一步问询。直到按照完整模板填写后,再行Reopen**。 > > 此外,我们都希望提出的问题是**“高质量的”**。因此,请[点击此处阅读《提出Issue》](https://github.com/Qihoo360/RePlugin/wiki/%E6%8F%90%E5%87%BAIssue)一文,来详细了解提交Issue的最佳实践,帮助更快的解决您遇到的问题。 #### 如果是意见和建议 我们的Issue模板只针对“问题反馈”而设计。而若您在RePlugin中有一些新的想法想讨论的话,可“无需按照Issue模板”来提,但请留意: 1. 请尽量将您的建议、提出建议的原因等描述清楚。不要只写个标题(除非这个标题让人一看就全懂) 2. 如果可能,请尽可能发动所有牛人的力量,共同参与讨论,并由社区(尤其是您自己)提出Pull Request,帮助所有人解决问题 3. 我们(以及社区大牛)会对您提的建议做评估,并将其贴上标签并待完成。有些建议虽然很好,但由于时间和精力关系,可能不会很快实现,对此我们不能、也不会承诺最终时间。 如果提出的Issue是“模棱两可”、无法看懂的,在一次回复没有答复后,我们可能将其标记为Invalid,并直接关闭。还请理解。 ## 有关 Pull Request 当然,和Issue相比,我们非常欢迎您研究RePlugin的源码,发现相应的问题和改善方案,并提交自己的Pull Request。**高质量的PR将有助于打造自己的技术品牌,好处很多**。 在提出Pull Request之前,**建议请先提下Issue,这样可以在编写代码前,和大家做较为充分的讨论**,以免在后期出现“大量修改”的情况。 > 有关如何完成一个“高质量”的Pull Request,请[点击此处阅读《贡献力量》](https://github.com/Qihoo360/RePlugin/wiki/%E8%B4%A1%E7%8C%AE%E5%8A%9B%E9%87%8F)一文了解更多。 > 如果想知道哪些“牛人”为RePlugin做出了杰出贡献,请[点击此处阅读《金牌贡献者》](https://github.com/Qihoo360/RePlugin/wiki/%E9%87%91%E7%89%8C%E8%B4%A1%E7%8C%AE%E8%80%85),看看哪些您熟知的人也在其中。 ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright (C) 2005-2017 Qihoo 360 Inc. 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 http://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. ================================================ FILE: README.md ================================================

RePlugin Logo

[![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/Qihoo360/RePlugin/blob/master/LICENSE) [![Release Version](https://img.shields.io/badge/release-2.3.3-brightgreen.svg)](https://github.com/Qihoo360/RePlugin/releases) ## !!!重要通知!!! 由于jcenter停服原因,RePlugin将迁移到http://maven.geelib.360.cn/nexus上 如果需要继续接入RePlugin,需要在项目的build.gradle内增加新的仓库地址: maven {url "http://maven.geelib.360.cn/nexus/repository/replugin/"} ## 通知 **360开源又一力作——[ArgusAPM移动性能监控平台](https://github.com/Qihoo360/ArgusAPM)** [ArgusAPM](https://github.com/Qihoo360/ArgusAPM)是360手机卫士客户端团队继RePlugin之后开源的又一个重量级开源项目。ArgusAPM是360移动端产品使用的可视化性能监控平台,为移动端APP提供性能监控与管理,可以迅速发现和定位各类APP性能和使用问题,帮助APP不断的提升用户体验。 **360移动技术最新活动通知:** 2018年12月16日,360移动性能开放日邀您参加,届时将会有360、美团技术大牛为大家分享Android、iOS性能监控实践。 欢迎报名参加,戳戳戳!!!-->https://mp.weixin.qq.com/s/-7DCnXI_EBMBwYG_PUuUDg ## RePlugin —— A flexible, stable, easy-to-use Android Plug-in Framework RePlugin is a complete Android plug-in solution which is suitable for general use. ([文档,还是中文的好](./README_CN.md)) It is major strengths are: * **Extreme flexibility**: Apps do not need to be upgraded to support new components, **even brand new plug-ins**. * **Extraordinary stability**: With only **ONE** hook (ClassLoader), **NO BINDER HOOK**. RePlugin’s Crash ratio is **as low as Ten thousandth (0.01%)**. In addition, RePlugin is compatible with almost **ALL Android ROMs** in the market. * **Rich features**: RePlugin supports **almost all features seamlessly as an installed application**, including static Receiver, Task-Affinity, user-defined Theme, AppCompat, DataBinding, etc. * **Easy integration**: It takes only couple lines to access, whether plug-ins or main programs. * **Mature management**: RePlugin owns stable plug-in management solution which supports installation, upgrade, uninstallation and version management. Process communication, protocol versions and security check are also included. * **Hundreds of millions support**: RePlugin possesses **hundreds of millions users from 360 MobileSafe.** After more than three-year verification, we guarantee the solution that Apps use is the most stable and suitable. By the end of June 2017, RePlugin has already made some achievements: | Feature | Achievement | |:-------------:|:-------------:| | **Plug-in Number** | **103** | | **Ratio of plug-ins to applications** | **83%** | | **Version released pre year** | **596** | | **Crash** | **0.01%, Extraordinary stability** | | **First Release** | **2014** | At present, almost **all Apps with hundreds of millions users from 360, and many mainstream third-party Apps, are using RePlugin solution**. ### We support: | Feature | Description | |:-------------:|:-------------:| | Components | **Activity, Service, Provider, Receiver(Including static)** | | Not need to upgrade when brand a new Plug-in | **Supported** | | Android Feature | **Supported almost all features** | | TaskAffinity & Multi-Process | **Perfect supported!** | | Support Plug-in Type | **Built-in (Only Two Step) and External(Download)** | | Plug-in Coupling | **Binder, Class Loader, Resources, etc.** | | Interprocess communication | **Sync, Async, Binder and Cross-plug-in broadcast** | | User-Defined Theme & AppComat | **Supported** | | DataBinding | **Supported** | | Safety check when installed | **Supported** | | Resources Solution | **Independent Resources + Context pass(No Adaptation ROM)** | | Android Version | **API Level 9 (Android 2.3 and above)** | ## Our Vision Make RePlugin be used in all kinds of ordinary Apps; and provide stable, flexible, liberal plug-ins which adopt for both large and small projects. ## Latest features Solved the Android P (Android 9.0) related adaptation issues, fully support the official version of Android P (Android 9.0). ## RePlugin Architecture

RePlugin Framework

## How to Use RePlugin Using RePlugin is very simple. Under most conditions, using it is no different than developing an App. If you are **the first-time user, please [click here to read Quick Start Guide(Chinese Version)](https://github.com/Qihoo360/RePlugin/wiki/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B).** Following our guide, you will learn more about RePlugin. If you wish to **learn more gameplays about RePlugin, please [click here to read Step-by-step Tutorial(Chinese Version)](https://github.com/Qihoo360/RePlugin/wiki/%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B)**. If you want to **view RePlugin’s sample project, and learn concrete usage of the frame, please [click here to check Sample SC](https://github.com/Qihoo360/RePlugin/blob/master/replugin-sample)**. If you **have any question, please [click here to read FAQ(Chinese Version)](https://github.com/Qihoo360/RePlugin/wiki/FAQ)**. ## These apps are using RePlugin
360 Mobile Safe 360 App Store 360 Mobile Browser HuaJiao Camera 360 Clean Master
360 Kan Movie JieQianBa 1 HaiTao HuaRun Tong JieLeMa
360OS App 360 Loan (Internal App) (Internal App) (Internal App)
## Plug-ins Accessed in RePlugin For your reference, plug-ins accessed can be classified into following categories: * **Expo plug-ins**: Safe Home Page, physical examination, information flow, etc. * **Business plug-ins**: cleaning, disturbance intercept, floating window, etc. * **Cooperation plug-ins**: App Lock, free Wi-Fi, security desktop, etc. * **Background plug-ins**: Push, service management, Protobuf, etc. * **Base plug-ins**: Security WebView, share, location service, etc. By the end of June 2017, we already have 102 plug-ins like these. We look forward to you becoming a part of RePlugin family! ## Contribute Your Share We sincerely welcome and appreciate your contribution of any kind. You can submit code, raise suggestions, write documentation, etc. For more information, please [click here to read Contribute Your Share(Chinese Version)](https://github.com/Qihoo360/RePlugin/wiki/%E8%B4%A1%E7%8C%AE%E5%8A%9B%E9%87%8F). ## License RePlugin is [Apache v2.0 licensed](./LICENSE). (Thanks Xiezihan(谢子晗) for providing the translations.) ================================================ FILE: README_CN.md ================================================

RePlugin Logo

[![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/Qihoo360/RePlugin/blob/master/LICENSE) [![Release Version](https://img.shields.io/badge/release-2.3.3-brightgreen.svg)](https://github.com/Qihoo360/RePlugin/releases) ## 通知 **360开源又一力作——[ArgusAPM移动性能监控平台](https://github.com/Qihoo360/ArgusAPM)** [ArgusAPM](https://github.com/Qihoo360/ArgusAPM)是360手机卫士客户端团队继RePlugin之后开源的又一个重量级开源项目。ArgusAPM是360移动端产品使用的可视化性能监控平台,为移动端APP提供性能监控与管理,可以迅速发现和定位各类APP性能和使用问题,帮助APP不断的提升用户体验。 **360移动技术最新活动通知:** 2018年12月16日,360移动性能开放日邀您参加,届时将会有360、美团技术大牛为大家分享Android、iOS性能监控实践。 欢迎报名参加,戳戳戳!!!-->https://mp.weixin.qq.com/s/-7DCnXI_EBMBwYG_PUuUDg ## RePlugin —— 历经三年多考验,数亿设备使用的,稳定占坑类插件化方案 RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。 其主要优势有: * **极其灵活**:主程序无需升级(无需在Manifest中预埋组件),即可支持新增的四大组件,甚至全新的插件 * **非常稳定**:Hook点**仅有一处(ClassLoader),无任何Binder Hook**!如此可做到其**崩溃率仅为“万分之一”,并完美兼容市面上近乎所有的Android ROM** * **特性丰富**:支持近乎所有在“单品”开发时的特性。**包括静态Receiver、Task-Affinity坑位、自定义Theme、进程坑位、AppCompat、DataBinding等** * **易于集成**:无论插件还是主程序,**只需“数行”就能完成接入** * **管理成熟**:拥有成熟稳定的“插件管理方案”,支持插件安装、升级、卸载、版本管理,甚至包括进程通讯、协议版本、安全校验等 * **数亿支撑**:有360手机卫士庞大的**数亿**用户做支撑,**三年多的残酷验证**,确保App用到的方案是最稳定、最适合使用的 截止2017年6月底,RePlugin的: | 特性 | 描述 | |:-------------:|:-------------:| | **插件数** | **103(核心57个)** | | **插件占应用比** | **高达83%** | | **年发版次数** | **高达596次(工作日均2次)** | | **崩溃率** | **万分之一(0.01%),极低** | | **时间** | **2014年应用,3年验证** | 目前360公司几乎**所有的亿级用户量的APP**,以及多款主流第三方APP,都采用了RePlugin方案。 有关RePlugin的详细介绍,请[点击这里阅读《RePlugin 官方 WiKi》](https://github.com/Qihoo360/RePlugin/wiki)。 ### 我们还支持以下特性 | 特性 | 描述 | |:-------------:|:-------------:| | 组件 | **四大组件(含静态Receiver)** | | 升级无需改主程序Manifest | **完美支持** | | Android特性 | **支持近乎所有(包括SO库等)** | | TaskAffinity & 多进程 | **支持(*坑位方案*)** | | 插件类型 | **支持自带插件(*自识别*)、外置插件** | | 插件间耦合 | **支持Binder、Class Loader、资源等** | | 进程间通讯 | **支持同步、异步、Binder、广播等** | | 自定义Theme & AppComat | **支持** | | DataBinding | **支持** | | 安全校验 | **支持** | | 资源方案 | **独立资源 + Context传递(相对稳定)** | | Android 版本 | **API Level 9+ (2.3及以上)** | ## 愿景 让插件化能**飞入寻常应用家**,做到稳定、灵活、自由,大小项目兼用。 ## 最新特性 解决了Android P(Android 9.0)相关适配问题,全面支持Android P(Android 9.0)正式版。 ## RePlugin 架构图

RePlugin Framework

以360手机卫士为例: * **系统层——Android**:为Android Framework层。**只有ClassLoader是Hook的**,而AMS、Resources等都没有做Hook,确保了其稳定性。 * **框架层——RePlugin框架**:RePlugin框架层,**只有RePlugin是对“上层完全公开”的**,其余均为Internal,或“动态编译方案”生效后的调用,对开发者而言是“无需关心”的。 * **插件层——各插件**:“标蓝部分”是各插件,包括大部分的业务插件(如体检、清理、桌面插件等)。而其中“标黄部分”是支撑一个应用的各种基础插件,如WebView、Download、Share,甚至Protobuf都能成为基础插件。 ## 使用方法 RePlugin的使用方法非常简单,大部分情况下和“单品”开发无异。 若您是**第一次接触RePlugin,则[请点击这里阅读《快速上手》](https://github.com/Qihoo360/RePlugin/wiki/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B)**,跟随我们的指引,了解更多的内容。 若您想**了解更多有关RePlugin的玩法,则[请点击这里阅读《详细教程》](https://github.com/Qihoo360/RePlugin/wiki/%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B)**,了解更多好玩的玩法。 若您想**看下RePlugin的Sample工程,进而了解框架的具体用法,则[请点击这里查看Sample源代码](https://github.com/Qihoo360/RePlugin/blob/master/replugin-sample)**。 若您在接入RePlugin中**遇到了任何问题,则[请点击这里阅读《FAQ》](https://github.com/Qihoo360/RePlugin/wiki/FAQ)**,相信会有您想要的答案。 ## 插件管理服务—与RePlugin配套的插件管理、下发、统计服务 至今为止有数不清的用户联系我们让做配套的插件管理功能,所以 RePlugin 团队联合 360 Web 平台部,合力推出 RePlugin 插件管理服务,再次大幅降低了用户使用RePlugin的门槛,插件管理服务功能介绍如下: * **插件版本管理**:对APK插件包名、别名和版本号的交集限制,防止下发出错。 * **打点统计**:上报即显示下发量、下载量、安装量和错误量数据。 * **升版管理**:严格要求用户新建下发任务为面向虚拟用户或部分真实用户的“测试版”下发任务(适用于内测、AB和灰度),测试没问题以后才能切换到面对所有真实用户的“线上版”下发任务,防止出错。 * **下发限速**:开发者可自定义自己想要的插件下发速度。 * **运营商和厂商限制**:开发者可自定义自己想下发的运营商和目标终端厂商。 * **灵活的下发条件设置**:根据用户群体对下发条件的要求程度,我们提供了4种条件设置功能,对下发条件要求不高的用户可直接使用便捷条件下发(包括按人数和指定设备下发);对下发条件要求高的用户可使用自定义条件下发(包括文字条件编辑器和代码条件编辑器)。 **PS**:我们原创的文字版条件编辑器很有意思哦,它能将复杂繁琐的条件代码还原成有语法有逻辑的中国话,真的是能让非技术人员第一次使用就看得懂会操作的优秀功能,目前为止我们应该是第一个将体验做到如此细腻的产品。 使用地址:[360移动开发者-RePlugin插件管理](https://dc.360.cn/) ## 已接入RePlugin的应用 我们诚挚期待您成为咱们RePlugin应用大家庭中的一员! 除了**360集团旗下的亿级别应用**以外,还有一些对**稳定要求极其严苛的“金融类”产品**,及第三方合作应用也接入了RePlugin(含SDK):                                                                                                        
360 手机卫士360 手机助手360 手机浏览器花椒相机360 清理大师
360 影视大全J借钱吧海淘1号华润通借了吗
360OS 系统应用360 借条(即将发布) (即将发布) (即将发布)
这里**衷心感谢** “360手机助手”,以及其它各App团队成员,帮助我们发现了很多需要改进的地方,并给予了非常积极的反馈。您们的鼓励与支持,让咱们的RePlugin能走的更远、更好! ## 已接入RePlugin的插件 目前已有的插件,可以分为以下几类,供各App开发者参考: * **展示插件**:如**卫士首页**(是的,你没看错)、体检、信息流等 * **业务插件**:如清理、骚扰拦截、悬浮窗等 * **合作插件**:如程序锁、免费WiFi、安全桌面等 * **后台插件**:如Push、服务管理、Protobuf等 * **基础插件**:如安全WebView、分享、定位等 截止2017年6月底,这样的插件,我们有**103**个。衷心希望您能成为这个数字中的新的一员! ## 贡献自己的力量 我们欢迎任何形式的贡献,并致以诚挚的感谢! 你可以贡献代码、提出问题、编写文档等。有关“贡献”相关的内容,请[点击这里阅读《贡献力量》](https://github.com/Qihoo360/RePlugin/wiki/%E8%B4%A1%E7%8C%AE%E5%8A%9B%E9%87%8F) ## 与我们联系 欢迎您加入到我们的RePlugin微信群、QQ群大家庭。 微信群已超过上限,请进入我们的QQ群 QQ群 1:**653205923** QQ群 2:**589652294** ## License RePlugin is [Apache v2.0 licensed](./LICENSE). ================================================ FILE: deploy.sh ================================================ #!/bin/bash export RP_BASE_DIR=$(cd "$(dirname "$0")"; pwd) export TARGET_PROJECTS=( replugin-host-gradle replugin-host-library replugin-plugin-gradle replugin-plugin-library ) __gradle_exec(){ if [[ -x gradlew ]];then ./gradlew ${@}; else gradle ${@}; fi; } __rp_deploy_project(){ [[ ! -d ${1} ]] && echo ">>> INVALID ${1}!!! <<<" && return # execute deploying echo ">>> ${1} <<<" && cd ${1} && __gradle_exec -p ${1} clean bintrayUpload # revert changed files git checkout ${1} } rp_revert_AppConstant(){ git status -s | sed s/^...// | grep '/AppConstant.groovy' | git checkout ${f} } rp_deploy(){ local current=`pwd` && cd ${RP_BASE_DIR} # revert AppConstant.groovy rp_revert_AppConstant # saving all changes: git stash save "saving stash for deploying!!!" # deploy for p in ${TARGET_PROJECTS}; do __rp_deploy_project ${RP_BASE_DIR}/${p}; done # revert local changes: git revert --hard HEAD; git stash pop rp_revert_AppConstant # back cd ${current} } rp_test(){ local projects=( # replugin-sample/host/app replugin-sample/host # replugin-sample/plugin/plugin-demo1/app replugin-sample/plugin/plugin-demo1 # replugin-sample/plugin/plugin-demo2/app replugin-sample/plugin/plugin-demo2 # replugin-sample/plugin/plugin-demo3-kotlin/app replugin-sample/plugin/plugin-demo3-kotlin # replugin-sample/plugin/plugin-webview/app replugin-sample/plugin/plugin-webview # replugin-sample-extra/fresco/FrescoHost/app replugin-sample-extra/fresco/FrescoHost # replugin-sample-extra/fresco/FrescoPlugin/app replugin-sample-extra/fresco/FrescoPlugin ) local log=${RP_BASE_DIR}/build/rp_test.log && [[ -f $log ]] && rm -f $log && touch $log local current=`pwd` for p in ${projects}; do local p=${RP_BASE_DIR}/${p} echo -e ">>> BUILDING ${RP_BASE_DIR}/${p}" cd ${p} && { __gradle_exec -p ${p} clean asDebug } ls -l ${p}/app/build/outputs/apk done cd ${current} } # grep --exclude-dir={.bzr,CVS,.git,.hg,.svn,.idea,build,.gradle} -inr '2\.3\.0' . ================================================ FILE: replugin-host-gradle/README.md ================================================ # RePlugin Host Gradle RePlugin Host Gradle是一个Gradle插件,由 **主程序** 负责引入。 该Gradle插件主要负责在主程序的编译期中做一些事情,此外,开发者可通过修改其属性而做一些自定义的操作。 大致包括: * 生成带 RePlugin 插件坑位的 AndroidManifest.xml(允许自定义数量) * 生成HostBuildConfig类,方便插件框架读取并自定义其属性 开发者需要依赖此Gradle插件,以实现对RePlugin的接入。请参见WiKi以了解接入方法。 有关RePlugin Host Gradle的详细描述,请访问我们的WiKi,以了解更多的内容。 (文档正在完善,请耐心等待) ================================================ FILE: replugin-host-gradle/build.gradle ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ plugins { id 'java' id 'groovy' id 'maven-publish' } repositories { google() mavenCentral() } dependencies { implementation 'com.android.tools.build:gradle:7.4.2' implementation 'org.json:json:20160212' implementation 'org.codehaus.groovy:groovy:2.4.7' implementation 'com.squareup:javapoet:1.5.1' implementation gradleApi() implementation localGroovy() implementation 'com.google.gradle:osdetector-gradle-plugin:1.2.1' implementation 'net.dongliu:apk-parser:2.2.0' } project.ext.RP_ARTIFACT_ID = 'replugin-host-gradle' apply from: '../rp-publish.gradle' ================================================ FILE: replugin-host-gradle/gradle/wrapper/gradle-wrapper.properties ================================================ # # Copyright (C) 2005-2017 Qihoo 360 Inc. # # 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 # # http://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. # #Mon Dec 28 10:00:20 PST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip ================================================ FILE: replugin-host-gradle/gradle.properties ================================================ # # Copyright (C) 2005-2017 Qihoo 360 Inc. # # 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 # # http://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. # # 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=-Xmx1536m # 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 ================================================ FILE: replugin-host-gradle/gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # 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 case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # 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 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" ] ; 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 # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: replugin-host-gradle/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 @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= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @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 Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_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=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software 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: replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/compat/ScopeCompat.groovy ================================================ package com.qihoo360.replugin.gradle.compat /** * @author hyongbai */ class ScopeCompat { static def getAdbExecutable(def scope) { final MetaClass scopeClz = scope.metaClass if (scopeClz.hasProperty(scope, "androidBuilder")) { return scope.androidBuilder.sdkInfo.adb } if (scopeClz.hasProperty(scope, "sdkComponents")) { return scope.sdkComponents.adbExecutableProvider.get() } } // TODO: getBuilderTarget // static def getBuilderTarget(def scope, def target){ // final MetaClass scopeClz = scope.metaClass // // if (scopeClz.hasProperty(scope, "androidBuilder")) { // return scope.getAndroidBuilder().getTarget().getPath(target) //IAndroidTarget.ANDROID_JAR // } // // return globalScope.getAndroidBuilder().getTarget().getPath(IAndroidTarget.ANDROID_JAR) // } // static def getAndroidJar(def scope){ // final MetaClass scopeClz = scope.metaClass // // if (scopeClz.hasProperty(scope, "androidBuilder")) { // return scope.getAndroidBuilder().getTarget().getPath(IAndroidTarget.ANDROID_JAR) // } // if (scopeClz.hasProperty(scope, "sdkComponents")) { // return scope.sdkComponents.androidJarProvider.get().getAbsolutePath() // } // } } ================================================ FILE: replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/compat/VariantCompat.groovy ================================================ package com.qihoo360.replugin.gradle.compat import org.gradle.api.Task /** * @author hyongbai */ class VariantCompat { static def getAssembleTask(def variant) { return compatGetTask(variant, "getAssembleProvider", "getAssemble") } static def getMergeAssetsTask(def variant) { return compatGetTask(variant, "getMergeAssetsProvider", "getMergeAssets") } static def getGenerateBuildConfigTask(def variant) { return compatGetTask(variant, "getGenerateBuildConfigProvider", "getGenerateBuildConfig") } static def getProcessManifestTask(def variant) { return compatGetTask(variant, "getProcessManifestProvider", "getProcessManifest") } static def compatGetTask(def variant, String... candidates) { candidates?.findResult { variant.metaClass.respondsTo(variant, it).with { if (!it.isEmpty()) return it } }?.find { it.getParameterTypes().length == 0 }?.invoke(variant)?.with { //TODO: check if is provider!!! Task.class.isInstance(it) ? it : it?.get() } } } ================================================ FILE: replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/host/AppConstant.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.gradle.host /** * 程序常量定义区 * @author RePlugin Team */ class AppConstant { /** 版本号 */ def static final VER = "${RP_VERSION}" /** 打印信息时候的前缀 */ def static final TAG = "< replugin-host-v${VER} >" /** 外部用户配置信息 */ def static final USER_CONFIG = "repluginHostConfig" /** 用户Task组 */ def static final TASKS_GROUP = "replugin-plugin" /** Task前缀 */ def static final TASKS_PREFIX = "rp" /** 用户Task:安装插件 */ def static final TASK_SHOW_PLUGIN = TASKS_PREFIX + "ShowPlugins" /** 用户Task:Generate任务 */ def static final TASK_GENERATE = TASKS_PREFIX + "Generate" private AppConstant() {} } ================================================ FILE: replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/host/RePlugin.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.gradle.host import com.android.build.gradle.AppExtension import com.android.build.gradle.AppPlugin import com.qihoo360.replugin.gradle.compat.VariantCompat import com.qihoo360.replugin.gradle.host.creator.FileCreators import com.qihoo360.replugin.gradle.host.creator.IFileCreator import com.qihoo360.replugin.gradle.host.creator.impl.json.PluginBuiltinJsonCreator import com.qihoo360.replugin.gradle.host.handlemanifest.ComponentsGenerator import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.logging.LogLevel /** * @author RePlugin Team */ public class Replugin implements Plugin { def static TAG = AppConstant.TAG def project def config @Override public void apply(Project project) { println "${TAG} Welcome to replugin world ! " this.project = project /* Extensions */ project.extensions.create(AppConstant.USER_CONFIG, RepluginConfig) if (project.plugins.hasPlugin(AppPlugin)) { def android = project.extensions.getByType(AppExtension) android.applicationVariants.all { variant -> addShowPluginTask(variant) if (config == null) { config = project.extensions.getByName(AppConstant.USER_CONFIG) checkUserConfig(config) } def generateBuildConfigTask = VariantCompat.getGenerateBuildConfigTask(variant) def appID = [variant.getApplicationId(), variant.buildType.applicationIdSuffix].findAll().join() def newManifest = ComponentsGenerator.generateComponent(appID, config) println "${TAG} countTask=${config.countTask}" //host generate task def generateHostConfigTaskName = getTaskName(variant, AppConstant.TASK_GENERATE, "HostConfig") def generateHostConfigTask = project.task(generateHostConfigTaskName) generateHostConfigTask.doLast { FileCreators.createHostConfig(project, generateBuildConfigTask, config) } generateHostConfigTask.group = AppConstant.TASKS_GROUP //depends on build config task if (generateBuildConfigTask) { generateHostConfigTask.dependsOn generateBuildConfigTask generateBuildConfigTask.finalizedBy generateHostConfigTask } //depends on mergeAssets Task def mergeAssetsTask = VariantCompat.getMergeAssetsTask(variant) if (mergeAssetsTask) { mergeAssetsTask.doLast{ FileCreators.createBuiltinJson(project, variant, config) } } variant.outputs.each { output -> VariantCompat.getProcessManifestTask(output).doLast { println "${AppConstant.TAG} processManifest: ${it.outputs.files}" it.outputs.files.each { File file -> updateManifest(file, newManifest) } } } } } } /** * * @hyongbai * * 在gradle plugin 3.0.0之前,file是文件,且文件名为AndroidManifest.xml * 在gradle plugin 3.0.0之后,file是目录,(特别是3.3.2)在这里改成递归的方式替换内部所有的 manifest 文件 * * @param file manifest文件 * @param newManifest 需要添加的 manifest 信息 */ def updateManifest(def file, def newManifest) { // 除了目录和AndroidManifest.xml之外,还可能会包含manifest-merger-debug-report.txt等不相干的文件,过滤它 if (file == null || !file.exists() || newManifest == null) return if (file.isDirectory()) { println "${AppConstant.TAG} updateManifest: ${file}" file.listFiles().each { updateManifest(it, newManifest) } } else if (file.name.equalsIgnoreCase("AndroidManifest.xml")) { appendManifest(file, newManifest) } } def appendManifest(def file, def content) { if (file == null || !file.exists()) return println "${AppConstant.TAG} appendManifest: ${file}" def updatedContent = file.getText("UTF-8").replaceAll("", content + "") file.write(updatedContent, 'UTF-8') } // 添加 【查看所有插件信息】 任务 def addShowPluginTask(def variant) { def showPluginsTaskName = getTaskName(variant, AppConstant.TASK_SHOW_PLUGIN, "") def showPluginsTask = project.task(showPluginsTaskName) showPluginsTask.doLast { IFileCreator creator = new PluginBuiltinJsonCreator(project, variant, config) def dir = creator.getFileDir() if (!dir.exists()) { println "${AppConstant.TAG} The ${dir.absolutePath} does not exist " println "${AppConstant.TAG} pluginsInfo=null" return } String fileContent = creator.getFileContent() if (null == fileContent) { return } new File(dir, creator.getFileName()).write(fileContent, 'UTF-8') } showPluginsTask.group = AppConstant.TASKS_GROUP //get mergeAssetsTask name, get real gradle task def mergeAssetsTask = VariantCompat.getMergeAssetsTask(variant) //depend on mergeAssetsTask so that assets have been merged if (mergeAssetsTask) { showPluginsTask.dependsOn mergeAssetsTask } } /** * 检查用户配置项 */ def checkUserConfig(config) { /* def persistentName = config.persistentName if (persistentName == null || persistentName.trim().equals("")) { project.logger.log(LogLevel.ERROR, "\n---------------------------------------------------------------------------------") project.logger.log(LogLevel.ERROR, " ERROR: persistentName can'te be empty, please set persistentName in replugin. ") project.logger.log(LogLevel.ERROR, "---------------------------------------------------------------------------------\n") System.exit(0) return } */ doCheckConfig("countProcess", config.countProcess) doCheckConfig("countTranslucentStandard", config.countTranslucentStandard) doCheckConfig("countTranslucentSingleTop", config.countTranslucentSingleTop) doCheckConfig("countTranslucentSingleTask", config.countTranslucentSingleTask) doCheckConfig("countTranslucentSingleInstance", config.countTranslucentSingleInstance) doCheckConfig("countNotTranslucentStandard", config.countNotTranslucentStandard) doCheckConfig("countNotTranslucentSingleTop", config.countNotTranslucentSingleTop) doCheckConfig("countNotTranslucentSingleTask", config.countNotTranslucentSingleTask) doCheckConfig("countNotTranslucentSingleInstance", config.countNotTranslucentSingleInstance) // 横屏 doCheckConfig("countTranslucentStandardLand", config.countTranslucentStandardLand) doCheckConfig("countTranslucentSingleTopLand", config.countTranslucentSingleTopLand) doCheckConfig("countTranslucentSingleTaskLand", config.countTranslucentSingleTaskLand) doCheckConfig("countTranslucentSingleInstanceLand", config.countTranslucentSingleInstanceLand) doCheckConfig("countNotTranslucentStandardLand", config.countNotTranslucentStandardLand) doCheckConfig("countNotTranslucentSingleTopLand", config.countNotTranslucentSingleTopLand) doCheckConfig("countNotTranslucentSingleTaskLand", config.countNotTranslucentSingleTaskLand) doCheckConfig("countNotTranslucentSingleInstanceLand", config.countNotTranslucentSingleInstanceLand) doCheckConfig("countTask", config.countTask) println '--------------------------------------------------------------------------' // println "${TAG} appID=${appID}" println "${TAG} useAppCompat=${config.useAppCompat}" println "${TAG} useOccupyLand=${config.useOccupyLand}" println "${TAG} useAndroidX=${config.useAndroidX}" // println "${TAG} persistentName=${config.persistentName}" println "${TAG} countProcess=${config.countProcess}" println "${TAG} countTranslucentStandard=${config.countTranslucentStandard}" println "${TAG} countTranslucentSingleTop=${config.countTranslucentSingleTop}" println "${TAG} countTranslucentSingleTask=${config.countTranslucentSingleTask}" println "${TAG} countTranslucentSingleInstance=${config.countTranslucentSingleInstance}" println "${TAG} countNotTranslucentStandard=${config.countNotTranslucentStandard}" println "${TAG} countNotTranslucentSingleTop=${config.countNotTranslucentSingleTop}" println "${TAG} countNotTranslucentSingleTask=${config.countNotTranslucentSingleTask}" println "${TAG} countNotTranslucentSingleInstance=${config.countNotTranslucentSingleInstance}" // 横屏 println "${TAG} countTranslucentStandardLand=${config.countTranslucentStandardLand()}" println "${TAG} countTranslucentSingleTopLand=${config.countTranslucentSingleTopLand()}" println "${TAG} countTranslucentSingleTaskLand=${config.countTranslucentSingleTaskLand()}" println "${TAG} countTranslucentSingleInstanceLand=${config.countTranslucentSingleInstanceLand()}" println "${TAG} countNotTranslucentStandardLand=${config.countNotTranslucentStandardLand()}" println "${TAG} countNotTranslucentSingleTopLand=${config.countNotTranslucentSingleTopLand()}" println "${TAG} countNotTranslucentSingleTaskLand=${config.countNotTranslucentSingleTaskLand()}" println "${TAG} countNotTranslucentSingleInstanceLand=${config.countNotTranslucentSingleInstanceLand()}" println "${TAG} countTask=${config.countTask}" println '--------------------------------------------------------------------------' } /** * 检查配置项是否正确 * @param name 配置项 * @param count 配置值 */ def doCheckConfig(def name, def count) { if (!(count instanceof Integer) || count < 0) { this.project.logger.log(LogLevel.ERROR, "\n--------------------------------------------------------") this.project.logger.log(LogLevel.ERROR, " ${TAG} ERROR: ${name} must be an positive integer. ") this.project.logger.log(LogLevel.ERROR, "--------------------------------------------------------\n") System.exit(0) } } String getTaskName(def variant, def prefix, def suffix){ return prefix + variant.name + suffix } } class RepluginConfig { /** 自定义进程的数量(除 UI 和 Persistent 进程) */ def countProcess = 3 /** 是否使用常驻进程? */ def persistentEnable = true /** 常驻进程名称(也就是上面说的 Persistent 进程,开发者可自定义)*/ def persistentName = ':GuardService' /** * 是否使用 横屏坑位 */ def useOccupyLand = false /** 背景不透明的坑的数量 */ def countNotTranslucentStandard = 6 def countNotTranslucentSingleTop = 2 def countNotTranslucentSingleTask = 3 def countNotTranslucentSingleInstance = 2 /** 背景透明的坑的数量 */ def countTranslucentStandard = 2 def countTranslucentSingleTop = 2 def countTranslucentSingleTask = 2 def countTranslucentSingleInstance = 3 /** 背景不透明的坑的数量 横屏 */ def countNotTranslucentStandardLand = 1 def countNotTranslucentSingleTopLand = 1 def countNotTranslucentSingleTaskLand = 1 def countNotTranslucentSingleInstanceLand = 1 def countNotTranslucentStandardLand(){useOccupyLand ? countNotTranslucentStandardLand : 0} def countNotTranslucentSingleTopLand(){useOccupyLand ? countNotTranslucentSingleTopLand : 0} def countNotTranslucentSingleTaskLand(){useOccupyLand ? countNotTranslucentSingleTaskLand : 0} def countNotTranslucentSingleInstanceLand(){useOccupyLand ? countNotTranslucentSingleInstanceLand : 0} /** 背景透明的坑的数量 横屏*/ def countTranslucentStandardLand = 1 def countTranslucentSingleTopLand = 1 def countTranslucentSingleTaskLand = 1 def countTranslucentSingleInstanceLand = 1 def countTranslucentStandardLand(){useOccupyLand ? countTranslucentStandardLand : 0} def countTranslucentSingleTopLand(){useOccupyLand ? countTranslucentSingleTopLand : 0} def countTranslucentSingleTaskLand(){useOccupyLand ? countTranslucentSingleTaskLand : 0} def countTranslucentSingleInstanceLand(){useOccupyLand ? countTranslucentSingleInstanceLand : 0} /** 宿主中声明的 TaskAffinity 的组数 */ def countTask = 2 /** * 是否使用 AppCompat 库 * com.android.support:appcompat-v7:25.2.0 */ def useAppCompat = false /** HOST 向下兼容的插件版本 */ def compatibleVersion = 10 /** HOST 插件版本 */ def currentVersion = 12 /** plugins-builtin.json 文件名自定义,默认是 "plugins-builtin.json" */ def builtInJsonFileName = "plugins-builtin.json" /** 是否自动管理 plugins-builtin.json 文件,默认自动管理 */ def autoManageBuiltInJsonFile = true /** assert目录下放置插件文件的目录自定义,默认是 assert 的 "plugins" */ def pluginDir = "plugins" /** 插件文件的后缀自定义,默认是".jar" 暂时支持 jar 格式*/ def pluginFilePostfix = ".jar" /** 当发现插件目录下面有不合法的插件 jar (有可能是特殊定制 jar)时是否停止构建,默认是 true */ def enablePluginFileIllegalStopBuild = true /** 宿主是否支持了 Androidx 库 */ def useAndroidX = false } ================================================ FILE: replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/host/creator/FileCreators.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.gradle.host.creator import com.qihoo360.replugin.gradle.host.AppConstant import com.qihoo360.replugin.gradle.host.creator.impl.java.RePluginHostConfigCreator import com.qihoo360.replugin.gradle.host.creator.impl.json.PluginBuiltinJsonCreator /** * @author RePlugin Team */ public class FileCreators { static def create(IFileCreator creator) { if (creator == null) { return } def dir = creator.getFileDir() if (!dir.exists()) { println "${AppConstant.TAG} mkdirs ${dir.getAbsolutePath()} : ${dir.mkdirs()}" } def targetFile = new File(dir, creator.getFileName()) String fileContent = creator.getFileContent() if (null == fileContent){ return } targetFile.write(fileContent, 'UTF-8') println "${AppConstant.TAG} rewrite ${targetFile.getAbsoluteFile()}" } static def createHostConfig(project, generateBuildConfigTask, config) { def creator = new RePluginHostConfigCreator(project, generateBuildConfigTask, config) create(creator) } static def createBuiltinJson(project, variant, config) { if (config.autoManageBuiltInJsonFile) { def creator = new PluginBuiltinJsonCreator(project, variant, config) create(creator) } } } ================================================ FILE: replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/host/creator/IFileCreator.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.gradle.host.creator; /** * @author RePlugin Team */ public interface IFileCreator { /** * 要生成的文件所在目录 */ File getFileDir() /** * 要生成的文件的名称 */ String getFileName() /** * 要生成的文件内容 */ String getFileContent() } ================================================ FILE: replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/host/creator/impl/java/RePluginHostConfigCreator.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.gradle.host.creator.impl.java import com.qihoo360.replugin.gradle.host.creator.IFileCreator /** * @author RePlugin Team */ public class RePluginHostConfigCreator implements IFileCreator { def static final HOST_CONFIG_PATH = '/com/qihoo360/replugin/gen/' def static final HOST_CONFIG_NAME = 'RePluginHostConfig.java' def config def project def generateBuildConfigTask def fileDir def fileName def RePluginHostConfigCreator(def project, def generateBuildConfigTask, def cfg) { this.project = project this.generateBuildConfigTask = generateBuildConfigTask this.config = cfg //make it generated in buildConfig output dir so that we don't need to hook anything File buildConfigGeneratedDir = this.generateBuildConfigTask.sourceOutputDir.asFile.get() fileName = HOST_CONFIG_NAME; fileDir = new File(buildConfigGeneratedDir, HOST_CONFIG_PATH) } @Override String getFileName() { fileName } @Override File getFileDir() { fileDir } @Override String getFileContent() { return """ package com.qihoo360.replugin.gen; /** * 注意:此文件由插件化框架自动生成,请不要手动修改。 */ public class RePluginHostConfig { // 常驻进程名字 public static String PERSISTENT_NAME = "${config.persistentName}"; // 是否使用“常驻进程”(见PERSISTENT_NAME)作为插件的管理进程。若为False,则会使用默认进程 public static boolean PERSISTENT_ENABLE = ${config.persistentEnable}; // 背景透明的坑的数量(每种 launchMode 不同) public static int ACTIVITY_PIT_COUNT_TS_STANDARD = ${config.countTranslucentStandard}; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TOP = ${config.countTranslucentSingleTop}; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TASK = ${config.countTranslucentSingleTask}; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE = ${ config.countTranslucentSingleInstance }; // 背景透明的坑的数量(每种 launchMode 不同)横屏 public static int ACTIVITY_PIT_COUNT_TS_STANDARD_LAND = ${config.countTranslucentStandardLand()}; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TOP_LAND = ${config.countTranslucentSingleTopLand()}; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TASK_LAND = ${config.countTranslucentSingleTaskLand()}; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE_LAND = ${ config.countTranslucentSingleInstanceLand() }; // 背景不透明的坑的数量(每种 launchMode 不同) public static int ACTIVITY_PIT_COUNT_NTS_STANDARD = ${config.countNotTranslucentStandard}; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP = ${config.countNotTranslucentSingleTop}; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK = ${config.countNotTranslucentSingleTask}; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE = ${ config.countNotTranslucentSingleInstance }; // 背景不透明的坑的数量(每种 launchMode 不同)横屏 public static int ACTIVITY_PIT_COUNT_NTS_STANDARD_LAND = ${config.countNotTranslucentStandardLand()}; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP_LAND = ${config.countNotTranslucentSingleTopLand()}; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK_LAND = ${config.countNotTranslucentSingleTaskLand()}; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE_LAND = ${ config.countNotTranslucentSingleInstanceLand() }; // TaskAffinity 组数 public static int ACTIVITY_PIT_COUNT_TASK = ${config.countTask}; // 是否使用 AppCompat 库 public static boolean ACTIVITY_PIT_USE_APPCOMPAT = ${config.useAppCompat}; // HOST 是否使用 androidx 库 public static boolean HOST_USE_ANDROIDX = ${config.useAndroidX}; // 是否使用 横屏坑位 public static boolean HOST_USE_OCCUPYLAND = ${config.useOccupyLand}; //------------------------------------------------------------ // 主程序支持的插件版本范围 //------------------------------------------------------------ // HOST 向下兼容的插件版本 public static int ADAPTER_COMPATIBLE_VERSION = ${config.compatibleVersion}; // HOST 插件版本 public static int ADAPTER_CURRENT_VERSION = ${config.currentVersion}; }""" } } ================================================ FILE: replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/host/creator/impl/json/PluginBuiltinJsonCreator.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.gradle.host.creator.impl.json import com.qihoo360.replugin.gradle.host.AppConstant import com.qihoo360.replugin.gradle.compat.VariantCompat import com.qihoo360.replugin.gradle.host.creator.IFileCreator import groovy.io.FileType import groovy.json.JsonOutput /** * @author RePlugin Team */ public class PluginBuiltinJsonCreator implements IFileCreator { def variant def config File fileDir def fileName def pluginInfos = [] def PluginBuiltinJsonCreator(def project, def variant, def cfg) { this.config = cfg this.variant = variant // make sure processResources Task execute after mergeAssets Task, get real gradle task // 在 com.android.tools.build:gradle:3.3.2 及之前 outputDir 为 File 类型。 // 但从 com.android.tools.build:gradle:3.4.1 开始 Google 将此类型改为 `Provider`。 final def out = VariantCompat.getMergeAssetsTask(variant)?.outputDir fileDir = File.class.isInstance(out) ? out : out?.get()?.getAsFile() fileName = config.builtInJsonFileName } @Override String getFileName() { fileName } @Override File getFileDir() { fileDir } @Override String getFileContent() { //查找插件文件并抽取信息,如果没有就直接返回null File pluginDirFile = new File(fileDir?.getAbsolutePath() + File.separator + config.pluginDir) if (!pluginDirFile.exists()) { println "${AppConstant.TAG} The ${pluginDirFile.absolutePath} does not exist " println "${AppConstant.TAG} pluginsInfo=null" return null } new File(fileDir.getAbsolutePath() + File.separator + config.pluginDir) .traverse(type: FileType.FILES, nameFilter: ~/.*\${config.pluginFilePostfix}/) { PluginInfoParser parser = null try { parser = new PluginInfoParser(it.absoluteFile, config) } catch (Exception e) { if (config.enablePluginFileIllegalStopBuild) { System.err.println "${AppConstant.TAG} the plugin(${it.absoluteFile.absolutePath}) is illegal !!!" throw new Exception(e) } } if (null != parser) { pluginInfos << parser.pluginInfo } } //插件为0个 if (pluginInfos.isEmpty()) { println "${AppConstant.TAG} pluginsSize=0" println "${AppConstant.TAG} pluginsInfo=null" return null } //构建插件们的json信息 def jsonOutput = new JsonOutput() String pluginInfosJson = jsonOutput.toJson(pluginInfos) //格式化打印插件们的json信息 println "${AppConstant.TAG} pluginsSize=${pluginInfos.size()}" println "${AppConstant.TAG} pluginsInfo=${jsonOutput.prettyPrint(pluginInfosJson)}" return pluginInfosJson } } ================================================ FILE: replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/host/creator/impl/json/PluginInfo.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.gradle.host.creator.impl.json /** * 插件信息模型 * @author RePlugin Team */ class PluginInfo { /** 插件文件路径 */ def path /** 插件包名 */ def pkg /** 插件名 */ def name /** 插件最低兼容版本 */ Long low /** 插件最高兼容版本 */ Long high /** 插件版本号 */ Long ver /** 框架版本号 */ Long frm } ================================================ FILE: replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/host/creator/impl/json/PluginInfoParser.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.gradle.host.creator.impl.json import net.dongliu.apk.parser.ApkFile import org.xml.sax.Attributes import org.xml.sax.SAXException import org.xml.sax.helpers.DefaultHandler import javax.xml.parsers.SAXParser import javax.xml.parsers.SAXParserFactory /** * 从manifest的xml中抽取PluginInfo信息 * @author RePlugin Team */ public class PluginInfoParser extends DefaultHandler { private final String ANDROID_NAME = "android:name" private final String ANDROID_VALUE = "android:value" private final String TAG_NAME = "com.qihoo360.plugin.name" private final String TAG_VERSION_LOW = "com.qihoo360.plugin.version.low" private final String TAG_VERSION_HIGH = "com.qihoo360.plugin.version.high" private final String TAG_VERSION_VER = "com.qihoo360.plugin.version.ver" private final String TAG_FRAMEWORK_VER = "com.qihoo360.framework.ver" private PluginInfo pluginInfo public PluginInfoParser(File pluginFile, def config) { pluginInfo = new PluginInfo() ApkFile apkFile = new ApkFile(pluginFile) String manifestXmlStr = apkFile.getManifestXml() ByteArrayInputStream inputStream = new ByteArrayInputStream(manifestXmlStr.getBytes("UTF-8")) SAXParserFactory factory = SAXParserFactory.newInstance() SAXParser parser = factory.newSAXParser() parser.parse(inputStream, this) String fullName = pluginFile.name pluginInfo.path = config.pluginDir + "/" + fullName String postfix = config.pluginFilePostfix pluginInfo.name = fullName.substring(0, fullName.length() - postfix.length()) } public PluginInfo getPluginInfo() { return pluginInfo; } @Override public void startDocument() throws SAXException { } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if ("meta-data" == qName) { switch (attributes.getValue(ANDROID_NAME)) { case TAG_NAME: pluginInfo.name = attributes.getValue(ANDROID_VALUE) break; case TAG_VERSION_LOW: pluginInfo.low = new Long(attributes.getValue(ANDROID_VALUE)) break; case TAG_VERSION_HIGH: pluginInfo.high = new Long(attributes.getValue(ANDROID_VALUE)) break; case TAG_VERSION_VER: pluginInfo.ver = new Long(attributes.getValue(ANDROID_VALUE)) break case TAG_FRAMEWORK_VER: pluginInfo.frm = new Long(attributes.getValue(ANDROID_VALUE)) break default: break } } else if ("manifest" == qName) { pluginInfo.pkg = attributes.getValue("package") pluginInfo.ver = new Long(attributes.getValue("android:versionCode")) } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { } @Override public void characters(char[] ch, int start, int length) throws SAXException { } } ================================================ FILE: replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/host/handlemanifest/ComponentsGenerator.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.gradle.host.handlemanifest import groovy.xml.MarkupBuilder /** * @author RePlugin Team */ class ComponentsGenerator { def static final infix = 'loader.a.Activity' def static final name = 'android:name' def static final process = 'android:process' def static final task = 'android:taskAffinity' def static final launchMode = 'android:launchMode' def static final authorities = 'android:authorities' def static final multiprocess = 'android:multiprocess' def static final cfg = 'android:configChanges' def static final cfgV = 'keyboard|keyboardHidden|orientation|screenSize' def static final exp = 'android:exported' def static final expV = 'false' def static final ori = 'android:screenOrientation' def static final oriV = 'portrait' def static final oriL = 'landscape' def static final LAND = 'LAND' def static final theme = 'android:theme' def static final themeTS = '@android:style/Theme.Translucent.NoTitleBar' def static final THEME_NTS_USE_APP_COMPAT = '@style/Theme.AppCompat' def static final THEME_NTS_NOT_USE_APP_COMPAT = '@android:style/Theme.NoTitleBar' def static themeNTS = THEME_NTS_NOT_USE_APP_COMPAT /** * 动态生成插件化框架中需要的组件 * * @param applicationID 宿主的 applicationID * @param config 用户配置 * @return String 插件化框架中需要的组件 */ def static generateComponent(def applicationID, def config) { // 是否使用 AppCompat 库(涉及到默认主题) if (config.useAppCompat) { themeNTS = THEME_NTS_USE_APP_COMPAT } else { themeNTS = THEME_NTS_NOT_USE_APP_COMPAT } def writer = new StringWriter() def xml = new MarkupBuilder(writer) /* UI 进程 */ xml.application { /* 需要编译期动态修改进程名的组件*/ String pluginMgrProcessName = config.persistentEnable ? config.persistentName : applicationID // 常驻进程Provider provider( "${name}":"com.qihoo360.replugin.component.process.ProcessPitProviderPersist", "${authorities}":"${applicationID}.loader.p.main", "${exp}":"false", "${process}":"${pluginMgrProcessName}") provider( "${name}":"com.qihoo360.replugin.component.provider.PluginPitProviderPersist", "${authorities}":"${applicationID}.Plugin.NP.PSP", "${exp}":"false", "${process}":"${pluginMgrProcessName}") // ServiceManager 服务框架 provider( "${name}":"com.qihoo360.mobilesafe.svcmanager.ServiceProvider", "${authorities}":"${applicationID}.svcmanager", "${exp}":"false", "${multiprocess}":"false", "${process}":"${pluginMgrProcessName}") service( "${name}":"com.qihoo360.replugin.component.service.server.PluginPitServiceGuard", "${process}":"${pluginMgrProcessName}") /* 透明坑 */ config.countTranslucentStandard.times { activity( "${name}": "${applicationID}.${infix}N1NRTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}") } config.countTranslucentSingleTop.times { activity( "${name}": "${applicationID}.${infix}N1STPTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${launchMode}": "singleTop") } config.countTranslucentSingleTask.times { activity( "${name}": "${applicationID}.${infix}N1STTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${launchMode}": "singleTask") } config.countTranslucentSingleInstance.times { activity( "${name}": "${applicationID}.${infix}N1SITS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${launchMode}": "singleInstance") } /* 透明坑 横屏*/ config.countTranslucentStandardLand().times { activity( "${name}": "${applicationID}.${infix}N1${LAND}NRTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}") } config.countTranslucentSingleTopLand().times { activity( "${name}": "${applicationID}.${infix}N1${LAND}STPTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${launchMode}": "singleTop") } config.countTranslucentSingleTaskLand().times { activity( "${name}": "${applicationID}.${infix}N1${LAND}STTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${launchMode}": "singleTask") } config.countTranslucentSingleInstanceLand().times { activity( "${name}": "${applicationID}.${infix}N1${LAND}SITS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${launchMode}": "singleInstance") } /* 不透明坑 */ config.countNotTranslucentStandard.times { activity( "${name}": "${applicationID}.${infix}N1NRNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}") } config.countNotTranslucentSingleTop.times { activity( "${name}": "${applicationID}.${infix}N1STPNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${launchMode}": "singleTop") } config.countNotTranslucentSingleTask.times { activity( "${name}": "${applicationID}.${infix}N1STNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${launchMode}": "singleTask",) } config.countNotTranslucentSingleInstance.times { activity( "${name}": "${applicationID}.${infix}N1SINTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${launchMode}": "singleInstance") } /* 不透明坑 */ config.countNotTranslucentStandardLand().times { activity( "${name}": "${applicationID}.${infix}N1${LAND}NRNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}") } config.countNotTranslucentSingleTopLand().times { activity( "${name}": "${applicationID}.${infix}N1${LAND}STPNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${launchMode}": "singleTop") } config.countNotTranslucentSingleTaskLand().times { activity( "${name}": "${applicationID}.${infix}N1${LAND}STNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${launchMode}": "singleTask",) } config.countNotTranslucentSingleInstanceLand().times { activity( "${name}": "${applicationID}.${infix}N1${LAND}SINTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${launchMode}": "singleInstance") } /* TaskAffinity */ // N1TA0NRTS1:UI进程->第0组->standardMode->透明主题->第1个坑位 (T: Task, NR: Standard, TS: Translucent) config.countTask.times { i -> config.countTranslucentStandard.times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}NRTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${task}": ":t${i}") } config.countTranslucentSingleTop.times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}STPTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${task}": ":t${i}", "${launchMode}": "singleTop") } config.countTranslucentSingleTask.times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}STTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${task}": ":t${i}", "${launchMode}": "singleTask") } config.countNotTranslucentStandard.times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}NRNTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${task}": ":t${i}") } config.countNotTranslucentSingleTop.times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}STPNTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${task}": ":t${i}", "${launchMode}": "singleTop") } config.countNotTranslucentSingleTask.times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}STNTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${task}": ":t${i}", "${launchMode}": "singleTask") } // 横屏 config.countTranslucentStandardLand().times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}${LAND}NRTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${task}": ":t${i}") } config.countTranslucentSingleTopLand().times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}${LAND}STPTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${task}": ":t${i}", "${launchMode}": "singleTop") } config.countTranslucentSingleTaskLand().times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}${LAND}STTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${task}": ":t${i}", "${launchMode}": "singleTask") } config.countNotTranslucentStandardLand().times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}${LAND}NRNTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${task}": ":t${i}") } config.countNotTranslucentSingleTopLand().times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}${LAND}STPNTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${task}": ":t${i}", "${launchMode}": "singleTop") } config.countNotTranslucentSingleTaskLand().times { j -> activity( "${name}": "${applicationID}.${infix}N1TA${i}${LAND}STNTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${task}": ":t${i}", "${launchMode}": "singleTask") } } } // 删除 application 标签 def normalStr = writer.toString().replace("", "").replace("", "") // 将单进程和多进程的组件相加 normalStr + generateMultiProcessComponent(applicationID, config) } /** * 生成多进程坑位配置 */ def static generateMultiProcessComponent(def applicationID, def config) { if (config.countProcess == 0) { return '' } def writer = new StringWriter() def xml = new MarkupBuilder(writer) /* 自定义进程 */ xml.application { config.countProcess.times { p -> config.countTranslucentStandard.times { activity( "${name}": "${applicationID}.${infix}P${p}NRTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${process}": ":p${p}") } config.countTranslucentSingleTop.times { activity( "${name}": "${applicationID}.${infix}P${p}STPTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${process}": ":p${p}", "${launchMode}": "singleTop") } config.countTranslucentSingleTask.times { activity( "${name}": "${applicationID}.${infix}P${p}STTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${process}": ":p${p}", "${launchMode}": "singleTask") } config.countTranslucentSingleInstance.times { activity( "${name}": "${applicationID}.${infix}P${p}SITS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${process}": ":p${p}", "${launchMode}": "singleInstance") } config.countNotTranslucentStandard.times { activity( "${name}": "${applicationID}.${infix}P${p}NRNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${process}": ":p${p}") } config.countNotTranslucentSingleTop.times { activity( "${name}": "${applicationID}.${infix}P${p}STPNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${process}": ":p${p}", "${launchMode}": "singleTop") } config.countNotTranslucentSingleTask.times { activity( "${name}": "${applicationID}.${infix}P${p}STNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${process}": ":p${p}", "${launchMode}": "singleTask") } config.countNotTranslucentSingleInstance.times { activity( "${name}": "${applicationID}.${infix}P${p}SINTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${process}": ":p${p}", "${launchMode}": "singleInstance") } config.countTranslucentStandardLand().times { activity( "${name}": "${applicationID}.${infix}P${p}${LAND}NRTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${process}": ":p${p}") } config.countTranslucentSingleTopLand().times { activity( "${name}": "${applicationID}.${infix}P${p}${LAND}STPTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${process}": ":p${p}", "${launchMode}": "singleTop") } config.countTranslucentSingleTaskLand().times { activity( "${name}": "${applicationID}.${infix}P${p}${LAND}STTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${process}": ":p${p}", "${launchMode}": "singleTask") } config.countTranslucentSingleInstanceLand().times { activity( "${name}": "${applicationID}.${infix}P${p}${LAND}SITS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${process}": ":p${p}", "${launchMode}": "singleInstance") } config.countNotTranslucentStandardLand().times { activity( "${name}": "${applicationID}.${infix}P${p}${LAND}NRNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${process}": ":p${p}") } config.countNotTranslucentSingleTopLand().times { activity( "${name}": "${applicationID}.${infix}P${p}${LAND}STPNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${process}": ":p${p}", "${launchMode}": "singleTop") } config.countNotTranslucentSingleTaskLand().times { activity( "${name}": "${applicationID}.${infix}P${p}${LAND}STNTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${process}": ":p${p}", "${launchMode}": "singleTask") } config.countNotTranslucentSingleInstanceLand().times { activity( "${name}": "${applicationID}.${infix}P${p}${LAND}SINTS${it}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${process}": ":p${p}", "${launchMode}": "singleInstance") } /* TaskAffinity */ config.countTask.times { i -> config.countTranslucentStandard.times { j -> activity( "${name}": "${applicationID}.${infix}P${p}TA${i}NRTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${process}": ":p${p}", "${task}": ":t${i}") } config.countTranslucentSingleTop.times { j -> activity( "${name}": "${applicationID}.${infix}P${p}TA${i}STPTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${launchMode}": "singleTop", "${process}": ":p${p}", "${task}": ":t${i}") } config.countTranslucentSingleTask.times { j -> activity( "${name}": "${applicationID}.${infix}P${p}TA${i}STTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeTS}", "${launchMode}": "singleTask", "${process}": ":p${p}", "${task}": ":t${i}") } config.countNotTranslucentStandard.times { j -> activity( "${name}": "${applicationID}.${infix}P${p}TA${i}NRNTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${process}": ":p${p}", "${task}": ":t${i}") } config.countNotTranslucentSingleTop.times { j -> activity( "${name}": "${applicationID}.${infix}P${p}TA${i}STPNTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${launchMode}": "singleTop", "${process}": ":p${p}", "${task}": ":t${i}") } config.countNotTranslucentSingleTask.times { j -> activity( "${name}": "${applicationID}.${infix}P${p}TA${i}STNTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriV}", "${theme}": "${themeNTS}", "${launchMode}": "singleTask", "${process}": ":p${p}", "${task}": ":t${i}") } // 横屏 config.countTranslucentStandardLand().times { j -> activity( "${name}": "${applicationID}.${infix}P${p}${LAND}TA${i}NRTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${process}": ":p${p}", "${task}": ":t${i}") } config.countTranslucentSingleTopLand().times { j -> activity( "${name}": "${applicationID}.${infix}P${p}${LAND}TA${i}STPTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${launchMode}": "singleTop", "${process}": ":p${p}", "${task}": ":t${i}") } config.countTranslucentSingleTaskLand().times { j -> activity( "${name}": "${applicationID}.${infix}P${p}${LAND}TA${i}STTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeTS}", "${launchMode}": "singleTask", "${process}": ":p${p}", "${task}": ":t${i}") } config.countNotTranslucentStandardLand().times { j -> activity( "${name}": "${applicationID}.${infix}P${p}TA${i}NRNTS${LAND}${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${process}": ":p${p}", "${task}": ":t${i}") } config.countNotTranslucentSingleTopLand().times { j -> activity( "${name}": "${applicationID}.${infix}P${p}${LAND}TA${i}STPNTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${launchMode}": "singleTop", "${process}": ":p${p}", "${task}": ":t${i}") } config.countNotTranslucentSingleTaskLand().times { j -> activity( "${name}": "${applicationID}.${infix}P${p}${LAND}TA${i}STNTS${j}", "${cfg}": "${cfgV}", "${exp}": "${expV}", "${ori}": "${oriL}", "${theme}": "${themeNTS}", "${launchMode}": "singleTask", "${process}": ":p${p}", "${task}": ":t${i}") } } /* Provider */ // 支持插件中的 Provider 调用 provider("${name}": "com.qihoo360.replugin.component.provider.PluginPitProviderP${p}", "android:authorities": "${applicationID}.Plugin.NP.${p}", "${process}": ":p${p}", "${exp}": "${expV}") // fixme hujunjie 100 不写死 // 支持进程Provider拉起 provider("${name}": "com.qihoo360.replugin.component.process.ProcessPitProviderP${p}", "android:authorities": "${applicationID}.loader.p.mainN${100 - p}", "${process}": ":p${p}", "${exp}": "${expV}") /* Service */ // 支持使用插件的Service坑位 // Added by Jiongxuan Zhang service("${name}": "com.qihoo360.replugin.component.service.server.PluginPitServiceP${p}", "${process}": ":p${p}", "${exp}": "${expV}") } } // 删除 application 标签 return writer.toString().replace("", "").replace("", "") } } ================================================ FILE: replugin-host-gradle/src/main/resources/META-INF/gradle-plugins/replugin-host-gradle.properties ================================================ # # Copyright (C) 2005-2017 Qihoo 360 Inc. # # 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 # # http://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. # implementation-class=com.qihoo360.replugin.gradle.host.Replugin ================================================ FILE: replugin-host-library/README.md ================================================ # RePlugin Host Library RePlugin Host Library是一个Java工程,由 **主程序** 负责引入。 几乎所有和RePlugin相关的代码都在其中,是核心工程。 开发者需要依赖此Library,以实现对RePlugin的接入。请参见WiKi以了解接入方法。 有关RePlugin Host Library的详细描述,请访问我们的WiKi,以了解更多的内容。 ================================================ FILE: replugin-host-library/build.gradle ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { id 'com.android.application' version '7.4.2' apply false id 'maven-publish' } configure(allprojects - project(':replugin-host-lib')) { println "applying java plugin to $project" apply plugin: 'java-library' } ================================================ FILE: replugin-host-library/gradle/wrapper/gradle-wrapper.properties ================================================ # # Copyright (C) 2005-2017 Qihoo 360 Inc. # # 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 # # http://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. # #Fri Mar 03 10:15:50 CST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip ================================================ FILE: replugin-host-library/gradle.properties ================================================ # # Copyright (C) 2005-2017 Qihoo 360 Inc. # # 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 # # http://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. # # 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=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # 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 ================================================ FILE: replugin-host-library/gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # 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 case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # 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 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" ] ; 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 # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: replugin-host-library/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 @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= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @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 Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_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=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software 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: replugin-host-library/replugin-host-lib/bintray.gradle ================================================ apply plugin: 'maven-publish' apply plugin: 'com.jfrog.bintray' def siteUrl = '' def gitUrl = '' group = 'com.qihoo360.replugin' version = '3.1.0' //生成文档注释 task androidJavadocs(type: Javadoc) { failOnError = false source = android.sourceSets.main.java.srcDirs ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" classpath += files(ext.androidJar) } //将文档打包成jar task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { archiveClassifier.set('javadoc') from androidJavadocs.destinationDir } //将源码打包,这一点对kotlin来说很重要,否则业务侧无法看到源码 task androidSourcesJar(type: Jar) { archiveClassifier.set('sources') from android.sourceSets.main.java.srcDirs } publishing { publications { mavenJava(MavenPublication) { groupId = group artifactId = 'replugin-host-lib' version = version artifact(androidSourcesJar)//将源码打包进aar,如果不需要可以去掉 artifact(androidJavadocsJar)//将注释打包进aar,如果不需要可以去掉 versionMapping { usage('java-api') { fromResolutionOf('runtimeClasspath') } usage('java-runtime') { fromResolutionResult() } } pom { name = 'My Library' description = 'RePlugin - A flexible, stable, easy-to-use Android Plug-in Framework' url = siteUrl properties = [ myProp: "value", "prop.with.dots": "anotherValue" ] licenses { license { name = 'The Apache License, Version 2.0' url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } developers { developer { id 'qihoo360' //填写的一些基本信息 name 'qihoo360' email 'replugin@gmail.com' } } scm { connection gitUrl developerConnection gitUrl url siteUrl } } } } // repositories { // maven { // // change URLs to point to your repos, e.g. http://my.org/repo // def releasesRepoUrl = layout.buildDirectory.dir('repos/releases') // def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots') // url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl // } // } } ================================================ FILE: replugin-host-library/replugin-host-lib/build.gradle ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ /** * @author RePlugin Team */ plugins { id 'com.android.library' id 'maven-publish' } android { namespace 'com.qihoo360.mobilesafe.core' compileSdk 34 defaultConfig { minSdkVersion 9 targetSdkVersion 34 versionCode 2 versionName version consumerProguardFiles 'replugin-rules.pro' buildConfigField "int", 'VERSION_CODE', String.valueOf(2) buildConfigField 'String', 'VERSION_NAME', "\"" + version + "\"" } lintOptions { abortOnError false } // 务必要加上此段话,这样默认会出Debug版AAR,会带上日志方便定位 defaultPublishConfig "debug" compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } project.ext.RP_ARTIFACT_ID = 'replugin-host-lib' apply from: '../../rp-publish.gradle' ================================================ FILE: replugin-host-library/replugin-host-lib/replugin-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in ./sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # 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 *; #} # —————————————————————————————————————— # aar 的接入方在构建 apk 时会使用的混淆规则 # # 即:此处 keep 的内容可以被宿主'插件'调用,宿主的 Release 中,以下内容也可以访问。 # 注:因为 proguardFiles 也添加了此文件,所以生成 aar 包时,也会应用此混淆规则。 # ———————————————————————————————————————————————————————————————————————————— # RePlugin混淆时必须用到的。有两个目的: # 1. 不添加该方法,则插件在运行时会抛出各种异常(因为需要调用一些Keep住的类) # 2. 防止恶意开发者对RePlugin做反编译,并对您的应用做出一些破坏性的处理 # —————————————————————————————————————— # 混淆时将LogDebug全部干掉,防止泄露到外界。需使用sdk/proguard-android-optimize.txt # 【有问题,已注释掉】,因为它对“带操作符”和“需要函数调用”的地方不会被优化掉。如,原来是 # LogDebug.d("XXX", "YYY" + a); # 优化后变成: # new StringBuilder("YYY").append(a); # 等于还是输出了Log。 # -assumenosideeffects class com.qihoo360.replugin.helper.LogDebug # -keepattributes SourceFile,LineNumberTable -keepattributes LineNumberTable # 架构基础类 -keep class com.qihoo360.replugin.RePlugin { public protected *; } # LocalBroadcastManager,插件会用 -keep public class android.support.v4.content.LocalBroadcastManager { public *; } # 架构具体实现,和插件反射调用部分 -keep class com.qihoo360.replugin.model.PluginInfo { public protected *; } -keep class com.qihoo360.replugin.IBinderGetter { public protected *; } -keep class com.qihoo360.replugin.component.ComponentList { public protected *; } -keep class com.qihoo360.framework.** { public protected *; } -keep class com.qihoo360.i.** { public protected *; } -keep class com.qihoo360.plugins.** { public protected *; } -keep class com.qihoo360.plugin.** { public protected *; } -keep class com.qihoo360.replugin.component.dummy.** { public protected *; } -keep class com.qihoo360.replugin.component.provider.PluginProviderClient { public protected *; } -keep class com.qihoo360.replugin.component.provider.PluginProviderClient2 { public protected *; } -keep class com.qihoo360.replugin.component.service.PluginServiceClient { public protected *; } -keep class com.qihoo360.replugin.component.provider.PluginPitProviderP0 { public protected *; } -keep class com.qihoo360.replugin.component.provider.PluginPitProviderP1 { public protected *; } -keep class com.qihoo360.replugin.component.provider.PluginPitProviderP2 { public protected *; } # ProcessPitProviderP0 未被自动 keep -keep class com.qihoo360.replugin.component.process.ProcessPitProviderP0 { public protected *; } -keep class com.qihoo360.replugin.component.process.ProcessPitProviderP1 { public protected *; } -keep class com.qihoo360.replugin.component.process.ProcessPitProviderP2 { public protected *; } # TODO 可能要废弃的类。目前旧卫士插件在用 # Pref -keep public class com.qihoo360.mobilesafe.api.Pref { public *; } # IPC -keep public class com.qihoo360.replugin.base.IPC { public *; } # QihooServiceManager -keep public class com.qihoo360.mobilesafe.svcmanager.QihooServiceManager { public *; } # Old PPC/PSC -keep class com.qihoo360.loader2.mgr.PluginProviderClient { public protected *; } -keep class com.qihoo360.loader2.mgr.PluginServiceClient { public protected *; } # ------------ keep 以下类,以防卫士主程序 AOP DEBUG 失败 ------------ -keep class com.qihoo360.replugin.component.activity.ActivityInjector { *;} # replugin-host-gradle 生成的 java 文件 -keep class com.qihoo360.replugin.gen.RePluginHostConfig { public *; } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/AndroidManifest.xml ================================================ ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/loader2/IPlugin.aidl ================================================ package com.qihoo360.loader2; /** * @author RePlugin Team */ interface IPlugin { IBinder query(String name); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/loader2/IPluginClient.aidl ================================================ package com.qihoo360.loader2; import com.qihoo360.replugin.component.service.server.IPluginServiceServer; /** * @author RePlugin Team */ interface IPluginClient { // 参数 plugin, process 可能有冗余,目前临时使用,后续可能优化 String allocActivityContainer(String plugin, int process, String target, in Intent intent); // 参数 plugin 用来处理多插件单进程情况 IBinder queryBinder(String plugin, String binder); void releaseBinder(); oneway void sendIntent(in Intent intent); void sendIntentSync(in Intent intent); int sumActivities(); IPluginServiceServer fetchServiceServer(); /** * 插件收到广播 * * @param plugin 插件名称 * @param receiver Receiver 名称 * @param Intent 广播的 Intent 数据 */ void onReceive(String plugin, String receiver, in Intent intent); /** * dump通过插件化框架启动起来的Service信息 */ String dumpServices(); /** * dump插件化框架中存储的详细Activity坑位映射表 */ String dumpActivities(); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/loader2/IPluginHost.aidl ================================================ package com.qihoo360.loader2; import android.content.IntentFilter; import android.content.Intent; import com.qihoo360.loader2.IPluginClient; import com.qihoo360.loader2.PluginBinderInfo; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.component.service.server.IPluginServiceServer; import com.qihoo360.replugin.packages.IPluginManagerServer; /** * @author RePlugin Team */ interface IPluginHost { void installBinder(String name, in IBinder binder); IBinder fetchBinder(String name); long fetchPersistentCookie(); IPluginClient startPluginProcess(String plugin, int process, inout PluginBinderInfo info); String attachPluginProcess(String process, int index, in IBinder binder, String def); List listPlugins(); void regActivity(int index, String plugin, String container, String activity); void unregActivity(int index, String plugin, String container, String activity); void regService(int index, String plugin, String service); void unregService(int index, String plugin, String service); void regPluginBinder(in PluginBinderInfo info, IBinder binder); void unregPluginBinder(in PluginBinderInfo info, IBinder binder); /** * 注册某插件下所有静态声明的的 receiver 到常驻进程 */ void regReceiver(String plugin, in Map receiverFilterMap); void unregReceiver(); /** * 插件收到广播 * * @param plugin 插件名称 * @param receiver Receiver 名称 * @param Intent 广播的 Intent 数据 */ void onReceive(String plugin, String receiver, in Intent intent); int sumBinders(int index); void updatePluginInfo(in PluginInfo info); PluginInfo pluginDownloaded(String path); boolean pluginUninstalled(in PluginInfo info); boolean pluginExtracted(String path); oneway void sendIntent2Process(String target, in Intent intent); oneway void sendIntent2Plugin(String target, in Intent intent); void sendIntent2ProcessSync(String target, in Intent intent); void sendIntent2PluginSync(String target, in Intent intent); boolean isProcessAlive(String name); IBinder queryPluginBinder(String plugin, String binder); /** * 根据 Inent 查询所有插件中的与之匹配的 Receivers */ List queryPluginsReceiverList(in Intent intent); /** * 获取“全新Service管理方案”在Server端的服务 * Added by Jiongxuan Zhang */ IPluginServiceServer fetchServiceServer(); /** * 获取 IPluginManagerServer(纯APK方案使用)的插件服务 * Added by Jiongxuan Zhang */ IPluginManagerServer fetchManagerServer(); /** * 根据 taskAffinity,判断应该取第几组 TaskAffinity * 由于 taskAffinity 是跨进程的属性,所以这里要将 taskAffinityGroup 的数据保存在常驻进程中 * Added by hujunjie */ int getTaskAffinityGroupIndex(String taskAffinity); /** * 通过进程名来获取PID */ int getPidByProcessName(String processName); /** * 通过PID来获取进程名 */ String getProcessNameByPid(int pid); /** * dump详细的运行时信息 */ String dump(); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/loader2/PluginBinderInfo.aidl ================================================ package com.qihoo360.loader2; parcelable PluginBinderInfo; ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/loader2/mgr/IServiceConnection.aidl ================================================ // IServiceConnection.aidl // Same as android.app.IServiceConnection package com.qihoo360.loader2.mgr; import android.content.ComponentName; /** @hide */ oneway interface IServiceConnection { void connected(in ComponentName name, IBinder service); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/loader2/sp/IPref.aidl ================================================ package com.qihoo360.loader2.sp; import android.os.Bundle; /** * @author RePlugin Team */ interface IPref { String get(String category, String key, String defValue); void set(String category, String key, String value); Bundle getAll(String category); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/mobilesafe/svcmanager/IServiceChannel.aidl ================================================ package com.qihoo360.mobilesafe.svcmanager; import com.qihoo360.replugin.IBinderGetter; interface IServiceChannel { IBinder getService(String serviceName); void addService(String serviceName, IBinder service); void addServiceDelayed(String serviceName, IBinderGetter getter); void removeService(String serviceName); IBinder getPluginService(String pluginName, String serviceName, IBinder deathMonitor); void onPluginServiceRefReleased(String pluginName, String serviceName); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/replugin/IBinderGetter.aidl ================================================ package com.qihoo360.replugin; /** * Binder的获取器,可用于延迟加载IBinder的情况。 *

* 目前用于: *

* * RePlugin.registerGlobalBinderDelayed * * @author RePlugin Team */ interface IBinderGetter { /** * 获取IBinder对象 */ IBinder get(); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/replugin/component/service/server/IPluginServiceServer.aidl ================================================ package com.qihoo360.replugin.component.service.server; import android.content.ComponentName; import android.os.Messenger; import com.qihoo360.loader2.mgr.IServiceConnection; /** * 负责Server端的服务调度、提供等工作,是服务的提供方,核心类之一 * * @hide 框架内部使用 * @author RePlugin Team */ interface IPluginServiceServer { ComponentName startService(in Intent intent, in Messenger client); int stopService(in Intent intent, in Messenger client); int bindService(in Intent intent, in IServiceConnection conn, int flags, in Messenger client); boolean unbindService(in IServiceConnection conn); String dump(); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/replugin/model/PluginInfo.aidl ================================================ package com.qihoo360.replugin.model; parcelable PluginInfo; ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/replugin/packages/IPluginManagerServer.aidl ================================================ package com.qihoo360.replugin.packages; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.packages.PluginRunningList; /** * 插件管理器。用来控制插件的安装、卸载、获取等。运行在常驻进程中 *

* 补充:涉及到插件交互、运行机制有关的管理器,在IPluginHost中 * * @author RePlugin Team */ interface IPluginManagerServer { /** * 安装一个插件 *

* 注意:若为旧插件(p-n开头),则应使用IPluginHost的pluginDownloaded方法 * * @return 安装的插件的PluginInfo对象 */ PluginInfo install(String path); /** * 卸载一个插件 *

* 注意:只针对“纯APK”插件方案 * * @param info 插件信息 * @return 是否成功卸载插件? */ boolean uninstall(in PluginInfo info); /** * 加载插件列表,方便之后使用 *

* TODO 这里只返回"新版插件",供PmBase使用。将来会合并 * * @return PluginInfo的列表 */ List load(); /** * 更新所有插件列表 * * @return PluginInfo的列表 */ List updateAll(); /** * 设置isUsed状态,并通知所有进程更新 * * @param pluginName 插件名 * @param used 是否已经使用 */ void updateUsed(String pluginName, boolean used); /** * 获取正在运行的插件列表 * * @return 正在运行的插件名列表 */ PluginRunningList getRunningPlugins(); /** * 插件是否正在运行? * * @param pluginName 插件名 * @param process 指定进程名,如为Null则表示查所有 * @return 是否在运行? */ boolean isPluginRunning(String pluginName, String process); /** * 当进程启动时,同步正在运行的插件状态到Server端 * * @param list 正在运行的插件名列表 */ void syncRunningPlugins(in PluginRunningList list); /** * 当进程启动时,同步正在运行的插件状态到Server端 * * @param processName 进程名 * @param pluginName 正在运行的插件名 */ void addToRunningPlugins(String processName, int pid, String pluginName); /** * 获取正在运行此插件的进程名列表 * * @param pluginName 要查询的插件名 * @return 正在运行此插件的进程名列表。一定不会为Null */ String[] getRunningProcessesByPlugin(String pluginName); /** * 预安装内置插件到app_p_a目录,只做p.l文件的写入,真正的安装执行,在做插件Load的时候执行 * * @param 待安装的插件列表 * @return 安装后的所有插件列表 */ List preInstallBuiltins(in List pluginInfos); void updateTP(String plugin,int type,String path); /** * 设置isUsed状态,并通知所有进程更新 * * @param pluginName 插件名 * @param path 是否已经使用 * @param type 是否已经使用 * @param used 是否已经使用 */ void updateUsedNew(String pluginName, String path, int type, boolean used); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/aidl/com/qihoo360/replugin/packages/PluginRunningList.aidl ================================================ package com.qihoo360.replugin.packages; parcelable PluginRunningList; ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/i/Factory.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.i; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.text.TextUtils; import com.qihoo360.loader2.PluginCommImpl; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.component.ComponentList; import java.util.List; /** * Wrapper类,简化使用代码 * *

插件文件名规范:barcode-1-10-2.jar

*

    *
  • * 最小支持版本 :例如 1
    * 插件可选择host/adapter,低于该版本的host/adapter是无法使用
    * 即插件可限制老的host/adapter不能使用,只能在特定版本以后的新的host/adapter使用 *
  • *
  • * 当前接口版本:例如 10
    * host/adapter可选择插件 *
  • *
  • * 插件自身版本:例如 2
    * 通常是用来表示是一个bug修复版本 *
  • *
* *

适配器文件名规范:adapter-1-10-3.jar

*

    *
  • * 支持接口版本 :例如 1
    * 表明该适配器支持的主程序 *
  • *
  • * 当前接口版本:例如 10
    * 当插件的当前接口版本小于或等于该值时才会加载,即适配器可选择特定版本以前的插件
    * 也就是说,限制只能使用较老插件,不支持超出适配器能力之外的插件 *
  • *
  • * 适配器自身版本:例如 3
    * 通常是用来表示是一个bug修复版本 *
  • *
* * @author RePlugin Team * @deprecated 慢慢会被废弃掉,只留着旧卫士插件反射用。现阶段先不做优化 */ public final class Factory { /** * 插件的入口包名前缀 * 在插件中,该包名不能混淆 * 例如,二维码的插件入口类为:com.qihoo360.plugin.barcode.Entry * @hide 内部框架使用 */ public static final String PLUGIN_ENTRY_PACKAGE_PREFIX = "com.qihoo360.plugin"; /** * 新版SDK(RePlugin-library)插件入口报名前缀 * 在插件中,该包名不能混淆 */ public static final String REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX = "com.qihoo360.replugin"; /** * 插件的入口类 * 在插件中,该名字不能混淆 * @hide 内部框架使用 */ public static final String PLUGIN_ENTRY_CLASS_NAME = "Entry"; /** * 插件的入口类导出函数 * 在插件中,该方法名不能混淆 * 通过该函数创建IPlugin对象 * @hide 内部框架使用 */ public static final String PLUGIN_ENTRY_EXPORT_METHOD_NAME = "create"; /** * 参数1:插件上下文,可通过它获取应用上下文 * 参数2: * @hide 内部框架使用 */ public static final Class PLUGIN_ENTRY_EXPORT_METHOD_PARAMS[] = { Context.class, IPluginManager.class }; /** * 参数1:插件上下文,可通过它获取应用上下文 * 参数2:HOST的类加载器 * 参数3:已废弃 * 返回:插件 IPlugin.aidl * @hide 内部框架使用 */ public static final Class PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS[] = { Context.class, ClassLoader.class, IBinder.class }; /** * @hide 内部框架使用 */ public static PluginCommImpl sPluginManager; /** * @deprecated 新插件框架不再用i接口依赖,此接口已废弃 * @param name 插件名 * @param c 需要查询的interface的类 * @return */ @Deprecated public static final IModule query(String name, Class c) { return sPluginManager.query(name, c); } /** * 调用此接口不会在当前进程加载插件 * @param name 插件名 * @return */ public static final boolean isPluginLoaded(String name) { return sPluginManager.isPluginLoaded(name); } /** * 调用此接口会在当前进程加载插件 * @param name 插件名 * @param binder 需要查询的binder的名称(不要用IXXX.class.getName,因为不再建议keep IXXX类,IXXX有可能被混淆) * @return */ public static final IBinder query(String name, String binder) { return sPluginManager.query(name, binder); } /** * 调用此接口会在指定进程加载插件 * @param name 插件名 * @param binder 需要查询的binder的名称(不要用IXXX.class.getName,因为不再建议keep IXXX类,IXXX有可能被混淆) * @param process 是否在指定进程中启动 * @return */ public static final IBinder query(String name, String binder, int process) { return sPluginManager.query(name, binder, process); } /** * 警告:低层接口 * 当插件升级之后,通过adapter.jar标准接口,甚至invoke接口都无法完成任务时,可通过此接口反射来完成任务 * 调用此接口会在当前进程加载插件 * @param name 插件名 * @return 插件的context,可通过此context得到插件的ClassLoader */ public static final Context queryPluginContext(String name) { return sPluginManager.queryPluginContext(name); } /** * 警告:低层接口 * 调用此接口会在当前进程加载插件(不加载代码,只加载资源和PackageInfo) * @param name 插件名 * @return 插件的Resources */ public static final Resources queryPluginResouces(String name) { return sPluginManager.queryPluginResouces(name); } /** * 警告:低层接口 * 调用此接口会在当前进程加载插件(不加载代码和资源,只获取PackageInfo) * @param name 插件名 * @return 插件的PackageInfo */ public static final PackageInfo queryPluginPackageInfo(String name) { return sPluginManager.queryPluginPackageInfo(name); } /** * 警告:低层接口 * 调用此接口会在当前进程加载插件(不加载代码和资源,只获取PackageInfo) * * @param pkgName 插件包名 * @param flags Flags * @return 插件的PackageInfo */ public static final PackageInfo queryPluginPackageInfo(String pkgName, int flags) { return sPluginManager.queryPluginPackageInfo(pkgName, flags); } /** * 警告:低层接口 * 调用此接口会在当前进程加载插件(不加载代码和资源,只获取ComponentList) * @param name 插件名 * @return 插件的ComponentList */ public static final ComponentList queryPluginComponentList(String name) { return sPluginManager.queryPluginComponentList(name); } /** * 警告:低层接口 * 调用此接口会在当前进程加载插件(不启动App) * @param name 插件名 * @return 插件的ComponentList */ public static final ClassLoader queryPluginClassLoader(String name) { return sPluginManager.queryPluginClassLoader(name); } /** * 根据 插件名称 和 Activity 名称 查询 ActivityInfo * * @param name 插件名称 * @param className Activity 名称 * @return Activity 对应的 ActivityInfo */ public static final ActivityInfo queryActivityInfo(String name, String className) { ComponentList componentList = sPluginManager.queryPluginComponentList(name); if (componentList != null) { return componentList.getActivity(className); } else { return null; } } /** * 根据 插件名称 和 Service 名称 查询 ServiceInfo * * @param name 插件名称 * @param className Service 名称 * @return Service 对应的 ServiceInfo */ public static final ServiceInfo queryServiceInfo(String name, String className) { ComponentList componentList = sPluginManager.queryPluginComponentList(name); if (componentList != null) { return componentList.getService(className); } else { return null; } } /** * 根据 activity 和 intent 中的数据获取 ActivityInfo 信息 * @param plugin 插件名 * @param activity Activity 名称 * @param intent 其中可能包含 action */ public static ActivityInfo getActivityInfo(String plugin, String activity, Intent intent) { return sPluginManager.getActivityInfo(plugin, activity, intent); } /** * 根据 action 从插件获取 receiver 列表 * * @return 符合 action 的所有 ReceiverInfo */ public static List queryPluginsReceiverList(Intent intent) { return sPluginManager.queryPluginsReceiverList(intent); } /** * 启动一个插件中的activity,如果插件不存在会触发下载界面 * @deprecated 只为旧插件而用。请使用RePlugin.startActivity方法 * @param context 应用上下文或者Activity上下文 * @param intent Intent对象 * @param plugin 插件名 * @param activity 待启动的activity类名 * @param process 是否在指定进程中启动 * @return 插件机制层,是否成功,例如没有插件存在、没有合适的Activity坑 */ public static final boolean startActivity(Context context, Intent intent, String plugin, String activity, int process) { // 此方法“唯一”调用路径是从插件或主程序中调用Factory.startActivity,表示调用方是“要求”打开插件Activity的,排除了要打开宿主Activity的情况 // 为了和旧插件Factory.startActivity方法兼容,判断当plugin和activity均有值,则自动帮其填入 // 注意: // 1. 仅在此方法上生效,其余方法均不能这么做,防止出现“本想打开宿主,结果定向到了插件”的问题 // 2. 若以Action打开,则无需(也不能)填写ComponentName // 3. plugin/activity会覆盖Intent.ComponentName(为兼容旧插件),毕竟在框架内部,这两个组合也是优先于CN的 // Added by Jiongxuan Zhang if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) { intent.setComponent(RePlugin.createComponentName(plugin, activity)); } return startActivityWithNoInjectCN(context, intent, plugin, activity, process); } /** * 内部接口,仅为Factory2.startActivity(context, intent) 和 RePlugin.startActivity方法而使用 * * @param context 应用上下文或者Activity上下文 * @param intent Intent对象 * @param plugin 插件名 * @param activity 待启动的activity类名 * @param process 是否在指定进程中启动 * @return 插件机制层,是否成功,例如没有插件存在、没有合适的Activity坑 * Added by Jiongxuan Zhang */ public static final boolean startActivityWithNoInjectCN(Context context, Intent intent, String plugin, String activity, int process) { boolean result = sPluginManager.startActivity(context, intent, plugin, activity, process); RePlugin.getConfig().getEventCallbacks().onStartActivityCompleted(plugin, activity, result); return result; } /** * 加载插件Activity,在startActivity之前调用 * @param intent * @param plugin 插件名 * @param target 目标Activity名,如果传null,则取获取到的第一个 * @param process 是否在指定进程中启动 * @return */ public static final ComponentName loadPluginActivity(Intent intent, String plugin, String target, int process) { return sPluginManager.loadPluginActivity(intent, plugin, target, process); } /** * 加载插件Service,在startService、bindService之前调用 * @param plugin 插件名 * @param target 目标Service名,如果传null,则取获取到的第一个 * @param process 是否在指定进程中启动 * @return */ public static final ComponentName loadPluginService(String plugin, String target, int process) { return sPluginManager.loadPluginService(plugin, target, process); } /** * 加载插件的Provider,在使用插件的Provider之前调用 * @param plugin 插件名 * @param target 目标Provider名,如果传null,则取获取到的第一个 * @param process 是否在指定进程中启动 * @return * * @deprecated 已废弃,请使用PluginProviderClient里面的方法 */ @Deprecated public static final Uri loadPluginProvider(String plugin, String target, int process) { return sPluginManager.loadPluginProvider(plugin, target, process); } /** * 不要直接使用该方法,否则会抛出异常(Debug) * @deprecated 已废弃,请使用PluginProviderClient里面的方法 */ public static final Uri makePluginProviderUri(String plugin, Uri uri, int process) { // 因目前没有插件要用,所以直接抛出异常即可 if (BuildConfig.DEBUG) { throw new IllegalStateException(); } return uri; } /** * 通过ClassLoader来获取插件名 * * @param cl ClassLoader对象 * @return 插件名,若和主程序一致,则返回IModule.PLUGIN_NAME_MAIN(“main”) * Added by Jiongxuan Zhang */ public static final String fetchPluginName(ClassLoader cl) { return sPluginManager.fetchPluginName(cl); } /** * 通过 forResult 方式启动一个插件的 Activity * * @param activity 源 Activity * @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名 * @param requestCode 请求码 * @param options 附加的数据 * @since 2.1.3 */ public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) { return sPluginManager.startActivityForResult(activity, intent, requestCode, options); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/i/Factory2.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.i; import android.app.Activity; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Bundle; import com.qihoo360.loader2.PluginLibraryInternalProxy; import org.json.JSONArray; /** * plugin-library中,通过“反射”调用的内部逻辑(如PluginActivity类的调用等)均在此处

* 注意:务必要Keep住此类,否则插件调用将失败 * * @author RePlugin Team */ public final class Factory2 { /** * @hide 内部框架使用 */ public static PluginLibraryInternalProxy sPLProxy; /** * @hide 内部方法,插件框架使用 * 插件的Activity创建成功后通过此方法获取其base context * @param activity * @param newBase * @return 为Activity构造一个base Context */ public static final Context createActivityContext(Activity activity, Context newBase) { return sPLProxy.createActivityContext(activity, newBase); } /** * @hide 内部方法,插件框架使用 * 插件的Activity的onCreate调用前调用此方法 * @param activity * @param savedInstanceState */ public static final void handleActivityCreateBefore(Activity activity, Bundle savedInstanceState) { sPLProxy.handleActivityCreateBefore(activity, savedInstanceState); } /** * @hide 内部方法,插件框架使用 * 插件的Activity的onCreate调用后调用此方法 * @param activity * @param savedInstanceState */ public static final void handleActivityCreate(Activity activity, Bundle savedInstanceState) { sPLProxy.handleActivityCreate(activity, savedInstanceState); } /** * @hide 内部方法,插件框架使用 * 插件的Activity的onDestroy调用后调用此方法 * @param activity */ public static final void handleActivityDestroy(Activity activity) { sPLProxy.handleActivityDestroy(activity); } /** * @hide 内部方法,插件框架使用 * 插件的Activity的onRestoreInstanceState调用后调用此方法 * @param activity * @param savedInstanceState */ public static final void handleRestoreInstanceState(Activity activity, Bundle savedInstanceState) { sPLProxy.handleRestoreInstanceState(activity, savedInstanceState); } /** * @hide 内部方法,插件框架使用 * 插件的Service的onCreate调用后调用此方法 * @param service */ public static final void handleServiceCreate(Service service) { sPLProxy.handleServiceCreate(service); } /** * @hide 内部方法,插件框架使用 * 插件的Service的onDestroy调用后调用此方法 * @param service */ public static final void handleServiceDestroy(Service service) { sPLProxy.handleServiceDestroy(service); } /** * @hide 内部方法,插件框架使用 * 启动一个插件中的activity * 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制 * @param context Context上下文 * @param intent * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑 */ public static final boolean startActivity(Context context, Intent intent) { return sPLProxy.startActivity(context, intent); } /** * @hide 内部方法,插件框架使用 * 启动一个插件中的activity * 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制 * @param activity Activity上下文 * @param intent * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑 */ public static final boolean startActivity(Activity activity, Intent intent) { return sPLProxy.startActivity(activity, intent); } /** * @hide 内部方法,插件框架使用 * 启动一个插件中的activity,如果插件不存在会触发下载界面 * @param context 应用上下文或者Activity上下文 * @param intent * @param plugin 插件名 * @param activity 待启动的activity类名 * @param process 是否在指定进程中启动 * @param download 下载 * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑 */ public static final boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) { return sPLProxy.startActivity(context, intent, plugin, activity, process, download); } /** * 通过 forResult 方式启动一个插件的 Activity * * @param activity 源 Activity * @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名 * @param requestCode 请求码 * @param options 附加的数据 * @see #startActivityForResult(Activity, Intent, int, Bundle) */ public static final boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) { return sPLProxy.startActivityForResult(activity, intent, requestCode, options); } /** * @hide 内部方法,插件框架使用 * 返回所有插件的json串,格式见plugins-builtin.json文件 * @param name 插件名,传null或者空串表示获取全部 * @return */ public static final JSONArray fetchPlugins(String name) { return sPLProxy.fetchPlugins(name); } /** * @hide 内部方法,插件框架使用 * 登记动态映射的类 * @param className 壳类名 * @param plugin 目标插件名 * @param type 目标类的类型: activity, service, provider * @param target 目标类名 * @return */ public static final boolean registerDynamicClass(String className, String plugin, String type, String target) { return sPLProxy.registerDynamicClass(className, plugin, type, target); } /** * @hide 内部方法,插件框架使用 * 登记动态映射的类 * @param className 壳类名 * @param plugin 目标插件名 * @param target 目标类名 * @return */ public static final boolean registerDynamicClass(String className, String plugin, String target, Class defClass) { return sPLProxy.registerDynamicClass(className, plugin, target, defClass); } /** * @hide 内部方法,插件框架使用 * 查询动态映射的类 * @param className 壳类名 * @param plugin 目标插件名 * @return */ public static final boolean isDynamicClass(String plugin, String className) { return sPLProxy.isDynamicClass(plugin, className); } public static void unregisterDynamicClass(String source) { sPLProxy.unregisterDynamicClass(source); } /** * @hide 内部方法,插件框架调用 * 根据动态注册的类,反查此类对应的插件名称 * * @param className 动态类名称 * @return 插件名称 */ public static final String getPluginByDynamicClass(String className) { return sPLProxy.getPluginByDynamicClass(className); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/i/IModule.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.i; /** * 所有可查询的接口都从此interface继承 * 在插件体系中,module是一种略高于interface的概念 * 一个插件可导出一个到多个module,这些module可输出自己业务的各种interface * * @author RePlugin Team * */ public interface IModule { /** * 万能接口:当不能升级adapter.jar的时候再考虑使用 * @param args * @return */ Object invoke(Object...args); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/i/IPlugin.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.i; /** * 此接口由插件负责导出 * 表示一个具体的物理上的插件实体,例如barcode.jar * 具体导出细节可看Factory * * @author RePlugin Team * */ public interface IPlugin { /** * @param c 需要查询的interface的类 * @return */ IModule query(Class c); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/i/IPluginManager.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.i; /** * 注意:这里目前仅放置一些常量,大部分方法已移动至 PluginCommImpl 中 * * @author RePlugin Team */ public interface IPluginManager { /** * 插件Activity上下文通过startActivity启动,用系统默认的启动方法 * 如果设置该值,总是为boolean值true */ String KEY_COMPATIBLE = "compatible"; /** * 通过Intent的extra参数指出目标插件名 * 插件Activity上下文通过startActivity启动其它插件Activity时用到 * 如果不指定,则默认用当前Activity的插件 * 如果设置了KEY_COMPATIBLE,则忽略此参数 */ String KEY_PLUGIN = "plugin"; /** * 通过Intent的extra参数指出目标Activity名 * 如果不指定,则默认用ComponentName参数的类名来启动 * 如果设置了KEY_COMPATIBLE,则忽略此参数 */ String KEY_ACTIVITY = "activity"; /** * 通过Intent的extra参数指出需要在指定进程中启动 * 只能启动standard的Activity,不能启动singleTask、singleInstance等 * 不指定时,自动分配插件进程,即PROCESS_AUTO */ String KEY_PROCESS = "process"; /** * 自动分配插件进程 */ int PROCESS_AUTO = Integer.MIN_VALUE; /** * UI进程 */ int PROCESS_UI = -1; /** * 常驻进程 */ int PROCESS_PERSIST = -2; } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/LocalBroadcastManager.java ================================================ package com.qihoo360.loader.utils; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import com.qihoo360.replugin.utils.ReflectUtils; public class LocalBroadcastManager { private static String V4_MANAGER = "android.support.v4.content.LocalBroadcastManager"; private static String ANDROIDX_MANAGER = "androidx.localbroadcastmanager.content.LocalBroadcastManager"; private static LocalBroadcastManager instance; private Context context; private boolean init; private static Object managerObj; public static LocalBroadcastManager getInstance(Context context) { if (instance == null) { synchronized (LocalBroadcastManager.class) { if (instance == null) { instance = new LocalBroadcastManager(context); } } } return instance; } private LocalBroadcastManager(Context context) { this.context = context.getApplicationContext(); loadClass(); } private void loadClass() { try { Class cls = null; try { cls = ReflectUtils.getClass(ANDROIDX_MANAGER); } catch (Exception e) { cls = ReflectUtils.getClass(V4_MANAGER); } if (cls == null) { return; } managerObj = ReflectUtils.getMethod(cls, "getInstance", new Class[]{Context.class}).invoke(null, context); init = true; } catch (Exception e) { } } public boolean registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { if (!init || managerObj == null) { return false; } try { ReflectUtils.getMethod(managerObj.getClass(), "registerReceiver", BroadcastReceiver.class, IntentFilter.class).invoke(managerObj, receiver, filter); return true; } catch (Exception e) { } return false; } public boolean unregisterReceiver(BroadcastReceiver receiver) { if (!init || managerObj == null) { return false; } try { ReflectUtils.getMethod(managerObj.getClass(), "unregisterReceiver", BroadcastReceiver.class).invoke(managerObj, receiver); return true; } catch (Exception e) { } return false; } public boolean sendBroadcast(Intent intent) { if (!init || managerObj == null) { return false; } try { ReflectUtils.getMethod(managerObj.getClass(), "sendBroadcast", Intent.class).invoke(managerObj, intent); return true; } catch (Exception e) { } return false; } public boolean sendBroadcastSync(Intent intent) { if (!init || managerObj == null) { return false; } try { ReflectUtils.getMethod(managerObj.getClass(), "sendBroadcastSync", Intent.class).invoke(managerObj, intent); return true; } catch (Exception e) { } return false; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/PackageUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader.utils; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import com.qihoo360.replugin.helper.LogDebug; /** * @author RePlugin Team */ public class PackageUtils { /** * 获取PackageInfo对象 *

* 注:getPackageArchiveInfo Android 2.x上,可能拿不到signatures,本可以通过反射去获取,但是考虑到会触发Android 的灰/黑名单,这个方法就不再继续适配2.X了 * * @return */ public static PackageInfo getPackageArchiveInfo(PackageManager pm, String pkgFilePath, int flags) { PackageInfo info = null; try { info = pm.getPackageArchiveInfo(pkgFilePath, flags); } catch (Throwable e) { if (LogDebug.LOG) { e.printStackTrace(); } } return info; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/PatchClassLoaderUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader.utils; import android.app.Application; import android.content.Context; import android.util.Log; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.utils.ReflectUtils; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 对宿主的HostClassLoader做修改。这是RePlugin中唯一需要修改宿主私有属性的位置了 * * @author RePlugin Team */ public class PatchClassLoaderUtils { private static final String TAG = "PatchClassLoaderUtils"; public static boolean patch(Application application) { try { // 获取Application的BaseContext (来自ContextWrapper) Context oBase = application.getBaseContext(); if (oBase == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "pclu.p: nf mb. ap cl=" + application.getClass()); } return false; } // 获取mBase.mPackageInfo // 1. ApplicationContext - Android 2.1 // 2. ContextImpl - Android 2.2 and higher // 3. AppContextImpl - Android 2.2 and higher Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo"); if (oPackageInfo == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass()); } return false; } // mPackageInfo的类型主要有两种: // 1. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3 // 2. android.app.LoadedApk - Android 2.3.3 and higher if (LOG) { Log.d(TAG, "patch: mBase cl=" + oBase.getClass() + "; mPackageInfo cl=" + oPackageInfo.getClass()); } // 获取mPackageInfo.mClassLoader ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader"); if (oClassLoader == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass() + "; mpi cl=" + oPackageInfo.getClass()); } return false; } // 外界可自定义ClassLoader的实现,但一定要基于RePluginClassLoader类 ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader); // 将新的ClassLoader写入mPackageInfo.mClassLoader ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl); // 设置线程上下文中的ClassLoader为RePluginClassLoader // 防止在个别Java库用到了Thread.currentThread().getContextClassLoader()时,“用了原来的PathClassLoader”,或为空指针 Thread.currentThread().setContextClassLoader(cl); if (LOG) { Log.d(TAG, "patch: patch mClassLoader ok"); } } catch (Throwable e) { e.printStackTrace(); return false; } return true; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/ProcessLocker.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader.utils; import android.content.Context; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.utils.FileUtils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import static com.qihoo360.replugin.helper.LogDebug.MAIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 进程锁 * * @author RePlugin Team */ public final class ProcessLocker { private static final String TAG = LogDebug.PLUGIN_TAG; private final Context mContext; private FileOutputStream mFileOutputStream; private FileChannel mFileChannel; private FileLock mFileLock; private File mFile; /** * @param context * @param filename */ public ProcessLocker(Context context, String filename) { mContext = context; try { mFile = new File(filename); mFileOutputStream = mContext.openFileOutput(filename, 0); if (mFileOutputStream != null) { mFileChannel = mFileOutputStream.getChannel(); } if (mFileChannel == null) { if (LOGR) { LogRelease.e(MAIN_TAG, "channel is null"); } } } catch (Throwable e) { if (LOGR) { LogRelease.e(MAIN_TAG, e.getMessage(), e); } } } /** * 允许传递绝对路径 * * @param context * @param dir * @param filename */ public ProcessLocker(Context context, String dir, String filename) { mContext = context; try { mFile = new File(dir, filename); if (!mFile.exists()) { FileUtils.forceMkdirParent(mFile); mFile.createNewFile(); } mFileOutputStream = new FileOutputStream(mFile, false); mFileChannel = mFileOutputStream.getChannel(); } catch (Throwable e) { if (LOGR) { LogRelease.e(MAIN_TAG, e.getMessage(), e); } } } /** * 查看文件是否已经被上锁 * * @return */ public final synchronized boolean isLocked() { boolean ret = tryLock(); // 加锁成功说明文件还未被上锁 // 在退出之前一定要进行unlock if (ret) { unlock(); } return !ret; } /** * 加锁 * * @return */ public final synchronized boolean tryLock() { if (mFileChannel == null) { return false; } try { mFileLock = mFileChannel.tryLock(); if (mFileLock != null) { return true; } } catch (Throwable e) { if (LOGR) { LogRelease.e(MAIN_TAG, e.getMessage(), e); } } return false; } /** * 加锁 * * @param ms 毫秒 * @param interval 间隔 * @return */ public final synchronized boolean tryLockTimeWait(int ms, int interval) { if (mFileChannel == null) { return false; } // 自动修正到最小值,避免死锁 if (ms <= 0) { ms = 1; } if (interval <= 0) { interval = 1; } try { for (int i = 0; i < ms; i += interval) { try { mFileLock = mFileChannel.tryLock(); } catch (IOException e) { // 获取锁失败会抛异常,此处忽略 // java.io.IOException: fcntl failed: EAGAIN (Try again) } if (mFileLock != null) { return true; } // 每秒钟输出一次日志,防止“刷屏” if (LOGR) { if (i % 1000 == 0) { LogRelease.i(TAG, "wait process lock: " + i + "/" + ms); } } Thread.sleep(interval, 0); } } catch (Throwable e) { if (LOGR) { LogRelease.e(MAIN_TAG, e.getMessage(), e); } } return false; } /** * 加锁 * * @return */ public final synchronized boolean lock() { if (mFileChannel == null) { return false; } try { mFileLock = mFileChannel.lock(); if (mFileLock != null) { return true; } } catch (Throwable e) { if (LOGR) { LogRelease.e(MAIN_TAG, e.getMessage(), e); } } return false; } /** * 释放并且删除该锁文件 */ public final synchronized void unlock() { if (mFileLock != null) { try { mFileLock.release(); } catch (Throwable e) { if (LOGR) { LogRelease.e(TAG, e.getMessage(), e); } } } if (mFileChannel != null) { try { mFileChannel.close(); } catch (Throwable e) { if (LOGR) { LogRelease.e(TAG, e.getMessage(), e); } } } if (mFileOutputStream != null) { try { mFileOutputStream.close(); } catch (Throwable e) { if (LOGR) { LogRelease.e(TAG, e.getMessage(), e); } } } // 删除锁文件 if (mFile != null && mFile.exists()) { mFile.delete(); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/StringUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader.utils; import android.util.Base64; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; /** * @author RePlugin Team */ public class StringUtils { private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; public static final String toHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(bytes.length * 2); for (byte b : bytes) { sb.append(HEX_DIGITS[(b & 0xf0) >> 4]); sb.append(HEX_DIGITS[b & 0x0f]); } return sb.toString(); } public static final String md5base64(byte buffer[]) throws NoSuchAlgorithmException { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(buffer); byte buf2[] = digest.digest(); return Base64.encodeToString(buf2, Base64.NO_WRAP | Base64.NO_PADDING | Base64.NO_CLOSE); } public static final String utf8md5base64(String str) throws NoSuchAlgorithmException { byte buf1[] = str.getBytes(); return md5base64(buf1); } public static String toStringWithLines(List list) { String t = list.toString(); return t.replace(", ", ", \n"); } public static char lastChar(String s) { return s.charAt(s.length() - 1); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils/SysUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader.utils; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.utils.CloseableUtils; import java.io.FileInputStream; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team */ public final class SysUtils { private static final String TAG = "Plugin.SysUtils"; /** * 返回当前的进程名 * * @return */ public static String getCurrentProcessName() { FileInputStream in = null; try { String fn = "/proc/self/cmdline"; in = new FileInputStream(fn); byte[] buffer = new byte[256]; int len = 0; int b; while ((b = in.read()) > 0 && len < buffer.length) { buffer[len++] = (byte) b; } if (len > 0) { String s = new String(buffer, 0, len, "UTF-8"); return s; } } catch (Throwable e) { if (LOGR) { LogRelease.e(TAG, e.getMessage(), e); } } finally { CloseableUtils.closeQuietly(in); } return null; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader/utils2/FilePermissionUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader.utils2; import com.qihoo360.replugin.utils.ReflectUtils; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @author RePlugin Team */ public class FilePermissionUtils { public static final int S_IRWXU = 00700; public static final int S_IRUSR = 00400; public static final int S_IWUSR = 00200; public static final int S_IXUSR = 00100; public static final int S_IRWXG = 00070; public static final int S_IRGRP = 00040; public static final int S_IWGRP = 00020; public static final int S_IXGRP = 00010; public static final int S_IRWXO = 00007; public static final int S_IROTH = 00004; public static final int S_IWOTH = 00002; public static final int S_IXOTH = 00001; private static Class sFileUtilsClass; private static Method sSetPermissionMethod; private static Method sGetPermissionMethod; /** * 设置文件的访问权限,使用反射调用系统隐藏同名函数。 * @param filePath 需要被设置访问权限的文件 * @param mode 文件访问权限,如0777,0755 * @param uid * @param gid * @return -1 表示设置失败 */ public static int setPermissions(String filePath, int mode, int uid, int gid) { try { initClass(); if (sSetPermissionMethod == null) { sSetPermissionMethod = ReflectUtils.getMethod(sFileUtilsClass, "setPermissions", String.class, Integer.TYPE, Integer.TYPE, Integer.TYPE); } Object retObj = sSetPermissionMethod.invoke(null, filePath, mode, uid, gid); if (retObj != null && retObj instanceof Integer) { return (int) retObj; } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return -1; } /** * 查询文件的访问权限,这个接口只能在4.1.2或之前有效 * @param filePath * @param outPermissions * @return -1 表示查询失败 */ public static int getPermissions(String filePath, int[] outPermissions) { try { initClass(); if (sGetPermissionMethod == null) { sGetPermissionMethod = ReflectUtils.getMethod(sFileUtilsClass, "getPermissions", String.class, int[].class); } Object retObj = sGetPermissionMethod.invoke(null, filePath, outPermissions); if (retObj != null && retObj instanceof Integer) { return (int) retObj; } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return -1; } private static void initClass() throws ClassNotFoundException { if (sFileUtilsClass == null) { sFileUtilsClass = ReflectUtils.getClass("android.os.FileUtils"); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/BinderCursor.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.database.Cursor; import android.database.MatrixCursor; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * @author RePlugin Team */ public class BinderCursor extends MatrixCursor { static final String BINDER_KEY = "binder"; Bundle mBinderExtra = new Bundle(); public static class BinderParcelable implements Parcelable { IBinder mBinder; public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public BinderParcelable createFromParcel(Parcel source) { return new BinderParcelable(source); } @Override public BinderParcelable[] newArray(int size) { return new BinderParcelable[size]; } }; BinderParcelable(IBinder binder) { mBinder = binder; } BinderParcelable() { // } BinderParcelable(Parcel source) { mBinder = source.readStrongBinder(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeStrongBinder(mBinder); } } public BinderCursor(String[] columnNames, IBinder binder) { super(columnNames); if (binder != null) { Parcelable value = new BinderParcelable(binder); mBinderExtra.putParcelable(BINDER_KEY, value); } } @Override public Bundle getExtras() { return mBinderExtra; } public static final Cursor queryBinder(IBinder binder) { if (LOG) { LogDebug.d(PLUGIN_TAG, "query binder = " + binder); } return new BinderCursor(PluginInfo.QUERY_COLUMNS, binder); } public static final IBinder getBinder(Cursor cursor) { Bundle extras = cursor.getExtras(); extras.setClassLoader(BinderCursor.class.getClassLoader()); BinderParcelable w = (BinderParcelable) extras.getParcelable(BINDER_KEY); if (LOG) { LogDebug.d(PLUGIN_TAG, "get binder = " + w.mBinder); } return w.mBinder; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/BuildCompat.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.os.Build; /** * @author RePlugin Team */ public class BuildCompat { public static final String ARM = "arm"; public static final String ARM64 = "arm64"; public static final String[] SUPPORTED_ABIS; public static final String[] SUPPORTED_32_BIT_ABIS; public static final String[] SUPPORTED_64_BIT_ABIS; static { //init SUPPORTED_ABIS if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.SUPPORTED_ABIS != null) { SUPPORTED_ABIS = new String[Build.SUPPORTED_ABIS.length]; System.arraycopy(Build.SUPPORTED_ABIS, 0, SUPPORTED_ABIS, 0, SUPPORTED_ABIS.length); } else { SUPPORTED_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } } else { SUPPORTED_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } //init SUPPORTED_32_BIT_ABIS if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.SUPPORTED_32_BIT_ABIS != null) { SUPPORTED_32_BIT_ABIS = new String[Build.SUPPORTED_32_BIT_ABIS.length]; System.arraycopy(Build.SUPPORTED_32_BIT_ABIS, 0, SUPPORTED_32_BIT_ABIS, 0, SUPPORTED_32_BIT_ABIS.length); } else { SUPPORTED_32_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } } else { SUPPORTED_32_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } //init SUPPORTED_64_BIT_ABIS if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.SUPPORTED_64_BIT_ABIS != null) { SUPPORTED_64_BIT_ABIS = new String[Build.SUPPORTED_64_BIT_ABIS.length]; System.arraycopy(Build.SUPPORTED_64_BIT_ABIS, 0, SUPPORTED_64_BIT_ABIS, 0, SUPPORTED_64_BIT_ABIS.length); } else { SUPPORTED_64_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } } else { SUPPORTED_64_BIT_ABIS = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Builder.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.Context; import android.os.Build; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.utils.FileUtils; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * @author RePlugin Team */ public class Builder { /** * Plugins 信息搜集 * 插件搜索顺序:1、内置插件;2、V5复合插件;3、V5插件;4、释放出来的插件 */ static final class PxAll { /** * 内置插件 */ private final ArrayList builtins = new ArrayList(); /** * V5单文件插件 */ private final ArrayList v5 = new ArrayList(); /** * 释放出来的普通插件 */ private final ArrayList normals = new ArrayList(); /** * 其它 */ private final HashSet others = new HashSet(); /** * 所有插件 */ private final ArrayList all = new ArrayList(); /** * 确保版本和插件唯一 * @param array * @param info * @param replace true表示更新相同的,false表示不更新相同的 * @return */ private final boolean insert(ArrayList array, PluginInfo info, boolean replace) { for (int i = 0; i < array.size(); i++) { PluginInfo pi = array.get(i); // 存在 if (pi.getName().equals(info.getName())) { // 忽略 if (replace) { if (PluginInfo.VERSION_COMPARATOR.compare(pi, info) > 0) { return false; } } else { if (PluginInfo.VERSION_COMPARATOR.compare(pi, info) >= 0) { return false; } } // 更新 others.add(array.get(i)); array.set(i, info); return true; } } // 不存在,添加 array.add(info); return true; } private final boolean hasOlder(ArrayList array, PluginInfo info) { for (PluginInfo pi : array) { if (pi.getName().equals(info.getName())) { if (PluginInfo.VERSION_COMPARATOR.compare(pi, info) < 0) { return true; } } } return false; } /** * @param name * @return */ private final PluginInfo getBuiltin(String name) { for (PluginInfo pi : builtins) { if (pi.getName().equals(name)) { return pi; } } return null; } /** * @param name * @return */ private final PluginInfo getV5(String name) { for (PluginInfo pi : v5) { if (pi.getName().equals(name)) { return pi; } } return null; } /** * @return */ final HashSet getOthers() { return others; } /** * @return */ final ArrayList getPlugins() { return all; } /** * @param info */ final void addBuiltin(PluginInfo info) { insert(builtins, info, false); insert(all, info, false); } /** * @param info */ final void addV5(PluginInfo info) { if (!insert(all, info, false)) { return; } insert(v5, info, false); } /** * @param info * @return */ final void addNormal(PluginInfo info) { PluginInfo pi = null; // FIXME 用all表 if ((pi = getBuiltin(info.getName())) != null && pi.getVersionValue() == info.getVersionValue()) { if (LOG) { LogDebug.d(PLUGIN_TAG, "builtin plugin: normal=" + info); } } else if ((pi = getV5(info.getName())) != null && pi.getVersionValue() == info.getVersionValue()) { if (LOG) { LogDebug.d(PLUGIN_TAG, "v5 plugin: normal=" + info); } } else { others.add(info); return; } insert(normals, info, false); } } static final void builder(Context context, PxAll all) { // 搜索所有本地插件和V5插件 Finder.search(context, all); } private static File getDexDir(Context context) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { return new File(context.getDir(Constant.LOCAL_PLUGIN_SUB_DIR, 0) + File.separator + "oat" + File.separator + VMRuntimeCompat.getArtOatCpuType()); } else { return context.getDir(Constant.LOCAL_PLUGIN_ODEX_SUB_DIR, 0); } } private static void deleteUnknownDexs(Context context, PxAll all) { HashSet names = new HashSet<>(); for (PluginInfo p : all.getPlugins()) { names.add(p.getDexFile().getName()); if (LOG) { LogDebug.d(PLUGIN_TAG, "dexFile:" + p.getDexFile().getName()); } // add vdex for Android O if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { String fileNameWithoutExt = FileUtils.getFileNameWithoutExt(p.getDexFile().getAbsolutePath()); if (LOG) { LogDebug.d(PLUGIN_TAG, "vdexFile:" + (fileNameWithoutExt + ".vdex")); } names.add(fileNameWithoutExt + ".vdex"); } } File dexDir = getDexDir(context); if (LOG) { LogDebug.d(PLUGIN_TAG, "to delete dex dir:" + dexDir); } File files[] = dexDir.listFiles(); if (files != null) { for (File f : files) { if (names.contains(f.getName())) { if (LOG) { LogDebug.d(PLUGIN_TAG, "no need delete " + f.getAbsolutePath()); } continue; } if (LOG) { LogDebug.d(PLUGIN_TAG, "delete unknown dex=" + f.getAbsolutePath()); } try { FileUtils.forceDelete(f); } catch (IOException e) { if (LOG) { LogDebug.d(PLUGIN_TAG, "can't delete unknown dex=" + f.getAbsolutePath(), e); } } catch (IllegalArgumentException e2) { if (LOG) { e2.printStackTrace(); } } } } } private static void deleteUnknownLibs(Context context, PxAll all) { HashSet names = new HashSet<>(); for (PluginInfo p : all.getPlugins()) { names.add(p.getOldNativeLibsDir().getName()); } File dir = context.getDir(Constant.LOCAL_PLUGIN_DATA_LIB_DIR, 0); File files[] = dir.listFiles(); if (files != null) { for (File f : files) { if (names.contains(f.getName())) { continue; } if (LOG) { LogDebug.d(PLUGIN_TAG, "delete unknown libs=" + f.getAbsolutePath()); } try { FileUtils.forceDelete(f); } catch (IOException e) { if (LOG) { LogDebug.d(PLUGIN_TAG, "can't delete unknown libs=" + f.getAbsolutePath(), e); } } catch (IllegalArgumentException e2) { if (LOG) { e2.printStackTrace(); } } } } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/CertUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.pm.PackageInfo; import android.content.pm.Signature; import android.text.TextUtils; import com.qihoo360.loader.utils.StringUtils; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * @author RePlugin Team */ public class CertUtils { /** * */ public static final ArrayList SIGNATURES = new ArrayList(); public static final boolean isPluginSignatures(PackageInfo info) { if (info == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "info is null"); } return false; } if (info.signatures == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "signatures is null"); } return false; } for (Signature signature : info.signatures) { boolean match = false; String md5 = StringUtils.toHexString(md5NonE(signature.toByteArray())); for (String element : SIGNATURES) { if (TextUtils.equals(md5, element)) { match = true; if (LOG) { LogDebug.i(PLUGIN_TAG, "isPluginSignatures: match. " + md5 + " package=" + info.packageName); } break; } } if (!match) { if (LOG) { LogDebug.e(PLUGIN_TAG, "isPluginSignatures: unknown signature: " + md5 + " package=" + info.packageName); } if (LogRelease.LOGR) { LogRelease.e(PLUGIN_TAG, "ibs: us " + md5); } return false; } } return true; } public static final byte[] md5(byte buffer[]) throws NoSuchAlgorithmException { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(buffer, 0, buffer.length); return digest.digest(); } public static final byte[] md5NonE(byte buffer[]) { try { return md5(buffer); } catch (NoSuchAlgorithmException e) { if (LOG) { LogDebug.d(PLUGIN_TAG, e.getMessage(), e); } } return new byte[0]; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Constant.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import com.qihoo360.replugin.helper.HostConfigHelper; /** * @author RePlugin Team */ public class Constant { /** * HOST向下兼容版本 */ public static final int ADAPTER_COMPATIBLE_VERSION = HostConfigHelper.ADAPTER_COMPATIBLE_VERSION; /** * HOST版本 */ public static final int ADAPTER_CURRENT_VERSION = HostConfigHelper.ADAPTER_CURRENT_VERSION; /** * 插件存放目录 */ public static final String LOCAL_PLUGIN_SUB_DIR = "plugins_v3"; /** * 插件ODEX存放目录 */ public static final String LOCAL_PLUGIN_ODEX_SUB_DIR = "plugins_v3_odex"; /** * 插件data存放目录 */ public static final String LOCAL_PLUGIN_DATA_SUB_DIR = "plugins_v3_data"; /** * 插件Native(SO库)存放目录 * Added by Jiongxuan Zhang */ public static final String LOCAL_PLUGIN_DATA_LIB_DIR = "plugins_v3_libs"; /** * "纯APK"插件存放目录 * Added by Jiongxuan Zhang */ public static final String LOCAL_PLUGIN_APK_SUB_DIR = "p_a"; /** * "纯APK"中释放Odex的目录 * Added by Jiongxuan Zhang */ public static final String LOCAL_PLUGIN_APK_ODEX_SUB_DIR = "p_od"; /** * 纯"APK"插件的Native(SO库)存放目录 * Added by Jiongxuan Zhang */ public static final String LOCAL_PLUGIN_APK_LIB_DIR = "p_n"; /** * "纯APK"插件同版本升级时插件、Odex、Native(SO库)的用于覆盖的存放目录 */ public static final String LOCAL_PLUGIN_APK_COVER_DIR = "p_c"; /** * 插件extra dex(优化前)释放的以插件名独立隔离的子目录 * 适用于 android 5.0 以下,5.0以上不会用到该目录 */ public static final String LOCAL_PLUGIN_INDEPENDENT_EXTRA_DEX_SUB_DIR = "_ed"; /** * 插件extra dex(优化后)释放的以插件名独立隔离的子目录 */ public static final String LOCAL_PLUGIN_INDEPENDENT_EXTRA_ODEX_SUB_DIR = "_eod"; /** * 插件文件名,name-low-high-current.jar * 插件文件名规范:barcode-1-10-2.jar */ public static final String LOCAL_PLUGIN_FILE_PATTERN = "^([^-]+)-([0-9]+)-([0-9]+)-([0-9]+).jar$"; /** * 插件加载时的进程锁文件,插件间不共用一把锁 */ public static final String LOAD_PLUGIN_LOCK = "plugin_v3_%s.lock"; /** * Stub进程数:不能超过10个 */ public static final int STUB_PROCESS_COUNT = 2; /** * Stub进程后缀 */ public static final String STUB_PROCESS_SUFFIX_PATTERN = "^(.*):loader([0-" + (STUB_PROCESS_COUNT - 1) + "])$"; /** * */ public static final String PLUGIN_NAME_UI = "ui"; /** * */ public static final boolean LOG_V5_FILE_SEARCH = false; /** * */ public static final boolean SIMPLE_QUIT_CONTROLLER = false; /** * */ public static final boolean ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS = true; } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/DumpUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.os.IBinder; import android.util.Log; import com.qihoo360.replugin.RePluginInternal; import java.io.FileDescriptor; import java.io.PrintWriter; /** * 运行时 dump 工具类 * * @author RePlugin Team */ public class DumpUtils { private static final String TAG = RePluginInternal.FOR_DEV ? DumpUtils.class.getSimpleName() : "DumpUtils"; /** * dump RePlugin框架运行时的详细信息,包括:Activity 坑位映射表,正在运行的 Service,以及详细的插件信息 * * @param fd * @param writer * @param args */ public static void dump(FileDescriptor fd, PrintWriter writer, String[] args) { IBinder binder = PluginProviderStub.proxyFetchHostBinder(RePluginInternal.getAppContext()); if (binder == null) { return; } IPluginHost pluginHost = IPluginHost.Stub.asInterface(binder); try { String dumpInfo = pluginHost.dump(); if (RePluginInternal.FOR_DEV) { Log.d(TAG, "dumpInfo:" + dumpInfo); } if (writer != null) { writer.println(dumpInfo); } } catch (Throwable e) { e.printStackTrace(); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Finder.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.Context; import com.qihoo360.loader2.Builder.PxAll; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import java.io.File; import java.util.HashSet; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * @author RePlugin Team */ public class Finder { /** * 扫描插件 */ static final void search(Context context, PxAll all) { // 扫描内置插件-builtin FinderBuiltin.loadPlugins(context, all); } private static final void searchLocalPlugins(File dir, PxAll all, HashSet others) { File files[] = dir.listFiles(); if (files == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "search local plugin: nothing"); } return; } for (File f : files) { if (f.isDirectory()) { continue; } if (f.length() <= 0) { if (LOG) { LogDebug.d(PLUGIN_TAG, "search local plugin: zero length, file=" + f.getAbsolutePath()); } if (others != null) { others.add(f); } continue; } PluginInfo info = PluginInfo.build(f); if (info == null) { if (others != null) { others.add(f); } continue; } if (!info.match()) { if (LOG) { LogDebug.d(PLUGIN_TAG, "search local plugin: mismatch, file=" + f.getAbsolutePath()); } if (others != null) { others.add(f); } continue; } all.addNormal(info); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/FinderBuiltin.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.Context; import android.text.TextUtils; import android.util.Log; import com.qihoo360.replugin.utils.Charsets; import com.qihoo360.replugin.utils.CloseableUtils; import com.qihoo360.loader2.Builder.PxAll; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.utils.IOUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogDebug.TAG_NO_PN; /** * @author RePlugin Team */ public class FinderBuiltin { static final void loadPlugins(Context context, PxAll all) { InputStream in; // 读取内部配置 in = null; try { in = context.getAssets().open("plugins-builtin.json"); // TODO 简化参数 all readConfig(in, all); } catch (FileNotFoundException e0) { if (LOG) { LogDebug.e(PLUGIN_TAG, "plugins-builtin.json" + " not found"); } } catch (Throwable e) { if (LOG) { LogDebug.d(PLUGIN_TAG, e.getMessage(), e); } } CloseableUtils.closeQuietly(in); } private static final void readConfig(InputStream in, PxAll all) throws IOException, JSONException { String str = IOUtils.toString(in, Charsets.UTF_8); JSONArray ja = new JSONArray(str); for (int i = 0; i < ja.length(); i++) { JSONObject jo = ja.getJSONObject(i); if (jo == null) { continue; } String name = jo.getString("name"); if (TextUtils.isEmpty(name)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "built-in plugins config: invalid item: name is empty, json=" + jo); } continue; } PluginInfo info = PluginInfo.buildFromBuiltInJson(jo); if (!info.match()) { if (LOG) { LogDebug.e(PLUGIN_TAG, "built-in plugins config: mismatch item: " + info); } continue; } if (LOG) { LogDebug.d(PLUGIN_TAG, "built-in plugins config: item: " + info); } if (LOG) { Log.d(TAG_NO_PN, "add builtin plugin=" + info); } all.addBuiltin(info); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/LaunchModeStates.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.pm.ActivityInfo; import android.util.Log; import com.qihoo360.loader2.PluginContainers.ActivityState; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.helper.HostConfigHelper; import com.qihoo360.replugin.helper.LogDebug; import java.util.HashMap; import java.util.HashSet; import java.util.Map; /** * 存储 LaunchMode + Theme -> 此种组合下的 ActivityState 状态集合 * * @author RePlugin Team */ class LaunchModeStates { public static final String TAG = "launchMode"; /** * 目前的策略是,针对每一种 launchMode 分配两种坑位(透明主题(TS)和不透明主题(NTS)) *

* 例:透明主题 *         * NR + TS - > *         *

* 例:不透明主题 *         * NR + NTS - > *         *

* 其中:N1 表示当前为 UI 进程,NR 表示 launchMode 为 Standard,NTS 表示坑的 theme 为 Not Translucent。 */ private Map> mStates = new HashMap<>(); /** * 初始化 LaunchMode 和 Theme 对应的坑位 * * @param containers 保存所有 activity 坑位的引用 * @param prefix 坑位前缀 * @param launchMode launchMode * @param translucent 是否是透明的坑 * @param count 坑位数 */ void addStates(Map allStates, HashSet containers, String prefix, int launchMode, boolean translucent, int count) { String infix = getInfix(launchMode, translucent); HashMap states = mStates.get(infix); if (states == null) { states = new HashMap<>(); mStates.put(infix, states); } for (int i = 0; i < count; i++) { String key = prefix + infix + i; // 只有开启“详细日志”时才输出每一个坑位的信息,防止刷屏 if (RePlugin.getConfig().isPrintDetailLog()) { Log.d(TAG, "LaunchModeStates.add(" + key + ")"); } ActivityState state = new ActivityState(key); states.put(key, state); allStates.put(key, state); containers.add(key); } } /** * 初始化 LaunchMode 和 Theme 对应的坑位 * * @param containers 保存所有 activity 坑位的引用 * @param prefix 坑位前缀 * @param launchMode launchMode * @param translucent 是否是透明的坑 * @param count 坑位数 */ void addLandStates(Map allStates, HashSet containers, String prefix, int launchMode, boolean translucent, int count) { if (!HostConfigHelper.HOST_USE_OCCUPYLAND || count <= 0){ return; } String infix = getInfix(launchMode, translucent); infix = "LAND" + infix; HashMap states = mStates.get(infix); if (states == null) { states = new HashMap<>(); mStates.put(infix, states); } for (int i = 0; i < count; i++) { String key = prefix + infix + i; // 只有开启“详细日志”时才输出每一个坑位的信息,防止刷屏 if (RePlugin.getConfig().isPrintDetailLog()) { LogDebug.d(TAG, "LaunchModeStates.add(" + key + ")"); } ActivityState state = new ActivityState(key); states.put(key, state); allStates.put(key, state); containers.add(key); } } /** * 根据 launchMode 和 theme 获取对应的坑位集合 */ HashMap getStates(int screenOrientation, int launchMode, int theme) { try { String infix = getInfix(launchMode, isTranslucentTheme(theme)); if (HostConfigHelper.HOST_USE_OCCUPYLAND && screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { infix = "LAND" + infix; } return mStates.get(infix); }catch (Exception e){ return null; } } /** * 根据 launchMode 和 '是否透明' 获取中缀符 * * @return 如果是透明主题,返回 'launchMode'_TS_,否则返回 'launchMode'_NOT_TS_ */ private static String getInfix(int launchMode, boolean translucent) { String launchModeInfix = getLaunchModeInfix(launchMode); return translucent ? launchModeInfix + "TS" : launchModeInfix + "NTS"; } /** * 手动判断主题是否是透明主题 */ public static boolean isTranslucentTheme(int theme) { return theme == android.R.style.Theme_Translucent || theme == android.R.style.Theme_Dialog || theme == android.R.style.Theme_Translucent_NoTitleBar || theme == android.R.style.Theme_Translucent_NoTitleBar_Fullscreen; } /** * 获取 launchMode 对应的前缀 */ private static String getLaunchModeInfix(int launchMode) { switch (launchMode) { case ActivityInfo.LAUNCH_SINGLE_TOP: return "STP"; case ActivityInfo.LAUNCH_SINGLE_TASK: return "ST"; case ActivityInfo.LAUNCH_SINGLE_INSTANCE: return "SI"; default: return "NR"; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Loader.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.Context; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.os.Bundle; import android.os.IBinder; import android.text.TextUtils; import android.util.Log; import com.qihoo360.i.Factory; import com.qihoo360.i.IModule; import com.qihoo360.i.IPlugin; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.mobilesafe.parser.manifest.ManifestParser; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.component.process.PluginProcessHost; import com.qihoo360.replugin.component.receiver.PluginReceiverProxy; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static com.qihoo360.replugin.helper.LogDebug.LOADER_TAG; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogDebug.TAG_NO_PN; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team */ class Loader { private final Context mContext; private final String mPluginName; final String mPath; final Plugin mPluginObj; PackageInfo mPackageInfo; Resources mPkgResources; Context mPkgContext; ClassLoader mClassLoader; /** * 记录所有缓存的Component列表 */ ComponentList mComponents; Method mCreateMethod; Method mCreateMethod2; IPlugin mPlugin; IPluginHost mPluginHost; ProxyPlugin mBinderPlugin; /** * layout缓存:忽略表 */ HashSet mIgnores = new HashSet(); /** * layout缓存:构造器表 */ HashMap> mConstructors = new HashMap>(); static class ProxyPlugin implements IPlugin { com.qihoo360.loader2.IPlugin mPlugin; ProxyPlugin(IBinder plugin) { mPlugin = com.qihoo360.loader2.IPlugin.Stub.asInterface(plugin); } @Override public IModule query(Class c) { IBinder b = null; try { b = mPlugin.query(c.getName()); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "query(" + c + ") exception: " + e.getMessage(), e); } } // TODO: return IModule return null; } } /** * 初始化Loader对象 * * @param p Plugin类的对象 * 为何会反向依赖plugin对象?因为plugin.mInfo对象会发生变化, * 缓存plugin可以实时拿到最新的mInfo对象,防止出现问题 * FIXME 有优化空间,但改动量会很大,暂缓 */ Loader(Context context, String name, String path, Plugin p) { mContext = context; mPluginName = name; mPath = path; mPluginObj = p; } final boolean isPackageInfoLoaded() { return mPackageInfo != null; } final boolean isResourcesLoaded() { return isPackageInfoLoaded() && mPkgResources != null; } final boolean isDexLoaded() { return isResourcesLoaded() && mClassLoader != null; } final boolean isAppLoaded() { return mPlugin != null; } final Context createBaseContext(Context newBase) { return new PluginContext(newBase, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this); } final boolean loadDex(ClassLoader parent, int load) { try { PackageManager pm = mContext.getPackageManager(); mPackageInfo = Plugin.queryCachedPackageInfo(mPath); if (mPackageInfo == null) { // PackageInfo mPackageInfo = pm.getPackageArchiveInfo(mPath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA); if (mPackageInfo == null || mPackageInfo.applicationInfo == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "get package archive info null"); } mPackageInfo = null; return false; } if (LOG) { LogDebug.d(PLUGIN_TAG, "get package archive info, pi=" + mPackageInfo); } mPackageInfo.applicationInfo.sourceDir = mPath; mPackageInfo.applicationInfo.publicSourceDir = mPath; if (TextUtils.isEmpty(mPackageInfo.applicationInfo.processName)) { mPackageInfo.applicationInfo.processName = mPackageInfo.applicationInfo.packageName; } // 添加针对SO库的加载 // 此属性最终用于ApplicationLoaders.getClassLoader,在创建PathClassLoader时成为其参数 // 这样findLibrary可不用覆写,即可直接实现SO的加载 // Added by Jiongxuan Zhang PluginInfo pi = mPluginObj.mInfo; File ld = pi.getNativeLibsDir(); mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath(); // // 若PluginInfo.getFrameworkVersion为FRAMEWORK_VERSION_UNKNOWN(p-n才会有),则这里需要读取并修改 // if (pi.getFrameworkVersion() == PluginInfo.FRAMEWORK_VERSION_UNKNOWN) { // pi.setFrameworkVersionByMeta(mPackageInfo.applicationInfo.metaData); // } // 缓存表: pkgName -> pluginName synchronized (Plugin.PKG_NAME_2_PLUGIN_NAME) { Plugin.PKG_NAME_2_PLUGIN_NAME.put(mPackageInfo.packageName, mPluginName); } // 缓存表: pluginName -> fileName synchronized (Plugin.PLUGIN_NAME_2_FILENAME) { Plugin.PLUGIN_NAME_2_FILENAME.put(mPluginName, mPath); } // 缓存表: fileName -> PackageInfo synchronized (Plugin.FILENAME_2_PACKAGE_INFO) { Plugin.FILENAME_2_PACKAGE_INFO.put(mPath, new WeakReference(mPackageInfo)); } } // TODO preload预加载虽然通知到常驻了(但pluginInfo是通过MP.getPlugin(name, true)完全clone出来的),本进程的PluginInfo并没有得到更新 // TODO 因此preload会造成某些插件真正生效时由于cache,造成插件版本号2.0或者以上无法生效。 // TODO 这里是临时做法,避免发版前出现重大问题,后面可以修过修改preload的流程来优化 // 若PluginInfo.getFrameworkVersion为FRAMEWORK_VERSION_UNKNOWN(p-n才会有),则这里需要读取并修改 if (mPluginObj.mInfo.getFrameworkVersion() == PluginInfo.FRAMEWORK_VERSION_UNKNOWN) { mPluginObj.mInfo.setFrameworkVersionByMeta(mPackageInfo.applicationInfo.metaData); // 只有“P-n”插件才会到这里,故无需调用“纯APK”的保存功能 // PluginInfoList.save(); } // 创建或获取ComponentList表 // Added by Jiongxuan Zhang mComponents = Plugin.queryCachedComponentList(mPath); if (mComponents == null) { // ComponentList mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo); // 动态注册插件中声明的 receiver regReceivers(); // 缓存表:ComponentList synchronized (Plugin.FILENAME_2_COMPONENT_LIST) { Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents)); } /* 只调整一次 */ // 调整插件中组件的进程名称 adjustPluginProcess(mPackageInfo.applicationInfo); // 调整插件中 Activity 的 TaskAffinity adjustPluginTaskAffinity(mPluginName, mPackageInfo.applicationInfo); } if (load == Plugin.LOAD_INFO) { return isPackageInfoLoaded(); } mPkgResources = Plugin.queryCachedResources(mPath); // LOAD_RESOURCES和LOAD_ALL都会获取资源,但LOAD_INFO不可以(只允许获取PackageInfo) if (mPkgResources == null) { // Resources try { if (BuildConfig.DEBUG) { // 如果是Debug模式的话,防止与Instant Run冲突,资源重新New一个 Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo); mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration()); } else { mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo); } } catch (NameNotFoundException e) { if (LOG) { LogDebug.d(PLUGIN_TAG, e.getMessage(), e); } return false; } if (mPkgResources == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "get resources null"); } return false; } if (LOG) { LogDebug.d(PLUGIN_TAG, "get resources for app, r=" + mPkgResources); } // 缓存表: Resources synchronized (Plugin.FILENAME_2_RESOURCES) { Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources)); } } if (load == Plugin.LOAD_RESOURCES) { return isResourcesLoaded(); } mClassLoader = Plugin.queryCachedClassLoader(mPath); if (mClassLoader == null) { // ClassLoader String out = mPluginObj.mInfo.getDexParentDir().getPath(); //changeDexMode(out); // Log.i("dex", "load " + mPath + " ..."); if (BuildConfig.DEBUG) { // 因为Instant Run会替换parent为IncrementalClassLoader,所以在DEBUG环境里 // 需要替换为BootClassLoader才行 // Added by yangchao-xy & Jiongxuan Zhang parent = ClassLoader.getSystemClassLoader(); } else { // 线上环境保持不变 parent = getClass().getClassLoader().getParent(); // TODO: 这里直接用父类加载器 } String soDir = mPackageInfo.applicationInfo.nativeLibraryDir; long begin = 0; boolean isDexExist = false; if (LOG) { begin = System.currentTimeMillis(); File dexFile = mPluginObj.mInfo.getDexFile(); if (dexFile.exists() && dexFile.length() > 0) { isDexExist = true; } } File dexFile = new File(mPath); if (dexFile.exists()){ // Android 14 安全的动态代码加载 dexFile.setWritable(false); } mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent); Log.i("dex", "load " + mPath + " = " + mClassLoader); if (dexFile.exists()){ // Android 14 安全的动态代码加载 dexFile.setWritable(true); } if (mClassLoader == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "get dex null"); } return false; } if (LOG) { if (!isDexExist) { Log.d(LOADER_TAG, " --释放DEX, " + "(plugin=" + mPluginName + ", version=" + mPluginObj.mInfo.getVersion() + ")" + ", use:" + (System.currentTimeMillis() - begin) + ", process:" + IPC.getCurrentProcessName()); } else { Log.d(LOADER_TAG, " --无需释放DEX, " + "(plugin=" + mPluginName + ", version=" + mPluginObj.mInfo.getVersion() + ")" + ", use:" + (System.currentTimeMillis() - begin) + ", process:" + IPC.getCurrentProcessName()); } } // 缓存表:ClassLoader synchronized (Plugin.FILENAME_2_DEX) { Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader)); } } if (load == Plugin.LOAD_DEX) { return isDexLoaded(); } // Context mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this); if (LOG) { LogDebug.d(PLUGIN_TAG, "pkg context=" + mPkgContext); } } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "p=" + mPath + " m=" + e.getMessage(), e); } return false; } return true; } /** * 动态注册插件中静态声明的 receiver 到常驻进程 * * @throws android.os.RemoteException */ private void regReceivers() throws android.os.RemoteException { String plugin = mPluginObj.mInfo.getName(); Map> map = ManifestParser.INS.getReceiverFilterMap(plugin); if (map == null || map.size() == 0) { return; } if (mPluginHost == null) { mPluginHost = getPluginHost(); } if (mPluginHost != null) { mPluginHost.regReceiver(plugin, map); } } /** * 获取 IPluginHost Binder 接口 */ private IPluginHost getPluginHost() { IBinder binder = PluginProviderStub.proxyFetchHostBinder(mContext); if (binder == null) { if (LOG) { LogDebug.e(PluginReceiverProxy.TAG, "p.p fhb fail"); } return null; } else { return IPluginHost.Stub.asInterface(binder); } } final boolean loadEntryMethod(boolean log) { // try { String className = Factory.PLUGIN_ENTRY_PACKAGE_PREFIX + "." + mPluginName + "." + Factory.PLUGIN_ENTRY_CLASS_NAME; Class c = mClassLoader.loadClass(className); if (LOG) { LogDebug.d(PLUGIN_TAG, "found entry: className=" + className + ", loader=" + c.getClassLoader()); } mCreateMethod = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD_PARAMS); } catch (Throwable e) { if (log) { if (LOGR) { LogRelease.e(PLUGIN_TAG, e.getMessage(), e); } } else { if (LOG) { LogDebug.d(PLUGIN_TAG, "loadEntryMethod exception"); } } } return mCreateMethod != null; } final boolean invoke(PluginCommImpl manager) { try { mPlugin = (IPlugin) mCreateMethod.invoke(null, mPkgContext, manager); if (LOG) { LogDebug.d(PLUGIN_TAG, "Loader.invoke(): plugin=" + mPath + ", cl=" + (mPlugin != null ? mPlugin.getClass().getClassLoader() : "null")); } } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, e.getMessage(), e); } return false; } return true; } final boolean loadEntryMethod2() { long start = System.currentTimeMillis(); // try { String className = Factory.PLUGIN_ENTRY_PACKAGE_PREFIX + "." + mPluginName + "." + Factory.PLUGIN_ENTRY_CLASS_NAME; Class c = mClassLoader.loadClass(className); if (LOG) { LogDebug.d(PLUGIN_TAG, "found entry: className=" + className + ", loader=" + c.getClassLoader()); } mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS); } catch (Throwable e) { // 老版本的插件才会用到这个方法,因后面还有新版本的load方式,这里不打log // if (LOGR) { // LogRelease.e(PLUGIN_TAG, e.getMessage(), e); // } } if (LOG) { Log.d(TAG_NO_PN, "load loadEntryMethod2 for " + mPluginName + " time=" + (System.currentTimeMillis() - start)); } return mCreateMethod2 != null; } final boolean loadEntryMethod3() { // try { String className = Factory.REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX + "." + Factory.PLUGIN_ENTRY_CLASS_NAME; Class c = mClassLoader.loadClass(className); if (LOG) { LogDebug.d(PLUGIN_TAG, "found entry: className=" + className + ", loader=" + c.getClassLoader()); } mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, e.getMessage(), e); } } return mCreateMethod2 != null; } final boolean invoke2(PluginCommImpl x) { try { long start = System.currentTimeMillis(); IBinder manager = null; // TODO IBinder b = (IBinder) mCreateMethod2.invoke(null, mPkgContext, getClass().getClassLoader(), manager); if (b == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "p.e.r.b n"); } return false; } mBinderPlugin = new ProxyPlugin(b); mPlugin = mBinderPlugin; if (LOG) { LogDebug.d(PLUGIN_TAG, "Loader.invoke2(): plugin=" + mPath + ", plugin.binder.cl=" + b.getClass().getClassLoader()); } } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, e.getMessage(), e); } return false; } return true; } /** * 获取宿主中可分配的自定义进程列表 * * @return */ private List getHostProcessList() { List pluginProcessList = new ArrayList<>(); for (int i = 0; i < PluginProcessHost.PROCESS_COUNT; i++) { pluginProcessList.add(IPC.getPackageName() + PluginProcessHost.PROCESS_PLUGIN_SUFFIX2 + i); } return pluginProcessList; } /** * 读取插件中自定义进程列表 * * @return */ private List getPluginProcessList() { Set processSet = new HashSet<>(); String pluginUIProcess = mComponents.getApplication().packageName; getPluginProcess(processSet, mComponents.getProviders()); getPluginProcess(processSet, mComponents.getActivities()); getPluginProcess(processSet, mComponents.getServices()); getPluginProcess(processSet, mComponents.getReceivers()); processSet.remove(pluginUIProcess); return Arrays.asList(processSet.toArray(new String[0])); } /** * 把来自插件的进程去重 * * @param processSet * @param componentInfos */ private void getPluginProcess(Set processSet, ComponentInfo[] componentInfos) { if (componentInfos != null) { for (ComponentInfo componentInfo : componentInfos) { processSet.add(componentInfo.processName); } } } /** * 生成进程映射表,把插件中的自定义进程映射到宿主 * * @return */ private HashMap genDynamicProcessMap() { HashMap processMap = new HashMap<>(); List hostProcessList = getHostProcessList(); List pluginProcessList = getPluginProcessList(); int hostProcessCount = hostProcessList != null ? hostProcessList.size() : 0; if (hostProcessCount <= 0) { return processMap; } int pluginProcessCount = pluginProcessList != null ? pluginProcessList.size() : 0; for (int i = 0; i < pluginProcessCount; i++) { int hostProcessIndex = i % hostProcessCount; processMap.put(pluginProcessList.get(i), hostProcessList.get(hostProcessIndex)); } return processMap; } /** * 获取插件AndroidMainfest中配置的静态进程映射表,meta-data:"process_map" * * @param appInfo * @return */ private HashMap getConfigProcessMap(ApplicationInfo appInfo) { HashMap processMap = new HashMap<>(); Bundle bdl = appInfo.metaData; if (bdl == null || TextUtils.isEmpty(bdl.getString("process_map"))) { return processMap; } try { String processMapStr = bdl.getString("process_map"); JSONArray ja = new JSONArray(processMapStr); for (int i = 0; i < ja.length(); i++) { JSONObject jo = (JSONObject) ja.get(i); if (jo != null) { String to = jo.getString("to").toLowerCase(); if (to.equals("$ui")) { to = IPC.getPackageName(); } else { // 非 UI 进程,且是用户自定义的进程 if (to.contains("$" + PluginProcessHost.PROCESS_PLUGIN_SUFFIX)) { to = PluginProcessHost.PROCESS_ADJUST_MAP.get(to); } } processMap.put(jo.getString("from"), to); } } } catch (JSONException e) { if (BuildConfig.DEBUG) { e.printStackTrace(); } } return processMap; } /** * 调整插件中组件的进程名称,用宿主中的进程坑位来接收插件中的自定义进程 * * 注: * 如果插件中没有配置静态的 “meta-data:process_map” 进行静态的进程映射,则自动为插件中组件分配进程 * * @param appInfo */ private void adjustPluginProcess(ApplicationInfo appInfo) { HashMap processMap = getConfigProcessMap(appInfo); if (processMap == null || processMap.isEmpty()) { PluginInfo pi = MP.getPlugin(mPluginName, false); if (pi != null && pi.getFrameworkVersion() >= 4) { processMap = genDynamicProcessMap(); } } if (LOG) { Log.d(PLUGIN_TAG, "--- 调整插件中组件的进程 BEGIN ---"); for (Map.Entry entry : processMap.entrySet()) { Log.d(PLUGIN_TAG, entry.getKey() + " -> " + entry.getValue()); } } doAdjust(processMap, mComponents.getActivityMap()); doAdjust(processMap, mComponents.getServiceMap()); doAdjust(processMap, mComponents.getReceiverMap()); doAdjust(processMap, mComponents.getProviderMap()); if (LOG) { Log.d(PLUGIN_TAG, "--- 调整插件中组件的进程 END --- " + IPC.getCurrentProcessName()); } } private void doAdjust(HashMap processMap, HashMap infos) { if (processMap == null || processMap.isEmpty()) { return; } for (HashMap.Entry entry : infos.entrySet()) { ComponentInfo info = entry.getValue(); if (info != null) { String targetProcess = processMap.get(info.processName); if (!TextUtils.isEmpty(targetProcess)) { if (LOG) { Log.d(TaskAffinityStates.TAG, String.format("--- 调整组件 %s, %s -> %s", info.name, info.processName, targetProcess)); } info.processName = targetProcess; } } } } /** * 调整插件中 Activity 的默认 TaskAffinity * * @param plugin 插件名称 */ private void adjustPluginTaskAffinity(String plugin, ApplicationInfo appInfo) { if (appInfo == null) { return; } Bundle bdl = appInfo.metaData; if (bdl != null) { boolean useDefault = bdl.getBoolean("use_default_task_affinity", true); if (LOG) { LogDebug.d(TaskAffinityStates.TAG, "useDefault = " + useDefault); } if (!useDefault) { if (LOG) { LogDebug.d(TaskAffinityStates.TAG, String.format("替换插件 %s 中默认的 TaskAffinity", plugin)); } String defaultPluginTaskAffinity = appInfo.packageName; for (HashMap.Entry entry : mComponents.getActivityMap().entrySet()) { ActivityInfo info = entry.getValue(); if (LOG) { if (info != null) { LogDebug.d(TaskAffinityStates.TAG, String.format("%s.taskAffinity = %s ", info.name, info.taskAffinity)); } } // 如果是默认 TaskAffinity if (info != null && info.taskAffinity.equals(defaultPluginTaskAffinity)) { info.taskAffinity = info.taskAffinity + "." + plugin; if (LOG) { LogDebug.d(TaskAffinityStates.TAG, String.format("修改 %s 的 TaskAffinity 为 %s", info.name, info.taskAffinity)); } } } } } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/MP.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.os.IBinder; import android.os.RemoteException; import com.qihoo360.loader.utils.ProcessLocker; import com.qihoo360.replugin.IHostBinderFetcher; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.MAIN_TAG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 对外接口代码 * * @author RePlugin Team */ public class MP { /** * 需要重启常驻进程 */ public static final String ACTION_REQUEST_RESTART = "com.qihoo360.loader2.ACTION_REQUEST_RESTART"; /** * 马上重启常驻服务 */ public static final String ACTION_QUICK_RESTART = "com.qihoo360.loader2.ACTION_QUICK_RESTART"; /** * 调试用 */ static volatile HashMap sBinderReasons; /** * 仿插件对象,用来实现主程序提供binder给其他模块 * * @param name * @param p */ public static final void installBuiltinPlugin(String name, IHostBinderFetcher p) { PMF.sPluginMgr.installBuiltinPlugin(name, p); } /** * @param name * @param binder */ public static final void installBinder(String name, IBinder binder) { if (LOG) { LogDebug.d(PLUGIN_TAG, "installBinder n=" + name + " b=" + binder); } try { PluginProcessMain.getPluginHost().installBinder(name, binder); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "mp i.b: " + e.getMessage(), e); } } } /** * @param name * @return */ public static final IBinder fetchBinder(String name) { try { IBinder binder = PluginProcessMain.getPluginHost().fetchBinder(name); if (LOG) { LogDebug.d(PLUGIN_TAG, "fetchBinder n=" + name + " b=" + binder); } return binder; } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "mp f.b: " + e.getMessage(), e); } } return null; } /** * 拉起插件(或UI)进程,并查询对应的binder接口对象 * * @param plugin * @param process * @param binder * @return */ public static final PluginBinder fetchPluginBinder(String plugin, int process, String binder) { if (LOG) { LogDebug.d(PLUGIN_TAG, "MP.fetchPluginBinder ... plugin=" + plugin + " binder.name=" + binder); } // 若开启了“打印详情”则打印调用栈,便于观察 if (RePlugin.getConfig().isPrintDetailLog()) { String reason = ""; StackTraceElement elements[] = Thread.currentThread().getStackTrace(); for (StackTraceElement item : elements) { if (item.isNativeMethod()) { continue; } String cn = item.getClassName(); String mn = item.getMethodName(); String filename = item.getFileName(); int line = item.getLineNumber(); reason += cn + "." + mn + "(" + filename + ":" + line + ")" + "\n"; } if (sBinderReasons == null) { sBinderReasons = new HashMap(); } sBinderReasons.put(plugin + ":" + binder, reason); } PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.BINDER_REQUEST); IBinder b = null; try { // 容器选择 IPluginClient client = MP.startPluginProcess(plugin, process, info); if (client == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "mp.f.p.b: s c fail"); } return null; } // 远程获取 b = client.queryBinder(plugin, binder); if (LOG) { LogDebug.d(PLUGIN_TAG, "MP.fetchPluginBinder binder.object=" + b + " pid=" + info.pid); } // 增加计数器 if (b != null) { PluginProcessMain.getPluginHost().regPluginBinder(info, b); } } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "mp.f.p.b: p=" + info.pid, e); } } if (b == null) { return null; } return new PluginBinder(plugin, binder, info.pid, b); } /** * @param binder */ public static final void releasePluginBinder(PluginBinder binder) { if (LOG) { LogDebug.d(PLUGIN_TAG, "MP.releasePluginBinder ... pid=" + binder.pid + " binder=" + binder.binder); } // 记录调用栈,便于观察:删除 if (LOG) { if (sBinderReasons != null) { sBinderReasons.remove(binder.plugin + ":" + binder.name); } } PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.BINDER_REQUEST); info.pid = binder.pid; try { PluginProcessMain.getPluginHost().unregPluginBinder(info, binder.binder); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "mp.r.p.b: " + e.getMessage(), e); } } } /** * @param path * @return */ public static final PluginInfo pluginDownloaded(String path) { if (LOG) { LogDebug.d(PLUGIN_TAG, "MP.pluginDownloaded ... path=" + path); } /** * 问题描述: * * 对于正在生效的插件,如果当前时机pluginHost没有存活,那么这里会先启动pluginHost,然后再调用它的PluginHost进程的pluginDownloaded接口 * * 这里的问题是:pluginHost进程在启动过程会通过扫描文件的方式将当前即将生效的插件识别到, * 而在进程ready后,再去调用pluginDownloaded接口的时候会认为它不是新插件,从而不会通过NEW_PLUGIN广播来周知所有进程新插件生效了 * 因此,当前进程也不会同步新插件生效的逻辑。 * so,问题就来了,当前进程新下载的插件由于pluginHost的逻辑无法正常生效。 * 当然该问题只针对p-n格式的插件,而纯APK格式的插件不再使用进程启动的时候通过扫描文件目录的方式来来识别所有准备好的插件 * * 解决办法: * 对于处于该流程的插件文件(p-n插件)加上lock文件,以表示当前插件正在生效,不需要plugHost进程再次扫描生效了,也就不存在新插件在新进程中成为了老插件 */ ProcessLocker lock = null; try { if (path != null) { File f = new File(path); String fileName = f.getName(); String fileDir = f.getParent(); if (fileName.startsWith("p-n-")) { lock = new ProcessLocker(RePluginInternal.getAppContext(), fileDir, fileName + ".lock"); } } if (lock != null && !lock.tryLock()) { // 加锁 if (LOG) { LogDebug.d(PLUGIN_TAG, "MP.pluginDownloaded ... lock file + " + path + " failed! "); } } PluginInfo info = PluginProcessMain.getPluginHost().pluginDownloaded(path); if (info != null) { RePlugin.getConfig().getEventCallbacks().onInstallPluginSucceed(info); } return info; } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "mp.pded: " + e.getMessage(), e); } } finally { // 去锁 if (lock != null) { lock.unlock(); } } return null; } /** * 插件卸载 * 判断插件是否已安装:插件未安装,不做处理 * 插件已安装,正在运行,则记录“卸载状态”,推迟到到主程序进程重启的时执行卸载 * 插件已安装,未在运行,则直接删除Dex、Native库等资源 * * @param pluginName * @return 插件卸载成功与否 */ public static final boolean pluginUninstall(String pluginName) { if (LOG) { LogDebug.d(PLUGIN_TAG, "MP.pluginUninstall ... pluginName=" + pluginName); } PluginInfo pi = getPlugin(pluginName, true); // 插件未安装 if (pi == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "Not installed. pluginName=" + pluginName); } return true; } try { return PluginProcessMain.getPluginHost().pluginUninstalled(pi); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "uninstall. error: " + e.getMessage(), e); } } return false; } /** * @param path * @return */ public static final boolean pluginExtracted(String path) { if (LOG) { LogDebug.d(PLUGIN_TAG, "MP.pluginExtracted ... path=" + path); } try { return PluginProcessMain.getPluginHost().pluginExtracted(path); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "mp.peed: " + e.getMessage(), e); } } return false; } /** * 获取当前所有插件信息快照。内部框架使用 * * @param clone true:深拷贝 false:浅拷贝 * @return */ public static final List getPlugins(boolean clone) { ArrayList array = new ArrayList<>(); Set pathSet = new HashSet<>(); synchronized (PluginTable.PLUGINS) { for (PluginInfo info : PluginTable.PLUGINS.values()) { String path = info.getPath(); // 避免加了两次,毕竟包名和别名都会加进来 if (!pathSet.contains(path)) { pathSet.add(path); PluginInfo addTo; if (clone) { addTo = (PluginInfo) info.clone(); } else { addTo = info; } array.add(addTo); } } } return array; } /** * 获取某个插件信息快照。内部框架使用 * @return */ public static final PluginInfo getPlugin(String name, boolean clone) { synchronized (PluginTable.PLUGINS) { PluginInfo info = PluginTable.PLUGINS.get(name); if (clone && info != null) { // 防止外界可以修改PluginTable表中的元素,故对外必须Clone一份 return (PluginInfo) info.clone(); } else { return info; } } } /** * 注:内部接口 * * @return */ public static final int sumActivities() { int rc = 0; rc = PluginProcessMain.sumActivities(); if (LOG) { LogDebug.d(MAIN_TAG, "MP.sumActivities = " + rc); } return rc; } /** * 注:内部接口 * * @return */ public static final int sumBinders() { if (LOG) { LogDebug.d(PLUGIN_TAG, "MP.sumBinders ... index=" + PluginManager.sPluginProcessIndex); } try { return PluginProcessMain.getPluginHost().sumBinders(PluginManager.sPluginProcessIndex); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "mp.s.b: " + e.getMessage(), e); } } return -2; } /** * 根据Activity坑的名称,返回对应的Activity对象 * * @param container Activity坑名称 * @return 插件名 + 插件Activity名 + 启动时间 */ public static final String[] resolvePluginActivity(String container) { return PluginContainers.resolvePluginActivity(container); } /** * 检测卫士进程是否正在运行 * * @param name 进程名(全名) * @return */ public static final boolean isMsProcessAlive(String name) { try { return PluginProcessMain.getPluginHost().isProcessAlive(name); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "mp.i.p.a: " + e.getMessage(), e); } } return false; } /** * 注:内部接口 * * @param plugin * @param process * @param info * @return * @throws RemoteException * @hide 内部框架使用 */ public static final IPluginClient startPluginProcess(String plugin, int process, PluginBinderInfo info) throws RemoteException { return PluginProcessMain.getPluginHost().startPluginProcess(plugin, process, info); } /** * 根据 taskAffinity,判断应该取第几组 TaskAffinity * 由于 taskAffinity 是跨进程的属性,所以这里要将 taskAffinityGroup 的数据保存在常驻进程中 * @param taskAffinity * @return 索引值 */ public static int getTaskAffinityGroupIndex(String taskAffinity) throws RemoteException { return PluginProcessMain.getPluginHost().getTaskAffinityGroupIndex(taskAffinity); } public static final class PluginBinder { public final String plugin; public final String name; public final int pid; public final IBinder binder; PluginBinder(String plugin, String name, int pid, IBinder binder) { this.plugin = plugin; this.name = name; this.binder = binder; this.pid = pid; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PMF.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.app.Activity; import android.app.Application; import android.content.Context; import android.content.Intent; import android.os.RemoteException; import android.text.TextUtils; import com.qihoo360.i.Factory; import com.qihoo360.i.Factory2; import com.qihoo360.i.IModule; import com.qihoo360.loader.utils.PatchClassLoaderUtils; import com.qihoo360.replugin.helper.LogRelease; import java.io.FileDescriptor; import java.io.PrintWriter; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 框架和主程序接口代码 * * @author RePlugin Team */ public class PMF { private static Context sContext; static PmBase sPluginMgr; /** * @param context */ private static final void setApplicationContext(Context context) { sContext = context; } /** * @return */ public static final Context getApplicationContext() { return sContext; } /** * @param application */ public static final void init(Application application) { setApplicationContext(application); PluginManager.init(application); sPluginMgr = new PmBase(application); sPluginMgr.init(); Factory.sPluginManager = PMF.getLocal(); Factory2.sPLProxy = PMF.getInternal(); PatchClassLoaderUtils.patch(application); } /** * */ public static final void callAppCreate() { sPluginMgr.callAppCreate(); } /** * */ public static final void callAttach() { sPluginMgr.callAttach(); } /** * @param name * @param modc * @param module */ public static final void addBuiltinModule(String name, Class modc, IModule module) { sPluginMgr.addBuiltinModule(name, modc, module); } /** * @return */ public static final PluginCommImpl getLocal() { return sPluginMgr.mLocal; } /** * @return */ public static final PluginLibraryInternalProxy getInternal() { return sPluginMgr.mInternal; } /** * @param className * @param resolve * @return */ public static final Class loadClass(String className, boolean resolve) { return sPluginMgr.loadClass(className, resolve); } /** * @param activity * @param intent */ public static final void forward(Activity activity, Intent intent) { // activity.finish(); // try { PluginIntent ii = new PluginIntent(intent); // 原容器 String original = ii.getOriginal(); if (TextUtils.isEmpty(original)) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a f: orig=nul i=" + intent); } return; } // 容器,检查 String container = ii.getContainer(); if (TextUtils.isEmpty(container)) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a f: c=nul i=" + intent); } return; } // 目标插件,检查 String plugin = ii.getPlugin(); if (TextUtils.isEmpty(plugin)) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a f: n=nul i=" + intent); } return; } // 目标activity,检查 String target = ii.getActivity(); if (TextUtils.isEmpty(target)) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a f: t=nul i=" + intent); } return; } // 进程,检查 int process = ii.getProcess(); if (!PluginManager.isValidActivityProcess(process)) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a f: p=" + process + " i=" + intent); } return; } // 计数器,检查 int counter = ii.getCounter(); if (counter < 0 || counter >= PluginManager.COUNTER_MAX) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a f: ooc c=" + counter); } return; } // 计数器,递增 counter++; ii.setCounter(counter); // sPluginMgr.mClient.mACM.forwardIntent(activity, intent, original, container, plugin, target, process); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a f: " + e.getMessage(), e); } } } public static final void dump(FileDescriptor fd, PrintWriter writer, String[] args) { sPluginMgr.dump(fd, writer, args); } // 只为PluginServiceServer调用而准备,不对外公开 // Added by Jiongxuan Zhang public static void stopService(Intent intent) throws RemoteException { sPluginMgr.mClient.fetchServiceServer().stopService(intent, null); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/Plugin.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.Context; import android.content.pm.PackageInfo; import android.content.res.Resources; import android.os.Build; import android.os.ConditionVariable; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import com.qihoo360.i.IModule; import com.qihoo360.i.IPlugin; import com.qihoo360.loader.utils.ProcessLocker; import com.qihoo360.mobilesafe.api.Tasks; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.component.app.PluginApplicationClient; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.packages.PluginManagerProxy; import com.qihoo360.replugin.utils.AssetsUtils; import com.qihoo360.replugin.utils.FileUtils; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.MAIN_TAG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogDebug.TAG_NO_PN; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team */ class Plugin { private static final String TAG = "Plugin"; // 只加载Service/Activity/ProviderInfo信息(包含ComponentList) static final int LOAD_INFO = 0; // 加载插件信息和资源 static final int LOAD_RESOURCES = 1; // 加载插件信息、资源和Dex static final int LOAD_DEX = 2; // 加载插件信息、资源、Dex,并运行Entry类 static final int LOAD_APP = 3; /** * 专门针对LoadEntry(见方法)的锁 */ private static final byte[] LOCK_LOAD_ENTRY = new byte[0]; /** * 保存插件 pkgName 至 pluginName 的映射 */ static final HashMap PKG_NAME_2_PLUGIN_NAME = new HashMap<>(); /** * 保存插件 pluginName 至 fileName 的映射 */ static final HashMap PLUGIN_NAME_2_FILENAME = new HashMap<>(); /** * */ static final HashMap> FILENAME_2_DEX = new HashMap<>(); /** * */ static final HashMap> FILENAME_2_RESOURCES = new HashMap<>(); /** * */ static final HashMap> FILENAME_2_PACKAGE_INFO = new HashMap<>(); /** * */ static final HashMap> FILENAME_2_COMPONENT_LIST = new HashMap<>(); /** * 调试用 */ static volatile ArrayList sLoadedReasons; /** * */ PluginInfo mInfo; /** * sync */ private final Object LOCK = new Object(); private final ConditionVariable APPLICATION_LOCK = new ConditionVariable(); /** * 没有IPlugin对象 */ boolean mDummyPlugin; /** * */ Context mContext; /** * */ ClassLoader mParent; /** * */ PluginCommImpl mPluginManager; /** * */ volatile boolean mInitialized; /** * */ Loader mLoader; /** * 跑在UI线程里的Handler对象 */ final Handler mMainH = new Handler(Looper.getMainLooper()); /** * 用来控制插件里的Application对象 */ volatile PluginApplicationClient mApplicationClient; private static class UpdateInfoTask implements Runnable { PluginInfo mInfo; UpdateInfoTask(PluginInfo info) { mInfo = info; } @Override public void run() { try { PluginProcessMain.getPluginHost().updatePluginInfo(mInfo); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "ph u p i: " + e.getMessage(), e); } } } } static final Plugin build(PluginInfo info) { return new Plugin(info); } static final Plugin cloneAndReattach(Context c, Plugin p, ClassLoader parent, PluginCommImpl pm) { if (p == null) { return null; } p = build(p.mInfo); p.attach(c, parent, pm); return p; } /** * 根据插件 pkgName 取 pluginName */ static final String queryPluginNameByPkgName(String pkgName) { String pluginName; synchronized (PKG_NAME_2_PLUGIN_NAME) { pluginName = PKG_NAME_2_PLUGIN_NAME.get(pkgName); if (LOG) { LogDebug.d(PLUGIN_TAG, "cached pluginName: " + pkgName + " -> " + pluginName); } } return pluginName; } static final String queryCachedFilename(String name) { String filename = null; synchronized (PLUGIN_NAME_2_FILENAME) { filename = PLUGIN_NAME_2_FILENAME.get(name); if (LOG) { LogDebug.d(PLUGIN_TAG, "cached filename: " + name + " -> " + filename); } } return filename; } static final ClassLoader queryCachedClassLoader(String filename) { ClassLoader dex = null; if (!TextUtils.isEmpty(filename)) { synchronized (FILENAME_2_DEX) { WeakReference ref = FILENAME_2_DEX.get(filename); if (ref != null) { dex = ref.get(); if (dex == null) { FILENAME_2_DEX.remove(filename); } if (LOG) { LogDebug.d(PLUGIN_TAG, "cached Dex " + filename + " -> " + dex); } } } } return dex; } static final Resources queryCachedResources(String filename) { Resources resources = null; if (!TextUtils.isEmpty(filename)) { synchronized (FILENAME_2_RESOURCES) { WeakReference ref = FILENAME_2_RESOURCES.get(filename); if (ref != null) { resources = ref.get(); if (resources == null) { FILENAME_2_RESOURCES.remove(filename); } if (LOG) { LogDebug.d(PLUGIN_TAG, "cached Resources " + filename + " -> " + resources); } } } } return resources; } static final PackageInfo queryCachedPackageInfo(String filename) { PackageInfo packageInfo = null; if (!TextUtils.isEmpty(filename)) { synchronized (FILENAME_2_PACKAGE_INFO) { WeakReference ref = FILENAME_2_PACKAGE_INFO.get(filename); if (ref != null) { packageInfo = ref.get(); if (packageInfo == null) { FILENAME_2_PACKAGE_INFO.remove(filename); } if (LOG) { LogDebug.d(PLUGIN_TAG, "cached packageInfo " + filename + " -> " + packageInfo); } } } } return packageInfo; } static final ComponentList queryCachedComponentList(String filename) { ComponentList cl = null; if (!TextUtils.isEmpty(filename)) { synchronized (FILENAME_2_COMPONENT_LIST) { WeakReference ref = FILENAME_2_COMPONENT_LIST.get(filename); if (ref != null) { cl = ref.get(); if (cl == null) { FILENAME_2_COMPONENT_LIST.remove(filename); } if (LOG) { LogDebug.d(PLUGIN_TAG, "cached componentList " + filename + " -> " + cl); } } } } return cl; } static final void clearCachedPlugin(String filename) { if (TextUtils.isEmpty(filename)) { return; } ClassLoader dex = null; synchronized (FILENAME_2_DEX) { WeakReference ref = FILENAME_2_DEX.get(filename); if (ref != null) { dex = ref.get(); FILENAME_2_DEX.remove(filename); if (LOG) { LogDebug.d(PLUGIN_TAG, "clear Cached Dex " + filename + " -> " + dex); } } } Resources resources = null; synchronized (FILENAME_2_RESOURCES) { WeakReference ref = FILENAME_2_RESOURCES.get(filename); if (ref != null) { resources = ref.get(); FILENAME_2_RESOURCES.remove(filename); if (LOG) { LogDebug.d(PLUGIN_TAG, "clear Cached Resources " + filename + " -> " + resources); } } } PackageInfo packageInfo = null; synchronized (FILENAME_2_PACKAGE_INFO) { WeakReference ref = FILENAME_2_PACKAGE_INFO.get(filename); if (ref != null) { packageInfo = ref.get(); FILENAME_2_PACKAGE_INFO.remove(filename); if (LOG) { LogDebug.d(PLUGIN_TAG, "clear Cached packageInfo " + filename + " -> " + packageInfo); } } } ComponentList cl = null; synchronized (FILENAME_2_COMPONENT_LIST) { WeakReference ref = FILENAME_2_COMPONENT_LIST.get(filename); if (ref != null) { cl = ref.get(); FILENAME_2_COMPONENT_LIST.remove(filename); if (LOG) { LogDebug.d(PLUGIN_TAG, "clear Cached componentList " + filename + " -> " + cl); } } } } static final void dump(FileDescriptor fd, PrintWriter writer, String[] args) { if (LogDebug.DUMP_ENABLED) { writer.println("--- cached plugin filename ---"); // 懒得锁了 for (String name : PLUGIN_NAME_2_FILENAME.keySet()) { writer.println(name + ": " + PLUGIN_NAME_2_FILENAME.get(name)); } writer.println("--- cached plugin Resources ---"); // 懒得锁了 for (String name : FILENAME_2_RESOURCES.keySet()) { writer.println(name + ": " + FILENAME_2_RESOURCES.get(name)); } writer.println("--- cached plugin PackageInfo ---"); // 懒得锁了 for (String name : FILENAME_2_PACKAGE_INFO.keySet()) { writer.println(name + ": " + FILENAME_2_PACKAGE_INFO.get(name)); } writer.println("--- cached plugin ComponentList ---"); // 懒得锁了 for (String name : FILENAME_2_COMPONENT_LIST.keySet()) { writer.println(name + ": " + FILENAME_2_COMPONENT_LIST.get(name)); } } } private Plugin(PluginInfo info) { mInfo = info; } @Override public String toString() { if (LOG) { return super.toString() + " {info=" + mInfo + "}"; } return super.toString(); } final void attach(Context context, ClassLoader parent, PluginCommImpl manager) { mContext = context; mParent = parent; mPluginManager = manager; } /** * @return */ final ClassLoader getClassLoader() { if (mLoader == null) { return null; } return mLoader.mClassLoader; } /** * @return */ final boolean isInitialized() { return mInitialized; } /** * @return */ final boolean isLoaded() { if (mLoader == null) { return false; } return mLoader.isAppLoaded(); } /** * @return */ final boolean isPackageInfoLoaded() { if (mLoader == null) { return false; } return mLoader.isPackageInfoLoaded(); } /** * */ final boolean load(int load, boolean useCache) { PluginInfo info = mInfo; boolean rc = loadLocked(load, useCache); // 尝试在此处调用Application.onCreate方法 // Added by Jiongxuan Zhang if (load == LOAD_APP && rc) { callApp(); } // 如果info改了,通知一下常驻 // 只针对P-n的Type转化来处理,一定要通知,这样Framework_Version也会得到更新 if (rc && mInfo != info) { UpdateInfoTask task = new UpdateInfoTask((PluginInfo) mInfo.clone()); Tasks.post2Thread(task); } return rc; } final void replaceInfo(PluginInfo info) { boolean rc = false; if (mInfo.canReplaceForPn(info)) { mInfo = info; rc = true; } if (LOG) { LogDebug.d(PLUGIN_TAG, "replace plugin info: info=" + info + " rc=" + rc); } } /** * 从缓存中读取Loader信息 * @param load 加载类型 * @return true: 缓存命中 false: 没有缓存 */ private boolean loadByCache(int load) { if (load == LOAD_INFO) { // 提取PackageInfo对象 String filename = queryCachedFilename(mInfo.getName()); PackageInfo pi = queryCachedPackageInfo(filename); ComponentList cl = queryCachedComponentList(filename); if (pi != null && cl != null) { mLoader = new Loader(mContext, mInfo.getName(), null, this); mLoader.mPackageInfo = pi; mLoader.mComponents = cl; if (LOG) { LogDebug.i(MAIN_TAG, "loadLocked(): Cached, pkgInfo loaded"); } return true; } } if (load == LOAD_RESOURCES) { // 提取PackageInfo和Resources对象 String filename = queryCachedFilename(mInfo.getName()); Resources r = queryCachedResources(filename); PackageInfo pi = queryCachedPackageInfo(filename); ComponentList cl = queryCachedComponentList(filename); if (r != null && pi != null && cl != null) { mLoader = new Loader(mContext, mInfo.getName(), null, this); mLoader.mPkgResources = r; mLoader.mPackageInfo = pi; mLoader.mComponents = cl; if (LOG) { LogDebug.i(MAIN_TAG, "loadLocked(): Cached, resource loaded"); } return true; } } if (load == LOAD_DEX) { // 提取PackageInfo、Resources和DexClassLoader对象 String filename = queryCachedFilename(mInfo.getName()); Resources r = queryCachedResources(filename); PackageInfo pi = queryCachedPackageInfo(filename); ComponentList cl = queryCachedComponentList(filename); ClassLoader clzl = queryCachedClassLoader(filename); if (r != null && pi != null && cl != null && clzl != null) { mLoader = new Loader(mContext, mInfo.getName(), null, this); mLoader.mPkgResources = r; mLoader.mPackageInfo = pi; mLoader.mComponents = cl; mLoader.mClassLoader = clzl; if (LOG) { LogDebug.i(MAIN_TAG, "loadLocked(): Cached, dex loaded"); } return true; } } return false; } /** * @param load * @return */ private boolean loadLocked(int load, boolean useCache) { // 若插件被“禁用”,则即便上次加载过(且进程一直活着),这次也不能再次使用了 // Added by Jiongxuan Zhang int status = PluginStatusController.getStatus(mInfo.getName(), mInfo.getVersion()); if (status < PluginStatusController.STATUS_OK) { if (LOG) { LogDebug.d(PLUGIN_TAG, "loadLocked(): Disable in=" + mInfo.getName() + ":" + mInfo.getVersion() + "; st=" + status); } return false; } synchronized (LOCK) { if (mInitialized) { if (mLoader == null) { if (LOG) { LogDebug.i(MAIN_TAG, "loadLocked(): Initialized but mLoader is Null"); } return false; } if (load == LOAD_INFO) { boolean rl = mLoader.isPackageInfoLoaded(); if (LOG) { LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, pkginfo loaded = " + rl); } return rl; } if (load == LOAD_RESOURCES) { boolean rl = mLoader.isResourcesLoaded(); if (LOG) { LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, resource loaded = " + rl); } return rl; } if (load == LOAD_DEX) { boolean rl = mLoader.isDexLoaded(); if (LOG) { LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, dex loaded = " + rl); } return rl; } boolean il = mLoader.isAppLoaded(); if (LOG) { LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, is loaded = " + il); } return il; } mInitialized = true; } // 若开启了“打印详情”则打印调用栈,便于观察 if (RePlugin.getConfig().isPrintDetailLog()) { String reason = ""; reason += "--- plugin: " + mInfo.getName() + " ---\n"; reason += "load=" + load + "\n"; StackTraceElement elements[] = Thread.currentThread().getStackTrace(); for (StackTraceElement item : elements) { if (item.isNativeMethod()) { continue; } String cn = item.getClassName(); String mn = item.getMethodName(); String filename = item.getFileName(); int line = item.getLineNumber(); if (LOG) { LogDebug.i(PLUGIN_TAG, cn + "." + mn + "(" + filename + ":" + line + ")"); } reason += cn + "." + mn + "(" + filename + ":" + line + ")" + "\n"; } if (sLoadedReasons == null) { sLoadedReasons = new ArrayList(); } sLoadedReasons.add(reason); } // 这里先处理一下,如果cache命中,省了后面插件提取(如释放Jar包等)操作 if (useCache) { boolean result = loadByCache(load); // 如果缓存命中,则直接返回 if (result) { return true; } } Context context = mContext; ClassLoader parent = mParent; PluginCommImpl manager = mPluginManager; // String logTag = "try1"; String lockFileName = String.format(Constant.LOAD_PLUGIN_LOCK, mInfo.getApkFile().getName()); ProcessLocker lock = new ProcessLocker(context, lockFileName); if (LOG) { LogDebug.i(PLUGIN_TAG, "loadLocked(): Ready to lock! logtag = " + logTag + "; pn = " + mInfo.getName()); } if (!lock.tryLockTimeWait(5000, 10)) { // 此处仅仅打印错误 if (LOGR) { LogRelease.w(PLUGIN_TAG, logTag + ": failed to lock: can't wait plugin ready"); } } // long t1 = System.currentTimeMillis(); boolean rc = doLoad(logTag, context, parent, manager, load); if (LOG) { LogDebug.i(PLUGIN_TAG, "load " + mInfo.getPath() + " " + hashCode() + " c=" + load + " rc=" + rc + " delta=" + (System.currentTimeMillis() - t1)); } // lock.unlock(); if (LOG) { LogDebug.i(PLUGIN_TAG, "loadLocked(): Unlock! logtag = " + logTag + "; pn = " + mInfo.getName()); } if (!rc) { if (LOGR) { LogRelease.e(PLUGIN_TAG, logTag + ": loading fail1"); } } if (rc) { // 打印当前内存占用情况,只针对Dex和App加载做输出 // 只有开启“详细日志”才会输出,防止“消耗性能” if (LOG && RePlugin.getConfig().isPrintDetailLog()) { if (load == LOAD_DEX || load == LOAD_APP) { LogDebug.printPluginInfo(mInfo, load); LogDebug.printMemoryStatus(LogDebug.TAG, "act=, loadLocked, flag=, End-1, pn=, " + mInfo.getName() + ", type=, " + load); } } try { // 至此,该插件已开始运行 PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName()); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "p.u.1: " + e.getMessage(), e); } } return true; } // logTag = "try2"; lock = new ProcessLocker(context, lockFileName); if (!lock.tryLockTimeWait(5000, 10)) { // 此处仅仅打印错误 if (LOGR) { LogRelease.w(PLUGIN_TAG, logTag + ": failed to lock: can't wait plugin ready"); } } // 删除优化dex文件 File odex = mInfo.getDexFile(); if (odex.exists()) { if (LOG) { LogDebug.d(PLUGIN_TAG, logTag + ": delete exist odex=" + odex.getAbsolutePath()); } odex.delete(); } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // support for multidex below LOLLIPOP:delete Extra odex,if need try { FileUtils.forceDelete(mInfo.getExtraOdexDir()); } catch (IOException e) { e.printStackTrace(); } catch (IllegalArgumentException e2) { e2.printStackTrace(); } } t1 = System.currentTimeMillis(); // 尝试再次加载该插件 rc = tryLoadAgain(logTag, context, parent, manager, load); if (LOG) { LogDebug.i(PLUGIN_TAG, "load2 " + mInfo.getPath() + " " + hashCode() + " c=" + load + " rc=" + rc + " delta=" + (System.currentTimeMillis() - t1)); } // lock.unlock(); if (!rc) { if (LOGR) { LogRelease.e(PLUGIN_TAG, logTag + ": loading fail2"); } return false; } // 打印当前内存占用情况,只针对Dex和App加载做输出 // 只有开启“详细日志”才会输出,防止“消耗性能” if (LOG && RePlugin.getConfig().isPrintDetailLog()) { if (load == LOAD_DEX || load == LOAD_APP) { LogDebug.printPluginInfo(mInfo, load); LogDebug.printMemoryStatus(LogDebug.TAG, "act=, loadLocked, flag=, End-2, pn=, " + mInfo.getName() + ", type=, " + load); } } try { // 至此,该插件已开始运行 PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName()); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "p.u.2: " + e.getMessage(), e); } } return true; } final IModule query(Class c) { return mLoader.mPlugin.query(c); } final IBinder query(String binder) { try { return mLoader.mBinderPlugin.mPlugin.query(binder); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "q.b.e.m" + e.getMessage(), e); } } return null; } /** * 抽出方法 */ private boolean tryLoadAgain(String tag, Context context, ClassLoader parent, PluginCommImpl manager, int load) { mLoader = null; return doLoad(tag, context, parent, manager, load); } private final boolean doLoad(String tag, Context context, ClassLoader parent, PluginCommImpl manager, int load) { if (mLoader == null) { // 试图释放文件 PluginInfo info = null; if (mInfo.getType() == PluginInfo.TYPE_BUILTIN) { //内置插件,首次加载的时候,把数据写到p.l中,然后把文件拷贝到对应目录 File dir = new File(mInfo.getApkDir()); File dexdir = mInfo.getDexParentDir(); String dstName = mInfo.getApkFile().getName(); boolean rc = AssetsUtils.quickExtractTo(context, mInfo, dir.getAbsolutePath(), dstName, dexdir.getAbsolutePath()); if (!rc) { // extract built-in plugin failed: plugin= if (LOGR) { LogRelease.e(PLUGIN_TAG, "p e b i p f " + mInfo); } return false; } File file = new File(dir, dstName); info = (PluginInfo) mInfo.clone(); info.setPath(file.getPath()); info.setType(PluginInfo.TYPE_EXTRACTED); } else if(mInfo.getType() == PluginInfo.TYPE_PN_INSTALLED || mInfo.getType() == PluginInfo.TYPE_EXTRACTED){ try { //针对升级上来的用户,重新释放已安装插件的so,更换路径 File oldSoLibDir = mInfo.getOldNativeLibsDir(); File soLibDir = mInfo.getNativeLibsDir(); if (oldSoLibDir.exists() && oldSoLibDir.listFiles() != null && oldSoLibDir.listFiles().length > 0 && (!soLibDir.exists() || soLibDir.listFiles() == null || soLibDir.listFiles().length == 0)){ PluginNativeLibsHelper.install(mInfo.getPath(), soLibDir); } }catch (Exception e){ return false; } } // if (info != null) { // 替换 mInfo = info; } // mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this); if (!mLoader.loadDex(parent, load)) { //内置插件加载失败后,需要把释放的文件路径和类型写入到p.l中去。 try { PluginManagerProxy.updateTP(mInfo.getName(), mInfo.getType(), mInfo.getPath()); } catch (RemoteException e) { } return false; } // 设置插件为“使用过的” // 注意,需要重新获取当前的PluginInfo对象,而非使用“可能是新插件”的mInfo try { long start = System.currentTimeMillis(); PluginManagerProxy.updateUsedIfNeeded(mInfo.getName(), mInfo.getPath(), mInfo.getType(), true); mInfo.setIsUsed(true); if (LOG) { Log.d(TAG_NO_PN, "update " + mInfo.getName() + " time=" + (System.currentTimeMillis() - start)); } } catch (RemoteException e) { // 同步出现问题,但仍继续进行 if (LOGR) { e.printStackTrace(); } } long startApp = System.currentTimeMillis(); // 若需要加载Dex,则还同时需要初始化插件里的Entry对象 if (load == LOAD_APP) { // NOTE Entry对象是可以在任何线程中被调用到 if (!loadEntryLocked(manager)) { return false; } // NOTE 在此处调用则必须Post到UI,但此时有可能Activity已被加载 // 会出现Activity.onCreate比Application更早的情况,故应放在load外面立即调用 // callApp(); } if (LOG) { Log.d(TAG_NO_PN, "load entry for " + mInfo.getName() + " time=" + (System.currentTimeMillis() - startApp)); } } if (load == LOAD_INFO) { return mLoader.isPackageInfoLoaded(); } else if (load == LOAD_RESOURCES) { return mLoader.isResourcesLoaded(); } else if (load == LOAD_DEX) { return mLoader.isDexLoaded(); } else { return mLoader.isAppLoaded(); } } private boolean loadEntryLocked(PluginCommImpl manager) { if (mDummyPlugin) { if (LOGR) { LogRelease.w(PLUGIN_TAG, "p.lel dm " + mInfo.getName()); } mLoader.mPlugin = new IPlugin() { @Override public IModule query(Class c) { return null; } }; } else { if (LOG) { LogDebug.d(PLUGIN_TAG, "Plugin.loadEntryLocked(): Load entry, info=" + mInfo); } if (mLoader.loadEntryMethod2()) { if (!mLoader.invoke2(manager)) { return false; } } else if (mLoader.loadEntryMethod(false)) { if (!mLoader.invoke(manager)) { return false; } } else if (mLoader.loadEntryMethod3()) { if (!mLoader.invoke2(manager)) { return false; } } else { if (LOGR) { LogRelease.e(PLUGIN_TAG, "p.lel f " + mInfo.getName()); } return false; } } return true; } // 确保在UI线程中调用 // ATTENTION 必须在LOCK锁之外调用此方法 // 否则一旦LOCK锁内任一位置再次调用Plugin.doLoad(如打开另一插件)时会造成循环锁 // Added by Jiongxuan Zhang private void callApp() { if (Looper.myLooper() == Looper.getMainLooper()) { callAppLocked(); } else { // 确保一定在UI的最早消息处调用 mMainH.postAtFrontOfQueue(new Runnable() { @Override public void run() { callAppLocked(); } }); if(!APPLICATION_LOCK.block(3 * 1000)){ if (LOGR) { LogRelease.e(PLUGIN_TAG, "p.cal timeout " + mInfo.getName()); } } } } private synchronized void callAppLocked() { // 获取并调用Application的几个核心方法 if (!mDummyPlugin) { // NOTE 不排除A的Application中调到了B,B又调回到A,或在同一插件内的onCreate开启Service/Activity,而内部逻辑又调用fetchContext并再次走到这里 // NOTE 因此需要对mApplicationClient做判断,确保永远只执行一次,无论是否成功 if (mApplicationClient != null) { // 已经初始化过,无需再次处理 APPLICATION_LOCK.open(); return; } mApplicationClient = PluginApplicationClient.getOrCreate( mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo); if (mApplicationClient != null) { mApplicationClient.callAttachBaseContext(mLoader.mPkgContext); mApplicationClient.callOnCreate(); } } else { if (LOGR) { LogRelease.e(PLUGIN_TAG, "p.cal dm " + mInfo.getName()); } } APPLICATION_LOCK.open(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginBinderInfo.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.os.Parcel; import android.os.Parcelable; /** * 插件框架内部接口 * * @hide 内部框架使用 * @author RePlugin Team */ public final class PluginBinderInfo implements Parcelable { public static final int NONE_REQUEST = 0; public static final int ACTIVITY_REQUEST = 1; public static final int SERVICE_REQUEST = 2; public static final int PROVIDER_REQUEST = 3; public static final int BINDER_REQUEST = 4; public int request; public int pid; public int index; public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public PluginBinderInfo createFromParcel(Parcel source) { return new PluginBinderInfo(source); } @Override public PluginBinderInfo[] newArray(int size) { return new PluginBinderInfo[size]; } }; PluginBinderInfo() { request = NONE_REQUEST; pid = -1; index = -1; } // @hide public PluginBinderInfo(int req) { request = req; pid = -1; index = -1; } PluginBinderInfo(Parcel source) { readFromParcel(source); } final void readFromParcel(Parcel source) { request = source.readInt(); pid = source.readInt(); index = source.readInt(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(request); dest.writeInt(pid); dest.writeInt(index); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginCommImpl.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.text.TextUtils; import com.qihoo360.i.IModule; import com.qihoo360.i.IPluginManager; import com.qihoo360.mobilesafe.svcmanager.QihooServiceManager; import com.qihoo360.replugin.IHostBinderFetcher; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.component.process.PluginProcessHost; import com.qihoo360.replugin.component.utils.IntentMatcherHelper; import com.qihoo360.replugin.component.utils.PluginClientHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import java.util.HashMap; import java.util.List; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 负责宿主与插件、插件间的互通,可通过插件的Factory直接调用,也可通过RePlugin来跳转

* TODO 原名为PmLocalImpl。新名字也不太好,待重构后会去掉 * * @author RePlugin Team */ public class PluginCommImpl { private static final String CONTAINER_PROVIDER_AUTHORITY_PART = ".loader.p.pr"; static final String INTENT_KEY_THEME_ID = "__themeId"; /** * */ Context mContext; /** * */ PmBase mPluginMgr; PluginCommImpl(Context context, PmBase pm) { mContext = context; mPluginMgr = pm; } /** * @param name 插件名 * @return */ public boolean isPluginLoaded(String name) { if (LOG) { LogDebug.d(PLUGIN_TAG, "isPluginLoaded: name=" + name); } Plugin plugin = mPluginMgr.getPlugin(name); if (plugin == null) { return false; } return plugin.isLoaded(); } /** * 此方法调用主程序或特定插件的IPlugin.query,当插件未加载时会尝试加载 * @param name 插件名 * @param c 需要查询的interface的类 * @return */ public IModule query(String name, Class c) { if (LOG) { LogDebug.d(PLUGIN_TAG, "query: name=" + name + " class=" + c.getName()); } HashMap modules = mPluginMgr.getBuiltinModules(name); if (modules != null) { return modules.get(c.getName()); } Plugin p = mPluginMgr.loadAppPlugin(name); if (p == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "query: not found plugin, name=" + name + " class=" + c.getName()); } return null; } return p.query(c); } /** * @param name 插件名 * @param binder 需要查询的binder的类 * @return */ public IBinder query(String name, String binder) { if (LOG) { LogDebug.d(PLUGIN_TAG, "query: name=" + name + " binder=" + binder); } // 先用仿插件对象判断 { IHostBinderFetcher p = mPluginMgr.getBuiltinPlugin(name); if (p != null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "use buildin plugin"); } return p.query(binder); } } Plugin p = mPluginMgr.loadAppPlugin(name); if (p == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "query: not found plugin, name=" + name + " binder=" + binder); } return null; } return p.query(binder); } /** * @param name 插件名 * @param binder 需要查询的binder的类 * @param process 是否在指定进程中启动 * @return */ public IBinder query(String name, String binder, int process) { // 自己进程 if (IPC.isPersistentProcess() && process == IPluginManager.PROCESS_PERSIST) { return query(name, binder); } // 自己进程 if (IPC.isUIProcess() && process == IPluginManager.PROCESS_UI) { return query(name, binder); } // 自己进程(自定义进程) String processTail = PluginProcessHost.processTail(IPC.getCurrentProcessName()); if (PluginProcessHost.PROCESS_INT_MAP.containsKey(processTail) && process == PluginProcessHost.PROCESS_INT_MAP.get(processTail)) { return query(name, binder); } // 需要在常驻里启动 if (process == IPluginManager.PROCESS_PERSIST) { try { return PluginProcessMain.getPluginHost().queryPluginBinder(name, binder); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "q.p.b: " + e.getMessage(), e); } } return null; } // 剩下的交给SvcManager去干 return QihooServiceManager.getPluginService(mContext, name, binder); } /** * 警告:低层接口 * 当插件升级之后,通过adapter.jar标准接口,甚至invoke接口都无法完成任务时,可通过此接口反射来完成任务 * @param name 插件名 * @return 插件的context,可通过此context得到插件的ClassLoader */ public Context queryPluginContext(String name) { Plugin p = mPluginMgr.loadAppPlugin(name); if (p != null) { return p.mLoader.mPkgContext; } if (LOG) { LogDebug.d(PLUGIN_TAG, "not found plugin=" + name); } return null; } /** * 警告:低层接口 * 调用此接口会在当前进程加载插件(不加载代码,只加载资源) * @param name 插件名 * @return 插件的Resources */ public Resources queryPluginResouces(String name) { // 先从缓存获取 Resources resources = Plugin.queryCachedResources(Plugin.queryCachedFilename(name)); if (resources != null) { return resources; } Plugin p = mPluginMgr.loadResourcePlugin(name, this); if (p != null) { return p.mLoader.mPkgResources; } if (LOG) { LogDebug.d(PLUGIN_TAG, "not found plugin=" + name); } return null; } /** * 警告:低层接口 * 调用此接口会在当前进程加载插件(不加载代码,只加载资源) * @param name 插件名 * @return 插件的PackageInfo */ public PackageInfo queryPluginPackageInfo(String name) { // 先从缓存获取 PackageInfo packageInfo = Plugin.queryCachedPackageInfo(Plugin.queryCachedFilename(name)); if (packageInfo != null) { return packageInfo; } Plugin p = mPluginMgr.loadPackageInfoPlugin(name, this); if (p != null) { return p.mLoader.mPackageInfo; } if (LOG) { LogDebug.d(PLUGIN_TAG, "not found plugin=" + name); } return null; } /** * 警告:低层接口 * 调用此接口会在当前进程加载插件(不加载代码和资源,只获取PackageInfo) * * @param pkgName 插件包名 * @param flags Flags * @return 插件的PackageInfo */ public PackageInfo queryPluginPackageInfo(String pkgName, int flags) { // 根据 pkgName 取得 pluginName String pluginName = Plugin.queryPluginNameByPkgName(pkgName); if (!TextUtils.isEmpty(pluginName)) { return queryPluginPackageInfo(pluginName); } return null; } /** * 警告:低层接口 * 调用此接口会在当前进程加载插件(不加载代码和资源,只获取ComponentList) * @param name 插件名 * @return 插件的ComponentList */ public ComponentList queryPluginComponentList(String name) { // 先从缓存获取 ComponentList cl = Plugin.queryCachedComponentList(Plugin.queryCachedFilename(name)); if (cl != null) { return cl; } Plugin p = mPluginMgr.loadPackageInfoPlugin(name, this); if (p != null) { return p.mLoader.mComponents; } if (LOG) { LogDebug.d(PLUGIN_TAG, "not found plugin=" + name); } return null; } /** * 警告:低层接口 * 调用此接口会在当前进程加载插件(不启动App) * @param name 插件名 * @return 插件的Resources */ public ClassLoader queryPluginClassLoader(String name) { // 先从缓存获取 ClassLoader cl = Plugin.queryCachedClassLoader(Plugin.queryCachedFilename(name)); if (cl != null) { return cl; } Plugin p = mPluginMgr.loadDexPlugin(name, this); if (p != null) { return p.mLoader.mClassLoader; } if (LOG) { LogDebug.d(PLUGIN_TAG, "not found plugin=" + name); } return null; } /** * 警告:低层接口 * 调用此接口会“依据PluginInfo中指定的插件信息”,在当前进程加载插件(不启动App)。通常用于“指定路径来直接安装”的情况 * 注意:调用此接口将不会“通知插件更新” * Added by Jiongxuan Zhang * @param pi 插件信息 * @return 插件的Resources */ public ClassLoader loadPluginClassLoader(PluginInfo pi) { // 不从缓存中获取,而是直接初始化ClassLoader Plugin p = mPluginMgr.loadPlugin(pi, this, Plugin.LOAD_DEX, false); if (p != null) { return p.mLoader.mClassLoader; } if (LOG) { LogDebug.d(PLUGIN_TAG, "not found plugin=" + pi.getName()); } return null; } /** * 警告:低层接口 * 调用此接口会在当前进程加载插件(不加载代码和资源,只获取Collection) * @return 符合 action 的所有 ReceiverInfo */ public List queryPluginsReceiverList(Intent intent) { IPluginHost pluginHost = PluginProcessMain.getPluginHost(); if (pluginHost != null) { try { return pluginHost.queryPluginsReceiverList(intent); } catch (Throwable e) { if (LOG) { LogDebug.e(PLUGIN_TAG, "Query PluginsReceiverList fail:" + e.toString()); } } } return null; } /** * 启动一个插件中的activity,如果插件不存在会触发下载界面 * @param context 应用上下文或者Activity上下文 * @param intent * @param plugin 插件名 * @param activity 待启动的activity类名 * @param process 是否在指定进程中启动 * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑 */ public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process) { if (LOG) { LogDebug.d(PLUGIN_TAG, "start activity: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process); } return mPluginMgr.mInternal.startActivity(context, intent, plugin, activity, process, true); } /** * 启动一个插件中的 activity 'forResult' * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑 */ public boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) { if (LOG) { LogDebug.d(PLUGIN_TAG, "startActivityForResult: intent=" + intent + " requestCode=" + requestCode+ " options=" + options); } return mPluginMgr.mInternal.startActivityForResult(activity, intent, requestCode, options); } /** * 加载插件Activity,在startActivity之前调用 * @param intent * @param plugin 插件名 * @param target 目标Service名,如果传null,则取获取到的第一个 * @param process 是否在指定进程中启动 * @return */ public ComponentName loadPluginActivity(Intent intent, String plugin, String activity, int process) { ActivityInfo ai = null; String container = null; PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.ACTIVITY_REQUEST); try { // 获取 ActivityInfo(可能是其它插件的 Activity,所以这里使用 pair 将 pluginName 也返回) ai = getActivityInfo(plugin, activity, intent); if (ai == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: bindActivity: activity not found"); } return null; } // 存储此 Activity 在插件 Manifest 中声明主题到 Intent intent.putExtra(INTENT_KEY_THEME_ID, ai.theme); if (LOG) { LogDebug.d("theme", String.format("intent.putExtra(%s, %s);", ai.name, ai.theme)); } // 根据 activity 的 processName,选择进程 ID 标识 if (ai.processName != null) { process = PluginClientHelper.getProcessInt(ai.processName); } // 容器选择(启动目标进程) IPluginClient client = MP.startPluginProcess(plugin, process, info); if (client == null) { return null; } // 远程分配坑位 container = client.allocActivityContainer(plugin, process, ai.name, intent); if (LOG) { LogDebug.i(PLUGIN_TAG, "alloc success: container=" + container + " plugin=" + plugin + " activity=" + activity); } } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "l.p.a spp|aac: " + e.getMessage(), e); } } // 分配失败 if (TextUtils.isEmpty(container)) { return null; } PmBase.cleanIntentPluginParams(intent); // TODO 是否重复 // 附上额外数据,进行校验 // intent.putExtra(PluginManager.EXTRA_PLUGIN, plugin); // intent.putExtra(PluginManager.EXTRA_ACTIVITY, activity); // intent.putExtra(PluginManager.EXTRA_PROCESS, process); // intent.putExtra(PluginManager.EXTRA_CONTAINER, container); PluginIntent ii = new PluginIntent(intent); ii.setPlugin(plugin); ii.setActivity(ai.name); ii.setProcess(IPluginManager.PROCESS_AUTO); ii.setContainer(container); ii.setCounter(0); return new ComponentName(IPC.getPackageName(), container); } /** * 启动插件Service,在startService、bindService之前调用 * @param plugin 插件名 * @param target 目标Service名,如果传null,则取获取到的第一个 * @param process 是否在指定进程中启动 * @return */ public ComponentName loadPluginService(String plugin, String target, int process) { String container = null; PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.SERVICE_REQUEST); try { // 容器选择 IPluginClient client = MP.startPluginProcess(plugin, process, info); if (client == null) { return null; } // 直接分配 container = IPC.getPackageName() + PmBase.CONTAINER_SERVICE_PART + info.index; return new ComponentName(IPC.getPackageName(), container); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "l.p.p spp: " + e.getMessage(), e); } } return null; } /** * 启动插件的Provider * @param plugin 插件名 * @param target 目标Provider名,如果传null,则取获取到的第一个 * @param process 是否在指定进程中启动 * @return * @deprecated 已废弃该方法,请使用PluginProviderClient里面的方法 */ public Uri loadPluginProvider(String plugin, String target, int process) { PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.PROVIDER_REQUEST); try { // 容器选择 IPluginClient client = MP.startPluginProcess(plugin, process, info); if (client == null) { return null; } String auth = IPC.getPackageName() + CONTAINER_PROVIDER_AUTHORITY_PART + info.index; // 直接分配 return new Uri.Builder().scheme("content").encodedAuthority(auth).encodedPath("main").build(); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "l.p.p spp: " + e.getMessage(), e); } } return null; } /** * 通过ClassLoader来获取插件名 * * @param cl ClassLoader对象 * @return 插件名,若和主程序一致,则返回IModule.PLUGIN_NAME_MAIN(“main”) * Added by Jiongxuan Zhang */ public String fetchPluginName(ClassLoader cl) { if (cl == mContext.getClassLoader()) { // Main工程的ClassLoader return RePlugin.PLUGIN_NAME_MAIN; } Plugin p = mPluginMgr.lookupPlugin(cl); if (p == null) { // 没有拿到插件的 return null; } return p.mInfo.getName(); } /** * 根据条件,查找 ActivityInfo 对象 * * @param plugin 插件名称 * @param activity Activity 名称 * @param intent 调用者传递过来的 Intent * @return 插件中 Activity 的 ActivityInfo */ public ActivityInfo getActivityInfo(String plugin, String activity, Intent intent) { // 获取插件对象 Plugin p = mPluginMgr.loadAppPlugin(plugin); if (p == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: bindActivity: may be invalid plugin name or load plugin failed: plugin=" + p); } return null; } ActivityInfo ai = null; // activity 不为空时,从插件声明的 Activity 集合中查找 if (!TextUtils.isEmpty(activity)) { ai = p.mLoader.mComponents.getActivity(activity); } else { // activity 为空时,根据 Intent 匹配 ai = IntentMatcherHelper.getActivityInfo(mContext, plugin, intent); } return ai; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginContainers.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.text.TextUtils; import com.qihoo360.i.IPluginManager; import com.qihoo360.mobilesafe.api.Pref; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.process.PluginProcessHost; import com.qihoo360.replugin.helper.HostConfigHelper; import com.qihoo360.replugin.helper.JSONHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import org.json.JSONArray; import org.json.JSONObject; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP; import static com.qihoo360.loader2.PluginContainers.ActivityState.toName; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 插件容器管理 * Plugin Activity Container Manager (PACM) * * @author RePlugin Team */ public class PluginContainers { /* 1 请求者:请求分配坑 2 代理模块:依次串联执行如下的 3,4,5 步骤,并返回坑给请求者 3 核心进程:启动宿主进程,返回宿主进程Binder 4 宿主进程:启动时从核心进程加载登记表(坑和目标activity表),减少forward activity和恢复activity时的二次触发 5 宿主进程:查找合适的坑并返回(登记) 6 请求者:发起start Activity请求给系统 7 系统:调度宿主进程 8 宿主进程:查找登记表,加载类 9 宿主进程:触发on Create,校验【Intent的目标和当前activity对象】是否一致,如果一致则确认登记,否则忽略 a 流程完成 登记: 登记用于明确的请求意图 并记录到当前宿主进程的登记表,同时让核心进程也记录(用于宿主进程挂掉时恢复用) 例外情况: 1 坑耗尽,强制分配 2 核心进程死亡,信息丢失 3 宿主进程意外死亡后恢复,系统重新发起Activity创建(恢复) 例外环节处理: 5 分配出不稳定的坑 原因:坑耗尽,复用;由于核心进程和宿主进程挂掉由系统恢复导致的无记录,新分配或复用; 处理:区分开坑不够还是宿主进程或核心进程意外重启导致的信息丢失;可以先不考虑复用的情况; 8 查找坑失败,登记表没有记录 原因:宿主进程和核心进程意外重启,由系统恢复Activity,因此此时无记录 处理:启动forward activity,该activity在on create时获取真正的目标后,立即登记,再次启动Intent的目标Activity 9 检查不合格,Intent的目标和当前activity对象不一致 原因:宿主进程或核心进程意外重启;或者坑跑飞? 处理: 1 如果登记表中找到分配记录,则说明这是一个明确的请求:重新启动分配的Activity 2 否则,表明是系统恢复Activity动作:立即登记,重新启动Intent的目标Activity 重启目标时: 增加计数器,当超过一定次数时停止 */ private static final String CONTAINER_ACTIVITY_PART = ".loader.a.Activity"; /** * */ private final Object mLock = new Object(); /** * 所有坑的状态集合 */ private HashMap mStates = new HashMap<>(); /** * 非默认 TaskAffinity 下,坑位的状态信息。 */ private TaskAffinityStates mTaskAffinityStates = new TaskAffinityStates(); /** * 默认 TaskAffinity 下,坑位的状态信息。 */ private LaunchModeStates mLaunchModeStates = new LaunchModeStates(); /** * 保存进程和进程中坑位状态的 Map */ private final Map mProcessStatesMap = new HashMap<>(); private static final int STATE_NONE = 0; private static final int STATE_OCCUPIED = 1; private static final int STATE_RESTORED = 2; static final class ActivityState { final String container; int state; String plugin; String activity; long timestamp; final ArrayList> refs; ActivityState(String container) { this.container = container; this.refs = new ArrayList>(); } public ActivityState(ActivityState state) { this.container = state.container; this.state = state.state; this.plugin = state.plugin; this.activity = state.activity; this.timestamp = state.timestamp; this.refs = new ArrayList>(state.refs); } @Override public String toString() { if (LogDebug.LOG) { String s = " state=" + toName(this.state); String p = " plugin=" + this.plugin; String a = " activity=" + this.activity; String sz = " size=" + refs.size(); return "ActivityState {container=" + this.container + s + p + a + sz + "}"; } return super.toString(); } static final String toName(int state) { switch (state) { case STATE_NONE: return "none"; case STATE_OCCUPIED: return "occupied"; case STATE_RESTORED: return "restored"; } return "unknown"; } private final boolean isTarget(String plugin, String activity) { if (TextUtils.equals(this.plugin, plugin) && TextUtils.equals(this.activity, activity)) { return true; } return false; } private final void occupy(String plugin, String activity) { if (TextUtils.isEmpty(plugin) || TextUtils.isEmpty(activity)) { if (LOG) { LogDebug.w(PLUGIN_TAG, "PACM: occupy: invalid s=" + toName(this.state) + " plugin=" + plugin + " activity=" + activity); } return; } this.state = STATE_OCCUPIED; this.plugin = plugin; this.activity = activity; cleanRefs(); this.timestamp = System.currentTimeMillis(); // save2Pref(this.plugin, this.activity, this.container); } private final void restore(String plugin, String activity, long timestamp) { if (TextUtils.isEmpty(plugin) || TextUtils.isEmpty(activity)) { if (LOG) { LogDebug.w(PLUGIN_TAG, "PACM: restore: invalid s=" + toName(this.state) + " plugin=" + plugin + " activity=" + activity); } return; } this.state = STATE_RESTORED; this.plugin = plugin; this.activity = activity; cleanRefs(); this.timestamp = timestamp; } private final void recycle() { this.state = STATE_NONE; this.plugin = null; this.activity = null; cleanRefs(); this.timestamp = System.currentTimeMillis(); } private final void create(String plugin, Activity activity) { if (this.state == STATE_OCCUPIED || this.state == STATE_RESTORED) { // 当处于restored状态时,表明是系统恢复activity(没有经过register,无显式start activity,核心进程有记录) if (!TextUtils.equals(this.plugin, plugin)) { if (LOG) { LogDebug.w(PLUGIN_TAG, "PACM: create: invalid plugin=" + plugin + " this.plugin=" + this.plugin); } return; } if (!TextUtils.equals(this.activity, activity.getClass().getName())) { if (LOG) { LogDebug.w(PLUGIN_TAG, "PACM: create: invalid a=" + activity.getClass().getName() + " this.a=" + this.activity); } return; } if (this.state == STATE_RESTORED) { if (LOG) { LogDebug.i(PLUGIN_TAG, "PACM: create: relaunch activity: history: container=" + container + " plugin=" + plugin + " activity=" + activity); } } } else if (this.state == STATE_NONE) { // 当处于none状态时,表明是系统恢复activity(没有经过register,无显式start activity,核心进程数据丢失) if (LOG) { LogDebug.i(PLUGIN_TAG, "PACM: create: relaunch activity: blank"); } return; } else { // Never: 当前已经在created状态?一坑多实例? if (LOG) { LogDebug.w(PLUGIN_TAG, "PACM: create: invalid s=" + toName(this.state) + " e=registered c=" + this.container); } return; } addRef(activity); this.timestamp = System.currentTimeMillis(); } private final boolean hasRef() { for (int i = refs.size() - 1; i >= 0; i--) { WeakReference ref = refs.get(i); if (ref.get() == null) { refs.remove(i); } } return refs.size() > 0; } private final void cleanRefs() { if (LOG) { for (WeakReference ref : refs) { if (ref.get() != null) { LogDebug.w(PLUGIN_TAG, "PACM: clean refs: exist a=" + ref.get()); } } } refs.clear(); } private final void addRef(Activity activity) { for (WeakReference ref : refs) { if (ref.get() == activity) { return; } } refs.add(new WeakReference(activity)); } private final void removeRef(Activity activity) { for (int i = refs.size() - 1; i >= 0; i--) { WeakReference ref = refs.get(i); if (ref.get() == activity) { refs.remove(i); break; } } } private final void finishRefs() { for (WeakReference ref : refs) { Activity a = ref.get(); if (a != null) { a.finish(); } } } final void forwardSelf(Activity activity1, Intent intent) { try { // 补齐参数:附上额外数据,进行校验 PluginIntent ii = new PluginIntent(intent); ii.setPlugin(plugin); ii.setActivity(activity); ii.setProcess(IPluginManager.PROCESS_AUTO); ii.setContainer(container); // 直接启动,避免再次进入插件框架 intent.putExtra(IPluginManager.KEY_COMPATIBLE, true); // 设定组件 intent.setComponent(new ComponentName(IPC.getPackageName(), container)); // activity1.startActivity(intent); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a fs: " + e.getMessage(), e); } } } } final void init(int process, HashSet containers) { if (process != IPluginManager.PROCESS_UI && !PluginProcessHost.isCustomPluginProcess(process) && !PluginManager.isPluginProcess()) { return; } String prefix = IPC.getPackageName() + CONTAINER_ACTIVITY_PART; // 因为自定义进程可能也会唤起使用 UI 进程的坑,所以这里使用'或'条件 if (process == IPluginManager.PROCESS_UI || PluginProcessHost.isCustomPluginProcess(process)) { /* UI 进程标识为 N1 */ String suffix = "N1"; // Standard mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD); mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD); // SingleTop mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP); mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP); // SingleTask mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK); mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK); // SingleInstance mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE); mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE); // Standard 横屏 mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD_LAND); mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD_LAND); // SingleTop 横屏 mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP_LAND); mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP_LAND); // SingleTask 横屏 mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK_LAND); mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK_LAND); // SingleInstance 横屏 mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE_LAND); mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE_LAND); // taskAffinity mTaskAffinityStates.init(prefix, suffix, mStates, containers); // 因为有可能会在 UI 进程启动自定义进程的 Activity,所以此处也要初始化自定义进程的坑位数据 for (int i = 0; i < PluginProcessHost.PROCESS_COUNT; i++) { ProcessStates processStates = new ProcessStates(); // [":p1": state("P1"), ":p2": state("P2")] mProcessStatesMap.put(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2 + i, processStates); init2(prefix, containers, processStates, PluginProcessHost.PROCESS_PLUGIN_SUFFIX + i); } // 从内存中加载 loadFromPref(); } } /** * 初始化自定义进程坑坑位 * * @param prefix xxx.xx.loader.a.Activity * @param containers 保存所有 Activity 坑名称 * @param states 当前进程所有坑位的状态 * @param suffix p0, p1, p2 */ private void init2(String prefix, HashSet containers, ProcessStates states, String suffix) { suffix = suffix.toUpperCase(); // Standard states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD); states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD); // SingleTop states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP); states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP); // SingleTask states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK); states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK); // SingleInstance states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE); states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE); // Standard 横屏 states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD_LAND); states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD_LAND); // SingleTop 横屏 states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP_LAND); states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP_LAND); // SingleTask 横屏 states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK_LAND); states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK_LAND); // SingleInstance 横屏 states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE_LAND); states.mLaunchModeStates.addLandStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE_LAND); // taskAffinity states.mTaskAffinityStates.init(prefix, suffix, mStates, containers); } private final void loadFromPref() { try { Map a = Pref.ipcGetAll(); if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: restore table: size=" + a.size()); } for (Entry i : a.entrySet()) { String k = i.getKey(); Object v = i.getValue(); ActivityState state = mStates.get(k); String item[] = v.toString().split(":"); if (state != null && item != null && item.length == 3) { String plugin = item[0]; String activity = item[1]; long timestamp = Long.parseLong(item[2]); if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: restore table: " + " container=" + k + " plugin=" + plugin + " activity=" + activity); } if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) { state.restore(plugin, activity, timestamp); } } else { if (LOG) { LogDebug.w(PLUGIN_TAG, "PACM: invalid table: k=" + k + " v=" + v); } } } } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "PACM: init e=" + e.getMessage(), e); } } } private static final void save2Pref(String plugin, String activity, String container) { String v = plugin + ":" + activity + ":" + System.currentTimeMillis(); if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: save 2 pref: k=" + container + " v=" + v); } Pref.ipcSet(container, v); } static final String[] resolvePluginActivity(String container) { String v = Pref.ipcGet(container, ""); //String v = plugin + ":" + activity + ":" + System.currentTimeMillis(); if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: load special pref: k=" + container + " v=" + v); } // if (TextUtils.isEmpty(v)) { return null; } return v.split(":"); } final void forwardIntent(Activity activity, Intent intent, String original, String container, String plugin, String target, int process) { // 找到容器 ActivityState so = null; ActivityState state = null; synchronized (mLock) { HashMap map = mStates; so = map.get(original); state = map.get(container); } if (so == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a fi: cc: inv c.c=" + original); } return; } if (state == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a fi: cc: inv t.c=" + container); } return; } // 检查 if (state.state == STATE_NONE) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a fi: cc: ok, t.c empty, t.c=" + container); } // 重新登记 state.occupy(plugin, target); } else if (!state.isTarget(plugin, target)) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a fi: cc: fly, force, t.c=" + container); } // 如果已经有实例存在了,只能打打日志 if (state.hasRef()) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a fi: cc: exists instances"); } } // 重新登记 state.occupy(plugin, target); } else { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a fi: cc: same, t.c=" + container); } } if (so != state) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a fi: t t.c=" + container); } if (LOG) { LogDebug.i(PLUGIN_TAG, "forward fly: container=" + container + " plugin=" + plugin + " activity=" + target); } so.recycle(); } else { if (LOGR) { LogRelease.i(PLUGIN_TAG, "f.a fi: same t.c=" + container); } if (LOG) { LogDebug.i(PLUGIN_TAG, "forward registered: container=" + container + " plugin=" + plugin + " activity=" + target); } } // 启动目标activity state.forwardSelf(activity, intent); } /** * 登记坑和activity的映射 * 当前进程:宿主进程 * * @param ai * @param plugin * @param activity * @param process * @param intent * @return */ final String alloc(ActivityInfo ai, String plugin, String activity, int process, Intent intent) { ActivityState state; String defaultPluginTaskAffinity = ai.applicationInfo.packageName; if (LOG) { LogDebug.d(TaskAffinityStates.TAG, "originTaskAffinity is " + ai.taskAffinity); } /* SingleInstance 优先级最高 */ if (ai.launchMode == LAUNCH_SINGLE_INSTANCE) { synchronized (mLock) { state = allocLocked(ai, mLaunchModeStates.getStates(ai.screenOrientation, ai.launchMode, ai.theme), plugin, activity, intent); } /* TaskAffinity */ } else if (!defaultPluginTaskAffinity.equals(ai.taskAffinity)) { // 非默认 taskAffinity synchronized (mLock) { state = allocLocked(ai, mTaskAffinityStates.getStates(ai), plugin, activity, intent); } /* SingleTask, SingleTop, Standard */ } else { synchronized (mLock) { state = allocLocked(ai, mLaunchModeStates.getStates(ai.screenOrientation, ai.launchMode, ai.theme), plugin, activity, intent); } } if (state != null) { return state.container; } return null; } /** * @param ai * @param map * @param plugin * @param activity * @param intent * @return */ private final ActivityState allocLocked(ActivityInfo ai, HashMap map, String plugin, String activity, Intent intent) { // 坑和状态的 map 为空 if (map == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: alloc fail, map is null"); } return null; } // 首先找上一个活的,或者已经注册的,避免多个坑到同一个activity的映射 for (ActivityState state : map.values()) { if (state.isTarget(plugin, activity)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: alloc registered container=" + state.container); } return state; } } // 新分配:找空白的,第一个 for (ActivityState state : map.values()) { if (state.state == STATE_NONE) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: alloc empty container=" + state.container); } state.occupy(plugin, activity); return state; } } ActivityState found; // 重用:则找最老的那个 found = null; for (ActivityState state : map.values()) { if (!state.hasRef()) { if (found == null) { found = state; } else if (state.timestamp < found.timestamp) { found = state; } } } if (found != null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: alloc recycled container=" + found.container); } found.occupy(plugin, activity); return found; } // 强挤:最后一招,挤掉:最老的那个 found = null; for (ActivityState state : map.values()) { if (found == null) { found = state; } else if (state.timestamp < found.timestamp) { found = state; } } if (found != null) { if (LOG) { LogDebug.w(PLUGIN_TAG, "PACM: force alloc container=" + found.container); } found.finishRefs(); found.occupy(plugin, activity); return found; } if (LOG) { LogDebug.w(PLUGIN_TAG, "PACM: alloc failed: plugin=" + plugin + " activity=" + activity); } // never reach here return null; } String alloc2(ActivityInfo ai, String plugin, String activity, int process, Intent intent, String processTail) { // 根据进程名称,取得该进程对应的 PluginContainerStates ProcessStates states = mProcessStatesMap.get(processTail); ActivityState state; String defaultPluginTaskAffinity = ai.applicationInfo.packageName; if (LOG) { LogDebug.d(TaskAffinityStates.TAG, String.format("插件 %s 默认 TaskAffinity 为 %s", plugin, defaultPluginTaskAffinity)); LogDebug.d(TaskAffinityStates.TAG, String.format("%s 的 TaskAffinity 为 %s", activity, ai.taskAffinity)); } /* SingleInstance */ if (ai.launchMode == LAUNCH_SINGLE_INSTANCE) { synchronized (mLock) { state = allocLocked(ai, states.mLaunchModeStates.getStates(ai.screenOrientation, ai.launchMode, ai.theme), plugin, activity, intent); } /* TaskAffinity */ } else if (!defaultPluginTaskAffinity.equals(ai.taskAffinity)) { // 非默认 taskAffinity synchronized (mLock) { state = allocLocked(ai, states.mTaskAffinityStates.getStates(ai), plugin, activity, intent); } /* other mode */ } else { synchronized (mLock) { state = allocLocked(ai, states.mLaunchModeStates.getStates(ai.screenOrientation, ai.launchMode, ai.theme), plugin, activity, intent); } } if (state != null) { return state.container; } return null; } final void handleCreate(String plugin, Activity activity, String container) { ComponentName cn = activity.getComponentName(); if (cn != null) { container = cn.getClassName(); } if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: activity created h=" + activity.hashCode() + " class=" + activity.getClass().getName() + " container=" + container); } synchronized (mLock) { HashMap map = mStates; ActivityState state = map.get(container); if (state != null) { state.create(plugin, activity); } } } final void handleDestroy(Activity activity) { String container = null; // ComponentName cn = activity.getComponentName(); if (cn != null) { container = cn.getClassName(); } if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: activity destroy h=" + activity.hashCode() + " class=" + activity.getClass().getName() + " container=" + container); } if (container == null) { return; } synchronized (mLock) { HashMap map = mStates; ActivityState state = map.get(container); if (state != null) { state.removeRef(activity); } } } /** * 容器对应的类 * * @param container * @return 注意,对于返回值:可能是登记的,可能是activity退出的残留,也可能是进程恢复后上次的记录 */ final ActivityState lookupByContainer(String container) { if (container == null) { return null; } synchronized (mLock) { HashMap map = mStates; ActivityState state = map.get(container); if (state != null && state.state != STATE_NONE) { if (LOG) { LogDebug.d(PLUGIN_TAG, "found: " + state); } return new ActivityState(state); } } // PC: lookupByContainer if (LOGR) { long s = mStates.size(); LogRelease.e(PLUGIN_TAG, "not found:" + " c=" + container + " pool=" + s); } return null; } final String dump() { JSONArray activityArr = new JSONArray(); JSONObject activityObj; for (Map.Entry entry : mStates.entrySet()) { String container = entry.getKey(); ActivityState state = entry.getValue(); if (!TextUtils.isEmpty(state.plugin) && !TextUtils.isEmpty(state.activity)) { activityObj = new JSONObject(); JSONHelper.putNoThrows(activityObj, "process", IPC.getCurrentProcessName()); JSONHelper.putNoThrows(activityObj, "className", container); JSONHelper.putNoThrows(activityObj, "plugin", state.plugin); JSONHelper.putNoThrows(activityObj, "realClassName", state.activity); JSONHelper.putNoThrows(activityObj, "state", toName(state.state)); JSONHelper.putNoThrows(activityObj, "refs", state.refs != null ? state.refs.size() : 0); activityArr.put(activityObj); } } return activityArr.toString(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginContext.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.res.AssetManager; import android.content.res.Resources; import android.os.Bundle; import android.util.AttributeSet; import android.view.ContextThemeWrapper; import android.view.InflateException; import android.view.LayoutInflater; import android.view.View; import android.view.ViewStub; import com.qihoo360.i.Factory2; import com.qihoo360.loader.utils2.FilePermissionUtils; import com.qihoo360.replugin.ContextInjector; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.component.service.PluginServiceClient; import com.qihoo360.replugin.component.utils.PluginClientHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.lang.reflect.Constructor; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team */ public class PluginContext extends ContextThemeWrapper { private final ClassLoader mNewClassLoader; private final Resources mNewResources; private final String mPlugin; private final Loader mLoader; private final Object mSync = new Object(); private File mFilesDir; private File mCacheDir; private File mDatabasesDir; private LayoutInflater mInflater; private ContextInjector mContextInjector; LayoutInflater.Factory mFactory = new LayoutInflater.Factory() { @Override public View onCreateView(String name, Context context, AttributeSet attrs) { return handleCreateView(name, context, attrs); } }; public PluginContext(Context base, int themeres, ClassLoader cl, Resources r, String plugin, Loader loader) { super(base, themeres); mNewClassLoader = cl; mNewResources = r; mPlugin = plugin; mLoader = loader; mContextInjector = RePlugin.getConfig().getCallbacks().createContextInjector(); } @Override public ClassLoader getClassLoader() { if (mNewClassLoader != null) { return mNewClassLoader; } return super.getClassLoader(); } @Override public Resources getResources() { if (mNewResources != null) { return mNewResources; } return super.getResources(); } @Override public AssetManager getAssets() { if (mNewResources != null) { return mNewResources.getAssets(); } return super.getAssets(); } @Override public Object getSystemService(String name) { if (LAYOUT_INFLATER_SERVICE.equals(name)) { if (mInflater == null) { LayoutInflater inflater = (LayoutInflater) super.getSystemService(name); // 新建一个,设置其工厂 mInflater = inflater.cloneInContext(this); mInflater.setFactory(mFactory); // 再新建一个,后续可再次设置工厂 mInflater = mInflater.cloneInContext(this); } return mInflater; } return super.getSystemService(name); } @Override public SharedPreferences getSharedPreferences(String name, int mode) { name = "plugin_" + name; return super.getSharedPreferences(name, mode); } @Override public FileInputStream openFileInput(String name) throws FileNotFoundException { File f = makeFilename(getFilesDir(), name); return new FileInputStream(f); } @Override public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { final boolean append = (mode & MODE_APPEND) != 0; File f = makeFilename(getFilesDir(), name); try { FileOutputStream fos = new FileOutputStream(f, append); setFilePermissionsFromMode(f.getPath(), mode, 0); return fos; } catch (FileNotFoundException e) { // } File parent = f.getParentFile(); parent.mkdir(); FilePermissionUtils.setPermissions(parent.getPath(), FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG, -1, -1); FileOutputStream fos = new FileOutputStream(f, append); setFilePermissionsFromMode(f.getPath(), mode, 0); return fos; } @Override public boolean deleteFile(String name) { File f = makeFilename(getFilesDir(), name); return f.delete(); } @Override public File getFilesDir() { synchronized (mSync) { if (mFilesDir == null) { mFilesDir = new File(getDataDirFile(), "files"); } if (!mFilesDir.exists()) { if (!mFilesDir.mkdirs()) { if (mFilesDir.exists()) { // spurious failure; probably racing with another process for this app return mFilesDir; } if (LOGR) { LogRelease.e(PLUGIN_TAG, "Unable to create files directory " + mFilesDir.getPath()); } return null; } FilePermissionUtils.setPermissions(mFilesDir.getPath(), FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH, -1, -1); } return mFilesDir; } } @Override public File getCacheDir() { synchronized (mSync) { if (mCacheDir == null) { mCacheDir = new File(getDataDirFile(), "cache"); } if (!mCacheDir.exists()) { if (!mCacheDir.mkdirs()) { if (mCacheDir.exists()) { // spurious failure; probably racing with another process for this app return mCacheDir; } if (LOGR) { LogRelease.e(PLUGIN_TAG, "Unable to create cache directory " + mCacheDir.getAbsolutePath()); } return null; } FilePermissionUtils.setPermissions(mCacheDir.getPath(), FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH, -1, -1); } } return mCacheDir; } /* 为了适配 Android 8.1 及后续版本,该方法不再重写,因此,需要各插件之间约定,防止出现重名数据库。 by cundong @Override public File getDatabasePath(String name) { return validateFilePath(name, false); } */ @Override public File getFileStreamPath(String name) { return makeFilename(getFilesDir(), name); } @Override public File getDir(String name, int mode) { name = "app_" + name; File file = makeFilename(getDataDirFile(), name); if (!file.exists()) { file.mkdir(); setFilePermissionsFromMode(file.getPath(), mode, FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH); } return file; } private File getDatabasesDir() { synchronized (mSync) { if (mDatabasesDir == null) { mDatabasesDir = new File(getDataDirFile(), "databases"); } if (mDatabasesDir.getPath().equals("databases")) { mDatabasesDir = new File("/data/system"); } return mDatabasesDir; } } private File validateFilePath(String name, boolean createDirectory) { File dir; File f; if (name.charAt(0) == File.separatorChar) { String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar)); dir = new File(dirPath); name = name.substring(name.lastIndexOf(File.separatorChar)); f = new File(dir, name); } else { dir = getDatabasesDir(); f = makeFilename(dir, name); } if (createDirectory && !dir.isDirectory() && dir.mkdir()) { FilePermissionUtils.setPermissions(dir.getPath(), FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH, -1, -1); } return f; } private final File makeFilename(File base, String name) { if (name.indexOf(File.separatorChar) < 0) { return new File(base, name); } throw new IllegalArgumentException("File " + name + " contains a path separator"); } /** * 设置文件的访问权限 * * @param name 需要被设置访问权限的文件 * @param mode 文件操作模式 * @param extraPermissions 文件访问权限 *

* 注意:

* 此部分经由360安全部门审核后,在所有者|同组用户|其他用户三部分的权限设置中,认为在其他用户的权限设置存在一定的安全风险

* 目前暂且忽略传入的文件操作模式参数,并移除了允许其他用户的读写权限的操作

* 对于文件操作模式以及其他用户访问权限的设置,开发者可自行评估

* @return */ private final void setFilePermissionsFromMode(String name, int mode, int extraPermissions) { int perms = FilePermissionUtils.S_IRUSR | FilePermissionUtils.S_IWUSR | FilePermissionUtils.S_IRGRP | FilePermissionUtils.S_IWGRP | extraPermissions; // if ((mode & MODE_WORLD_READABLE) != 0) { // perms |= FilePermissionUtils.S_IROTH; // } // if ((mode & MODE_WORLD_WRITEABLE) != 0) { // perms |= FilePermissionUtils.S_IWOTH; // } if (LOG) { LogDebug.d(PLUGIN_TAG, "File " + name + ": mode=0x" + Integer.toHexString(mode) + ", perms=0x" + Integer.toHexString(perms)); } FilePermissionUtils.setPermissions(name, perms, -1, -1); } /** * @return */ private final File getDataDirFile() { // 原本用 getDir(Constant.LOCAL_PLUGIN_DATA_SUB_DIR) // 由于有些模块的数据写死在files目录下,这里不得已改为getFilesDir + Constant.LOCAL_PLUGIN_DATA_SUB_DIR // File dir = getApplicationContext().getDir(Constant.LOCAL_PLUGIN_DATA_SUB_DIR, 0); // files // huchangqing getApplicationContext()会返回null File dir0 = getBaseContext().getFilesDir(); // v3 data File dir = new File(dir0, Constant.LOCAL_PLUGIN_DATA_SUB_DIR); if (!dir.exists()) { if (!dir.mkdir()) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "can't create dir: " + dir.getAbsolutePath()); } return null; } setFilePermissionsFromMode(dir.getPath(), 0, FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH); } // 插件名 File file = makeFilename(dir, mPlugin); if (!file.exists()) { if (!file.mkdir()) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "can't create dir: " + file.getAbsolutePath()); } return null; } setFilePermissionsFromMode(file.getPath(), 0, FilePermissionUtils.S_IRWXU | FilePermissionUtils.S_IRWXG | FilePermissionUtils.S_IXOTH); } return file; } private final View handleCreateView(String name, Context context, AttributeSet attrs) { // 忽略表命中,返回 if (mLoader.mIgnores.contains(name)) { // 只有开启“详细日志”才会输出,防止“刷屏”现象 if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.d(PLUGIN_TAG, "layout.cache: ignore plugin=" + mPlugin + " name=" + name); } return null; } // 构造器缓存 Constructor construct = mLoader.mConstructors.get(name); // 缓存失败 if (construct == null) { // 找类 Class c = null; boolean found = false; do { try { c = mNewClassLoader.loadClass(name); if (c == null) { // 没找到,不管 break; } if (c == ViewStub.class) { // 系统特殊类,不管 break; } if (c.getClassLoader() != mNewClassLoader) { // 不是插件类,不管 break; } // 找到 found = true; } catch (ClassNotFoundException e) { // 失败,不管 break; } } while (false); if (!found) { // 只有开启“详细日志”才会输出,防止“刷屏”现象 if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.d(PLUGIN_TAG, "layout.cache: new ignore plugin=" + mPlugin + " name=" + name); } mLoader.mIgnores.add(name); return null; } // 找构造器 try { construct = c.getConstructor(Context.class, AttributeSet.class); if (LOG) { LogDebug.d(PLUGIN_TAG, "layout.cache: new constructor. plugin=" + mPlugin + " name=" + name); } mLoader.mConstructors.put(name, construct); } catch (Exception e) { InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating mobilesafe class " + name, e); throw ie; } } // 构造 try { View v = (View) construct.newInstance(context, attrs); // 只有开启“详细日志”才会输出,防止“刷屏”现象 if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.d(PLUGIN_TAG, "layout.cache: create view ok. plugin=" + mPlugin + " name=" + name); } return v; } catch (Exception e) { InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating mobilesafe class " + name, e); throw ie; } } @Override public String getPackageName() { // NOTE 请不要修改此方法,因为有太多的地方用到了PackageName // 为兼容性考虑,请直接返回卫士自身的包名 return super.getPackageName(); } // -------------- // WARNING 注意! // -------------- // 以下所有方法均需框架版本(Framework Ver,见说明书)>=3时才有效(有的需要更高版本) // Added by Jiongxuan Zhang @Override public Context getApplicationContext() { if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) { // 仅框架版本为3及以上的,才支持直接获取PluginContext,而非卫士ApplicationContext return super.getApplicationContext(); } // 直接获取插件的Application对象 // NOTE 切勿获取mLoader.mPkgContext,因为里面的一些方法会调用getApplicationContext(如registerComponentCallback) // NOTE 这样会造成StackOverflow异常。所以只能获取Application对象(框架版本为3以上的会创建此对象) //entry中调用context.getApplicationContext时mApplicationClient还没被赋值,会导致空指针造成插件安装失败 if (mLoader.mPluginObj.mApplicationClient == null) { return this; } else { return mLoader.mPluginObj.mApplicationClient.getObj(); } } @Override public void startActivity(Intent intent) { // HINT 只有插件Application才会走这里 // 而Activity.startActivity系统最终会走startActivityForResult,不会走这儿 // 这里会被调用两次: // 第一次:获取各种信息,最终确认坑位,并走startActivity,再次回到这里 // 第二次:判断要打开的是“坑位Activity”,则返回False,直接走super,后面的事情你们都懂的 // 当然,如果在获取坑位信息时遇到任何情况(例如要打开的是宿主的Activity),则直接返回false,走super if (!Factory2.startActivity(this, intent)) { if (mContextInjector != null) { mContextInjector.startActivityBefore(intent); } super.startActivity(intent); if (mContextInjector != null) { mContextInjector.startActivityAfter(intent); } } } @Override public void startActivity(Intent intent, Bundle options) { // HINT 保险起见,startActivity写两套相似逻辑 // 具体见startActivity(intent)的描述(上面) if (!Factory2.startActivity(this, intent)) { if (mContextInjector != null) { mContextInjector.startActivityBefore(intent, options); } super.startActivity(intent, options); if (mContextInjector != null) { mContextInjector.startActivityAfter(intent, options); } } } @Override public ComponentName startService(Intent service) { if (mContextInjector != null) { mContextInjector.startServiceBefore(service); } if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) { // 仅框架版本为3及以上的才支持 return super.startService(service); } try { return PluginServiceClient.startService(this, service, true); } catch (PluginClientHelper.ShouldCallSystem e) { // 若打开插件出错,则直接走系统逻辑 return super.startService(service); } finally { if (mContextInjector != null) { mContextInjector.startServiceAfter(service); } } } @Override public boolean stopService(Intent name) { if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) { // 仅框架版本为3及以上的才支持 return super.stopService(name); } try { return PluginServiceClient.stopService(this, name, true); } catch (PluginClientHelper.ShouldCallSystem e) { // 若打开插件出错,则直接走系统逻辑 return super.stopService(name); } } @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) { // 仅框架版本为3及以上的才支持 return super.bindService(service, conn, flags); } try { return PluginServiceClient.bindService(this, service, conn, flags, true); } catch (PluginClientHelper.ShouldCallSystem e) { // 若打开插件出错,则直接走系统逻辑 return super.bindService(service, conn, flags); } } @Override public void unbindService(ServiceConnection conn) { if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) { // 仅框架版本为3及以上的才支持 super.unbindService(conn); return; } // 先走一遍系统的逻辑 try { super.unbindService(conn); } catch (Throwable e) { // Ignore } // 再走插件的unbindService // NOTE 由于不应重新调用context.unbind命令,故传进去的是false PluginServiceClient.unbindService(this, conn, false); } @Override public String getPackageCodePath() { if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) { // 仅框架版本为3及以上的才支持 return super.getPackageCodePath(); } // 获取插件Apk的路径 return mLoader.mPath; } @Override public ApplicationInfo getApplicationInfo() { if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) { // 仅框架版本为3及以上的才支持 return super.getApplicationInfo(); } return mLoader.mComponents.getApplication(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginDesc.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.text.TextUtils; import android.util.Log; import com.qihoo360.loader.utils.LocalBroadcastManager; import com.qihoo360.replugin.utils.Charsets; import com.qihoo360.replugin.utils.CloseableUtils; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.utils.IOUtils; import org.json.JSONArray; import org.json.JSONObject; import java.io.InputStream; import java.util.HashMap; /** * 获取插件介绍有关的信息,包括: * 插件显示名、简介、是否为大插件等。此文件可被云控 * * 注意:此类有别于PluginInfo,前者主要记录版本、路径等 * * @author RePlugin Team */ public class PluginDesc { private static final String TAG = PluginDesc.class.getSimpleName(); private static final boolean DEBUG = BuildConfig.DEBUG; private static final byte[] INSTANCE_LOCKER = new byte[0]; private static volatile BroadcastReceiver sUpdateReceiver; private static final byte[] REG_RECEIVER_LOCKER = new byte[0]; private static volatile boolean sChanged; public static final String ACTION_UPDATE = "com.qihoo360.mobilesafe.plugin_desc_update"; private String mDisplay; private String mPlugin; private String mDesc; private boolean mLarge; private static volatile HashMap sMap; /** * 通过插件名来获取PluginDesc对象 */ public static PluginDesc get(String pn) { return getCurrentMap().get(pn); } private static HashMap getCurrentMap() { registerReceiverIfNeeded(); if (sMap != null && !sChanged) { return sMap; } synchronized (INSTANCE_LOCKER) { if (sMap != null && !sChanged) { return sMap; } if (DEBUG) { Log.d(TAG, "load(): Change, Ready to load"); } sMap = new HashMap<>(); load(PMF.getApplicationContext()); sChanged = false; } return sMap; } public PluginDesc(String plugin) { mPlugin = plugin; } /** * 获取插件名 */ public String getPluginName() { return mPlugin; } /** * 下载时显示插件的名字 * @return 如果display为空,则返回插件名 */ public String getDisplayName() { if (!TextUtils.isEmpty(mDisplay)) { return mDisplay; } return mPlugin; } /** * 下载时的具体描述信息 */ public String getDescription() { return mDesc; } /** * 是否为“大插件”,也即首次加载其Activity前,需要弹窗让用户等待 * 绝大多数插件都比较小,但如“手心”、“通讯录”等可能需要特殊对待 */ public boolean isLarge() { return mLarge; } private static boolean load(Context context) { JSONArray jsonArray = loadArray(context); if (jsonArray == null) { return false; } for (int i = 0; i < jsonArray.length(); i++) { JSONObject jo = jsonArray.optJSONObject(i); if (jo == null) { continue; } String pn = jo.optString("name"); if (TextUtils.isEmpty(pn)) { continue; } PluginDesc pi = new PluginDesc(pn); pi.mDisplay = jo.optString("display"); pi.mDesc = jo.optString("desc"); pi.mLarge = jo.optBoolean("large"); sMap.put(pn, pi); } return true; } private static JSONArray loadArray(Context context) { InputStream in; // 读取内部配置 in = null; try { in = RePlugin.getConfig().getCallbacks().openLatestFile(context, "plugins-list.json"); if (in != null) { String str = IOUtils.toString(in, Charsets.UTF_8); return new JSONArray(str); } } catch (Exception e) { if (DEBUG) { Log.e(TAG, e.getMessage(), e); } } finally { CloseableUtils.closeQuietly(in); } return null; } private static void registerReceiverIfNeeded() { if (sUpdateReceiver != null) { return; } synchronized (REG_RECEIVER_LOCKER) { if (sUpdateReceiver != null) { return; } sUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // 标记已改,可以重新Load了 if (DEBUG) { Log.d(TAG, "Receiver.onReceive(): Mark change!"); } // 重新加载 sChanged = true; getCurrentMap(); } }; IntentFilter filter = new IntentFilter(ACTION_UPDATE); LocalBroadcastManager.getInstance(PMF.getApplicationContext()).registerReceiver(sUpdateReceiver,filter); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginIntent.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.ComponentName; import android.content.Intent; import android.text.TextUtils; import com.qihoo360.i.IPluginManager; import com.qihoo360.replugin.helper.LogRelease; import java.util.Set; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team */ class PluginIntent { public static final String EXTRA_PLUGIN = "plugin:"; public static final String EXTRA_ACTIVITY = "activity:"; public static final String EXTRA_PROCESS = "process:"; public static final String EXTRA_CONTAINER = "container:"; public static final String EXTRA_COUNTER = "counter:"; private final Intent mIntent; PluginIntent(Intent intent) { mIntent = intent; } private final void remove(String prefix) { Set categories = mIntent.getCategories(); if (categories != null) { for (String category : categories) { if (category.startsWith(prefix)) { mIntent.removeCategory(category); break; } } } } private final String getS(String prefix) { Set categories = mIntent.getCategories(); if (categories != null) { for (String category : categories) { if (category.startsWith(prefix)) { return category.substring(prefix.length()); } } } return null; } private final void setS(String prefix, String value) { remove(prefix); mIntent.addCategory(prefix + value); } private final int getI(String prefix, int defValue) { Set categories = mIntent.getCategories(); if (categories != null) { String v = ""; for (String category : categories) { if (category.startsWith(prefix)) { v = category.substring(prefix.length()); break; } } if (!TextUtils.isEmpty(v)) { try { int i = Integer.parseInt(v); return i; } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, e.getMessage(), e); } } } } return defValue; } private final void setI(String prefix, int value) { remove(prefix); mIntent.addCategory(prefix + value); } /** * @return */ final String getOriginal() { ComponentName cn = mIntent.getComponent(); if (cn != null) { return cn.getClassName(); } return null; } final String getPlugin() { return getS(EXTRA_PLUGIN); } final void setPlugin(String plugin) { setS(EXTRA_PLUGIN, plugin); } final String getActivity() { return getS(EXTRA_ACTIVITY); } final void setActivity(String activity) { setS(EXTRA_ACTIVITY, activity); } final int getProcess() { return getI(EXTRA_PROCESS, IPluginManager.PROCESS_AUTO); } final void setProcess(int process) { setI(EXTRA_PROCESS, process); } final String getContainer() { return getS(EXTRA_CONTAINER); } final void setContainer(String container) { setS(EXTRA_CONTAINER, container); } final int getCounter() { return getI(EXTRA_COUNTER, 0); } final void setCounter(int counter) { setI(EXTRA_COUNTER, counter); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginLibraryInternalProxy.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.app.Activity; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.text.TextUtils; import com.qihoo360.i.Factory; import com.qihoo360.i.Factory2; import com.qihoo360.i.IPluginManager; import com.qihoo360.replugin.utils.FixOTranslucentOrientation; import com.qihoo360.replugin.utils.ReflectUtils; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.activity.ActivityInjector; import com.qihoo360.replugin.helper.HostConfigHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import org.json.JSONArray; import org.json.JSONObject; import java.io.File; import java.util.List; import java.util.Set; import static com.qihoo360.i.Factory.loadPluginActivity; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * plugin-library中,通过“反射”调用的内部逻辑(如PluginActivity类的调用、Factory2等)均在此处 * * @author RePlugin Team */ public class PluginLibraryInternalProxy { /** * */ PmBase mPluginMgr; PluginLibraryInternalProxy(PmBase pm) { mPluginMgr = pm; } /** * @hide 内部方法,插件框架使用 * 启动一个插件中的activity * 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制 * @param context Context上下文 * @param intent * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑 */ public boolean startActivity(Context context, Intent intent) { if (LOG) { LogDebug.d(PLUGIN_TAG, "start context: intent=" + intent); } // 兼容模式,直接使用标准方式启动 if (intent.getBooleanExtra(IPluginManager.KEY_COMPATIBLE, false)) { PmBase.cleanIntentPluginParams(intent); if (LOG) { LogDebug.d(PLUGIN_TAG, "start context: COMPATIBLE is true, direct start"); } return false; } // 获取Activity的名字,有两种途径: // 1. 从Intent里取。通常是明确知道要打开的插件的Activity时会用 // 2. 从Intent的ComponentName中获取 String name = intent.getStringExtra(IPluginManager.KEY_ACTIVITY); if (TextUtils.isEmpty(name)) { ComponentName cn = intent.getComponent(); if (cn != null) { name = cn.getClassName(); if (LOG) { LogDebug.d(PLUGIN_TAG, "start context: custom context=" + context); } } } // 已经是标准坑了(例如N1ST1这样的),则无需再过“坑位分配”逻辑,直接使用标准方式启动 if (mPluginMgr.isActivity(name)) { PmBase.cleanIntentPluginParams(intent); if (LOG) { LogDebug.d(PLUGIN_TAG, "start context: context is container, direct start"); } return false; } // 获取插件名,有三种途径: // 1. 从Intent里取。通常是明确知道要打开的插件时会用 // 2. 根据当前Activity的坑位名来“反查”其插件名。通常是插件内开启自己的Activity时用到 // 3. 通过获得Context的类加载器来判断其插件名 String plugin = intent.getStringExtra(IPluginManager.KEY_PLUGIN); /* 检查是否是动态注册的类 */ // 如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。 // 原因:宿主的某些动态注册的类不能运行在坑位中(如'桌面'插件的入口Activity) ComponentName componentName = intent.getComponent(); if (componentName != null) { if (LogDebug.LOG) { LogDebug.d("loadClass", "isHookingClass(" + plugin + "," + componentName.getClassName() + ") = " + isDynamicClass(plugin, componentName.getClassName())); } if (isDynamicClass(plugin, componentName.getClassName())) { intent.putExtra(IPluginManager.KEY_COMPATIBLE, true); intent.setComponent(new ComponentName(IPC.getPackageName(), componentName.getClassName())); context.startActivity(intent); return false; } } if (TextUtils.isEmpty(plugin)) { // 看下Context是否为Activity,如是则直接从坑位中获取插件名(最准确) if (context instanceof Activity) { plugin = fetchPluginByPitActivity((Activity) context); } if (LOG) { LogDebug.d(PLUGIN_TAG, "start context: custom plugin is empty, query plugin=" + plugin); } } // 没拿到插件名?再从 ClassLoader 获取插件名称(兜底) if (TextUtils.isEmpty(plugin)) { plugin = RePlugin.fetchPluginNameByClassLoader(context.getClassLoader()); } // 仍然拿不到插件名?(例如从宿主中调用),则打开的Activity可能是宿主的。直接使用标准方式启动 if (TextUtils.isEmpty(plugin)) { PmBase.cleanIntentPluginParams(intent); if (LOG) { LogDebug.d(PLUGIN_TAG, "start context: plugin and context is empty, direct start"); } return false; } // 获取进程值,看目标Activity要打开哪个进程 int process = intent.getIntExtra(IPluginManager.KEY_PROCESS, Integer.MIN_VALUE); PmBase.cleanIntentPluginParams(intent); // 调用“特殊版”的startActivity,不让自动填写ComponentName,防止外界再用时出错 return Factory.startActivityWithNoInjectCN(context, intent, plugin, name, process); } // 通过Activity坑位来获取插件名 private String fetchPluginByPitActivity(Activity a) { PluginContainers.ActivityState state = null; if (a.getComponentName() != null) { state = mPluginMgr.mClient.mACM.lookupByContainer(a.getComponentName().getClassName()); } if (state != null) { return state.plugin; } else { return null; } } // FIXME 建议去掉plugin和activity参数,直接用intent代替 /** * @hide 内部方法,插件框架使用 * 启动一个插件中的activity,如果插件不存在会触发下载界面 * @param context 应用上下文或者Activity上下文 * @param intent * @param plugin 插件名 * @param activity 待启动的activity类名 * @param process 是否在指定进程中启动 * @param download 下载 * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑 */ public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) { if (LOG) { LogDebug.d(PLUGIN_TAG, "start activity: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process + " download=" + download); } // 是否启动下载 // 若插件不可用(不存在或版本不匹配),则直接弹出“下载插件”对话框 // 因为已经打开UpdateActivity,故在这里返回True,告诉外界已经打开,无需处理 if (download) { if (PluginTable.getPluginInfo(plugin) == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin=" + plugin + " not found, start download ..."); } // 如果用户在下载即将完成时突然点按“取消”,则有可能出现插件已下载成功,但没有及时加载进来的情况 // 因此我们会判断这种情况,如果是,则重新加载一次即可,反之则提示用户下载 // 原因:“取消”会触发Task.release方法,最终调用mDownloadTask.destroy,导致“下载服务”的Receiver被注销,即使文件下载了也没有回调回来 // NOTE isNeedToDownload方法会调用pluginDownloaded再次尝试加载 if (isNeedToDownload(context, plugin)) { return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process); } } } /* 检查是否是动态注册的类 */ // 如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。 // 原因:宿主的某些动态注册的类不能运行在坑位中(如'桌面'插件的入口Activity) if (LOG) { LogDebug.d("loadClass", "isHookingClass(" + plugin + " , " + activity + ") = " + Factory2.isDynamicClass(plugin, activity)); } if (Factory2.isDynamicClass(plugin, activity)) { intent.putExtra(IPluginManager.KEY_COMPATIBLE, true); intent.setComponent(new ComponentName(IPC.getPackageName(), activity)); context.startActivity(intent); return true; } // 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版) // Added by Jiongxuan Zhang if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginLibraryInternalProxy.startActivity(): Plugin Disabled. pn=" + plugin); } return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process); } // 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中” // Added by Jiongxuan Zhang if (!RePlugin.isPluginDexExtracted(plugin)) { PluginDesc pd = PluginDesc.get(plugin); if (pd != null && pd.isLarge()) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PM.startActivity(): Large Plugin! p=" + plugin); } return RePlugin.getConfig().getCallbacks().onLoadLargePluginForActivity(context, plugin, intent, process); } } // WARNING:千万不要修改intent内容,尤其不要修改其ComponentName // 因为一旦分配坑位有误(或压根不是插件Activity),则外界还需要原封不动的startActivity到系统中 // 可防止出现“本来要打开宿主,结果被改成插件”,进而无法打开宿主Activity的问题 // 缓存打开前的Intent对象,里面将包括Action等内容 Intent from = new Intent(intent); // 帮助填写打开前的Intent的ComponentName信息(如有。没有的情况如直接通过Action打开等) if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) { from.setComponent(new ComponentName(plugin, activity)); } ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process); if (cn == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin cn not found: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process); } return false; } // 将Intent指向到“坑位”。这样: // from:插件原Intent // to:坑位Intent intent.setComponent(cn); if (LOG) { LogDebug.d(PLUGIN_TAG, "start activity: real intent=" + intent); } // if (RePluginInternal.FOR_DEV) { // try { // String str = cn.getPackageName() + "/" + cn.getClassName(); // if (LOG) { // LogDebug.d(PLUGIN_TAG, "str=" + str); // } // new ProcessBuilder().command("am", "start", "-D", "--user", "0", "-n", str).start(); // } catch (IOException e) { // e.printStackTrace(); // } // } else { context.startActivity(intent); // 通知外界,已准备好要打开Activity了 // 其中:from为要打开的插件的Intent,to为坑位Intent RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent); return true; } /** * 通过 forResult 方式启动一个插件的 Activity * * @param activity 源 Activity * @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名 * @param requestCode 请求码 * @param options 附加的数据 */ public boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) { String plugin = getPluginName(activity, intent); if (LOG) { LogDebug.d(PLUGIN_TAG, "start activity with startActivityForResult: intent=" + intent); } if (TextUtils.isEmpty(plugin)) { return false; } ComponentName cn = intent.getComponent(); if (cn == null) { return false; } String name = cn.getClassName(); ComponentName cnNew = loadPluginActivity(intent, plugin, name, IPluginManager.PROCESS_AUTO); if (cnNew == null) { return false; } intent.setComponent(cnNew); if (Build.VERSION.SDK_INT >= 16) { activity.startActivityForResult(intent, requestCode, options); } else { activity.startActivityForResult(intent, requestCode); } return true; } /** * 获取插件名称 */ private static String getPluginName(Activity activity, Intent intent) { String plugin = ""; if (intent.getComponent() != null) { plugin = intent.getComponent().getPackageName(); } // 如果 plugin 是包名,则说明启动的是本插件。 if (TextUtils.isEmpty(plugin) || plugin.contains(".")) { plugin = RePlugin.fetchPluginNameByClassLoader(activity.getClassLoader()); } // 否则是其它插件 return plugin; } private boolean isNeedToDownload(Context context, String plugin) { // 以下两种情况需要下载插件: // 1、V5文件不存在(常见); // 2、V5文件非法(加载失败) String n = V5FileInfo.getFileName(plugin); File f = new File(RePlugin.getConfig().getPnInstallDir(), n); if (!f.exists()) { if (LOG) { LogDebug.d(PLUGIN_TAG, "isNeedToDownload(): V5 file not exists. Plugin = " + plugin); } return true; } if (LOG) { LogDebug.d(PLUGIN_TAG, "isNeedToDownload(): V5 file exists. Extracting... Plugin = " + plugin); } PluginInfo i = MP.pluginDownloaded(f.getAbsolutePath()); if (i == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "isNeedToDownload(): V5 file is invalid. Plugin = " + plugin); } return true; } if (LOG) { LogDebug.d(PLUGIN_TAG, "isNeedToDownload(): V5 file is Okay. Loading... Plugin = " + plugin); } return false; } /** * @hide 内部方法,插件框架使用 * 插件的Activity创建成功后通过此方法获取其base context * @param activity * @param newBase * @return 为Activity构造一个base Context */ public Context createActivityContext(Activity activity, Context newBase) { // PluginContainers.ActivityState state = mPluginMgr.mClient.mACM.lookupLastLoading(activity.getClass().getName()); // if (state == null) { // if (LOG) { // LogDebug.w(PLUGIN_TAG, "PACM: createActivityContext: can't found plugin activity: activity=" + activity.getClass().getName()); // } // return null; // } // Plugin plugin = mPluginMgr.loadAppPlugin(state.mCN.getPackageName()); // 此时插件必须被加载,因此通过class loader一定能找到对应的PLUGIN对象 Plugin plugin = mPluginMgr.lookupPlugin(activity.getClass().getClassLoader()); if (plugin == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: createActivityContext: can't found plugin object for activity=" + activity.getClass().getName()); } return null; } return plugin.mLoader.createBaseContext(newBase); } /** * @hide 内部方法,插件框架使用 * 插件的Activity的onCreate调用前调用此方法 * @param activity * @param savedInstanceState */ public void handleActivityCreateBefore(Activity activity, Bundle savedInstanceState) { if (LOG) { LogDebug.d(PLUGIN_TAG, "activity create before: " + activity.getClass().getName() + " this=" + activity.hashCode() + " taskid=" + activity.getTaskId()); } // 对FragmentActivity做特殊处理 if (savedInstanceState != null) { // savedInstanceState.setClassLoader(activity.getClassLoader()); // try { savedInstanceState.remove("android:support:fragments"); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "a.c.b1: " + e.getMessage(), e); } } } // 对FragmentActivity做特殊处理 Intent intent = activity.getIntent(); if (intent != null) { intent.setExtrasClassLoader(activity.getClassLoader()); activity.setTheme(getThemeId(activity, intent)); } FixOTranslucentOrientation.fix(activity); } /** * @hide 内部方法,插件框架使用 * 插件的Activity的onCreate调用后调用此方法 * @param activity * @param savedInstanceState */ public void handleActivityCreate(Activity activity, Bundle savedInstanceState) { if (LOG) { LogDebug.d(PLUGIN_TAG, "activity create: " + activity.getClass().getName() + " this=" + activity.hashCode() + " taskid=" + activity.getTaskId()); } if (activity.getIntent() != null) { try { Intent intent = new Intent(activity.getIntent()); // String pluginName = intent.getStringExtra(PluginManager.EXTRA_PLUGIN); // String activityName = intent.getStringExtra(PluginManager.EXTRA_ACTIVITY); // int process = intent.getIntExtra(PluginManager.EXTRA_PROCESS, PluginManager.PROCESS_AUTO); // String container = intent.getStringExtra(PluginManager.EXTRA_CONTAINER); // int counter = intent.getIntExtra(PluginManager.EXTRA_COUNTER, 0); PluginIntent ii = new PluginIntent(intent); String pluginName = ii.getPlugin(); String activityName = ii.getActivity(); int process = ii.getProcess(); String container = ii.getContainer(); int counter = ii.getCounter(); if (LOG) { LogDebug.d(PLUGIN_TAG, "activity create: name=" + pluginName + " activity=" + activityName + " process=" + process + " container=" + container + " counter=" + counter); } // activity跑飞 if (!TextUtils.equals(activityName, activity.getClass().getName())) { // activity=, l= if (LOGR) { LogRelease.w(PLUGIN_TAG, "a.c.1: a=" + activityName + " l=" + activity.getClass().getName()); } PMF.forward(activity, intent); return; } if (LOG) { LogDebug.i(PLUGIN_TAG, "perfect: container=" + container + " plugin=" + pluginName + " activity=" + activityName); } } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "a.c.2: exception: " + e.getMessage(), e); } } } // PluginContainers.ActivityState state = null; if (activity.getComponentName() != null) { state = mPluginMgr.mClient.mACM.lookupByContainer(activity.getComponentName().getClassName()); } if (state == null) { // PACM: handleActivityCreate: can't found PLUGIN activity: loaded= if (LOGR) { LogRelease.e(PLUGIN_TAG, "a.c1: l=" + activity.getClass().getName()); } return; } // 记录坑 mPluginMgr.mClient.mACM.handleCreate(state.plugin, activity, state.container); // 插件进程信息登记,用于插件进程管理(例如可能用于插件进程分配/回收) try { PluginProcessMain.getPluginHost().regActivity(PluginManager.sPluginProcessIndex, state.plugin, state.container, activity.getClass().getName()); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "a.c2: " + e.getMessage(), e); } } // if (savedInstanceState != null) { savedInstanceState.setClassLoader(activity.getClassLoader()); } // Intent intent = activity.getIntent(); if (intent != null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "set activity intent cl=" + activity.getClassLoader()); } intent.setExtrasClassLoader(activity.getClassLoader()); } // 开始填充一些必要的属性给Activity对象 // Added by Jiongxuan Zhang ActivityInjector.inject(activity, state.plugin, state.activity); } /** * @hide 内部方法,插件框架使用 * 插件的Activity的onRestoreInstanceState调用后调用此方法 * @param activity * @param savedInstanceState */ public void handleRestoreInstanceState(Activity activity, Bundle savedInstanceState) { if (LOG) { LogDebug.d(PLUGIN_TAG, "activity restore instance state: " + activity.getClass().getName()); } // if (savedInstanceState != null) { savedInstanceState.setClassLoader(activity.getClassLoader()); // 二级修正 Set set = savedInstanceState.keySet(); if (set != null) { for (String key : set) { Object obj = savedInstanceState.get(key); if (obj instanceof Bundle) { ((Bundle) obj).setClassLoader(activity.getClassLoader()); } } } } } /** * @hide 内部方法,插件框架使用 * 插件的Activity的onDestroy调用后调用此方法 * @param activity */ public void handleActivityDestroy(Activity activity) { if (LOG) { LogDebug.d(PLUGIN_TAG, "activity destroy: " + activity.getClass().getName() + " this=" + activity.hashCode() + " taskid=" + activity.getTaskId()); } // 回收坑 mPluginMgr.mClient.mACM.handleDestroy(activity); // PluginContainers.ActivityState state = null; if (activity.getComponentName() != null) { state = mPluginMgr.mClient.mACM.lookupByContainer(activity.getComponentName().getClassName()); } if (state == null) { // PACM: handleActivityDestroy: can't found plugin activity: activity= if (LOGR) { LogRelease.e(PLUGIN_TAG, "p a h a d c f p a " + activity.getClass().getName()); } return; } // 插件进程信息登记,用于插件进程管理(例如可能用于插件进程分配/回收) // int pid = Process.myPid(); String plugin = state.plugin; String container = state.container; try { PluginProcessMain.getPluginHost().unregActivity(PluginManager.sPluginProcessIndex, plugin, container, activity.getClass().getName()); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "ur.a: " + e.getMessage(), e); } } // 触发退出检测 RePlugin.getConfig().getEventCallbacks().onActivityDestroyed(activity); } /** * @hide 内部方法,插件框架使用 * 插件的Service的onCreate调用后调用此方法 * @param service */ public void handleServiceCreate(Service service) { mPluginMgr.handleServiceCreated(service); } /** * @hide 内部方法,插件框架使用 * 插件的Service的onDestroy调用后调用此方法 * @param service */ public void handleServiceDestroy(Service service) { mPluginMgr.handleServiceDestroyed(service); } /** * @hide 内部方法,插件框架使用 * 返回所有插件的json串,格式见plugins-builtin.json文件 * @param name 插件名,传null或者空串表示获取全部 * @return */ public JSONArray fetchPlugins(String name) { // 先获取List,然后再逐步搞JSON List l = MP.getPlugins(false); JSONArray ja = new JSONArray(); synchronized (PluginTable.PLUGINS) { for (PluginInfo info : l) { if (TextUtils.isEmpty(name) || TextUtils.equals(info.getName(), name)) { JSONObject jo = info.getJSON(); ja.put(jo); } } } return ja; } /** * @hide 内部方法,插件框架使用 * 登记动态映射的类(6.5.0 later) * @param className 壳类名 * @param plugin 目标插件名 * @param type 目标类的类型: activity, service, provider * @param target 目标类名 * @return */ public boolean registerDynamicClass(String className, String plugin, String type, String target) { return mPluginMgr.addDynamicClass(className, plugin, type, target, null); } /** * @hide 内部方法,插件框架使用 * 登记动态映射的类(7.7.0 later) */ public boolean registerDynamicClass(String className, String plugin, String target, Class defClass) { return mPluginMgr.addDynamicClass(className, plugin, "", target, defClass); } /** * @hide 内部方法,插件框架使用 * 查询某个类是否是动态映射的类(7.7.0 later) */ public boolean isDynamicClass(String plugin, String className) { return mPluginMgr.isDynamicClass(plugin, className); } /** * @hide 内部方法,插件框架使用 * 取消动态映射类的注册 */ public void unregisterDynamicClass(String className) { mPluginMgr.removeDynamicClass(className); } /** * @hide 内部方法,插件框架使用 * 查询某个动态映射的类对应的插件(7.7.0 later) */ public String getPluginByDynamicClass(String className) { return mPluginMgr.getPluginByDynamicClass(className); } /** * 获取插件中使用代码设置的主题 id */ private int getDynamicThemeId(Activity activity) { int dynamicThemeId = -1; try { dynamicThemeId = (int) ReflectUtils.invokeMethod(activity.getClassLoader(), "android.view.ContextThemeWrapper", "getThemeResId", activity, null); } catch (Exception e) { e.printStackTrace(); } return dynamicThemeId; } /** * 获取坑位应该使用的主题 */ private int getThemeId(Activity activity, Intent intent) { // 通过反射获取主题(可能获取到坑的主题,或者程序员通过代码设置的主题) int dynamicThemeId = getDynamicThemeId(activity); // 插件 manifest 中设置的 ThemeId int manifestThemeId = intent.getIntExtra(PluginCommImpl.INTENT_KEY_THEME_ID, 0); //如果插件上没有主题则使用Application节点的Theme if (manifestThemeId == 0) { manifestThemeId = activity.getApplicationInfo().theme; } // 根据 manifest 中声明主题是否透明,获取默认主题 int defaultThemeId = getDefaultThemeId(); if (LaunchModeStates.isTranslucentTheme(manifestThemeId)) { defaultThemeId = android.R.style.Theme_Translucent_NoTitleBar; } int themeId; if (LOG) { LogDebug.d("theme", "defaultThemeId = " + defaultThemeId); LogDebug.d("theme", "dynamicThemeId = " + dynamicThemeId); LogDebug.d("theme", "manifestThemeId = " + manifestThemeId); } // 通过反射获取主题成功 if (dynamicThemeId != -1) { // 如果动态主题是默认主题,说明插件未通过代码设置主题,此时应该使用 AndroidManifest 中设置的主题。 if (dynamicThemeId == defaultThemeId) { // AndroidManifest 中有声明主题 if (manifestThemeId != 0) { themeId = manifestThemeId; } else { themeId = defaultThemeId; } } else { // 动态主题不是默认主题,说明主题是插件通过代码设置的,使用此代码设置的主题。 themeId = dynamicThemeId; } // 反射失败,检查 AndroidManifest 是否有声明主题 } else { if (manifestThemeId != 0) { themeId = manifestThemeId; } else { themeId = defaultThemeId; } } if (LOG) { LogDebug.d("theme", "themeId = " + themeId); } return themeId; } /** * 获取默认 ThemeID * 如果 Host 配置了使用 AppCompat,则此处通过反射调用 AppCompat 主题。 *

* 注:Host 必须配置 AppCompat 依赖,否则反射调用会失败,导致宿主编译不过。 */ private static int getDefaultThemeId() { if (HostConfigHelper.ACTIVITY_PIT_USE_APPCOMPAT) { Class clazz; try { if (HostConfigHelper.HOST_USE_ANDROIDX){ clazz = ReflectUtils.getClass("androidx.appcompat.R$style"); } else { clazz = ReflectUtils.getClass("android.support.v7.appcompat.R$style"); } return (int) ReflectUtils.readStaticField(clazz, "Theme_AppCompat"); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } } return android.R.style.Theme_NoTitleBar; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginManager.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.Context; import android.text.TextUtils; import com.qihoo360.i.IPluginManager; import com.qihoo360.mobilesafe.api.Tasks; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.process.PluginProcessHost; import com.qihoo360.replugin.helper.LogDebug; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * @author RePlugin Team */ public class PluginManager { private static final Pattern PROCESS_NAME_PATTERN = Pattern.compile(Constant.STUB_PROCESS_SUFFIX_PATTERN); public static final int PROCESS_AUTO = IPluginManager.PROCESS_AUTO; public static final int COUNTER_MAX = 10; /** * @deprecated 临时实现 */ @Deprecated static int sUid; /** * @deprecated 临时实现 */ @Deprecated static int sPluginProcessIndex = -1; /** * @return */ public static final boolean isPluginProcess() { return sPluginProcessIndex >= 0 && sPluginProcessIndex < Constant.STUB_PROCESS_COUNT; } public static final boolean isValidActivityProcess(int process) { if (process == IPluginManager.PROCESS_UI || process == IPluginManager.PROCESS_AUTO || isPluginProcess(process)) { return true; } return false; } /** * @return */ static final boolean isPluginProcess(int index) { return index >= 0 && index < Constant.STUB_PROCESS_COUNT; } static final int getPluginProcessIndex() { return sPluginProcessIndex; } /** * @deprecated 临时实现 */ @Deprecated static final void init(Context context) { // 初始化操作,方便后面执行任务,不必担心Handler为空的情况 Tasks.init(); // sUid = android.os.Process.myUid(); // sPluginProcessIndex = evalPluginProcess(IPC.getCurrentProcessName()); } static final int evalPluginProcess(String name) { int index = IPluginManager.PROCESS_AUTO; try { if (TextUtils.equals(IPC.getPackageName(), name)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin process checker: default, index=" + 0); } return IPluginManager.PROCESS_UI; } if (!TextUtils.isEmpty(name)) { if (name.contains(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2)) { String tail = PluginProcessHost.processTail(name); return PluginProcessHost.PROCESS_INT_MAP.get(tail); } } Matcher m = PROCESS_NAME_PATTERN.matcher(name); if (m == null || !m.matches()) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin process checker: non plugin process in=" + name); } return IPluginManager.PROCESS_AUTO; } MatchResult r = m.toMatchResult(); if (r == null || r.groupCount() != 2) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin process checker: no group in=" + name); } return IPluginManager.PROCESS_AUTO; } String pr = r.group(1); if (!TextUtils.equals(IPC.getPackageName(), pr)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin process checker: package name not match in=" + name); } return IPluginManager.PROCESS_AUTO; } String str = r.group(2); index = Integer.parseInt(str); if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin process checker: index=" + index); } } catch (Throwable e) { if (LOG) { LogDebug.d(PLUGIN_TAG, e.getMessage(), e); } } return index; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginNativeLibsHelper.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.text.TextUtils; import android.util.Log; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.utils.CloseableUtils; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.utils.FileUtils; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * 与插件Native(SO)库有关的方法,都放在此处

* 该类主要用于:安装插件的SO库,获取SO库路径等

* 【仅框架内部使用】 * * @author RePlugin Team */ public class PluginNativeLibsHelper { private static final String TAG = "PluginNativeLibsHelper"; /** * 安装Native SO库

* 模拟系统安装流程,最终只释放一个最合身的SO库进入Libs目录中 * * @param apkPath APK文件路径 * @param nativeDir 要释放的Libs目录,通常从getLibDir中获取 * @return 安装是否成功 */ public static boolean install(String apkPath, File nativeDir) { if (BuildConfig.DEBUG) { Log.d(TAG, "install(): Start. apkp=" + apkPath + "; nd=" + nativeDir.getAbsolutePath()); } // TODO 线程同步 // 为防止加载旧SO,先清空目录 clear(nativeDir); ZipFile zipFile = null; try { zipFile = new ZipFile(apkPath); Map libZipEntries = new HashMap<>(); Map> soList = new HashMap<>(); // 找到所有的SO库,包括各种版本的,方便findSoPathForAbis中过滤 injectEntriesAndLibsMap(zipFile, libZipEntries, soList); for (String soName : soList.keySet()) { Set soPaths = soList.get(soName); String soPath = findSoPathForAbis(soPaths, soName); if (BuildConfig.DEBUG) { Log.d(TAG, "install(): Ready to extract. so=" + soName + "; sop=" + soPath); } if (soPath == null) { continue; } File file = new File(nativeDir, soName); extractFile(zipFile, libZipEntries.get(soPath), file); } return true; } catch (Throwable e) { if (BuildConfig.DEBUG) { e.printStackTrace(); } // 清除所有释放的文件,防止释放了一半 clear(nativeDir); return false; } finally { CloseableUtils.closeQuietly(zipFile); } } /** * 删除插件的SO库,通常在插件SO释放失败后,或者已有新插件,需要清除老插件时才会生效 */ public static void clear(File nativeDir) { if (!nativeDir.exists()) { return; } try { FileUtils.forceDelete(nativeDir); } catch (IOException e) { // IOException:有可能是IO,如权限出现问题等,打出日志 e.printStackTrace(); } catch (IllegalArgumentException e2) { if (LogRelease.LOGR) { e2.printStackTrace(); } } } private static void injectEntriesAndLibsMap(ZipFile zipFile, Map libZipEntries, Map> soList) { Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); String name = entry.getName(); if (name.contains("../")) { // 过滤,防止被攻击 continue; } if (name.startsWith("lib/") && !entry.isDirectory()) { libZipEntries.put(name, entry); String soName = new File(name).getName(); Set fs = soList.get(soName); if (fs == null) { fs = new TreeSet<>(); soList.put(soName, fs); } fs.add(name); } } } private static void extractFile(ZipFile zipFile, ZipEntry ze, File outFile) throws IOException { InputStream in = null; try { in = zipFile.getInputStream(ze); FileUtils.copyInputStreamToFile(in, outFile); if (BuildConfig.DEBUG) { Log.i(TAG, "extractFile(): Success! fn=" + outFile.getName()); } } finally { CloseableUtils.closeQuietly(in); } } // 根据Abi来获取需要释放的SO在压缩包中的位置 private static String findSoPathForAbis(Set soPaths, String soName) { if (soPaths == null || soPaths.size() <= 0) { return null; } // 若主程序用的是64位进程,则所属的SO必须只拷贝64位的,否则会出异常。32位也是如此 // 问:如果用户用的是64位处理器,宿主没有放任何SO,那么插件会如何? // 答:宿主在被安装时,系统会标记此为64位App,则之后的SO加载则只认64位的 // 问:如何让插件支持32位? // 答:宿主需被标记为32位才可以。可在宿主App中放入任意32位的SO(如放到libs/armeabi目录下)即可。 // 获取指令集列表 boolean is64 = VMRuntimeCompat.is64Bit(); String[] abis; if (is64) { abis = BuildCompat.SUPPORTED_64_BIT_ABIS; } else { abis = BuildCompat.SUPPORTED_32_BIT_ABIS; } // 开始寻找合适指定指令集的SO路径 String soPath = findSoPathWithAbiList(soPaths, soName, abis); if (LogDebug.LOG) { LogDebug.d(TAG, "findSoPathForAbis: Find so path. name=" + soName + "; list=" + soPath + "; Host-is-64bit?=" + is64 + "; abis=" + Arrays.toString(abis)); } return soPath; } private static String findSoPathWithAbiList(Set soPaths, String soName, String[] supportAbis) { Arrays.sort(supportAbis); for (String soPath : soPaths) { String abi = soPath.replaceFirst("lib/", ""); abi = abi.replace("/" + soName, ""); if (!TextUtils.isEmpty(abi) && Arrays.binarySearch(supportAbis, abi) >= 0) { return soPath; } } return null; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginProcessMain.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import com.qihoo360.i.IPluginManager; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.process.PluginProcessHost; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.packages.PluginManagerProxy; import com.qihoo360.replugin.packages.PluginManagerServer; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.MAIN_TAG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 进程管理类 * @author RePlugin Team */ public class PluginProcessMain { public static final String TAG = PluginProcessMain.class.getSimpleName(); /** * 常驻进程使用,非常驻进程为null buyuntao */ private static IPluginHost sPluginHostLocal; /** * 非常驻进程使用,常驻进程为null,用于非常驻进程连接常驻进程 buyuntao */ private static IPluginHost sPluginHostRemote; /** * 提供binder保存功能,无其他逻辑 */ static HashMap sBinders = new HashMap(); /** * 当前运行的所有进程的列表(常驻进程除外) */ private static final Map ALL = new HashMap(); /** * ALL的读写锁,用于并发时的性能提升 */ private static final ReentrantReadWriteLock PROCESS_CLIENT_LOCK = new ReentrantReadWriteLock(); private static final Object COOKIE_LOCK = new Object(); private static boolean sPersisistCookieInitialized; /** * 常驻进程cookie,用来控制卫士进程组是否需要退出等 */ private static long sPersisistCookie; /** * 进程记录,用于进程及进程列表管理 buyuntao */ private static final class ProcessClientRecord implements IBinder.DeathRecipient { String name; //进程名称 String plugin; int pid; int index; IBinder binder; IPluginClient client; PluginManagerServer pluginManager; //单个进程的插件管理类 public ProcessClientRecord(String process, String plugin, int pid, int index, IBinder binder, IPluginClient client, PluginManagerServer pms) { this.name = process; this.plugin = plugin; this.pid = pid; this.index = index; this.binder = binder; this.client = client; this.pluginManager = pms; } @Override public void binderDied() { handleBinderDied(this); } @Override public String toString() { if (LOG) { return super.toString() + " {name=" + name + " plugin=" + plugin + " pid=" + pid + " index=" + index + " binder=" + binder + " client=" + client + "}"; } return super.toString(); } public IPluginClient getClient() { return client; } } static final String dump() { // 1.dump Activity映射表, service列表 JSONArray activityArr = new JSONArray(); JSONArray serviceArr = new JSONArray(); for (ProcessClientRecord clientRecord : ALL.values()) { try { IPluginClient pluginClient = clientRecord.getClient(); if (pluginClient == null) { continue; } String activityDumpInfo = pluginClient.dumpActivities(); if (!TextUtils.isEmpty(activityDumpInfo)) { JSONArray activityList = new JSONArray(activityDumpInfo); int activityCount = activityList.length(); if (activityCount > 0) { for (int i = 0; i < activityCount; i++) { activityArr.put(activityList.getJSONObject(i)); } } } String serviceDumpInfo = pluginClient.dumpServices(); if (!TextUtils.isEmpty(serviceDumpInfo)) { JSONArray serviceList = new JSONArray(serviceDumpInfo); int serviceCount = serviceList.length(); if (serviceCount > 0) { for (int i = 0; i < serviceCount; i++) { serviceArr.put(serviceList.getJSONObject(i)); } } } } catch (Throwable e) { e.printStackTrace(); } } // 2.dump 插件信息表 JSONArray pluginArr = new JSONArray(); List pluginList = MP.getPlugins(false); if (pluginList != null) { JSONObject pluginObj; for (PluginInfo pluginInfo : pluginList) { try { pluginObj = new JSONObject(); pluginObj.put(pluginInfo.getName(), pluginInfo.toString()); pluginArr.put(pluginObj); } catch (JSONException e) { e.printStackTrace(); } } } JSONObject detailObj = new JSONObject(); try { detailObj.put("activity", activityArr); detailObj.put("service", serviceArr); detailObj.put("plugin", pluginArr); } catch (JSONException e) { e.printStackTrace(); } return detailObj.toString(); } static final void dump(FileDescriptor fd, PrintWriter writer, String[] args) { if (LogDebug.DUMP_ENABLED) { writer.println("--- ALL.length = " + ALL.size() + " ---"); for (ProcessClientRecord r : ALL.values()) { writer.println(r); } writer.println(); StubProcessManager.dump(writer); writer.println(); // writer.println("--- USED_PLUGINS.size = " + USED_PLUGINS.size() + " ---"); // for (ProcessPluginInfo r : USED_PLUGINS.values()) { // writer.println(r); // } writer.println(); PluginTable.dump(fd, writer, args); } } /** * 常驻进程调用,缓存自己的 IPluginHost */ static final void installHost(IPluginHost host) { sPluginHostLocal = host; // 连接到插件化管理器的服务端 // Added by Jiongxuan Zhang try { PluginManagerProxy.connectToServer(sPluginHostLocal); } catch (RemoteException e) { // 基本不太可能到这里,直接打出日志 if (LOGR) { e.printStackTrace(); } } } /** * 连接常驻进程后的Action */ public interface DiedAction { void onDied(); } /** * 非常驻进程调用,获取常驻进程的 IPluginHost */ static final void connectToHostSvc() { connectToHostSvc(null); } /** * 非常驻进程调用,获取常驻进程的 IPluginHost */ static final void connectToHostSvc(final DiedAction diedAction) { Context context = PMF.getApplicationContext(); IBinder binder = PluginProviderStub.proxyFetchHostBinder(context); if (LOG) { LogDebug.d(PLUGIN_TAG, "host binder = " + binder); } if (binder == null) { // 无法连接到常驻进程,当前进程自杀 if (LOGR) { LogRelease.e(PLUGIN_TAG, "p.p fhb fail"); } System.exit(1); } try { binder.linkToDeath(new IBinder.DeathRecipient() { @Override public void binderDied() { if (LOGR) { LogRelease.i(PLUGIN_TAG, "p.p d, p.h s n"); } // 检测到常驻进程退出,插件进程自杀 if (PluginManager.isPluginProcess()) { if (LOGR) { // persistent process exception, PLUGIN process quit now LogRelease.i(MAIN_TAG, "p p e, pp q n"); } System.exit(0); } sPluginHostRemote = null; // 断开和插件化管理器服务端的连接,因为已经失效 PluginManagerProxy.disconnect(); //断开连接后,需要尝试重新连接 if (diedAction != null) { diedAction.onDied(); } } }, 0); } catch (RemoteException e) { // 无法连接到常驻进程,当前进程自杀 if (LOGR) { LogRelease.e(PLUGIN_TAG, "p.p p.h l2a: " + e.getMessage(), e); } System.exit(1); } // sPluginHostRemote = IPluginHost.Stub.asInterface(binder); if (LOG) { LogDebug.d(PLUGIN_TAG, "host binder.i = " + PluginProcessMain.sPluginHostRemote); } // 连接到插件化管理器的服务端 // Added by Jiongxuan Zhang try { PluginManagerProxy.connectToServer(sPluginHostRemote); // 将当前进程的"正在运行"列表和常驻做同步 // TODO 若常驻进程重启,则应在启动时发送广播,各存活着的进程调用该方法来同步 PluginManagerProxy.syncRunningPlugins(); } catch (RemoteException e) { // 获取PluginManagerServer时出现问题,可能常驻进程突然挂掉等,当前进程自杀 if (LOGR) { LogRelease.e(PLUGIN_TAG, "p.p p.h l3a: " + e.getMessage(), e); } System.exit(1); } // 注册该进程信息到“插件管理进程”中 PMF.sPluginMgr.attach(); } /** * sPluginHostLocal 常驻进程使用,非常驻进程为null buyuntao * sPluginHostRemote 非常驻进程使用,常驻进程为null,用于非常驻进程连接常驻进程 buyuntao * @hide 内部框架使用 */ public static final IPluginHost getPluginHost() { if (sPluginHostLocal != null) { return sPluginHostLocal; } // 可能是第一次,或者常驻进程退出了 if (sPluginHostRemote == null) { if (LogDebug.LOG) { if (IPC.isPersistentProcess()) { LogDebug.e(PLUGIN_TAG, "插件框架未正常初始化"); throw new RuntimeException("插件框架未正常初始化"); } } // 再次唤起常驻进程 connectToHostSvc(); } return sPluginHostRemote; } static final long getPersistentCookie() { synchronized (COOKIE_LOCK) { if (!sPersisistCookieInitialized) { sPersisistCookieInitialized = true; if (IPC.isPersistentProcess()) { sPersisistCookie = System.currentTimeMillis(); if (LOG) { LogDebug.d(PLUGIN_TAG, "generate cookie: " + sPersisistCookie); } } } return sPersisistCookie; } } /** * @param plugin * @param process * @param info * @return */ static final IPluginClient probePluginClient(final String plugin, final int process, final PluginBinderInfo info) { return readProcessClientLock(new Action() { @Override public IPluginClient call() { for (ProcessClientRecord r : ALL.values()) { if (process == IPluginManager.PROCESS_UI) { if (!TextUtils.equals(r.plugin, Constant.PLUGIN_NAME_UI)) { continue; } /* 是否是用户自定义进程 */ } else if (PluginProcessHost.isCustomPluginProcess(process)) { if (!TextUtils.equals(r.plugin, getProcessStringByIndex(process))) { continue; } } else { if (!TextUtils.equals(r.plugin, plugin)) { continue; } } if (!isBinderAlive(r)) { return null; } if (!r.binder.pingBinder()) { return null; } info.pid = r.pid; info.index = r.index; return r.client; } return null; } }); } /** * 根据进程索引,取进程名称标识 * * @param index -99 * @return :p1 */ private static String getProcessStringByIndex(int index) { return PluginProcessHost.PROCESS_PLUGIN_SUFFIX2 + (index - PluginProcessHost.PROCESS_INIT); } /** * @param pid * @param info * @return */ static final IPluginClient probePluginClientByPid(final int pid, final PluginBinderInfo info) { return readProcessClientLock(new Action() { @Override public IPluginClient call() { for (ProcessClientRecord r : ALL.values()) { if (r.pid != pid) { continue; } if (!isBinderAlive(r)) { return null; } if (!r.binder.pingBinder()) { return null; } info.pid = r.pid; info.index = r.index; return r.client; } return null; } }); } /** * 发送intent给进程 buyuntao * @param target * @param intent */ static final void sendIntent2Process(final String target, Intent intent, boolean sync) { final Map map = readProcessClientLock(new Action>() { @Override public Map call() { Map map = new HashMap<>(); for (ProcessClientRecord r : ALL.values()) { if (TextUtils.isEmpty(target)) { // 所有 } else if (TextUtils.equals(r.name, target)) { // 特定目标 } else { continue; } map.put(r.name, r); } return map; } }); sendIntent2Client(map, intent, sync); } /** * 发送intent给指定插件 buyuntao * @param target * @param intent */ static final void sendIntent2Plugin(final String target, Intent intent, boolean sync) { if (TextUtils.isEmpty(target)) { return; } final Map map = readProcessClientLock(new Action>() { @Override public Map call() { final Map map = new HashMap<>(1 << 4); for (ProcessClientRecord r : ALL.values()) { if (TextUtils.equals(r.plugin, target)) { // 特定目标 } else { continue; } map.put(r.name, r); } return map; } }); sendIntent2Client(map, intent, sync); } /** * 发送intent给进程Client buyuntao * @param map * @param intent */ private static void sendIntent2Client(Map map, Intent intent, boolean sync){ for (ProcessClientRecord r : map.values()) { if (!isBinderAlive(r)) { continue; } try { if (sync) { r.client.sendIntentSync(intent); } else { r.client.sendIntent(intent); } } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "p.p sic e: " + e.getMessage(), e); } } } } /** * 判断进程是否存活 buyuntao * @param name * @return */ static final boolean isProcessAlive(final String name) { if (TextUtils.isEmpty(name)){ return false; } return readProcessClientLock(new Action() { @Override public Boolean call() { ProcessClientRecord r = ALL.get(name); return isBinderAlive(r); } }); } private static boolean isBinderAlive(ProcessClientRecord r) { return r != null && r.binder != null && r.client != null && r.binder.isBinderAlive(); } static final int sumActivities() { return readProcessClientLock(new Action() { @Override public Integer call() { int sum = 0; for (ProcessClientRecord r : ALL.values()) { if (!isBinderAlive(r)) { continue; } int rc = 0; try { rc = r.client.sumActivities(); if (rc == -1) { return -1; } sum += rc; } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "ppm.sa e: " + e.getMessage(), e); } } } return sum; } }); } /** * @param plugin * @param process * @return * @deprecated 待优化 * 插件进程调度 */ @Deprecated static final int allocProcess(String plugin, int process) { if (Constant.PLUGIN_NAME_UI.equals(plugin) || process == IPluginManager.PROCESS_UI) { return IPluginManager.PROCESS_UI; } if (PluginProcessHost.isCustomPluginProcess(process)) { return process; } PluginInfo info = PluginTable.getPluginInfo(plugin); if (info == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "alloc process: plugin not found: name=" + plugin); } return IPluginManager.PROCESS_AUTO; } return StubProcessManager.allocProcess(plugin); } /** * 常驻进程调用,添加进程信息到进程管理列表 * @param pid * @param process * @param index * @param binder * @param client * @return 进程的默认插件名称(非框架内的进程返回null) */ static final String attachProcess(int pid, String process, int index, IBinder binder, IPluginClient client, String def, PluginManagerServer pms) { final String plugin = getDefaultPluginName(pid, index, binder, client, def); final ProcessClientRecord pr = new ProcessClientRecord(process, plugin, pid, index, binder, client, pms); try { pr.binder.linkToDeath(pr, 0); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "ap l2d: " + e.getMessage(), e); } } writeProcessClientLock(new Action() { @Override public Void call() { ALL.put(pr.name, pr); return null; } }); return plugin; } /** * @param pid * @param index * @param plugin * @param activity * @param container * @return */ static final boolean attachActivity(int pid, int index, String plugin, String activity, String container) { return StubProcessManager.attachActivity(pid, index, plugin, activity, container); } /** * @param pid * @param index * @param plugin * @param activity * @param container * @return */ static final boolean detachActivity(int pid, int index, String plugin, String activity, String container) { return StubProcessManager.detachActivity(pid, index, plugin, activity, container); } /** * @param pid * @param index * @param plugin * @param service * @return */ static final boolean attachService(int pid, int index, String plugin, String service) { return StubProcessManager.attachService(pid, index, plugin, service); } /** * @param pid * @param index * @param plugin * @param service * @return */ static final boolean detachService(int pid, int index, String plugin, String service) { return StubProcessManager.detachService(pid, index, plugin, service); } static final void attachBinder(int pid, IBinder binder) { StubProcessManager.attachBinder(pid, binder); } static final void detachBinder(int pid, IBinder binder) { StubProcessManager.detachBinder(pid, binder); } static final int sumBinders(int index) { return StubProcessManager.sumBinders(index); } //change by buyuntao static final int getPidByProcessName(final String processName) { if (TextUtils.isEmpty(processName)){ return -1; } // 获取的是常驻进程自己?直接返回 if (TextUtils.equals(processName, IPC.getCurrentProcessName())) { return IPC.getCurrentProcessId(); } // 在“进程列表”中寻找“线索” return readProcessClientLock(new Action() { @Override public Integer call() { ProcessClientRecord r = ALL.get(processName); if (r != null && isBinderAlive(r)) { return r.pid; } return -1; } }); } // Added by Jiongxuan Zhang static final String getProcessNameByPid(final int pid) { // 获取的是常驻进程自己?直接返回 if (pid == IPC.getCurrentProcessId()) { return IPC.getCurrentProcessName(); } return readProcessClientLock(new Action() { @Override public String call() { for (ProcessClientRecord r : ALL.values()) { if (r.pid != pid) { continue; } if (!isBinderAlive(r)) { continue; } return r.name; } return null; } }); } private static final void handleBinderDied(ProcessClientRecord p) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin process has died: plugin=" + p.plugin + " index=" + p.index + " pid=" + p.pid); } handleBinderDiedLocked(p); } /** * 获取进程的默认插件名 * @param pid * @param index * @param binder * @param client * @param def * @return */ private static final String getDefaultPluginName(int pid, int index, IBinder binder, IPluginClient client, String def) { if (index == IPluginManager.PROCESS_UI) { return Constant.PLUGIN_NAME_UI; } /* 是否是用户自定义进程 */ if (PluginProcessHost.isCustomPluginProcess(index)) { return getProcessStringByIndex(index); } if (PluginManager.isPluginProcess(index)) { return StubProcessManager.attachStubProcess(pid, index, binder, client, def); } return null; } /** * 进程结束后执行操作 buyuntao * @param p */ private static final void handleBinderDiedLocked(final ProcessClientRecord p) { if (p == null){ return; } writeProcessClientLock(new Action() { @Override public Void call() { ProcessClientRecord r = ALL.get(p.name); if (r == p){ //防止进程重启误判 ALL.remove(r.name); } return null; } }); StubProcessManager.setProcessStop(p.binder); // 通知 PluginManagerServer 客户端进程链接已断开 p.pluginManager.onClientProcessKilled(p.name); } /// private static T writeProcessClientLock( final Action action) { final long start = System.currentTimeMillis(); // final String stack = OptUtil.stack2Str(Thread.currentThread().getStackTrace()[3]); try { PROCESS_CLIENT_LOCK.writeLock().lock(); if (LogDebug.LOG) { Log.d(TAG, String.format("%s(%sms@%s) WRITING", Thread.currentThread().getStackTrace()[3], System.currentTimeMillis() - start, Thread.currentThread())); } return action.call(); } finally { PROCESS_CLIENT_LOCK.writeLock().unlock(); if (LogDebug.LOG) { Log.d(TAG, String.format("%s(%sms@%s) WRITING DONE", Thread.currentThread().getStackTrace()[3], System.currentTimeMillis() - start, Thread.currentThread())); } } } private static T readProcessClientLock( final Action action) { final long start = System.currentTimeMillis(); // final String stack = OptUtil.stack2Str(Thread.currentThread().getStackTrace()[3]); try { PROCESS_CLIENT_LOCK.readLock().lock(); if (LogDebug.LOG) { Log.d(TAG, String.format("%s(%sms@%s) READING", Thread.currentThread().getStackTrace()[3], System.currentTimeMillis() - start, Thread.currentThread())); } return action.call(); } finally { PROCESS_CLIENT_LOCK.readLock().unlock(); if (LogDebug.LOG) { Log.d(TAG, String.format("%s(%sms@%s) READING DONE", Thread.currentThread().getStackTrace()[3], System.currentTimeMillis() - start, Thread.currentThread())); } } } private interface Action { T call(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginProcessPer.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import com.qihoo360.loader.utils.LocalBroadcastManager; import com.qihoo360.i.IPluginManager; import com.qihoo360.loader2.alc.ActivityController; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.base.LocalBroadcastHelper; import com.qihoo360.replugin.component.dummy.ForwardActivity; import com.qihoo360.replugin.component.process.PluginProcessHost; import com.qihoo360.replugin.component.receiver.PluginReceiverHelper; import com.qihoo360.replugin.component.service.server.IPluginServiceServer; import com.qihoo360.replugin.component.service.server.PluginServiceServer; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import java.util.HashMap; import java.util.HashSet; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team */ class PluginProcessPer extends IPluginClient.Stub { private final Context mContext; private final PmBase mPluginMgr; final PluginServiceServer mServiceMgr; final PluginContainers mACM; // TODO 考虑去掉 {package}权限 private Plugin mDefaultPlugin; /** * 保存 plugin-receiver -> Receiver 的关系 */ private HashMap mReceivers = new HashMap<>(); PluginProcessPer(Context context, PmBase pm, int process, HashSet containers) { mContext = context; mPluginMgr = pm; mServiceMgr = new PluginServiceServer(context); // mACM = new PluginContainers(); mACM.init(process, containers); } final void init(Plugin p) { mDefaultPlugin = p; } /** * 类加载器根据容器解析到目标的activity * @param container * @return */ final Class resolveActivityClass(String container) { String plugin = null; String activity = null; // 先找登记的,如果找不到,则用forward activity PluginContainers.ActivityState state = mACM.lookupByContainer(container); if (state == null) { // PACM: loadActivityClass, not register, use forward activity, container= if (LOGR) { LogRelease.w(PLUGIN_TAG, "use f.a, c=" + container); } return ForwardActivity.class; } plugin = state.plugin; activity = state.activity; if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: loadActivityClass in=" + container + " target=" + activity + " plugin=" + plugin); } Plugin p = mPluginMgr.loadAppPlugin(plugin); if (p == null) { // PACM: loadActivityClass, not found plugin if (LOGR) { LogRelease.e(PLUGIN_TAG, "load fail: c=" + container + " p=" + plugin + " t=" + activity); } return null; } ClassLoader cl = p.getClassLoader(); if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: loadActivityClass, plugin activity loader: in=" + container + " activity=" + activity); } Class c = null; try { c = cl.loadClass(activity); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, e.getMessage(), e); } } if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: loadActivityClass, plugin activity loader: c=" + c + ", loader=" + cl); } return c; } @Override public String allocActivityContainer(String plugin, int process, String target, Intent intent) throws RemoteException { // 一旦有分配,则进入监控状态(一是避免不退出的情况,二也是最重要的是避免现在就退出的情况) RePlugin.getConfig().getEventCallbacks().onPrepareAllocPitActivity(intent); String loadPlugin = null; // 如果UI进程启用,尝试使用传过来的插件,强制用UI进程 if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) { if (IPC.isUIProcess()) { loadPlugin = plugin; process = IPluginManager.PROCESS_UI; } else { loadPlugin = plugin; } } // 如果不成,则再次尝试使用默认插件 if (TextUtils.isEmpty(loadPlugin)) { if (mDefaultPlugin == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "a.a.c p i n"); } return null; } loadPlugin = mDefaultPlugin.mInfo.getName(); } // String container = bindActivity(loadPlugin, process, target, intent); if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: eval plugin " + loadPlugin + ", target=" + target + ", container=" + container); } return container; } @Override public IBinder queryBinder(String plugin, String binder) throws RemoteException { Plugin p = null; if (TextUtils.isEmpty(plugin)) { p = mDefaultPlugin; } else { p = mPluginMgr.loadAppPlugin(plugin); } if (p == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "q.b p i n"); } return null; } if (p.mLoader == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "q.b p l i n"); } return null; } if (p.mLoader.mBinderPlugin == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "q.b p l b i n"); } return null; } if (p.mLoader.mBinderPlugin.mPlugin == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "q.b p l b p i n"); } return null; } IBinder b = p.mLoader.mBinderPlugin.mPlugin.query(binder); if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginImpl.query: call plugin aidl: plugin=" + p.mInfo.getName() + " binder.name=" + binder + " binder.object=" + b); } if (b != null) { // TODO 增加计数器 } return b; } @Override public void releaseBinder() throws RemoteException { if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginImpl.releaseBinder"); } // 告诉外界Binder已经被释放 RePlugin.getConfig().getEventCallbacks().onBinderReleased(); } @Override public void sendIntent(Intent intent) throws RemoteException { sendIntent(intent, false); } @Override public void sendIntentSync(Intent intent) throws RemoteException { sendIntent(intent, true); } private void sendIntent(Intent intent, boolean sync) throws RemoteException { if (LOG) { LogDebug.d(PLUGIN_TAG, "sendIntent pr=" + IPC.getCurrentProcessName() + " intent=" + intent); } intent.setExtrasClassLoader(getClass().getClassLoader()); if (sync) { LocalBroadcastHelper.sendBroadcastSyncUi(mContext, intent); } else { LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent); } } @Override public int sumActivities() throws RemoteException { return ActivityController.sumActivities(); } @Override public IPluginServiceServer fetchServiceServer() throws RemoteException { return mServiceMgr.getService(); } /** * 加载插件;找到目标Activity;搜索匹配容器;加载目标Activity类;建立临时映射;返回容器 * * @param plugin 插件名称 * @param process 进程 * @param activity Activity 名称 * @param intent 调用者传入的 Intent * @return 坑位 */ final String bindActivity(String plugin, int process, String activity, Intent intent) { /* 获取插件对象 */ Plugin p = mPluginMgr.loadAppPlugin(plugin); if (p == null) { if (LOG) { LogDebug.w(PLUGIN_TAG, "PACM: bindActivity: may be invalid plugin name or load plugin failed: plugin=" + plugin); } return null; } /* 获取 ActivityInfo */ ActivityInfo ai = p.mLoader.mComponents.getActivity(activity); if (ai == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: bindActivity: activity not found: activity=" + activity); } return null; } if (ai.processName == null) { ai.processName = ai.applicationInfo.processName; } if (ai.processName == null) { ai.processName = ai.packageName; } /* 获取 Container */ String container; // 自定义进程 if (ai.processName.contains(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2)) { String processTail = PluginProcessHost.processTail(ai.processName); container = mACM.alloc2(ai, plugin, activity, process, intent, processTail); } else { container = mACM.alloc(ai, plugin, activity, process, intent); } if (TextUtils.isEmpty(container)) { if (LOG) { LogDebug.w(PLUGIN_TAG, "PACM: bindActivity: activity container is empty"); } return null; } if (LOG) { LogDebug.d(PLUGIN_TAG, "PACM: bindActivity: lookup activity container: container=" + container); } /* 检查 activity 是否存在 */ Class c = null; try { c = p.mLoader.mClassLoader.loadClass(activity); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, e.getMessage(), e); } } if (c == null) { if (LOG) { LogDebug.w(PLUGIN_TAG, "PACM: bindActivity: plugin activity class not found: c=" + activity); } return null; } return container; } @Override public void onReceive(String plugin, final String receiver, final Intent intent) { PluginReceiverHelper.onPluginReceiverReceived(plugin, receiver, mReceivers, intent); } @Override public String dumpServices() { try { IPluginServiceServer pss = fetchServiceServer(); if (pss != null) { try { return pss.dump(); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.sts: pss e", e); } } } } catch (RemoteException e) { e.printStackTrace(); } return null; } @Override public String dumpActivities() { return mACM.dump(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginProviderStub.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.RemoteException; import android.text.TextUtils; import com.qihoo360.loader2.sp.IPref; import com.qihoo360.loader2.sp.PrefImpl; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.process.ProcessPitProviderBase; import com.qihoo360.replugin.component.process.ProcessPitProviderPersist; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.utils.CloseableUtils; import java.util.Arrays; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * @author RePlugin Team */ public class PluginProviderStub { private static final String KEY_METHOD = "main_method"; private static final String KEY_COOKIE = "cookie"; private static final String URL_PARAM_KEY_LOADED = "loaded"; private static final String PROJECTION_MAIN[] = { "main" }; private static final String SELECTION_MAIN_BINDER = "main_binder"; private static final String SELECTION_MAIN_PREF = "main_pref"; private static final String METHOD_START_PROCESS = "start_process"; /** * 需要枷锁否? */ static PrefImpl sPrefImpl; /** * */ static IPref sPref; public static final Cursor stubMain(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // if (LOG) { LogDebug.d(PLUGIN_TAG, "stubMain projection=" + Arrays.toString(projection) + " selection=" + selection); } if (SELECTION_MAIN_BINDER.equals(selection)) { return BinderCursor.queryBinder(PMF.sPluginMgr.getHostBinder()); } if (SELECTION_MAIN_PREF.equals(selection)) { // 需要枷锁否? initPref(); return BinderCursor.queryBinder(sPrefImpl); } return null; } /** * 在目标插件进程中运行 * @param uri * @param values * @return */ public static final Uri stubPlugin(Uri uri, ContentValues values) { // if (LOG) { LogDebug.d(PLUGIN_TAG, "stubPlugin values=" + values); } String method = values.getAsString(KEY_METHOD); if (TextUtils.equals(method, METHOD_START_PROCESS)) { // Uri rc = new Uri.Builder().scheme("content").authority("process").encodedPath("status").encodedQuery(URL_PARAM_KEY_LOADED + "=" + 1).build(); if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin provider: return uri=" + rc); } long cookie = values.getAsLong(KEY_COOKIE); // 首次 if (PMF.sPluginMgr.mLocalCookie == 0L) { if (LOG) { LogDebug.d(PLUGIN_TAG, "set cookie: " + cookie); } // PMF.sPluginMgr.mLocalCookie = cookie; } else { // 常驻进程重新启动了 if (PMF.sPluginMgr.mLocalCookie != cookie) { if (LOG) { LogDebug.d(PLUGIN_TAG, "reset cookie: " + cookie); } // PMF.sPluginMgr.mLocalCookie = cookie; // PluginProcessMain.connectToHostSvc(); } } return rc; } return null; } /** * @param context * @return */ static final IBinder proxyFetchHostBinder(Context context) { return proxyFetchHostBinder(context, SELECTION_MAIN_BINDER); } /** * @param context * @return */ static final IBinder proxyFetchHostPref(Context context) { return proxyFetchHostBinder(context, SELECTION_MAIN_PREF); } /** * @param context * @param selection * @return */ private static final IBinder proxyFetchHostBinder(Context context, String selection) { // Cursor cursor = null; try { Uri uri = ProcessPitProviderPersist.URI; cursor = context.getContentResolver().query(uri, PROJECTION_MAIN, selection, null, null); if (cursor == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "proxy fetch binder: cursor is null"); } return null; } while (cursor.moveToNext()) { // } IBinder binder = BinderCursor.getBinder(cursor); if (LOG) { LogDebug.d(PLUGIN_TAG, "proxy fetch binder: binder=" + binder); } return binder; } finally { CloseableUtils.closeQuietly(cursor); } } /** * @param context * @param index * @return */ static final boolean proxyStartPluginProcess(Context context, int index) { // ContentValues values = new ContentValues(); values.put(KEY_METHOD, METHOD_START_PROCESS); values.put(KEY_COOKIE, PMF.sPluginMgr.mLocalCookie); Uri uri = context.getContentResolver().insert(ProcessPitProviderBase.buildUri(index), values); if (LOG) { LogDebug.d(PLUGIN_TAG, "proxyStartPluginProcess insert.rc=" + (uri != null ? uri.toString() : "null")); } if (uri == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "proxyStartPluginProcess failed"); } return false; } return true; } /** * @param context * @return * @throws RemoteException */ public static final IPref getPref(Context context) throws RemoteException { if (sPref == null) { if (IPC.isPersistentProcess()) { // 需要枷锁否? initPref(); } else { IBinder b = PluginProviderStub.proxyFetchHostPref(context); b.linkToDeath(new DeathRecipient() { @Override public void binderDied() { sPref = null; } }, 0); sPref = IPref.Stub.asInterface(b); } } return sPref; } static final void initPref() { if (sPrefImpl == null) { sPrefImpl = new PrefImpl(); sPref = sPrefImpl; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginStatusController.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.annotation.SuppressLint; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; import com.qihoo360.replugin.helper.LogDebug; import org.json.JSONException; import org.json.JSONObject; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * 用来管理插件的状态:正常运行、被禁用,还是其它情况 * * @author RePlugin Team */ public class PluginStatusController { private static final String PREF_FILE = "plugins"; private static final String KEY_STATUS_NAME_PREFIX = "ps-"; /** * 表示插件是正常的 * 值:0(小于0的结果都是异常情况,可直接判断) */ public static final int STATUS_OK = 0; /** * 因为崩溃次数过多而被禁用 * 值:-1 */ public static final int STATUS_DISABLE_BY_CRASH = -1; /** * 因为被云端Push而禁用 * 值:-2 */ public static final int STATUS_DISABLE_BY_CLOUD = -2; @SuppressLint("StaticFieldLeak") private static Application sAppContext; /** * 设置指定版本的插件的状态 * * @param pn 要修改状态的插件名 * @param ver 要修改状态的插件的版本号。若设置状态为OK,则忽略此参数 * @param status 最终修改的状态 */ public static void setStatus(String pn, int ver, int status) { // 若要设置状态为“OK”,不管版本为何,都需删除此插件的状态记录(解禁) if (status == STATUS_OK) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PStatusC.setStatus(): Status is OK, Clear. pn=" + pn + "; ver=" + ver); } removeStatusToPref(sAppContext, pn); return; } PluginStatus ps = new PluginStatus(pn, ver, status); addStatusToPref(sAppContext, pn, ps.toJsonString()); if (LOG) { LogDebug.d(PLUGIN_TAG, "PStatusC.setStatus(): Set Status, pn=" + pn + "; ver=" + ver + "; st=" + status); } } /** * 不关心版本,直接获取当前插件的状态 * * @param pn 要获取状态的插件名 * @return PluginStatus中的任何一个常量 */ public static int getStatus(String pn) { // 注意:不能使用MP.getPlugin来获取版本号,因为此时插件已“无效”,自然不会有PluginInfo // PluginInfo pi = MP.getPlugin(pn); return getStatus(pn, -1); } /** * 获取指定版本的插件的状态 * * @param pn 要获取状态的插件名 * @param ver 要获取状态的插件的版本号 * @return PluginStatus中的任何一个常量 */ public static int getStatus(String pn, int ver) { PluginStatus ps = getStatusImpl(pn); // 获取PS有任何异常?直接返回 if (ps == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PStatusC.getStatus(): ps is null. pn=" + pn); } return STATUS_OK; } // 不是此版本,可直接忽略 if (ver != -1 && ps.getVersion() != ver) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PStatusC.getStatus(): ver not match. ver=" + ver + "; expect=" + ps.getVersion() + "; pn=" + pn); } return STATUS_OK; } int st = ps.getStatus(); if (LOG) { LogDebug.d(PLUGIN_TAG, "PStatusC.getStatus(): ver match. ver=" + ver + "; pn=" + pn + "; st=" + st); } return st; } /** * 清除所有插件的状态(完全解禁) * 通常在“卫士主程序”升级上来以后才会生效 */ public static void clearStatus() { SharedPreferences pref = sAppContext.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); SharedPreferences.Editor e = pref.edit(); for (String key : pref.getAll().keySet()) { if (key.contains(KEY_STATUS_NAME_PREFIX)) { e.remove(key); } } e.commit(); } /** 设置ApplicationContext,仅在MobileSafeApplication中使用 */ public static void setAppContext(Application context) { sAppContext = context; } private static PluginStatus getStatusImpl(String pn) { String pst = getStatusFromPref(sAppContext, pn); if (TextUtils.isEmpty(pst)) { return null; } PluginStatus ps; try { ps = new PluginStatus(pst); } catch (JSONException e) { // 解析出错,删除 if (LOG) { LogDebug.d(PLUGIN_TAG, "PStatusC.getStatus(): json err.", e); } removeStatusToPref(sAppContext, pn); return null; } return ps; } private static void addStatusToPref(Context context, String pn, String json) { SharedPreferences pref = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); pref.edit().putString(KEY_STATUS_NAME_PREFIX + pn, json).commit(); } private static void removeStatusToPref(Context context, String pn) { SharedPreferences pref = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); pref.edit().remove(KEY_STATUS_NAME_PREFIX + pn).commit(); } private static String getStatusFromPref(Context context, String pn) { SharedPreferences pref = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); return pref.getString(KEY_STATUS_NAME_PREFIX + pn, null); } private static class PluginStatus { JSONObject mJo; PluginStatus(String pn, int ver, int status) { try { mJo = new JSONObject(); mJo.put("pn", pn); mJo.put("ver", ver); mJo.put("ctime", System.currentTimeMillis()); mJo.put("st", status); } catch (JSONException e) { e.printStackTrace(); } } PluginStatus(String json) throws JSONException { mJo = new JSONObject(json); } public int getVersion() { return mJo.optInt("ver"); } public long getChangeTime() { return mJo.optLong("ctime"); } public int getStatus() { return mJo.optInt("st"); } String toJsonString() { return mJo.toString(); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PluginTable.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.text.TextUtils; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * @author RePlugin Team */ class PluginTable { /** * */ static final HashMap PLUGINS = new HashMap(); static final void dump(FileDescriptor fd, PrintWriter writer, String[] args) { if (LogDebug.DUMP_ENABLED) { writer.println("--- PluginTable.size = " + PLUGINS.size() + " ---"); List l = MP.getPlugins(false); for (PluginInfo r : l) { writer.println(r); } writer.println(); } } static final void initPlugins(Map plugins) { synchronized (PLUGINS) { for (Plugin plugin : plugins.values()) { putPluginInfo(plugin.mInfo); } } } static final void updatePlugin(PluginInfo info) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update plugin table: info=" + info); } synchronized (PLUGINS) { // 检查插件是否已经被禁用 if (RePlugin.getConfig().getCallbacks().isPluginBlocked(info)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update plugin table: plugin is blocked, in=" + info); } return; } // 此处直接使用该插件,没有考虑是否只采用最新版 putPluginInfo(info); } } static final void replaceInfo(PluginInfo info) { boolean rc = false; PluginInfo pi = null; synchronized (PLUGINS) { pi = PLUGINS.get(info.getName()); if (pi != null) { if (pi.canReplaceForPn(info)) { putPluginInfo(info); rc = true; } } } if (LOG) { LogDebug.d(PLUGIN_TAG, "replace plugin table: info=" + info + " rc=" + rc); } } static final void removeInfo(PluginInfo info) { boolean rc = false; PluginInfo pi = null; synchronized (PLUGINS) { pi = PLUGINS.get(info.getName()); if (pi != null) { removePluginInfo(info); rc = true; } } if (LOG) { LogDebug.d(PLUGIN_TAG, "removeInfo plugin table: info=" + info + " rc=" + rc); } } static final PluginInfo getPluginInfo(String plugin) { synchronized (PLUGINS) { return PLUGINS.get(plugin); } } static final List buildPlugins() { if (LOG) { LogDebug.d(PLUGIN_TAG, "build plugins"); } List lst = MP.getPlugins(false); if (LOG) { LogDebug.d(PLUGIN_TAG, "build " + lst.size() + " plugins"); } return lst; } private static void putPluginInfo(PluginInfo info) { // 同时加入PackageName和Alias(如有) PLUGINS.put(info.getPackageName(), info); if (!TextUtils.isEmpty(info.getAlias())) { // 即便Alias和包名相同也可以再Put一次,反正只是覆盖了相同Value而已 PLUGINS.put(info.getAlias(), info); } } private static void removePluginInfo(PluginInfo info) { PLUGINS.remove(info.getPackageName()); PLUGINS.remove(info.getAlias()); } // /** // * @deprecated // * // * @return // */ // @Deprecated // static final Cursor fetchPlugins() { // if (LOG) { // LogDebug.d(PLUGIN_TAG, "build plugins"); // } // // MatrixCursor cursor = new MatrixCursor(PluginInfo.QUERY_COLUMNS); // // synchronized (sPlugins) { // if (sAdapter != null) { // sAdapter.to(cursor); // } // for (PluginInfo p : sPlugins.values()) { // p.to(cursor); // } // if (LOG) { // LogDebug.d(PLUGIN_TAG, "build " + sPlugins.size() + " plugins"); // } // } // // return cursor; // } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmBase.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.app.Service; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.os.IBinder; import android.os.Parcelable; import android.os.RemoteException; import com.qihoo360.loader.utils.LocalBroadcastManager; import android.text.TextUtils; import android.util.Log; import com.qihoo360.i.Factory; import com.qihoo360.i.IModule; import com.qihoo360.i.IPluginManager; import com.qihoo360.mobilesafe.api.Tasks; import com.qihoo360.replugin.IHostBinderFetcher; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginConstants; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.activity.DynamicClassProxyActivity; import com.qihoo360.replugin.component.dummy.DummyActivity; import com.qihoo360.replugin.component.dummy.DummyProvider; import com.qihoo360.replugin.component.dummy.DummyService; import com.qihoo360.replugin.component.process.PluginProcessHost; import com.qihoo360.replugin.component.service.server.PluginPitService; import com.qihoo360.replugin.helper.HostConfigHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.packages.PluginManagerProxy; import com.qihoo360.replugin.utils.ReflectUtils; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogDebug.TAG_NO_PN; import static com.qihoo360.replugin.helper.LogRelease.LOGR; import static com.qihoo360.replugin.packages.PluginInfoUpdater.ACTION_UNINSTALL_PLUGIN; /** * @author RePlugin Team */ class PmBase { private static final String TAG = "PmBase"; static final String ACTION_NEW_PLUGIN = "ACTION_NEW_PLUGIN"; static final String CONTAINER_SERVICE_PART = ".loader.s.Service"; private static final String CONTAINER_PROVIDER_PART = ".loader.p.Provider"; /** * */ private final Context mContext; /** * */ private final HashSet mContainerActivities = new HashSet(); /** * */ private final HashSet mContainerProviders = new HashSet(); /** * */ private final HashSet mContainerServices = new HashSet(); /** * */ private final HashMap> mBuiltinModules = new HashMap>(); /** * */ private ClassLoader mClassLoader; /** * 所有插件 */ private final Map mPlugins = new ConcurrentHashMap<>(); /** * 仿插件对象,用来实现主程序提供binder给其他模块 */ private final HashMap mBuiltinPlugins = new HashMap(); /** * 动态的类查找表 */ private final HashMap mDynamicClasses = new HashMap(); /** * */ private String mDefaultPluginName; /** * */ private Plugin mDefaultPlugin; /** * TODO init */ long mLocalCookie; /** * TODO init */ private boolean mNeedRestart; /** * */ Builder.PxAll mAll; /** * */ private PmHostSvc mHostSvc; /** * */ PluginProcessPer mClient; /** * */ PluginCommImpl mLocal; /** * */ PluginLibraryInternalProxy mInternal; /** * insertNewPlugin 时使用的线程锁 */ private static final byte[] LOCKER = new byte[0]; /** * 广播接收器,声明为成员变量以避免重复创建 */ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (TextUtils.isEmpty(action)) { return; } if (action.equals(intent.getAction())) { PluginInfo info = intent.getParcelableExtra("obj"); if (info != null) { switch (action) { case ACTION_NEW_PLUGIN: // 非常驻进程上下文 newPluginFound(info, intent.getBooleanExtra(RePluginConstants.KEY_PERSIST_NEED_RESTART, false)); break; case ACTION_UNINSTALL_PLUGIN: pluginUninstalled(info); break; } } } } }; /** * 类映射 */ private static class DynamicClass { String plugin; /** @deprecated */ String classType; // activity, service, provider Class defClass; String className; } static final void cleanIntentPluginParams(Intent intent) { // 防止 intent 攻击 try { intent.removeExtra(IPluginManager.KEY_COMPATIBLE); intent.removeExtra(IPluginManager.KEY_PLUGIN); intent.removeExtra(IPluginManager.KEY_ACTIVITY); } catch (Exception e) { // ignore } } PmBase(Context context) { // mContext = context; // TODO init //init(context, this); if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI || PluginManager.isPluginProcess()) { String suffix; if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI) { suffix = "N1"; } else { suffix = "" + PluginManager.sPluginProcessIndex; } // mContainerProviders.add(IPC.getPackageName() + CONTAINER_PROVIDER_PART + suffix); // mContainerServices.add(IPC.getPackageName() + CONTAINER_SERVICE_PART + suffix); } // mClient = new PluginProcessPer(context, this, PluginManager.sPluginProcessIndex, mContainerActivities); // mLocal = new PluginCommImpl(context, this); // mInternal = new PluginLibraryInternalProxy(this); } void init() { RePlugin.getConfig().getCallbacks().initPnPluginOverride(); if (HostConfigHelper.PERSISTENT_ENABLE) { // (默认)“常驻进程”作为插件管理进程,则常驻进程作为Server,其余进程作为Client if (IPC.isPersistentProcess()) { // 初始化“Server”所做工作 initForServer(); } else { // 连接到Server initForClient(); } } else { // “UI进程”作为插件管理进程(唯一进程),则UI进程既可以作为Server也可以作为Client if (IPC.isUIProcess()) { // 1. 尝试初始化Server所做工作, initForServer(); // 2. 注册该进程信息到“插件管理进程”中 // 注意:这里无需再做 initForClient,因为不需要再走一次Binder PMF.sPluginMgr.attach(); } else { // 其它进程?直接连接到Server即可 initForClient(); } } // 最新快照 PluginTable.initPlugins(mPlugins); // 输出 if (LOG) { for (Plugin p : mPlugins.values()) { LogDebug.d(PLUGIN_TAG, "plugin: p=" + p.mInfo); } } } /** * Persistent(常驻)进程的初始化 * */ private final void initForServer() { if (LOG) { LogDebug.d(PLUGIN_TAG, "search plugins from file system"); } mHostSvc = new PmHostSvc(mContext, this); PluginProcessMain.installHost(mHostSvc); StubProcessManager.schedulePluginProcessLoop(StubProcessManager.CHECK_STAGE1_DELAY); // 兼容即将废弃的p-n方案 by Jiongxuan Zhang mAll = new Builder.PxAll(); Builder.builder(mContext, mAll); // [Newest!] 使用全新的RePlugin APK方案 // Added by Jiongxuan Zhang try { List l = PluginManagerProxy.load(); if (l == null || l.isEmpty()) { //说明是第一次启动,内置信息还没有添加进去,需要添加内置信息,自己单纯的更新到p.l文件,没有执行文件的拷贝和安装,路径还是plugins/xxx.jar //主要是为了让内置插件被Replugin认识,加载的时候,我们做真正的安装(拷贝文件到app_p_a) l = PluginManagerProxy.preInstallBuiltins(mAll.getPlugins()); if (LOG && l != null) { Log.d(TAG_NO_PN, "installBuiltins, plugin size=" + l.size()); } } else { //需要找到内置有更新的插件,然后重新写入p.l文件中 List newestBuiltin = findNewestBuiltin(mAll.getPlugins(), l); if (!newestBuiltin.isEmpty()) { newestBuiltin = PluginManagerProxy.preInstallBuiltins(newestBuiltin); refreshPluginMap(newestBuiltin); } } if (l != null) { // 将"纯APK"插件信息并入总的插件信息表中,方便查询 // 这里有可能会覆盖之前在p-n中加入的信息。本来我们就想这么干,以"纯APK"插件为准 refreshPluginMap(l); } } catch (RemoteException e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "lst.p: " + e.getMessage(), e); } } } /** * 找到内置插件列表中,有更新的插件 * @param builtin 内置插件列表 * @param plList p.l插件列表 */ private List findNewestBuiltin(List builtin, List plList) { List newest = new ArrayList<>(); if (builtin != null) { for (PluginInfo builtinItem : builtin) { boolean found = false; for (PluginInfo plItem : plList) { if (TextUtils.equals(plItem.getName(), builtinItem.getName())) { found = true; if (builtinItem.getVersion() > plItem.getVersion()) { //找到最新版 if (LOGR) { LogRelease.d(TAG_NO_PN, "发现新版内置插件:" + builtinItem); } newest.add(builtinItem); break; } } } //新增的内置插件,也需要写入p.l if (!found) { newest.add(builtinItem); } } } return newest; } /** * Client(UI进程)的初始化 * */ private final void initForClient() { if (LOG) { LogDebug.d(PLUGIN_TAG, "list plugins from persistent process"); } // 1. 先尝试连接,需要注册binder连接断开监听,以便于能重新建立连接 PluginProcessMain.connectToHostSvc(new PluginProcessMain.DiedAction() { @Override public void onDied() { //重新建立连接,然后刷新插件列表 initForClient(); // 最新快照 PluginTable.initPlugins(mPlugins); //重新加载后,需要为当前进程的所有Plugin对象attach上context等属性,避免出现后续插件加载失败 callAttach(); } }); // 2. 然后从常驻进程获取插件列表 refreshPluginsFromHostSvc(); } /** * 从HostSvc(插件管理所在进程)获取所有的插件信息 */ private void refreshPluginsFromHostSvc() { List plugins = null; try { plugins = PluginProcessMain.getPluginHost().listPlugins(); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "lst.p: " + e.getMessage(), e); } } // 将插件信息存入Map refreshPluginMap(plugins); // 判断是否有需要更新的插件 // FIXME 执行此操作前,判断下当前插件的运行进程,具体可以限制仅允许该插件运行在一个进程且为自身进程中 List updatedPlugins = null; if (isNeedToUpdate(plugins)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugins need to perform update operations"); } try { updatedPlugins = PluginManagerProxy.updateAllPlugins(); } catch (RemoteException e) { e.printStackTrace(); } } // 将需要更新的插件更新到Map中 if (updatedPlugins != null) { refreshPluginMap(updatedPlugins); } } /** * 判断列表中是否有需要更新的插件 * * @param plugins 要检查的插件的列表 * @return 是否有需要更新的插件 */ private final boolean isNeedToUpdate(List plugins) { if (plugins != null) { for (PluginInfo info : plugins) { if (info.getJSON().optJSONObject("upinfo") != null) { return true; } } } return false; } /** * 更新所有的插件信息 * * @param plugins */ private final void refreshPluginMap(List plugins) { if (plugins == null) { return; } for (PluginInfo info : plugins) { Plugin plugin = Plugin.build(info); putPluginObject(info, plugin); } } /** * 把插件Add到插件列表 * * @param info 待add插件的PluginInfo对象 * @param plugin 待add插件的Plugin对象 */ private void putPluginObject(PluginInfo info, Plugin plugin) { if (mPlugins.containsKey(info.getAlias()) || mPlugins.containsKey(info.getPackageName())) { if (LOG) { LogDebug.d(PLUGIN_TAG, "当前内置插件列表中已经有" + info.getName() + ",需要看看谁的版本号大。"); } // 找到已经存在的 Plugin existedPlugin = mPlugins.get(info.getPackageName()); if (existedPlugin == null) { existedPlugin = mPlugins.get(info.getAlias()); } if (existedPlugin.mInfo.getVersion() < info.getVersion()) { if (LOG) { LogDebug.d(PLUGIN_TAG, "新传入的纯APK插件, name=" + info.getName() + ", 版本号比较大,ver=" + info.getVersion() + ",以TA为准。"); } // 同时加入PackageName和Alias(如有) mPlugins.put(info.getPackageName(), plugin); if (!TextUtils.isEmpty(info.getAlias())) { // 即便Alias和包名相同也可以再Put一次,反正只是覆盖了相同Value而已 mPlugins.put(info.getAlias(), plugin); } } else { if (LOG) { LogDebug.d(PLUGIN_TAG, "新传入的纯APK插件" + info.getName() + "版本号还没有内置的大,什么都不做。"); } } } else { if (LOG) { LogRelease.i(PLUGIN_TAG, "更新插件,plugin=" + info); } // 同时加入PackageName和Alias(如有) mPlugins.put(info.getPackageName(), plugin); if (!TextUtils.isEmpty(info.getAlias())) { // 即便Alias和包名相同也可以再Put一次,反正只是覆盖了相同Value而已 mPlugins.put(info.getAlias(), plugin); } } } final void attach() { // try { mDefaultPluginName = PluginProcessMain.getPluginHost().attachPluginProcess(IPC.getCurrentProcessName(), PluginManager.sPluginProcessIndex, mClient, mDefaultPluginName); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "c.n.a: " + e.getMessage(), e); } } } final void installBuiltinPlugin(String name, IHostBinderFetcher p) { synchronized (mBuiltinPlugins) { mBuiltinPlugins.put(name, p); } } final void callAttach() { // mClassLoader = PmBase.class.getClassLoader(); // 挂载 for (Plugin p : mPlugins.values()) { p.attach(mContext, mClassLoader, mLocal); } // 加载默认插件 if (PluginManager.isPluginProcess()) { if (!TextUtils.isEmpty(mDefaultPluginName)) { // Plugin p = mPlugins.get(mDefaultPluginName); if (p != null) { boolean rc = p.load(Plugin.LOAD_APP, true); if (!rc) { if (LOG) { LogDebug.d(PLUGIN_TAG, "failed to load default plugin=" + mDefaultPluginName); } } if (rc) { mDefaultPlugin = p; mClient.init(p); } } } } } /** * @param name * @param modc * @param module */ final void addBuiltinModule(String name, Class modc, IModule module) { HashMap modules = mBuiltinModules.get(name); if (modules == null) { modules = new HashMap(); mBuiltinModules.put(name, modules); } modules.put(modc.getName(), module); } final boolean addDynamicClass(String className, String plugin, String type, String target, Class defClass) { if (LOG) { LogDebug.d(PLUGIN_TAG, "addDynamicClass: class=" + className + " plugin=" + plugin + " type=" + type + " target=" + target + " def=" + defClass); } if (mDynamicClasses.containsKey(className)) { return false; } DynamicClass dc = new DynamicClass(); dc.plugin = plugin; dc.classType = type; dc.className = target; dc.defClass = defClass; mDynamicClasses.put(className, dc); return true; } /** * 检查插件的某个类是否是动态注册的 * * @param plugin 插件名称 * @param className 要动态注册的类 * @return 插件的这个类是否是动态类 */ final boolean isDynamicClass(String plugin, String className) { if (!TextUtils.isEmpty(className) && !TextUtils.isEmpty(plugin)) { DynamicClass dc = mDynamicClasses.get(className); if (dc != null) { return plugin.equals(dc.plugin); } } return false; } final void removeDynamicClass(String className) { mDynamicClasses.remove(className); } /** * 返回 className 对应的 插件名称 * * @param className 插件名称 * @return 返回动态注册类对应的插件名称 */ final String getPluginByDynamicClass(String className) { DynamicClass dc = mDynamicClasses.get(className); if (dc != null) { return dc.plugin; } return ""; } final void callAppCreate() { // 计算/获取cookie if (IPC.isPersistentProcess()) { mLocalCookie = PluginProcessMain.getPersistentCookie(); } else { // try { // mLocalCookie = PmCore.fetchPersistentCookie(); // } catch (RuntimeException e) { // // // LogDebug.i(PLUGIN_TAG, "catch exception: " + e.getMessage(), e); // // // String processName = mContext.getApplicationInfo().packageName + MobileSafeApplication.PERSIST_PROCESS_POSFIX; // int uid = mContext.getApplicationInfo().uid; // int flags = 0; // List providers = mContext.getPackageManager().queryContentProviders(processName, uid, flags); // LogDebug.i(PLUGIN_TAG, "providers.size=" + (providers != null ? providers.size() : "null")); // if (providers != null) { // for (ProviderInfo pi : providers) { // LogDebug.i(PLUGIN_TAG, "name=" + pi.name + " auth=" + pi.authority); // } // } // // // throw e; // } } if (LOG) { LogDebug.d(PLUGIN_TAG, "initial local cookie=" + mLocalCookie); } // // 退出监控 // if (IPC.isPersistentProcess()) { // // 异步通知,否则可能发生如下错误: // //Attempt to invoke virtual method 'android.os.Looper android.content.Context.getMainLooper()' on a null object reference // //java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Looper android.content.Context.getMainLooper()' on a null object reference // // at android.os.Parcel.readException(Parcel.java:1546) // // at android.os.Parcel.readException(Parcel.java:1493) // // at com.qihoo360.loader2.IPluginClient$Stub$Proxy.sendIntent(IPluginClient.java:165) // // at com.qihoo360.loader2.PmCore.sendIntent2Process(PmCore.java:386) // // at com.qihoo360.loader2.PluginManager.sendIntent2Process(PluginManager.java:1124) // // at com.qihoo360.loader2.MP.sendLocalBroadcast2All(MP.java:83) // // at com.qihoo360.mobilesafe.ui.index.IPC.sendLocalBroadcast2All(IPC.java:196) // // at com.qihoo360.mobilesafe.api.IPC.sendLocalBroadcast2All(IPC.java:131) // Tasks.post2UI(new Runnable() { // // @Override // public void run() { // Intent intent = new Intent(ACTION_PERSISTENT_NEW_COOKIE); // intent.putExtra(KEY_COOKIE, mLocalCookie); // IPC.sendLocalBroadcast2All(mContext, intent); // } // }); // } // // if (sPluginProcessIndex >= 0 && sPluginProcessIndex < Constant.STUB_PROCESS_COUNT) { // IntentFilter filter = new IntentFilter(ACTION_PERSISTENT_NEW_COOKIE); // LocalBroadcastManager.getInstance(mContext).registerReceiver(new BroadcastReceiver() { // // @Override // public void onReceive(Context context, Intent intent) { // if (ACTION_PERSISTENT_NEW_COOKIE.equals(intent.getAction())) { // long cookie = intent.getLongExtra(KEY_COOKIE, 0); // if (LOG) { // LogDebug.d(PLUGIN_TAG, "received cookie=" + cookie); // } // if (mLocalCookie != cookie) { // if (LOG) { // LogDebug.d(PLUGIN_TAG, "received new cookie=" + cookie + " old=" + mLocalCookie + " quit ..."); // } // // 退出 // System.exit(0); // } // } // } // }, filter); // } if (!IPC.isPersistentProcess()) { // 由于常驻进程已经在内部做了相关的处理,此处仅需要在UI进程注册并更新即可 IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_NEW_PLUGIN); intentFilter.addAction(ACTION_UNINSTALL_PLUGIN); try { LocalBroadcastManager.getInstance(mContext).registerReceiver(mBroadcastReceiver, intentFilter); } catch (Exception e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "p m hlc a r e: " + e.getMessage(), e); } } } } /** * @param className * @param resolve * @return */ final Class loadClass(String className, boolean resolve) { // 加载Service中介坑位 if (className.startsWith(PluginPitService.class.getName())) { if (LOG) { LogDebug.i(TAG, "loadClass: Loading PitService Class... clz=" + className); } return PluginPitService.class; } // if (mContainerActivities.contains(className)) { Class c = mClient.resolveActivityClass(className); if (c != null) { return c; } // 输出warn日志便于查看 // use DummyActivity orig= if (LOGR) { LogRelease.w(PLUGIN_TAG, "p m hlc u d a o " + className); } return DummyActivity.class; } // if (mContainerServices.contains(className)) { Class c = loadServiceClass(className); if (c != null) { return c; } // 输出warn日志便于查看 // use DummyService orig= if (LOGR) { LogRelease.w(PLUGIN_TAG, "p m hlc u d s o " + className); } return DummyService.class; } // if (mContainerProviders.contains(className)) { Class c = loadProviderClass(className); if (c != null) { return c; } // 输出warn日志便于查看 // use DummyProvider orig= if (LOGR) { LogRelease.w(PLUGIN_TAG, "p m hlc u d p o " + className); } return DummyProvider.class; } // 插件定制表 DynamicClass dc = mDynamicClasses.get(className); if (dc != null) { final Context context = RePluginInternal.getAppContext(); PluginDesc desc = PluginDesc.get(dc.plugin); if (LOG) { LogDebug.d("loadClass", "desc=" + desc); if (desc != null) { LogDebug.d("loadClass", "desc.isLarge()=" + desc.isLarge()); } LogDebug.d("loadClass", "RePlugin.isPluginDexExtracted(" + dc.plugin + ") = " + RePlugin.isPluginDexExtracted(dc.plugin)); } // 加载动态类时,如果其对应的插件未下载,则转到代理类 if (desc != null) { String plugin = desc.getPluginName(); if (PluginTable.getPluginInfo(plugin) == null) { if (LOG) { LogDebug.d("loadClass", "plugin=" + plugin + " not found, return DynamicClassProxyActivity.class"); } return DynamicClassProxyActivity.class; } } /* 加载未安装的大插件时,启动一个过度 Activity */ // todo fixme 仅对 activity 类型才弹窗 boolean needStartLoadingActivity = (desc != null && desc.isLarge() && !RePlugin.isPluginDexExtracted(dc.plugin)); if (LOG) { LogDebug.d("loadClass", "needStartLoadingActivity = " + needStartLoadingActivity); } if (needStartLoadingActivity) { Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // fixme 将 PluginLoadingActivity2 移到 replugin 中来,不写死 intent.setComponent(new ComponentName(IPC.getPackageName(), "com.qihoo360.loader2.updater.PluginLoadingActivity2")); context.startActivity(intent); } Plugin p = loadAppPlugin(dc.plugin); if (LOG) { LogDebug.d("loadClass", "p=" + p); } if (p != null) { try { Class cls = p.getClassLoader().loadClass(dc.className); if (needStartLoadingActivity) { // 发广播给过度 Activity,让其关闭 // fixme 发送给 UI 进程 Tasks.postDelayed2Thread(new Runnable() { @Override public void run() { if (LOG) { LogDebug.d("loadClass", "发广播,让 PluginLoadingActivity2 消失"); } IPC.sendLocalBroadcast2All(context, new Intent("com.qihoo360.replugin.load_large_plugin.dismiss_dlg")); } }, 300); // IPC.sendLocalBroadcast2Process(context, IPC.getPersistentProcessName(), new Intent("com.qihoo360.replugin.load_large_plugin.dismiss_dlg"), ) } return cls; } catch (Throwable e) { if (LOGR) { LogRelease.w(PLUGIN_TAG, "p m hlc dc " + className, e); } } } else { if (LOG) { LogDebug.d("loadClass", "加载 " + dc.plugin + " 失败"); } Tasks.postDelayed2Thread(new Runnable() { @Override public void run() { IPC.sendLocalBroadcast2All(context, new Intent("com.qihoo360.replugin.load_large_plugin.dismiss_dlg")); } }, 300); } if (LOGR) { LogRelease.w(PLUGIN_TAG, "p m hlc dc failed: " + className + " t=" + dc.className + " tp=" + dc.classType + " df=" + dc.defClass); } // return dummy class if ("activity".equals(dc.classType)) { return DummyActivity.class; } else if ("service".equals(dc.classType)) { return DummyService.class; } else if ("provider".equals(dc.classType)) { return DummyProvider.class; } return dc.defClass; } // return loadDefaultClass(className); } /** * @param className * @return */ private final Class loadServiceClass(String className) { // Plugin p = mDefaultPlugin; if (p == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin service loader: not found default plugin, in=" + className); } return null; } ServiceInfo services[] = p.mLoader.mPackageInfo.services; if (services == null || services.length <= 0) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin service loader: manifest not item found"); } return null; } String service = services[0].name; ClassLoader cl = p.getClassLoader(); if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin service loader: in=" + className + " target=" + service); } Class c = null; try { c = cl.loadClass(service); } catch (Throwable e) { if (LOG) { LogDebug.d(PLUGIN_TAG, e.getMessage(), e); } } if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin service loader: c=" + c + ", loader=" + cl); } return c; } /** * @param className * @return */ private final Class loadProviderClass(String className) { // Plugin p = mDefaultPlugin; if (p == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin provider loader: not found default plugin, in=" + className); } return null; } ProviderInfo[] providers = p.mLoader.mPackageInfo.providers; if (providers == null || providers.length <= 0) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin provider loader: manifest not item found"); } return null; } String provider = providers[0].name; ClassLoader cl = p.getClassLoader(); if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin provider loader: in=" + className + " target=" + provider); } Class c = null; try { c = cl.loadClass(provider); } catch (Throwable e) { if (LOG) { LogDebug.d(PLUGIN_TAG, e.getMessage(), e); } } if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin provider loader: c=" + c + ", loader=" + cl); } return c; } /** * @param className * @return */ private final Class loadDefaultClass(String className) { // Plugin p = mDefaultPlugin; if (p == null) { if (PluginManager.isPluginProcess()) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin class loader: not found default plugin, in=" + className); } } return null; } ClassLoader cl = p.getClassLoader(); if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin class loader: in=" + className); } Class c = null; try { c = cl.loadClass(className); } catch (Throwable e) { if (LOG) { if (e != null && e.getCause() instanceof ClassNotFoundException) { if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin classloader not found className=" + className); } } else { if (LOG) { LogDebug.d(PLUGIN_TAG, e.getMessage(), e); } } } } if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin class loader: c=" + c + ", loader=" + cl); } return c; } void dump(FileDescriptor fd, PrintWriter writer, String[] args) { if (RePluginInternal.FOR_DEV) { // 是否加载插件指令 { boolean load = false; for (String a : args) { if (load) { Context c = Factory.queryPluginContext(a); writer.println("plugin.c=" + c); return; } if (a.equals("--load")) { load = true; } } } // 是否启动插件进程 { boolean load = false; for (String a : args) { if (load) { try { PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.BINDER_REQUEST); /*IPluginClient client = */MP.startPluginProcess(a, IPluginManager.PROCESS_AUTO, info); } catch (Throwable e) { e.printStackTrace(); } return; } if (a.equals("--start-plugin-process")) { load = true; } } } // dump原因 { for (String a : args) { if (a.equals("--reason")) { writer.println("--- Reason ---"); if (Plugin.sLoadedReasons != null) { for (String reason : Plugin.sLoadedReasons) { writer.println(reason); } } return; } } } // dump binder原因 { for (String a : args) { if (a.equals("--binder-reason")) { writer.println("--- Binder Reason ---"); if (MP.sBinderReasons != null) { for (String key : MP.sBinderReasons.keySet()) { writer.println("binder: " + key); writer.println(MP.sBinderReasons.get(key)); } } return; } } } // 是否启动插件指令 { boolean start = false; String plugin = ""; String activity = ""; for (String a : args) { if (start) { if (TextUtils.isEmpty(plugin)) { plugin = a; continue; } if (TextUtils.isEmpty(activity)) { activity = a; continue; } } if (a.equals("--start")) { start = true; } } if (start) { if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) { Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Factory.startActivity(mContext, intent, plugin, activity, IPluginManager.PROCESS_AUTO); } else { if (LOG) { LogDebug.d(PLUGIN_TAG, "need {plugin} and {activity}"); } } return; } } ReflectUtils.dumpObject(this, fd, writer, args); writer.println(); writer.println("--- plugins V2 ---"); writer.println("--- plugins.size = " + mPlugins.size() + " ---"); for (Plugin p : mPlugins.values()) { writer.println(p.mInfo); } writer.println(); PluginProcessMain.dump(fd, writer, args); writer.println("--- plugins.cached objects ---"); Plugin.dump(fd, writer, args); writer.println(); } } final IBinder getHostBinder() { return mHostSvc; } final boolean isActivity(String name) { return mContainerActivities.contains(name); } final Plugin getPlugin(String plugin) { return mPlugins.get(plugin); } final Plugin loadPackageInfoPlugin(String plugin, PluginCommImpl pm) { Plugin p = Plugin.cloneAndReattach(mContext, mPlugins.get(plugin), mClassLoader, pm); return loadPlugin(p, Plugin.LOAD_INFO, true); } final Plugin loadResourcePlugin(String plugin, PluginCommImpl pm) { Plugin p = Plugin.cloneAndReattach(mContext, mPlugins.get(plugin), mClassLoader, pm); return loadPlugin(p, Plugin.LOAD_RESOURCES, true); } final Plugin loadDexPlugin(String plugin, PluginCommImpl pm) { Plugin p = Plugin.cloneAndReattach(mContext, mPlugins.get(plugin), mClassLoader, pm); return loadPlugin(p, Plugin.LOAD_DEX, true); } final Plugin loadAppPlugin(String plugin) { return loadPlugin(mPlugins.get(plugin), Plugin.LOAD_APP, true); } // 底层接口 final Plugin loadPlugin(PluginInfo pi, PluginCommImpl pm, int loadType, boolean useCache) { Plugin p = Plugin.build(pi); p.attach(mContext, mClassLoader, pm); return loadPlugin(p, loadType, useCache); } // 底层接口 final Plugin loadPlugin(Plugin p, int loadType, boolean useCache) { if (p == null) { return null; } if (!p.load(loadType, useCache)) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "pmb.lp: f to l. lt=" + loadType + "; i=" + p.mInfo); } return null; } return p; } final Plugin lookupPlugin(ClassLoader loader) { for (Plugin p : mPlugins.values()) { if (p != null && p.getClassLoader() == loader) { return p; } } return null; } final void insertNewPlugin(PluginInfo info) { if (LOG) { LogDebug.d(PLUGIN_TAG, "insert new plugin: info=" + info); } synchronized (LOCKER) { // 检查插件是否已经被禁用 if (RePlugin.getConfig().getCallbacks().isPluginBlocked(info)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "insert new plugin: plugin is blocked, in=" + info); } return; } Plugin p = mPlugins.get(info.getName()); // 如果是内置插件,新插件extract成功,则直接替换 // TODO 考虑加锁? if (p != null && p.mInfo.getType() == PluginInfo.TYPE_BUILTIN && info.getType() == PluginInfo.TYPE_PN_INSTALLED) { // next } else if (p != null && p.isInitialized()) { // 检查该插件是否已加载 if (LOG) { LogDebug.d(PLUGIN_TAG, "insert new plugin: failed cause plugin has loaded, plugin=" + info); } // 设置是否需要重启标志 mNeedRestart = true; return; } // 此处直接使用该插件,没有考虑是否只采用最新版 if (LOG) { LogDebug.d(PLUGIN_TAG, "insert new plugin: ok: plugin=" + info); } Plugin plugin = Plugin.build(info); plugin.attach(mContext, mClassLoader, mLocal); // 同时加入PackageName和Alias(如有) putPluginObject(info, plugin); } } final void newPluginFound(PluginInfo info, boolean persistNeedRestart) { if (LOGR) { LogRelease.i(PLUGIN_TAG, "newPluginFound=" + info + ",execute update plugintable"); } // 更新最新插件表 PluginTable.updatePlugin(info); // 更新可加载插件表 insertNewPlugin(info); // 清空插件的状态(解禁) PluginStatusController.setStatus(info.getName(), info.getVersion(), PluginStatusController.STATUS_OK); if (IPC.isPersistentProcess()) { persistNeedRestart = mNeedRestart; } // 输出一个日志 if (LOGR) { LogRelease.i(PLUGIN_TAG, "p.m. n p f n=" + info.getName() + " b1=" + persistNeedRestart + " b2=" + mNeedRestart); } // 通知本进程:通知给外部使用者 Intent intent = new Intent(RePluginConstants.ACTION_NEW_PLUGIN); intent.putExtra(RePluginConstants.KEY_PLUGIN_INFO, (Parcelable) info); intent.putExtra(RePluginConstants.KEY_PERSIST_NEED_RESTART, persistNeedRestart); intent.putExtra(RePluginConstants.KEY_SELF_NEED_RESTART, mNeedRestart); LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent); } final void pluginUninstalled(PluginInfo info) { if (LOG) { LogDebug.d(PLUGIN_TAG, "Clear plugin cache. pn=" + info.getName()); } // 移除卸载插件的HashMap缓存 if (mPlugins.containsKey(info.getName())) { mPlugins.remove(info.getName()); } // 移除卸载插件表快照 PluginTable.removeInfo(info); // 移除内存中插件的PackageInfo、Resources、ComponentList和DexClassLoader缓存对象 Plugin.clearCachedPlugin(Plugin.queryCachedFilename(info.getName())); } final IPluginClient startPluginProcessLocked(String plugin, int process, PluginBinderInfo info) { if (LOG) { LogDebug.d(PLUGIN_TAG, "start plugin process: plugin=" + plugin + " info=" + info); } // 强制使用UI进程 if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) { if (info.request == PluginBinderInfo.ACTIVITY_REQUEST) { if (process == IPluginManager.PROCESS_AUTO) { process = IPluginManager.PROCESS_UI; } } if (info.request == PluginBinderInfo.BINDER_REQUEST) { if (process == IPluginManager.PROCESS_AUTO) { process = IPluginManager.PROCESS_UI; } } } // StubProcessManager.schedulePluginProcessLoop(StubProcessManager.CHECK_STAGE1_DELAY); // 获取 IPluginClient client = PluginProcessMain.probePluginClient(plugin, process, info); if (client != null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "start plugin process: probe client ok, already running, plugin=" + plugin + " client=" + client); } return client; } // 分配 int index = IPluginManager.PROCESS_AUTO; try { index = PluginProcessMain.allocProcess(plugin, process); if (LOG) { LogDebug.d(PLUGIN_TAG, "start plugin process: alloc process ok, plugin=" + plugin + " index=" + index); } } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "a.p.p: " + e.getMessage(), e); } } // 分配的坑位不属于UI、自定义进程或Stub坑位进程,就返回。(没找到有效进程) if (!(index == IPluginManager.PROCESS_UI || PluginProcessHost.isCustomPluginProcess(index) || PluginManager.isPluginProcess(index))) { return null; } // 启动 boolean rc = PluginProviderStub.proxyStartPluginProcess(mContext, index); if (LOG) { LogDebug.d(PLUGIN_TAG, "start plugin process: start process ok, plugin=" + plugin + " index=" + index); } if (!rc) { return null; } // 再次获取 client = PluginProcessMain.probePluginClient(plugin, process, info); if (client == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "spp pc n"); } return null; } if (LOG) { LogDebug.d(PLUGIN_TAG, "start plugin process: probe client ok, plugin=" + plugin + " index=" + info.index); } return client; } final IHostBinderFetcher getBuiltinPlugin(String plugin) { synchronized (mBuiltinPlugins) { return mBuiltinPlugins.get(plugin); } } final HashMap getBuiltinModules(String plugin) { return mBuiltinModules.get(plugin); } final void handleServiceCreated(Service service) { // int pid = Process.myPid(); try { PluginProcessMain.getPluginHost().regService(PluginManager.sPluginProcessIndex, mDefaultPlugin.mInfo.getName(), service.getClass().getName()); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "r.s: " + e.getMessage(), e); } } // // TODO 设置插件服务类的类加载器吗? // Intent intent = service.getIntent(); // if (intent != null) { // if (LOG) { // LogDebug.d(PLUGIN_TAG, "set service intent cl=" + service.getClassLoader()); // } // intent.setExtrasClassLoader(service.getClassLoader()); // } } final void handleServiceDestroyed(Service service) { // int pid = Process.myPid(); try { PluginProcessMain.getPluginHost().unregService(PluginManager.sPluginProcessIndex, mDefaultPlugin.mInfo.getName(), service.getClass().getName()); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "ur.s: " + e.getMessage(), e); } } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/PmHostSvc.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.os.Parcelable; import android.os.RemoteException; import android.text.TextUtils; import com.qihoo360.loader.utils.LocalBroadcastManager; import com.qihoo360.mobilesafe.api.Tasks; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginConstants; import com.qihoo360.replugin.RePluginEventCallbacks; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.base.LocalBroadcastHelper; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.component.receiver.PluginReceiverHelper; import com.qihoo360.replugin.component.receiver.PluginReceiverProxy; import com.qihoo360.replugin.component.service.server.IPluginServiceServer; import com.qihoo360.replugin.component.service.server.PluginServiceServer; import com.qihoo360.replugin.helper.HostConfigHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.packages.IPluginManagerServer; import com.qihoo360.replugin.packages.PluginInfoUpdater; import com.qihoo360.replugin.packages.PluginManagerServer; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.qihoo360.loader2.TaskAffinityStates.TAG; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team */ class PmHostSvc extends IPluginHost.Stub { /** * */ Context mContext; /** * */ PmBase mPluginMgr; /** * */ PluginServiceServer mServiceMgr; /** * 新插件管理器 */ PluginManagerServer mManager; /** * */ private boolean mNeedRestart; PluginReceiverProxy mReceiverProxy; /** * 保存 plugin-receiver -> Receiver 的关系 */ private HashMap mReceivers = new HashMap<>(); /** * 保存 action 与 plugin,receiver 的对应关系 *

* ------------------------------------------------------- * | action | action | * ------------------------------------------------------- * | plugin | plugin | plugin | ... | * ------------------------------------------------------- * | receiver | receiver | receiver | ... | * | receiver | receiver | receiver | ... | * | receiver | receiver | receiver | ... | * | ... | ... | ... | ... | * ------------------------------------------------------- *

* Map < Action, Map < Plugin, List< Receiver >>> */ private final HashMap>> mActionPluginComponents = new HashMap<>(); private static final class BinderDied implements DeathRecipient { String name; IBinder binder; BinderDied(String name, IBinder binder) { this.name = name; this.binder = binder; } @Override public void binderDied() { if (LOG) { LogDebug.d(PLUGIN_TAG, "binder died: n=" + name + " b=" + binder); } synchronized (PluginProcessMain.sBinders) { PluginProcessMain.sBinders.remove(name); } } } PmHostSvc(Context context, PmBase packm) { mContext = context; mPluginMgr = packm; mServiceMgr = new PluginServiceServer(context); mManager = new PluginManagerServer(context); } @Override public void installBinder(String name, IBinder binder) throws RemoteException { if (LOG) { LogDebug.d(PLUGIN_TAG, "install binder: n=" + name + " b=" + binder); } synchronized (PluginProcessMain.sBinders) { if (binder != null) { PluginProcessMain.sBinders.put(name, binder); binder.linkToDeath(new BinderDied(name, binder), 0); } else { PluginProcessMain.sBinders.remove(name); } } } @Override public IBinder fetchBinder(String name) throws RemoteException { IBinder binder = null; synchronized (PluginProcessMain.sBinders) { binder = PluginProcessMain.sBinders.get(name); } if (LOG) { LogDebug.d(PLUGIN_TAG, "fetch binder: n=" + name + name + " b=" + binder); } return binder; } @Override public long fetchPersistentCookie() throws RemoteException { return PluginProcessMain.getPersistentCookie(); } @Override public IPluginClient startPluginProcess(String plugin, int process, PluginBinderInfo info) throws RemoteException { return mPluginMgr.startPluginProcessLocked(plugin, process, info); } @Override public String attachPluginProcess(String process, int index, IBinder binder, String def) throws RemoteException { int pid = Binder.getCallingPid(); IPluginClient client = null; try { client = IPluginClient.Stub.asInterface(binder); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "a.p.p pc.s.ai: " + e.getMessage(), e); } } if (client == null) { return null; } return PluginProcessMain.attachProcess(pid, process, index, binder, client, def, mManager); } @Override public List listPlugins() throws RemoteException { return PluginTable.buildPlugins(); } @Override public void regActivity(int index, String plugin, String container, String activity) throws RemoteException { int pid = Binder.getCallingPid(); PluginProcessMain.attachActivity(pid, index, plugin, activity, container); } @Override public void unregActivity(int index, String plugin, String container, String activity) throws RemoteException { int pid = Binder.getCallingPid(); PluginProcessMain.detachActivity(pid, index, plugin, activity, container); } @Override public void regService(int index, String plugin, String service) throws RemoteException { int pid = Binder.getCallingPid(); PluginProcessMain.attachService(pid, index, plugin, service); } @Override public void unregService(int index, String plugin, String service) throws RemoteException { int pid = Binder.getCallingPid(); PluginProcessMain.detachService(pid, index, plugin, service); } @Override public void regPluginBinder(PluginBinderInfo info, IBinder binder) throws RemoteException { PluginProcessMain.attachBinder(info.pid, binder); } @Override public void unregPluginBinder(PluginBinderInfo info, IBinder binder) throws RemoteException { PluginProcessMain.detachBinder(info.pid, binder); // 通知插件进程,是否需要退出 IPluginClient client = PluginProcessMain.probePluginClientByPid(info.pid, info); if (client == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "unregPluginBinder ... client is null"); } return; } // 通知释放 client.releaseBinder(); } @Override public void regReceiver(String plugin, Map rcvFilMap) throws RemoteException { PluginInfo pi = MP.getPlugin(plugin, false); if (pi == null || pi.getFrameworkVersion() < 4) { return; } if (rcvFilMap == null) { return; } HashMap> receiverFilterMap = (HashMap>) rcvFilMap; // 遍历此插件中所有静态声明的 Receiver for (HashMap.Entry> entry : receiverFilterMap.entrySet()) { if (mReceiverProxy == null) { mReceiverProxy = new PluginReceiverProxy(); mReceiverProxy.setActionPluginMap(mActionPluginComponents); } /* 保存 action-plugin-receiver 的关系 */ String receiver = entry.getKey(); List filters = entry.getValue(); if (filters != null) { for (IntentFilter filter : filters) { int actionCount = filter.countActions(); while (actionCount >= 1) { saveAction(filter.getAction(actionCount - 1), plugin, receiver); actionCount--; } // 注册 Receiver if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { mContext.registerReceiver(mReceiverProxy, filter, Context.RECEIVER_EXPORTED); } else { mContext.registerReceiver(mReceiverProxy, filter); } } } } } @Override public void unregReceiver() throws RemoteException { try { mContext.unregisterReceiver(mReceiverProxy); } catch (Throwable t) { if (LOG) { LogDebug.d(PluginReceiverProxy.TAG, "unregProxyReceiver failed, " + t.toString()); } } } @Override public void onReceive(String plugin, String receiver, Intent intent) { PluginReceiverHelper.onPluginReceiverReceived(plugin, receiver, mReceivers, intent); } @Override public int sumBinders(int index) throws RemoteException { return PluginProcessMain.sumBinders(index); } @Override public void updatePluginInfo(PluginInfo info) throws RemoteException { Plugin p = null; p = mPluginMgr.getPlugin(info.getName()); if (p != null) { p.replaceInfo(info); } PluginTable.replaceInfo(info); } @Override public PluginInfo pluginDownloaded(String path) throws RemoteException { if (LOG) { LogDebug.d(PLUGIN_TAG, "pluginDownloaded: path=" + path); } // 通过路径来判断是采用新方案,还是旧的P-N(即将废弃,有多种)方案 PluginInfo pi; String fn = new File(path).getName(); if (fn.startsWith("p-n-") || fn.startsWith("v-plugin-") || fn.startsWith("plugin-s-") || fn.startsWith("p-m-")) { pi = pluginDownloadedForPn(path); } else { pi = mManager.getService().install(path); } if (pi != null) { // 通常到这里,表示“安装已成功”,这时不管处于什么状态,都应该通知外界更新插件内存表 if (LOG) { LogDebug.d(PLUGIN_TAG, "plugin install success,pi=" + pi); } syncInstalledPluginInfo2All(pi); } return pi; } @Override public boolean pluginUninstalled(PluginInfo info) throws RemoteException { if (LOG) { LogDebug.d(PLUGIN_TAG, "pluginUninstalled: pn=" + info.getName()); } final boolean result = mManager.getService().uninstall(info); // 卸载完成 if (result) { syncUninstalledPluginInfo2All(info); } return result; } private void syncInstalledPluginInfo2All(PluginInfo pi) { if (LOG) { LogDebug.d(PLUGIN_TAG, "syncInstalledPluginInfo2All: pn=" + pi); } // PS:若更新了“正在运行”的插件(属于“下次重启进程后更新”),则由于install返回的是“新的PluginInfo”,为防止出现“错误更新”,需要使用原来的 // // 举例,有一个正在运行的插件A(其Info为PluginInfoOld)升级到新版(其Info为PluginInfoNew),则: // 1. mManager.getService().install(path) 的返回值为:PluginInfoNew // 2. PluginInfoOld在常驻进程中的内容修改为:PluginInfoOld.mPendingUpdate = PendingInfoNew // 3. 同步到各进程,这里存在两种可能: // a) (有问题)同步的是PluginInfoNew,则所有进程的内存表都强制更新到新的Info上,因此【正在运行的】插件信息将丢失,会出现严重问题 // b) (没问题)同步的是PluginInfoOld,只不过这个Old里面有个mPendingUpdate指向PendingInfoNew,则不会有问题,旧的仍被使用,符合预期 // 4. 最终install方法的返回值是PluginInfoNew,这样最外面拿到的就是安装成功的新插件信息,符合开发者的预期 PluginInfo needToSyncPi; PluginInfo parent = pi.getParentInfo(); if (parent != null) { needToSyncPi = parent; } else { needToSyncPi = pi; } // 在常驻进程内更新插件内存表 if (LOG) { LogDebug.d(PLUGIN_TAG, "syncInstalledPluginInfo2All,newPluginFound=" + needToSyncPi); } mPluginMgr.newPluginFound(needToSyncPi, false); // 通知其它进程去更新 Intent intent = new Intent(PmBase.ACTION_NEW_PLUGIN); intent.putExtra(RePluginConstants.KEY_PERSIST_NEED_RESTART, mNeedRestart); intent.putExtra("obj", (Parcelable) needToSyncPi); if (LOG) { LogDebug.d(PLUGIN_TAG, "ACTION_NEW_PLUGIN broadcast start"); } IPC.sendLocalBroadcast2AllSync(mContext, intent); if (LOG) { LogDebug.d(TAG, "syncInstalledPluginInfo2All: Sync complete! syncPi=" + needToSyncPi); } } private void syncUninstalledPluginInfo2All(PluginInfo pi) { // 在常驻进程内更新插件内存表 mPluginMgr.pluginUninstalled(pi); // 给各进程发送广播,同步更新 final Intent intent = new Intent(PluginInfoUpdater.ACTION_UNINSTALL_PLUGIN); intent.putExtra("obj", (Parcelable) pi); // 注意:若在attachBaseContext中调用此方法,则由于此时getApplicationContext为空,导致发送广播时会出现空指针异常。 // 则应该Post一下,待getApplicationContext有值后再发送广播。 if (RePluginInternal.getAppContext().getApplicationContext() != null) { IPC.sendLocalBroadcast2AllSync(RePluginInternal.getAppContext(), intent); } else { Tasks.post2UI(new Runnable() { @Override public void run() { IPC.sendLocalBroadcast2All(RePluginInternal.getAppContext(), intent); } }); } } private PluginInfo pluginDownloadedForPn(String path) { File f = new File(path); V5FileInfo v5f = V5FileInfo.build(f, V5FileInfo.NORMAL_PLUGIN); if (v5f == null) { v5f = V5FileInfo.build(f, V5FileInfo.INCREMENT_PLUGIN); if (v5f == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "pluginDownloaded: unknown v5 plugin file: " + path); } // 插件安装失败 RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.V5_FILE_BUILD_FAIL); return null; } } File ddir = mContext.getDir(Constant.LOCAL_PLUGIN_SUB_DIR, 0); PluginInfo info = v5f.updateV5FileTo(mContext, ddir, false, true); if (info == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "pluginDownloaded: failed to update v5 plugin: " + path); } // 插件安装失败 RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.V5_FILE_UPDATE_FAIL); return null; } return info; } @Override public boolean pluginExtracted(String path) throws RemoteException { if (LOG) { LogDebug.d(PLUGIN_TAG, "pluginExtracted: path=" + path); } // File f = new File(path); PluginInfo info = PluginInfo.build(f); if (info == null) { return false; } // 常驻进程上下文 mPluginMgr.newPluginFound(info, false); // 通知其它进程 Intent intent = new Intent(PmBase.ACTION_NEW_PLUGIN); intent.putExtra(RePluginConstants.KEY_PERSIST_NEED_RESTART, mNeedRestart); intent.putExtra("obj", (Parcelable) info); IPC.sendLocalBroadcast2AllSync(mContext, intent); return true; } @Override public void sendIntent2Process(String target, Intent intent) throws RemoteException { sendIntent2Process(target, intent, false); } @Override public void sendIntent2ProcessSync(String target, Intent intent) throws RemoteException { sendIntent2Process(target, intent, true); } private void sendIntent2Process(String target, Intent intent, boolean sync) throws RemoteException { if (LOG) { LogDebug.d(PLUGIN_TAG, "sendIntent2Process target=" + target + " intent=" + intent); } if (TextUtils.equals(target, IPC.getPluginHostProcessName())) { sendIntent2PluginHostProcess(intent, sync); return; } if (TextUtils.isEmpty(target)) { sendIntent2PluginHostProcess(intent, sync); } PluginProcessMain.sendIntent2Process(target, intent, sync); } private void sendIntent2PluginHostProcess(Intent intent, boolean sync) { intent.setExtrasClassLoader(getClass().getClassLoader()); if (sync) { LocalBroadcastHelper.sendBroadcastSyncUi(mContext, intent); } else { LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent); } } @Override public void sendIntent2Plugin(String target, Intent intent) throws RemoteException { sendIntent2Plugin(target, intent, false); } @Override public void sendIntent2PluginSync(String target, Intent intent) throws RemoteException { sendIntent2Plugin(target, intent, true); } private void sendIntent2Plugin(String target, Intent intent, boolean sync) throws RemoteException { if (LOG) { LogDebug.d(PLUGIN_TAG, "sendIntent2Plugin target=" + target + " intent=" + intent); } // 通知目标插件进程(不包含UI进程) if (!TextUtils.equals(target, Constant.PLUGIN_NAME_UI)) { PluginProcessMain.sendIntent2Plugin(target, intent, sync); } // 如果插件Activity强制运行在UI,则通知UI if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) { target = Constant.PLUGIN_NAME_UI; PluginProcessMain.sendIntent2Plugin(target, intent, sync); } } @Override public boolean isProcessAlive(String name) throws RemoteException { return PluginProcessMain.isProcessAlive(name); } @Override public IBinder queryPluginBinder(String plugin, String binder) throws RemoteException { return mPluginMgr.mLocal.query(plugin, binder); } @Override public IPluginServiceServer fetchServiceServer() throws RemoteException { return mServiceMgr.getService(); } /** * 保存 action-plugin-receiver 的关系 */ private void saveAction(String action, String plugin, String receiver) { HashMap> pluginReceivers = mActionPluginComponents.get(action); if (pluginReceivers == null) { pluginReceivers = new HashMap<>(); mActionPluginComponents.put(action, pluginReceivers); } // 根据插件名称,取 Receiver 集合 List receivers = pluginReceivers.get(plugin); if (receivers == null) { receivers = new ArrayList<>(); pluginReceivers.put(plugin, receivers); } // 添加 Receiver 到 Receiver 集合 if (!receivers.contains(receiver)) { receivers.add(receiver); if (LOG) { LogDebug.d(PluginReceiverProxy.TAG, String.format("保存 Receiver (%s, %s, %s)", action, plugin, receiver)); } } } @Override public List queryPluginsReceiverList(Intent intent) { List infos = new ArrayList<>(); if (intent == null) { return infos; } String action = intent.getAction(); if (TextUtils.isEmpty(action)) { return infos; } Map> pluginReceiverMap = mActionPluginComponents.get(action); if (pluginReceiverMap.isEmpty()) { return infos; } // 根据 action 找到插件的 Receivers for (Map.Entry> entry : pluginReceiverMap.entrySet()) { String plugin = entry.getKey(); // 根据插件名称,找到所有 Receiver ComponentList list = mPluginMgr.mLocal.queryPluginComponentList(plugin); if (list != null) { Map receiversMap = list.getReceiverMap(); if (receiversMap != null) { infos.addAll(receiversMap.values()); } } } return infos; } @Override public IPluginManagerServer fetchManagerServer() throws RemoteException { return mManager.getService(); } /* ------------------------------------------------------- */ /* 由于 TaskAffinity 是可以跨进程的,所以将数据保存在常驻进程 */ /* -------------------------------------------------------- */ /** * TaskAffinity 的组数 */ private static final int GROUP_COUNT = HostConfigHelper.ACTIVITY_PIT_COUNT_TASK; /** * 插件的 TaskAffinity 和 GroupID 的对应关系 */ private static Map mPluginGroupMap = new HashMap<>(); public int getTaskAffinityGroupIndex(String pTaskAffinity) { int index; // 此 taskaffinity 之前不存在 if (!mPluginGroupMap.containsKey(pTaskAffinity)) { index = getValidGroup(); if (index == -1) { // group 不够 if (LOG) { LogDebug.d(TAG, "Get groupID fail, not enough TaskAffinity group"); } return -1; } mPluginGroupMap.put(pTaskAffinity, index); } else { index = mPluginGroupMap.get(pTaskAffinity); } return index; } /** * 获取一个未被占用的 GroupId */ private int getValidGroup() { for (int i = 0; i < GROUP_COUNT; i++) { // groupID i 未被占用 if (!mPluginGroupMap.containsValue(i)) { return i; } } return -1; } @Override public int getPidByProcessName(String processName) throws RemoteException { return PluginProcessMain.getPidByProcessName(processName); } @Override public String getProcessNameByPid(int pid) throws RemoteException { return PluginProcessMain.getProcessNameByPid(pid); } @Override public String dump() { return PluginProcessMain.dump(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/ProcessStates.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; /** * 保存自定义进程中,每个进程里的坑位信息 * * @author RePlugin Team */ class ProcessStates { /** * 保存非默认 TaskAffinity 下,坑位的状态信息。 */ TaskAffinityStates mTaskAffinityStates = new TaskAffinityStates(); /** * 保存默认 TaskAffinity 下,坑位的状态信息。 */ LaunchModeStates mLaunchModeStates = new LaunchModeStates(); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/StubProcessManager.java ================================================ package com.qihoo360.loader2; import android.app.ActivityManager; import android.os.IBinder; import android.text.TextUtils; import com.qihoo360.i.IPluginManager; import com.qihoo360.mobilesafe.api.Tasks; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.base.AMSUtils; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import java.io.PrintWriter; import java.util.List; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team * dec: 坑位进程管理 buyuntao */ public class StubProcessManager { /** * 坑位进程列表 */ static final ProcessRecord STUB_PROCESSES[] = new ProcessRecord[Constant.STUB_PROCESS_COUNT]; static final int CHECK_STAGE1_DELAY = 17 * 1000; private static final int CHECK_STAGE2_DELAY = 11 * 1000; private static final int CHECK_STAGE3_DELAY = 3 * 1000; private static final Runnable CHECK = new Runnable() { @Override public void run() { doPluginProcessLoop(); } }; static { for (int i = 0; i < Constant.STUB_PROCESS_COUNT; i++) { ProcessRecord r = new ProcessRecord(i, StubProcessState.STATE_UNUSED); STUB_PROCESSES[i] = r; } } /** * 分配坑位进程 buyuntao(外部调用端已经加锁) * @param plugin * @return 进程index值 */ static final int allocProcess(String plugin) { if (LOG) { LogDebug.d(PLUGIN_TAG, "alloc plugin process: plugin=" + plugin); } // 取运行列表 List processes = AMSUtils.getRunningAppProcessesNoThrows(RePluginInternal.getAppContext()); // 取运行列表失败,则直接返回失败 if (processes == null || processes.isEmpty()) { if (LOG) { LogDebug.d(PLUGIN_TAG, "alloc plugin process: get running processes is empty"); LogDebug.i(PLUGIN_TAG, "get list exception p=" + plugin); } return IPluginManager.PROCESS_AUTO; } //根据优先级分配坑位进程 int prevMatchPriority = -1; //临时变量,保存上一个ProcessRecord的进程分配优先级 ProcessRecord selectRecord = null; //被选中的坑位进程 for (ProcessRecord r : STUB_PROCESSES) { synchronized (r) { if (r.calculateMatchPriority(plugin) > prevMatchPriority) { prevMatchPriority = r.calculateMatchPriority(plugin); selectRecord = r; } else if (r.calculateMatchPriority(plugin) == prevMatchPriority) { if (r.mobified < selectRecord.mobified) { selectRecord = r; } } } } if (selectRecord == null) { //不应该出现 return IPluginManager.PROCESS_AUTO; } synchronized (selectRecord){ //插件已在分配进程中运行,直接返回 if (selectRecord.calculateMatchPriority(plugin) == Integer.MAX_VALUE && (selectRecord.state == StubProcessState.STATE_ALLOCATED || selectRecord.state == StubProcessState.STATE_RUNNING)) { return selectRecord.index; } selectRecord.resetAllocate(plugin, processes); return selectRecord.index; } } private static final int lookupPluginProcess(List processes, int index) { for (ActivityManager.RunningAppProcessInfo pi : processes) { if (pi.uid != PluginManager.sUid) { continue; } int i = PluginManager.evalPluginProcess(pi.processName); if (i == index) { return pi.pid; } } return -1; } private static final void waitKilled(int pid) { for (int i = 0; i < 10; i++) { try { Thread.sleep(100, 0); } catch (Throwable e) { // } // List processes = AMSUtils.getRunningAppProcessesNoThrows(RePluginInternal.getAppContext()); if (processes == null || processes.isEmpty()) { continue; } boolean found = false; for (ActivityManager.RunningAppProcessInfo info : processes) { if (info.pid == pid) { found = true; } } if (!found) { return; } } } static final void cancelPluginProcessLoop() { if (Constant.SIMPLE_QUIT_CONTROLLER) { Tasks.cancelThreadTask(CHECK); } } /** * @param pid * @param index * @param plugin * @param activity * @param container * @return */ static final boolean attachActivity(int pid, int index, String plugin, String activity, String container) { if (LOG) { LogDebug.d(PLUGIN_TAG, "reg activity: pid=" + pid + " index=" + index + " plugin=" + plugin + " activity=" + activity + " container=" + container); } if (index < 0 || index >= STUB_PROCESSES.length) { if (LOG) { LogDebug.d(PLUGIN_TAG, "reg activity: invalid index=" + index); } return false; } ProcessRecord r = STUB_PROCESSES[index]; synchronized (r){ r.activities++; r.mobified = System.currentTimeMillis(); if (LOG) { LogDebug.d(PLUGIN_TAG, "activities=" + r.activities + " services=" + r.services + " binders=" + r.binders); } } cancelPluginProcessLoop(); return true; } /** * @param pid * @param index * @param plugin * @param activity * @param container * @return */ static final boolean detachActivity(int pid, int index, String plugin, String activity, String container) { if (LOG) { LogDebug.d(PLUGIN_TAG, "unreg activity: pid=" + pid + " index=" + index + " plugin=" + plugin + " activity=" + activity + " container=" + container); } if (index < 0 || index >= STUB_PROCESSES.length) { if (LOG) { LogDebug.d(PLUGIN_TAG, "unreg activity: invalid index=" + index); } return false; } ProcessRecord r = STUB_PROCESSES[index]; synchronized (r){ r.activities--; r.mobified = System.currentTimeMillis(); if (LOG) { LogDebug.d(PLUGIN_TAG, "activities=" + r.activities + " services=" + r.services + " binders=" + r.binders); } } schedulePluginProcessLoop(CHECK_STAGE2_DELAY); return true; } /** * @param pid * @param index * @param plugin * @param service * @return */ static final boolean attachService(int pid, int index, String plugin, String service) { if (LOG) { LogDebug.d(PLUGIN_TAG, "reg service: pid=" + pid + " index=" + index + " plugin=" + plugin + " service=" + service); } if (index < 0 || index >= STUB_PROCESSES.length) { if (LOG) { LogDebug.d(PLUGIN_TAG, "reg service: invalid index=" + index); } return false; } ProcessRecord r = STUB_PROCESSES[index]; synchronized (r) { r.services++; r.mobified = System.currentTimeMillis(); if (LOG) { LogDebug.d(PLUGIN_TAG, "activities=" + r.activities + " services=" + r.services + " binders=" + r.binders); } } cancelPluginProcessLoop(); return true; } /** * @param pid * @param index * @param plugin * @param service * @return */ static final boolean detachService(int pid, int index, String plugin, String service) { if (LOG) { LogDebug.d(PLUGIN_TAG, "unreg service: pid=" + pid + " index=" + index + " plugin=" + plugin + " service=" + service); } if (index < 0 || index >= STUB_PROCESSES.length) { if (LOG) { LogDebug.d(PLUGIN_TAG, "unreg service: invalid index=" + index); } return false; } ProcessRecord r = STUB_PROCESSES[index]; synchronized (r){ r.services--; r.mobified = System.currentTimeMillis(); if (LOG) { LogDebug.d(PLUGIN_TAG, "activities=" + r.activities + " services=" + r.services + " binders=" + r.binders); } } schedulePluginProcessLoop(CHECK_STAGE2_DELAY); return true; } static final void attachBinder(int pid, IBinder binder) { if (LOG) { LogDebug.d(PLUGIN_TAG, "reg binder: pid=" + pid + " binder=" + binder); } for (ProcessRecord r : STUB_PROCESSES) { if (r.pid == pid) { synchronized (r) { r.binders++; r.mobified = System.currentTimeMillis(); if (LOG) { LogDebug.d(PLUGIN_TAG, "activities=" + r.activities + " services=" + r.services + " binders=" + r.binders); } } break; } } cancelPluginProcessLoop(); } static final void detachBinder(int pid, IBinder binder) { if (LOG) { LogDebug.d(PLUGIN_TAG, "unreg binder: pid=" + pid + " binder=" + binder); } for (ProcessRecord r : STUB_PROCESSES) { if (r.pid == pid) { synchronized (r){ r.binders--; r.mobified = System.currentTimeMillis(); if (LOG) { LogDebug.d(PLUGIN_TAG, "activities=" + r.activities + " services=" + r.services + " binders=" + r.binders); } } break; } } schedulePluginProcessLoop(CHECK_STAGE2_DELAY); } static final int sumBinders(int index) { if (index >=0 && index < STUB_PROCESSES.length){ ProcessRecord r = STUB_PROCESSES[index]; synchronized (r) { return STUB_PROCESSES[index].binders; } } return -1; } /** * attach坑位进程,设置坑位进程为运行状态,并返回正在使用坑位进程的插件名称 buyuntao * * @param pid * @param index * @param binder * @param client * @param def * @return */ static final String attachStubProcess(int pid, int index, IBinder binder, IPluginClient client, String def) { // 检测状态是否一致 ProcessRecord r = STUB_PROCESSES[index]; synchronized (r) { if (!TextUtils.isEmpty(def)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "attach process: allocate now"); } r.allocate(def); } if (r.state != StubProcessState.STATE_ALLOCATED) { if (LOG) { LogDebug.d(PLUGIN_TAG, "attach process: state not allocated: state=" + r.state); } return null; } r.setRunning(pid); r.setClient(binder, client); return r.plugin; } } static final void setProcessStop(final IBinder binder) { for (ProcessRecord r : STUB_PROCESSES) { synchronized (r) { if (r.binder == binder) { r.setStoped(); break; } } } } private static final void doPluginProcessLoop() { if (Constant.SIMPLE_QUIT_CONTROLLER) { if (LOG) { LogDebug.d(PLUGIN_TAG, "do plugin process quit check"); } for (ProcessRecord r : STUB_PROCESSES) { synchronized (r) { if (r.state != StubProcessState.STATE_RUNNING) { continue; } if (r.activities > 0) { continue; } if (r.services > 0) { continue; } if (r.binders > 0) { continue; } if (LOGR) { // terminate empty process LogRelease.i(PLUGIN_TAG, "t e p " + r.pid); } // android.os.Process.killProcess(r.pid); waitKilled(r.pid); r.setStoped(); // schedulePluginProcessLoop(CHECK_STAGE3_DELAY); return; } } } } static final void schedulePluginProcessLoop(long delayMillis) { if (Constant.SIMPLE_QUIT_CONTROLLER) { if (LOG) { LogDebug.d(PLUGIN_TAG, "schedule plugin process quit check: delay=" + (delayMillis / 1000)); } Tasks.cancelThreadTask(CHECK); Tasks.postDelayed2Thread(CHECK, delayMillis); } } static final void dump(PrintWriter writer) { writer.println("--- STUB_PROCESSES.length = " + STUB_PROCESSES.length + " ---"); for (ProcessRecord r : STUB_PROCESSES) { synchronized (r){ writer.println(r); } } } /** * 坑位进程的状态 buyuntao */ public class StubProcessState { public static final int STATE_UNUSED = 0; public static final int STATE_ALLOCATED = 1; public static final int STATE_RUNNING = 2; public static final int STATE_STOPED = 4; } private static final class ProcessRecord { final int index; int state; long mobified; String plugin; int pid; IBinder binder; IPluginClient client; int activities; int services; int binders; ProcessRecord(int index, int state) { this.index = index; this.state = state; } void allocate(String plugin) { this.state = StubProcessState.STATE_ALLOCATED; this.mobified = System.currentTimeMillis(); this.plugin = plugin; this.pid = 0; this.binder = null; this.client = null; this.activities = 0; this.services = 0; this.binders = 0; } void setRunning(int pid) { this.state = StubProcessState.STATE_RUNNING; this.pid = pid; } void setClient(IBinder binder, IPluginClient client) { this.binder = binder; this.client = client; } void setStoped() { this.state = StubProcessState.STATE_STOPED; this.pid = 0; this.binder = null; this.client = null; } /** * 当前坑位的选择优先级(值越大被选中的概率越高) * * @param newPluginName * @return 坑位的选择优先级 */ int calculateMatchPriority(String newPluginName) { int priority = Integer.MAX_VALUE; if (TextUtils.equals(newPluginName, plugin)) { //插件可能用过的进程 return priority; } if (state == StubProcessState.STATE_UNUSED) { //空闲的进程 priority = Integer.MAX_VALUE - 1; return priority; } if (state == StubProcessState.STATE_STOPED) { //已停止的进程 priority = Integer.MAX_VALUE - 2; return priority; } if ((System.currentTimeMillis() - mobified) > 10 * 1000) { //分配时间超过10秒的 priority = Integer.MAX_VALUE - 3; return priority; } if ((activities <= 0) && (services <= 0) && (binders <= 0)) { //组件为空的 priority = Integer.MAX_VALUE - 4; return priority; } priority = 0; //默认值 return priority; } void resetAllocate(String plugin, List processes) { killProcess(processes); allocate(plugin); } private void killProcess(List processes) { // 确保进程为空 int pid = lookupPluginProcess(processes, index); if (pid > 0) { if (LOGR) { LogRelease.i(PLUGIN_TAG, "ppr k i: " + pid); } android.os.Process.killProcess(pid); waitKilled(pid); } } @Override public String toString() { if (LOG) { return super.toString() + " {index=" + index + " state=" + state + " mobified=" + mobified + " plugin=" + plugin + " pid=" + pid + " binder=" + binder + " client=" + client + " activities=" + activities + " services=" + services + " binders=" + binders + "}"; } return super.toString(); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/TaskAffinityStates.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.pm.ActivityInfo; import android.os.RemoteException; import com.qihoo360.replugin.helper.HostConfigHelper; import java.util.HashMap; import java.util.HashSet; import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP; /** * @author RePlugin Team */ class TaskAffinityStates { public static final String TAG = "task-affinity"; /** * TaskAffinity 的组数 */ private static final int GROUP_COUNT = HostConfigHelper.ACTIVITY_PIT_COUNT_TASK; /** * 保存所有 taskAffinity 的坑位的状态,数组索引为 TaskAffinity 索引。 */ private LaunchModeStates[] mLaunchModeStates = new LaunchModeStates[GROUP_COUNT]; /** * 初始化 TaskAffinity 的坑位数据 * * @param prefix {applicationID}.loader.a.Activity * @param suffix [N1, P0, P1] * @param allStates 存储在 PluginContainer 中的所有的坑位的状态 * @param containers 所有坑位名称 */ public void init(String prefix, String suffix, HashMap allStates, HashSet containers) { // 外层循环为组数,内层循环为每组的坑的数量 for (int i = 0; i < GROUP_COUNT; i++) { if (mLaunchModeStates[i] == null) { mLaunchModeStates[i] = new LaunchModeStates(); } LaunchModeStates states = mLaunchModeStates[i]; /* Standard */ states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD); states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD); /* SingleTop */ states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP); states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP); /* SingleTask */ states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK); states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK); /* SingleInstance */ states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE); states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE); /* Standard 横屏*/ states.addLandStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD_LAND); states.addLandStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD_LAND); /* SingleTop 横屏*/ states.addLandStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP_LAND); states.addLandStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP_LAND); /* SingleTask 横屏*/ states.addLandStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK_LAND); states.addLandStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK_LAND); } } /** * 根据插件 Activity 的信息,找到宿主对应的坑位集合 */ HashMap getStates(ActivityInfo ai) { if (ai != null) { // 找到应该取第几个 TaskAffinity 中的坑 int index = 0; try { index = MP.getTaskAffinityGroupIndex(ai.taskAffinity); } catch (RemoteException e) { e.printStackTrace(); } LaunchModeStates states = mLaunchModeStates[index]; if (states != null) { return states.getStates(ai.screenOrientation, ai.launchMode, ai.theme); } } return null; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/V5FileInfo.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import com.qihoo360.replugin.utils.CloseableUtils; import com.qihoo360.loader.utils.PackageUtils; import com.qihoo360.loader.utils.StringUtils; import com.qihoo360.replugin.utils.basic.SecurityUtil; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.utils.FileUtils; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Locale; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.MAIN_TAG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team */ public class V5FileInfo { /** * */ public static final int NONE_PLUGIN = 0; /** * */ public static final int NORMAL_PLUGIN = 1; /** * */ public static final int SINGLE_PLUGIN = 2; /** * */ public static final int INCREMENT_PLUGIN = 3; /** * V5复合插件 */ public static final int MULTI_PLUGIN = 4; /** * (通过V5下载的)增量模式的插件 */ private static final String INCREMENT_PLUGIN_FILE_PATTERN = "^v-plugin-([^.-]+).jar$"; /** * (通过V5下载的)非增量模式的single插件 */ private static final String SINGLE_PLUGIN_FILE_PATTERN = "^plugin-s-([^.-]+).jar$"; /** * (通过V5下载的)非增量模式的插件 */ private static final String NORMAL_PLUGIN_FILE_PATTERN = "^p-n-([^.-]+).jar$"; /** * (通过V5下载的)非增量模式的复合插件 */ private static final String MULTI_PLUGIN_FILE_PATTERN = "^p-m-([^.-]+).jar$"; private static final String NORMAL_PREFIX = "p-n-"; private static final String EXTENSION = ".jar"; /** * V5增量文件头 */ private static final int V5_FILE_HEADER_SIZE = 16; private static final Pattern INCREMENT_REGEX; private static final Pattern INCREMENT_SINGLE_REGEX; private static final Pattern NORMAL_REGEX; private static final Pattern MULTI_REGEX; /** * 插件名 */ String mName; /** * 原始路径(V5文件路径),需要转换为加载路径 */ File mFile; /** * 插件类型 */ int mType; static { INCREMENT_REGEX = Pattern.compile(INCREMENT_PLUGIN_FILE_PATTERN); INCREMENT_SINGLE_REGEX = Pattern.compile(SINGLE_PLUGIN_FILE_PATTERN); NORMAL_REGEX = Pattern.compile(NORMAL_PLUGIN_FILE_PATTERN); MULTI_REGEX = Pattern.compile(MULTI_PLUGIN_FILE_PATTERN); } static final String getFileName(String plugin) { return NORMAL_PREFIX + plugin + EXTENSION; } /** * 通过文件名和文件类型,构建V5FileInfo对象 * * @param f * @param type * @return */ static final V5FileInfo build(File f, int type) { Matcher m = null; String fullname = f.getName(); if (type == INCREMENT_PLUGIN) { m = INCREMENT_REGEX.matcher(fullname); } else if (type == SINGLE_PLUGIN) { m = INCREMENT_SINGLE_REGEX.matcher(fullname); } else if (type == MULTI_PLUGIN) { m = MULTI_REGEX.matcher(fullname); } else { m = NORMAL_REGEX.matcher(fullname); } if (m == null || !m.matches()) { if (Constant.LOG_V5_FILE_SEARCH) { if (LOG) { LogDebug.d(PLUGIN_TAG, "V5FileInfo.build: skip, no match1, type=" + type + " file=" + f.getAbsolutePath()); } } return null; } MatchResult r = m.toMatchResult(); if (r == null || r.groupCount() != 1) { if (Constant.LOG_V5_FILE_SEARCH) { if (LOG) { LogDebug.d(PLUGIN_TAG, "V5FileInfo.build: skip, no match2, type=" + type + " file=" + f.getAbsolutePath()); } } return null; } if (!f.exists() || !f.isFile()) { if (Constant.LOG_V5_FILE_SEARCH) { if (LOG) { LogDebug.d(PLUGIN_TAG, "V5FileInfo.build: nor exist or file, file=" + f.getAbsolutePath()); } } return null; } V5FileInfo p = new V5FileInfo(); p.mName = r.group(1); p.mFile = f; p.mType = type; if (LOG) { LogDebug.d(PLUGIN_TAG, "V5FileInfo.build: found plugin, name=" + p.mName + " file=" + f.getAbsolutePath()); } return p; } /** * 根据文件名得到插件名 * * @param fullname * @param type * @return */ public static final String parseName(String fullname, int type) { Matcher m = null; if (type == INCREMENT_PLUGIN) { m = INCREMENT_REGEX.matcher(fullname); } else if (type == SINGLE_PLUGIN) { m = INCREMENT_SINGLE_REGEX.matcher(fullname); } else if (type == MULTI_PLUGIN) { m = MULTI_REGEX.matcher(fullname); } else { m = NORMAL_REGEX.matcher(fullname); } if (m == null || !m.matches()) { return null; } MatchResult r = m.toMatchResult(); if (r == null || r.groupCount() != 1) { return null; } return r.group(1); } /** * 获取插件的名称 * * @return */ public String getName() { return mName; } final PluginInfo updateV5FileTo(Context context, File dir, boolean updateNow, boolean verifyCert) { return updateV5FileTo(context, dir, true, updateNow, verifyCert); } final PluginInfo updateV5FileTo(Context context, File dir, boolean checkOverride, boolean updateNow, boolean verifyCert) { FileInputStream is = null; FileOutputStream os = null; DataInputStream dis = null; try { is = new FileInputStream(mFile); dis = new DataInputStream(is); int pos = 0; // V5头:跳过V5文件头的n个字节 if (mType == INCREMENT_PLUGIN) { dis.skip(V5_FILE_HEADER_SIZE); pos += V5_FILE_HEADER_SIZE; } // 插件基础字段 int low = dis.readInt(); pos += 4; int high = dis.readInt(); pos += 4; int ver = dis.readInt(); pos += 4; String md5 = dis.readUTF(); if (md5.length() != 32) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: invalid md5 length: length=" + md5.length() + " name=" + mName); } return null; } pos += 2 + md5.length(); // 扩展字段 int custom = dis.readInt(); pos += 4; dis.skip(custom); pos += custom; // 文件长度 int length = dis.readInt(); pos += 4; // if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: low=" + low + " high=" + high + " ver=" + ver + " md5=" + md5 + " custom=" + custom + " length=" + length + " name=" + mName); } // 校验文件长度如果不一致则返回,则返回 if (pos + length != mFile.length()) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: invalid length: calc.length=" + (mFile.length() - pos) + " name=" + mName); } return null; } // 如果插件版本太低,则返回 if (low < Constant.ADAPTER_COMPATIBLE_VERSION) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: not supported plugin.low=" + low + " host.compatible.ver=" + Constant.ADAPTER_COMPATIBLE_VERSION + " name=" + mName); } return null; } if (high < low || (high - low) > 1024) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: invalid plugin.high=" + high + " plugin.low=" + low); } return null; } PluginInfo pluginInfo = PluginInfo.build(mName, low, high, ver); if (checkOverride && RePlugin.getConfig().getCallbacks().isPluginBlocked(pluginInfo)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: failed, plugin is blocked, name=" + mName + ",low=" + low + ",high=" + high + ",ver=" + ver); } return null; } // 如果不是立即更新,则延迟再释放 if (!updateNow) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: delay extract f=" + mFile); } return PluginInfo.buildV5(mName, low, high, ver, mType, mFile.getAbsolutePath(), -1, -1, -1, null); } // 目标文件名 File target = new File(dir, PluginInfo.format(mName, low, high, ver) + ".jar"); // 如果目标文件存在且校验MD5一致,表示目标文件已是最新,则跳过 if (target.exists() && target.length() == length) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: checking target ... " + " name=" + mName); } byte rc[] = SecurityUtil.MD5(target); String tmpMD5 = rc != null ? StringUtils.toHexString(rc) : ""; tmpMD5 = tmpMD5.toLowerCase(Locale.ENGLISH); if (md5.equals(tmpMD5)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: target match" + " name=" + mName); } return PluginInfo.build(target); } } if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: extract ..." + " name=" + mName); } // File tmpfile = new File(dir, String.format("%s_plugin.tmp", mName)); FileUtils.copyInputStreamToFile(dis, tmpfile); // 检查结果,如果不成功就删除该文件 boolean deleted = false; // 长度校验 if (tmpfile.length() != length) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: extract file length=" + tmpfile.length() + " expected=" + length); } deleted = true; } // MD5校验 if (!deleted) { byte rc[] = SecurityUtil.MD5(tmpfile); String tmpMD5 = rc != null ? StringUtils.toHexString(rc) : ""; tmpMD5 = tmpMD5.toLowerCase(Locale.ENGLISH); if (!md5.equals(tmpMD5)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: extract=" + tmpMD5 + " orig=" + md5 + ", delete tmpfile" + " name=" + mName); } deleted = true; } } // 证书校验 if (!deleted) { PackageManager pm = context.getPackageManager(); PackageInfo info = null; try { info = PackageUtils.getPackageArchiveInfo(pm, tmpfile.getAbsolutePath(), PackageManager.GET_SIGNATURES); } catch (Throwable e) { if (LOG) { LogDebug.d(PLUGIN_TAG, e.getMessage(), e); } } if (info == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: can't fetch package info: " + " name=" + mName); } deleted = true; } if (verifyCert) { // 无论Debug还是Release都做下签名校验。只不过Debug下不会删除"校验失败"的文件 if (!CertUtils.isPluginSignatures(info)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: invalid cert: " + " name=" + mName); } if (LOGR) { LogRelease.e(PLUGIN_TAG, "uv5p ic n=" + mName); } // 只有Release环境才会删除"校验失败"的文件 if (!RePluginInternal.FOR_DEV) { deleted = true; } } } if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: package=" + (info != null ? info.packageName : "") + " delete=" + (deleted ? "true" : "false") + " name=" + mName); } } // 释放Native(SO)库文件 // Added by Jiongxuan Zhang // Jar和Dex都释放完了,到这一步,基本上可以创建PluginInfo对象了 PluginInfo pi = PluginInfo.build(target); if (pi == null) { // 不太可能走到这里,因为target已经被验证过了 deleted = true; } if (!deleted) { File libDir = pi.getNativeLibsDir(); if (!PluginNativeLibsHelper.install(tmpfile.getAbsolutePath(), libDir)) { // 释放失败,删除插件文件 deleted = true; } } if (deleted) { FileUtils.forceDelete(tmpfile); return null; } if (LOG) { LogDebug.d(PLUGIN_TAG, "update v5 plugin: extract success" + " name=" + mName); } if (target.exists()) { FileUtils.forceDelete(target); } // 更名 FileUtils.moveFile(tmpfile, target); return pi; } catch (Throwable e) { if (LOG) { LogDebug.d(PLUGIN_TAG, e.getMessage(), e); } } finally { CloseableUtils.closeQuietly(is); CloseableUtils.closeQuietly(dis); } return null; } /** * 查看是否有相应的V5插件存在(不管是否生效) *

* 供V5升级新逻辑使用 * * @param context * @param pName * @return */ public static PluginInfo fetchPluginInfo(Context context, String pName) { File f = new File(context.getFilesDir(), "p-n-" + pName + ".jar"); if (LOG) { LogDebug.d(MAIN_TAG, "needUpdate(): local file = " + f.getAbsolutePath()); } if (!f.exists()) { if (LOG) { LogDebug.d(MAIN_TAG, "needUpdate(): file is not exists, file = " + f.getAbsolutePath()); } return null; } V5FileInfo p = V5FileInfo.build(f, V5FileInfo.NORMAL_PLUGIN); if (p == null) { p = V5FileInfo.build(f, V5FileInfo.INCREMENT_PLUGIN); } if (p == null) { p = V5FileInfo.build(f, V5FileInfo.MULTI_PLUGIN); } if (LOG) { LogDebug.d(MAIN_TAG, "needUpdate(): localFileInfo = " + p); } if (p != null) { return p.updateV5FileTo(context, context.getDir(Constant.LOCAL_PLUGIN_SUB_DIR, 0), false, false, false); } return null; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/V5Finder.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.content.Context; import com.qihoo360.loader.utils.ProcessLocker; import com.qihoo360.loader2.Builder.PxAll; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import java.io.File; import java.util.ArrayList; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * @author RePlugin Team */ public class V5Finder { static final void search(Context context, File pluginDir, PxAll all) { // 扫描V5下载目录 ArrayList v5Plugins = new ArrayList(); { File dir = RePlugin.getConfig().getPnInstallDir(); if (LOG) { LogDebug.d(PLUGIN_TAG, "search v5 files: dir=" + dir.getAbsolutePath()); } searchV5Plugins(dir, v5Plugins); } // 同步V5原始插件文件到插件目录 for (V5FileInfo p : v5Plugins) { ProcessLocker lock = new ProcessLocker(RePluginInternal.getAppContext(), p.mFile.getParent(), p.mFile.getName() + ".lock"); /** * 此处逻辑的详细介绍请参照 * * @see com.qihoo360.loader2.MP.pluginDownloaded(String path) */ if (lock.isLocked()) { // 插件文件不可用,直接跳过 continue; } PluginInfo info = p.updateV5FileTo(context, pluginDir, false, true); // 已检查版本 if (info == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "search: fail to update v5 plugin"); } } else { all.addV5(info); } } } private static final void searchV5Plugins(File dir, ArrayList plugins) { File files[] = dir.listFiles(); if (files == null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "search v5 plugin: nothing"); } return; } for (File f : files) { if (f.isDirectory()) { continue; } if (f.length() <= 0) { continue; } V5FileInfo p = null; p = V5FileInfo.build(f, V5FileInfo.NORMAL_PLUGIN); if (p != null) { plugins.add(p); continue; } p = V5FileInfo.build(f, V5FileInfo.INCREMENT_PLUGIN); if (p != null) { plugins.add(p); continue; } } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/VMRuntimeCompat.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2; import android.os.Build; import com.qihoo360.mobilesafe.core.BuildConfig; import java.lang.reflect.Method; /** * 通过反射 VMRuntime.is64Bit() 获取是否为64位 * * @author RePlugin Team */ public class VMRuntimeCompat { private static final byte[] GET_LOCKER = new byte[0]; private static volatile Boolean sIs64Bit; /** * 精确判断是否为64位 */ public static boolean is64Bit() { // 最终使用下列方法: // VMRuntime.getRuntime().is64Bit(); if (sIs64Bit != null) { return sIs64Bit; } synchronized (GET_LOCKER) { if (sIs64Bit != null) { return sIs64Bit; } // 确保只获取一次。但不排除个别手机一上来获取会有问题(没遇到) sIs64Bit = is64BitImpl(); return sIs64Bit; } } private static boolean is64BitImpl() { try { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // Android API 21之前不支持64位CPU return false; } Class clzVMRuntime = Class.forName("dalvik.system.VMRuntime"); if (clzVMRuntime == null) { return false; } Method mthVMRuntimeGet = clzVMRuntime.getDeclaredMethod("getRuntime"); if (mthVMRuntimeGet == null) { return false; } Object objVMRuntime = mthVMRuntimeGet.invoke(null); if (objVMRuntime == null) { return false; } Method sVMRuntimeIs64BitMethod = clzVMRuntime.getDeclaredMethod("is64Bit"); if (sVMRuntimeIs64BitMethod == null) { return false; } Object objIs64Bit = sVMRuntimeIs64BitMethod.invoke(objVMRuntime); if (objIs64Bit instanceof Boolean) { return (boolean) objIs64Bit; } } catch (Throwable e) { if (BuildConfig.DEBUG) { e.printStackTrace(); } } return false; } /** * Art虚拟机,引入AOT编译后,读取oat目录下当前正在使用的目录 * TODO 目前仅支持arm * * @return */ public static String getArtOatCpuType() { return VMRuntimeCompat.is64Bit() ? BuildCompat.ARM64 : BuildCompat.ARM; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/alc/ActivityController.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2.alc; import android.app.Activity; import android.app.Application; import android.os.Build; import android.os.RemoteException; import com.qihoo360.replugin.helper.LogDebug; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Map; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.MAIN_TAG; /** * @author RePlugin Team */ public final class ActivityController { private static IActivityWatcher.Stub sStub; private static InvocationHandler sHandler; private static Map sActivityThreadActivities; private static Map sActivityThreadServices; private static ArrayList> sActivities; /** * TODO 优化 */ private static IActivityUpdate sListener; /** * */ public static final void init() { loadVar(); } public static final void install(Application application) { if (Build.VERSION.SDK_INT < 14) { if (LOG) { LogDebug.d(MAIN_TAG, "install activity watcher"); } install2x(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "install activity lifecycle callbacks"); } install4x(application); } /** * TODO 优化 */ public static final void setListener(IActivityUpdate listener) { sListener = listener; } public static final int sumActivities() { int sum = -1; if (sActivities != null) { sum = sActivities.size(); } else if (sActivityThreadActivities != null) { sum = sActivityThreadActivities.size(); } if (LOG) { LogDebug.d(MAIN_TAG, "process sumActivities = " + sum); } return sum; } private static final void install2x() { // sStub = new IActivityWatcher.Stub() { @Override public void closingSystemDialogs(String reason) throws RemoteException { } @Override public void activityResuming(int activityId) throws RemoteException { int activityCount = -1; if (sActivityThreadActivities != null) { activityCount = sActivityThreadActivities.size(); } int serviceCount = -1; if (sActivityThreadServices != null) { serviceCount = sActivityThreadServices.size(); } if (LOG) { LogDebug.d(MAIN_TAG, "activityResuming: activities=" + activityCount + " services=" + serviceCount); } if (sListener != null) { sListener.handleActivityUpdate(); } } }; Class clsAMN = null; Class clsIAW = null; try { clsAMN = Class.forName("android.app.ActivityManagerNative"); clsIAW = Class.forName("android.app.IActivityWatcher"); } catch (ClassNotFoundException e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "AMN=" + clsAMN + " IAW=" + clsIAW); } Method m1 = null; Method m2 = null; try { m1 = clsAMN.getDeclaredMethod("getDefault"); m2 = clsAMN.getMethod("registerActivityWatcher", clsIAW); } catch (SecurityException e) { e.printStackTrace(); return; } catch (NoSuchMethodException e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "getDefault=" + m1 + " registerActivityWatcher=" + m2); } Object oAMN = null; try { oAMN = m1.invoke(null); m2.invoke(oAMN, sStub); } catch (IllegalArgumentException e) { e.printStackTrace(); return; } catch (IllegalAccessException e) { e.printStackTrace(); return; } catch (InvocationTargetException e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "oAMN=" + oAMN); } } private static final void install4x(Application application) { // sHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("onActivityCreated".equals(method.getName())) { if (args.length > 0 && args[0] instanceof Activity) { Activity activity = (Activity) args[0]; WeakReference ref = new WeakReference(activity); sActivities.add(ref); if (LOG) { LogDebug.d(MAIN_TAG, "onActivityCreated: a=" + activity + " total=" + sActivities.size()); } } } else if ("onActivityDestroyed".equals(method.getName())) { if (args.length > 0 && args[0] instanceof Activity) { Activity activity = (Activity) args[0]; for (int index = sActivities.size() - 1; index >= 0; index--) { Activity a = sActivities.get(index).get(); if (a == activity || a == null) { sActivities.remove(index); } } if (LOG) { LogDebug.d(MAIN_TAG, "onActivityDestroyed: a=" + activity + " total=" + sActivities.size()); } if (sListener != null) { sListener.handleActivityUpdate(); } } } return null; } }; // sActivities = new ArrayList>(); Class appClass = null; Class cbClass = null; try { appClass = Class.forName("android.app.Application"); cbClass = Class.forName("android.app.Application$ActivityLifecycleCallbacks"); } catch (ClassNotFoundException e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "install activity lifecycle callbacks: class=" + cbClass); } Method m = null; try { m = appClass.getDeclaredMethod("registerActivityLifecycleCallbacks", cbClass); } catch (SecurityException e) { e.printStackTrace(); return; } catch (NoSuchMethodException e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "install activity lifecycle callbacks: m=" + m); } Object cb = Proxy.newProxyInstance(ActivityController.class.getClassLoader(), new Class[]{cbClass}, sHandler); if (LOG) { LogDebug.d(MAIN_TAG, "install activity lifecycle callbacks: cb=" + cb); } try { m.invoke(application, cb); } catch (IllegalArgumentException e) { e.printStackTrace(); return; } catch (IllegalAccessException e) { e.printStackTrace(); return; } catch (InvocationTargetException e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "install activity lifecycle callbacks: ok"); } } private static final void loadVar() { Class clsAT = null; try { clsAT = Class.forName("android.app.ActivityThread"); } catch (ClassNotFoundException e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "clsAT=" + clsAT); } Method m0 = null; try { m0 = clsAT.getDeclaredMethod("currentActivityThread"); } catch (SecurityException e) { e.printStackTrace(); return; } catch (NoSuchMethodException e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "currentActivityThread=" + m0); } Object oAT = null; try { oAT = m0.invoke(null); } catch (IllegalArgumentException e) { e.printStackTrace(); return; } catch (IllegalAccessException e) { e.printStackTrace(); return; } catch (InvocationTargetException e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "oAT=" + oAT); } Field f1 = null; Field f2 = null; try { f1 = clsAT.getDeclaredField("mActivities"); f2 = clsAT.getDeclaredField("mServices"); } catch (SecurityException e) { e.printStackTrace(); return; } catch (NoSuchFieldException e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "AT f1=" + f1 + " f2=" + f2); } Object o1 = null; Object o2 = null; try { f1.setAccessible(true); o1 = f1.get(oAT); f2.setAccessible(true); o2 = f2.get(oAT); } catch (IllegalArgumentException e) { e.printStackTrace(); return; } catch (IllegalAccessException e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "AT activities=" + o1 + " services=" + o2); } try { sActivityThreadActivities = (Map) o1; sActivityThreadServices = (Map) o2; } catch (Throwable e) { e.printStackTrace(); return; } if (LOG) { LogDebug.d(MAIN_TAG, "converted: activities=" + sActivityThreadActivities + " services=" + sActivityThreadServices); } } public interface IActivityUpdate { void handleActivityUpdate(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/alc/IActivityWatcher.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ /* * This file is auto-generated. DO NOT MODIFY. * Original file: D:\\work\\p\\mobilesafe_v6_frame\\main\\src\\android\\app\\IActivityWatcher.aidl */ package com.qihoo360.loader2.alc; /** * Callback interface to watch the user's traversal through activities. * {@hide} */ public interface IActivityWatcher extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public abstract static class Stub extends android.os.Binder implements IActivityWatcher { private static final java.lang.String DESCRIPTOR = "android.app.IActivityWatcher"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_ACTIVITY_RESUMING: { data.enforceInterface(DESCRIPTOR); int arg0; arg0 = data.readInt(); this.activityResuming(arg0); return true; } case TRANSACTION_CLOSING_SYSTEM_DIALOGS: { data.enforceInterface(DESCRIPTOR); java.lang.String arg0; arg0 = data.readString(); this.closingSystemDialogs(arg0); return true; } } return super.onTransact(code, data, reply, flags); } static final int TRANSACTION_ACTIVITY_RESUMING = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_CLOSING_SYSTEM_DIALOGS = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public void activityResuming(int activityId) throws android.os.RemoteException; public void closingSystemDialogs(java.lang.String reason) throws android.os.RemoteException; } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/mgr/PluginProviderClient.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2.mgr; import android.annotation.TargetApi; import android.content.ContentProviderClient; import android.content.ContentValues; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import java.io.InputStream; import java.io.OutputStream; /** * (该类仅为兼容360手机卫士的旧插件而存在,因涉及到反射而保留此类) * * @deprecated 请使用新类 * @see com.qihoo360.replugin.component.provider.PluginProviderClient * @author RePlugin Team */ public class PluginProviderClient { /** * @deprecated */ public static ContentProviderClient acquireContentProviderClient(Context c, String name) { return com.qihoo360.replugin.component.provider.PluginProviderClient.acquireContentProviderClient(c, name); } /** * @deprecated */ public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return com.qihoo360.replugin.component.provider.PluginProviderClient.query(c, uri, projection, selection, selectionArgs, sortOrder); } /** * @deprecated */ @TargetApi(16) public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { return com.qihoo360.replugin.component.provider.PluginProviderClient.query(c, uri, projection, selection, selectionArgs, sortOrder, cancellationSignal); } /** * @deprecated */ public static String getType(Context c, Uri uri) { return com.qihoo360.replugin.component.provider.PluginProviderClient.getType(c, uri); } /** * @deprecated */ public static Uri insert(Context c, Uri uri, ContentValues values) { return com.qihoo360.replugin.component.provider.PluginProviderClient.insert(c, uri, values); } /** * @deprecated */ public static int bulkInsert(Context c, Uri uri, ContentValues[] values) { return com.qihoo360.replugin.component.provider.PluginProviderClient.bulkInsert(c, uri, values); } /** * @deprecated */ public static int delete(Context c, Uri uri, String selection, String[] selectionArgs) { return com.qihoo360.replugin.component.provider.PluginProviderClient.delete(c, uri, selection, selectionArgs); } /** * @deprecated */ public static int update(Context c, Uri uri, ContentValues values, String selection, String[] selectionArgs) { return com.qihoo360.replugin.component.provider.PluginProviderClient.update(c, uri, values, selection, selectionArgs); } /** * @deprecated */ public static InputStream openInputStream(Context c, Uri uri) { return com.qihoo360.replugin.component.provider.PluginProviderClient.openInputStream(c, uri); } /** * @deprecated */ public static OutputStream openOutputStream(Context c, Uri uri) { return com.qihoo360.replugin.component.provider.PluginProviderClient.openOutputStream(c, uri); } /** * @deprecated */ public static OutputStream openOutputStream(Context c, Uri uri, String mode) { return com.qihoo360.replugin.component.provider.PluginProviderClient.openOutputStream(c, uri, mode); } /** * @deprecated */ public static ParcelFileDescriptor openFileDescriptor(Context c, Uri uri, String mode) { return com.qihoo360.replugin.component.provider.PluginProviderClient.openFileDescriptor(c, uri, mode); } /** * @deprecated */ @TargetApi(Build.VERSION_CODES.KITKAT) public static ParcelFileDescriptor openFileDescriptor(Context c, Uri uri, String mode, CancellationSignal cancellationSignal) { return com.qihoo360.replugin.component.provider.PluginProviderClient.openFileDescriptor(c, uri, mode, cancellationSignal); } /** * @deprecated */ public static void registerContentObserver(Context c, Uri uri, boolean notifyForDescendents, ContentObserver observer) { com.qihoo360.replugin.component.provider.PluginProviderClient.registerContentObserver(c, uri, notifyForDescendents, observer); } /** * @deprecated */ public static void notifyChange(Context c, Uri uri, ContentObserver observer) { com.qihoo360.replugin.component.provider.PluginProviderClient.notifyChange(c, uri, observer); } /** * @deprecated */ public static void notifyChange(Context c, Uri uri, ContentObserver observer, boolean b) { com.qihoo360.replugin.component.provider.PluginProviderClient.notifyChange(c, uri, observer, b); } /** * @deprecated */ public static Uri toCalledUri(Context c, Uri uri) { return com.qihoo360.replugin.component.provider.PluginProviderClient.toCalledUri(c, uri); } /** * @deprecated */ public static Uri toCalledUri(Context context, String plugin, Uri uri, int process) { return com.qihoo360.replugin.component.provider.PluginProviderClient.toCalledUri(context, plugin, uri, process); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/mgr/PluginServiceClient.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2.mgr; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; /** * (该类仅为兼容360手机卫士的旧插件而存在,因涉及到反射而保留此类) * * @deprecated 请使用新类 * @see com.qihoo360.replugin.component.service.PluginServiceClient * @author RePlugin Team */ public class PluginServiceClient { /** * @deprecated */ public static ComponentName startService(Context context, Intent intent) { return com.qihoo360.replugin.component.service.PluginServiceClient.startService(context, intent); } /** * @deprecated */ public static boolean stopService(Context context, Intent intent) { return com.qihoo360.replugin.component.service.PluginServiceClient.stopService(context, intent); } /** * @deprecated */ public static boolean bindService(Context context, Intent intent, ServiceConnection sc, int flags) { return com.qihoo360.replugin.component.service.PluginServiceClient.bindService(context, intent, sc, flags); } /** * @deprecated */ public static boolean unbindService(Context context, ServiceConnection sc) { return com.qihoo360.replugin.component.service.PluginServiceClient.unbindService(context, sc); } /** * @deprecated */ public static void stopSelf(Service s) { com.qihoo360.replugin.component.service.PluginServiceClient.stopSelf(s); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/loader2/sp/PrefImpl.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.loader2.sp; import android.os.Bundle; import android.os.RemoteException; import com.qihoo360.replugin.helper.LogDebug; import java.util.HashMap; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * @author RePlugin Team */ public final class PrefImpl extends IPref.Stub { HashMap mBundles = new HashMap(); private Bundle load(String category) { synchronized (mBundles) { Bundle bundle = mBundles.get(category); if (bundle == null) { bundle = new Bundle(); mBundles.put(category, bundle); } return bundle; } } @Override public String get(String category, String key, String defValue) throws RemoteException { Bundle bundle = load(category); if (LOG) { LogDebug.d(PLUGIN_TAG, "get: category=" + category + " bundle=" + bundle + " key=" + key); } if (bundle.containsKey(key)) { return bundle.getString(key); } return defValue; } @Override public void set(String category, String key, String value) throws RemoteException { Bundle bundle = load(category); if (LOG) { LogDebug.d(PLUGIN_TAG, "set: category=" + category + " bundle=" + bundle + " key=" + key + " value=" + value); } bundle.putString(key, value); } @Override public Bundle getAll(String category) throws RemoteException { Bundle bundle = load(category); if (LOG) { LogDebug.d(PLUGIN_TAG, "getAll: category=" + category + " bundle=" + bundle); } return bundle; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/api/AppVar.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.api; import android.content.Context; /** * @author RePlugin Team */ public class AppVar { /** * API模块内部使用,记得初始化 */ public static Context sAppContext; } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/api/IPC.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.api; import android.app.ActivityManager.RunningAppProcessInfo; import android.content.Context; import android.content.Intent; import android.text.TextUtils; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.base.AMSUtils; import java.util.List; /** * 包装类,封装一些跨进程通讯的接口 * * @author RePlugin Team */ public final class IPC { public static final int getUIProcessPID(Context c) { String pkg = c.getApplicationInfo().packageName; return getRunningProcessPID(c, pkg); } public static final int getRunningProcessPID(Context c, String processName) { List processes = AMSUtils.getRunningAppProcessesNoThrows(c); if (processes != null) { for (RunningAppProcessInfo appInfo : processes) { if (TextUtils.equals(appInfo.processName, processName)) { return appInfo.pid; } } } return 0; } /** * 判断当前包名是否在“运行进程列表”中 * @param packageName * @return */ public static final boolean isRunningProcess(String packageName) { List processes = AMSUtils.getRunningAppProcessesNoThrows(RePluginInternal.getAppContext()); if (processes != null) { for (RunningAppProcessInfo appInfo : processes) { if (TextUtils.equals(appInfo.processName, packageName)) { return true; } } } return false; } /** * @return */ public static final String getCurrentProcessName() { return com.qihoo360.replugin.base.IPC.getCurrentProcessName(); } // 双进程使用, 判断目前进程是否是常驻进程 public static final boolean isPersistentProcess() { return com.qihoo360.replugin.base.IPC.isPersistentProcess(); } /** * 双进程使用, 判断目前进程是否是UI进程 * @return */ public static final boolean isUIProcess() { return com.qihoo360.replugin.base.IPC.isUIProcess(); } /** * 多进程使用, 将intent送到目标进程,对方将受到Local Broadcast * 只有当目标进程存活时才能将数据送达 * 常驻进程通过Local Broadcast注册处理代码 * @param c * @param target * @param intent */ public static final void sendLocalBroadcast2Process(Context c, String target, Intent intent) { com.qihoo360.replugin.base.IPC.sendLocalBroadcast2Process(c, target, intent); } /** * 多进程使用, 将intent送到目标插件所在进程,对方将受到Local Broadcast * 只有当目标进程存活时才能将数据送达 * 常驻进程通过Local Broadcast注册处理代码 * @param c * @param target * @param intent */ public static final void sendLocalBroadcast2Plugin(Context c, String target, Intent intent) { com.qihoo360.replugin.base.IPC.sendLocalBroadcast2Plugin(c, target, intent); } /** * 发送广播到所有卫士进程(底层已实现权限控制,不会被第三方APP污染) * 只有当目标进程存活时才能将数据送达 * 卫士任意进程(非single插件进程)只需要通过{@code LocalBroadcastManager}注册即可收到 * @param c * @param intent */ public static final void sendLocalBroadcast2All(Context c, Intent intent) { com.qihoo360.replugin.base.IPC.sendLocalBroadcast2All(c, intent); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/api/Intents.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.api; /** * @author RePlugin Team */ public class Intents { public static final String SCREEN_ON = "com.qihoo360.mobilesafe.api.SCREEN_ON"; public static final String SCREEN_OFF = "com.qihoo360.mobilesafe.api.SCREEN_OFF"; public static final String ACTIVITY_EVENT = "com.qihoo360.mobilesafe.api.ACTIVITY_EVENT"; /** * 当应用被安装时会收到此通知 * 该值有个Extra,参见 PACKAGE_KEY_INTENT的说明 */ public static final String PACKAGE_ADDED = "com.qihoo360.mobilesafe.api.PACKAGE_ADDED"; /** * 当应用被卸载时会收到此通知 * 该值有个Extra,参见 PACKAGE_KEY_INTENT的说明 */ public static final String PACKAGE_REMOVED = "com.qihoo360.mobilesafe.api.PACKAGE_REMOVED"; /** * 当应用被修改时(包括安装、卸载、升级等)会收到此通知 * 该值有个Extra,参见 PACKAGE_KEY_INTENT的说明 */ public static final String PACKAGE_ALL = "com.qihoo360.mobilesafe.api.PACKAGE_ALL"; /** * 针对PACKAGE_*这三个事件,可直接通过intent.getParcelableExtra来获取系统的Intent * 进而获取到如intent.getData(包名信息)、Intent.EXTRA_REPLACING(是否升级的)等的值。 * * @since 6.5.0 */ public static final String PACKAGE_KEY_INTENT = "intent"; /** * TopActivity 改变 * 这个Action不能随便修改, 因为插件也在使用. * * @since 6.5.0 */ public static final String ACTIVITY_CHANGE_EVENT = "com.qihoo360.mobilesafe.api.ACTIVITY_CHANGE_EVENT"; /** * 修改 ActivityMonitor 轮询间隔 * * @since 7.0.0 */ public static final String CHANGE_ACTIVITY_MONITOR_INTERVAL = "com.qihoo360.mobilesafe.api.CHANGE_ACTIVITY_MONITOR_INTERVAL"; /** * 修改 ActivityMonitor 轮询间隔时, 传递过来的时间间隔, 单位毫秒 * * @since 7.0.0 */ public static final String CHANGE_ACTIVITY_MONITOR_KEY_INTERVAL = "interval"; /** * 手机内存占用 改变 * 这个Action不能随便修改, 因为插件也在使用. */ public static final String MEM_CHANGE_EVENT = "com.qihoo360.mobilesafe.api.MEM_CHANGE_EVENT"; /** * 引导界面显示时间结束,可以进行权限弹窗的任务(用户点击完成,或者超过显示超过一定时间如10分钟,界面没在后台,特定机型) * 通过LocalBroadcast注册 */ public static final String PERM_TASK_ALLOWED = "com.qihoo360.mobilesafe.api.PERM_TASK_ALLOWED"; } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/api/Pref.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.api; import android.content.Context; import android.content.SharedPreferences; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginInternal; import java.util.Map; /** * @author RePlugin Team */ public final class Pref { public static SharedPreferences getSharedPreferences(String name) { return RePlugin.getConfig().getCallbacks().getSharedPreferences(RePluginInternal.getAppContext(), name, Context.MODE_PRIVATE); } public static SharedPreferences getDefaultSharedPreferences() { return getSharedPreferences(RePluginInternal.getAppContext().getPackageName() + "_preferences"); } public static SharedPreferences getTempSharedPreferences(String name) { String realName = name + ".temp"; return getSharedPreferences(realName); } // ----------------------------------------------------------------------- // 以下来自IPluginHost中的内容,兼容以前的插件框架内容(主要用于临时信息的读写) // ----------------------------------------------------------------------- public static final String PREF_TEMP_FILE_PACM = "plugins_PACM"; public static String ipcGet(String key, String defValue) { SharedPreferences x = getTempSharedPreferences(PREF_TEMP_FILE_PACM); String v = x.getString(key, defValue); return v; } public static void ipcSet(String key, String value) { SharedPreferences x = Pref.getTempSharedPreferences(PREF_TEMP_FILE_PACM); x.edit().putString(key, value).commit(); } public static Map ipcGetAll() { SharedPreferences x = Pref.getTempSharedPreferences(PREF_TEMP_FILE_PACM); Map a = x.getAll(); return a; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/api/Tasks.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.api; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; /** * 一些任务:需要运行在进程的UI主线程,或者运行在进程的HandlerThread * * @author RePlugin Team */ public final class Tasks { private static Handler sMainHandler; private static Object sLock = new Object(); private static Handler sThreadHandler; /** * @param r * @return */ public static final boolean post2UI(Runnable r) { return sMainHandler.post(r); } /** * @param r * @param delayMillis * @return */ public static final boolean postDelayed2UI(Runnable r, long delayMillis) { return sMainHandler.postDelayed(r, delayMillis); } /** * 取消UI线程任务 * @param r */ public static final void cancelTask(Runnable r) { initThread(); sMainHandler.removeCallbacks(r); } /** * @param r * @return */ public static final boolean post2Thread(Runnable r) { initThread(); return sThreadHandler.post(r); } /** * @param r * @param delayMillis * @return */ public static final boolean postDelayed2Thread(Runnable r, long delayMillis) { initThread(); return sThreadHandler.postDelayed(r, delayMillis); } /** * 取消后台线程任务 * @param r */ public static final void cancelThreadTask(Runnable r) { initThread(); sThreadHandler.removeCallbacks(r); } /** * @hide 内部接口 */ public static final void init() { sMainHandler = new Handler(Looper.getMainLooper()); } public static final void init(Handler handler) { sMainHandler = handler; } private static final void initThread() { synchronized (sLock) { if (sThreadHandler == null) { HandlerThread t = new HandlerThread("daemon-handler-thread"); t.start(); sThreadHandler = new Handler(t.getLooper()); } } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/loader/a/DummyActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.loader.a; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.utils.FixOTranslucentOrientation; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team */ public class DummyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // INFO dummy activity on create finish me. if (LOGR) { LogRelease.i(PLUGIN_TAG, "d a o c f m"); } FixOTranslucentOrientation.fix(this); // 之所以传Null,是因为系统会直接解析savedInstanceState // 这时如果常驻进程已被杀,这时立即恢复后,由于插件还没有准备好,故会出现崩溃情况 // 详细见:Crash Hash = 5C863A3E0CACDAEA9DBD05B9A7D353FE super.onCreate(null); setIntent(new Intent()); try { finish(); } catch (Exception e) { } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/loader/p/DummyProvider.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.loader.p; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; /** * @author RePlugin Team */ public class DummyProvider extends ContentProvider { @Override public boolean onCreate() { return false; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/loader/s/DummyService.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.loader.s; import android.app.Service; import android.content.Intent; import android.os.IBinder; /** * @author RePlugin Team */ public class DummyService extends Service { @Override public IBinder onBind(Intent intent) { return null; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/parser/manifest/ManifestParser.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.parser.manifest; import android.content.IntentFilter; import android.os.PatternMatcher; import com.qihoo360.mobilesafe.parser.manifest.bean.ComponentBean; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import static com.qihoo360.replugin.helper.LogDebug.LOG; /** * @author RePlugin Team */ public enum ManifestParser { INS; public static final String TAG = "ms-parser"; /** * 因为多插件中的组件可能存在重名,所以这里加了一层插件的对应关系。 *

* HashMap<插件名称, HashMap<插件内组件名称,List<组件的 IntentFilter>>> */ private Map>> mPluginActivityInfoMap = new HashMap<>(); private Map>> mPluginServiceInfoMap = new HashMap<>(); private Map>> mPluginReceiverInfoMap = new HashMap<>(); /** * 保存 action 与 组件及 filters 的对应关系 *

* HashMap> */ private Map> mActivityActionPluginsMap = new HashMap<>(); private Map> mServiceActionPluginsMap = new HashMap<>(); /** * 解析 AndroidManifest * * @param pli 插件信息 * @param manifestStr AndroidManifest.xml 字符串 */ public void parse(PluginInfo pli, String manifestStr) { XmlHandler handler = parseManifest(manifestStr); Map> activityFilterMap = new HashMap<>(); putToMap(mPluginActivityInfoMap, activityFilterMap, pli); parseComponent(pli.getName(), activityFilterMap, handler.getActivities(), mActivityActionPluginsMap); Map> serviceFilterMap = new HashMap<>(); putToMap(mPluginServiceInfoMap, serviceFilterMap, pli); parseComponent(pli.getName(), serviceFilterMap, handler.getServices(), mServiceActionPluginsMap); Map> receiverFilterMap = new HashMap<>(); putToMap(mPluginReceiverInfoMap, receiverFilterMap, pli); parseComponent(pli.getName(), receiverFilterMap, handler.getReceivers(), null); /* 打印日志 */ if (LOG) { printFilters(activityFilterMap, serviceFilterMap, receiverFilterMap); } } private void putToMap(Map>> infoMap, Map> filterMap, PluginInfo pi) { infoMap.put(pi.getPackageName(), filterMap); infoMap.put(pi.getAlias(), filterMap); } /** * parseComponent * * @param plugin 插件名称 * @param filterMap HashMap<组件名称, List> * @param componentBeans 从 manifest 中解析到的组件列表 */ private void parseComponent(String plugin, Map> filterMap, List componentBeans, Map> actionPluginsMap) { if (componentBeans != null) { for (ComponentBean componentBean : componentBeans) { doFillFilters(componentBean, filterMap); doFillActionPlugins(plugin, componentBean, actionPluginsMap); } } } private void doFillFilters(ComponentBean b, Map> filterMap) { String cn = b.name; List filterList = filterMap.get(cn); if (filterList == null) { filterList = new ArrayList<>(); filterMap.put(cn, filterList); } List filters = b.intentFilters; if (filters != null) { filterList.addAll(filters); } } /** * 将 filters 中 action 和组件的对应关系保存起来 */ private void doFillActionPlugins(String plugin, ComponentBean componentBean, Map> actionPluginsMap) { if (actionPluginsMap == null || componentBean.intentFilters == null) { return; } for (IntentFilter filter : componentBean.intentFilters) { Iterator iterator = filter.actionsIterator(); while (iterator.hasNext()) { String action = iterator.next(); Set plugins = actionPluginsMap.get(action); if (plugins == null) { plugins = new HashSet<>(); actionPluginsMap.put(action, plugins); } plugins.add(plugin); } } } /** * 如果某插件的 Activity 的 IntentFilter 包含了参数中的 action, * 则把此插件放入要返回的集合中。 * * @param action action * @return 插件名称列表 */ public Set getPluginsByActionWhenStartActivity(String action) { return mActivityActionPluginsMap.get(action); } /** * 如果某插件的 Service 的 IntentFilter 包含了参数中的 action, * 则把此插件放入要返回的集合中。 * * @param action action * @return 插件名称列表 */ public Set getPluginsByActionWhenStartService(String action) { return mServiceActionPluginsMap.get(action); } public Map> getActivityFilterMap(String plugin) { return mPluginActivityInfoMap.get(plugin); } public Map> getServiceFilterMap(String plugin) { return mPluginServiceInfoMap.get(plugin); } public Map> getReceiverFilterMap(String plugin) { return mPluginReceiverInfoMap.get(plugin); } /** * 将 manifest 中的数据存储在 XmlHandler 中 * * @param manifestStr AndroidManifest 内容 * @return XmlHandler */ private XmlHandler parseManifest(String manifestStr) { XMLReader xmlReader = null; XmlHandler handler = new XmlHandler(); /* 解析字符串 */ try { SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); xmlReader = parser.getXMLReader(); xmlReader.setContentHandler(handler); } catch (Throwable e) { e.printStackTrace(); } if (xmlReader != null) { StringReader strReader = null; try { strReader = new StringReader(manifestStr); xmlReader.parse(new InputSource(strReader)); } catch (Throwable e) { e.printStackTrace(); } finally { if (strReader != null) { strReader.close(); } } } return handler; } /** * 打印 filter */ private void printFilters(Map> actFilterMap, Map> svcFilterMap, Map> rcvFilterMap) { if (!actFilterMap.entrySet().isEmpty()) { LogDebug.d(TAG, "\n打印 Activity - IntentFilter"); } for (HashMap.Entry> entry : actFilterMap.entrySet()) { List filter = entry.getValue(); LogDebug.d(TAG, "key:" + entry.getKey() + "; val:" + intentFilterStr(filter)); } if (!svcFilterMap.entrySet().isEmpty()) { LogDebug.d(TAG, "\n打印 Service - IntentFilter"); } for (HashMap.Entry> entry : svcFilterMap.entrySet()) { List filter = entry.getValue(); LogDebug.d(TAG, "key:" + entry.getKey() + "; val:" + intentFilterStr(filter)); } if (!rcvFilterMap.entrySet().isEmpty()) { LogDebug.d(TAG, "\n打印 Receiver - IntentFilter"); } for (HashMap.Entry> entry : rcvFilterMap.entrySet()) { List filter = entry.getValue(); LogDebug.d(TAG, "key:" + entry.getKey() + "; val:" + intentFilterStr(filter)); } } /** * 将 IntentFilter 列表转换成字符串 * * @param filters IntentFilter 列表 * @return IntentFilter 列表的字符串形式 */ private String intentFilterStr(List filters) { StringBuilder builder = new StringBuilder(); builder.append("["); for (IntentFilter filter : filters) { builder.append("{"); // actions int c = filter.countActions(); if (c > 0) { builder.append("action:{"); } while (c > 0) { builder.append(filter.getAction(c - 1)).append(","); c--; if (c == 0) { builder.append("}"); } } // category c = filter.countCategories(); if (c > 0) { builder.append(", category:{"); } while (c > 0) { builder.append(filter.getCategory(c - 1)).append(","); c--; if (c == 0) { builder.append("}"); } } // data-schema c = filter.countDataSchemes(); if (c > 0) { builder.append(", data-scheme:{"); } while (c > 0) { builder.append(filter.getDataScheme(c - 1)).append(","); c--; if (c == 0) { builder.append("}"); } } // data-path c = filter.countDataPaths(); if (c > 0) { builder.append(", data-path:{"); } while (c > 0) { PatternMatcher matcher = filter.getDataPath(c - 1); builder.append(matcher.getPath()).append(",").append(matcher.getType()); c--; if (c == 0) { builder.append("}"); } } // data-type c = filter.countDataTypes(); if (c > 0) { builder.append(", data-type:{"); } while (c > 0) { builder.append(filter.getDataType(c - 1)).append(","); c--; if (c == 0) { builder.append("}"); } } builder.append("}, "); } builder.append("]"); return builder.toString(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/parser/manifest/XmlHandler.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.parser.manifest; import android.content.IntentFilter; import android.text.TextUtils; import com.qihoo360.mobilesafe.parser.manifest.bean.ComponentBean; import com.qihoo360.mobilesafe.parser.manifest.bean.DataBean; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.util.ArrayList; import java.util.List; /** * @author RePlugin Team */ class XmlHandler extends DefaultHandler { private ArrayList activities; private ArrayList services; private ArrayList receivers; private String pkg; private ComponentBean curComponent; private IntentFilter curFilter; private List filters; private List curActions; private List curCategories; private List curDataBeans; public List getActivities() { return activities; } public List getServices() { return services; } public List getReceivers() { return receivers; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); switch (qName) { case "manifest": pkg = attributes.getValue("package"); break; case "activity": if (activities == null) { activities = new ArrayList<>(); } curComponent = new ComponentBean(); filters = new ArrayList<>(); curComponent.intentFilters = filters; curComponent.name = repairAttrName(attributes.getValue("android:name")); break; case "service": if (services == null) { services = new ArrayList<>(); } curComponent = new ComponentBean(); filters = new ArrayList<>(); curComponent.intentFilters = filters; curComponent.name = repairAttrName(attributes.getValue("android:name")); break; case "receiver": if (receivers == null) { receivers = new ArrayList<>(); } curComponent = new ComponentBean(); filters = new ArrayList<>(); curComponent.intentFilters = filters; curComponent.name = repairAttrName(attributes.getValue("android:name")); break; case "intent-filter": curFilter = new IntentFilter(); filters.add(curFilter); break; case "action": if (curActions == null) { curActions = new ArrayList<>(); } curActions.add(attributes.getValue("android:name")); break; case "category": if (curCategories == null) { curCategories = new ArrayList<>(); } curCategories.add(attributes.getValue("android:name")); break; case "data": if (curDataBeans == null) { curDataBeans = new ArrayList<>(); } DataBean bean = new DataBean(); bean.scheme = attributes.getValue("android:scheme"); bean.mimeType = attributes.getValue("android:mimeType"); bean.host = attributes.getValue("android:host"); bean.port = attributes.getValue("android:port"); bean.path = attributes.getValue("android:path"); bean.pathPattern = attributes.getValue("android:pathPattern"); bean.pathPrefix = attributes.getValue("android:pathPrefix"); curDataBeans.add(bean); break; } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); switch (qName) { case "intent-filter": if (curActions != null) { for (String action : curActions) { curFilter.addAction(action); } } if (curCategories != null) { for (String cate : curCategories) { curFilter.addCategory(cate); } } if (curDataBeans != null) { for (DataBean bean : curDataBeans) { if (!TextUtils.isEmpty(bean.scheme)) { curFilter.addDataScheme(bean.scheme); } if (!TextUtils.isEmpty(bean.host) && !TextUtils.isEmpty(bean.port)) { curFilter.addDataAuthority(bean.host, bean.port); } if (!TextUtils.isEmpty(bean.path)) { curFilter.addDataPath(bean.path, bean.getPatternMatcherType()); } try { if (!TextUtils.isEmpty(bean.mimeType)) { curFilter.addDataType(bean.mimeType); } } catch (IntentFilter.MalformedMimeTypeException e) { e.printStackTrace(); } } } curActions = null; curCategories = null; curDataBeans = null; break; case "activity": activities.add(curComponent); break; case "service": services.add(curComponent); break; case "receiver": receivers.add(curComponent); break; } } /** * 如果 android:name 中未包含 pkg,则添加 pkg * * @param val android:name 属性的值 * @return 包含 package 的 android:name */ private String repairAttrName(String val) { // val 中未包含 pkg if (!val.startsWith(".")) { return val; } else { return (pkg + val).intern(); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/parser/manifest/bean/ComponentBean.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.parser.manifest.bean; import android.content.IntentFilter; import java.util.List; /** * @author RePlugin Team */ public class ComponentBean { public String name; public List intentFilters; @Override public String toString() { return String.format("{name:%s, intent-filter.size():%s}", name, intentFilters.size()); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/parser/manifest/bean/DataBean.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.parser.manifest.bean; import android.os.PatternMatcher; import android.text.TextUtils; /** * @author RePlugin Team */ public class DataBean { public String scheme; public String host; public String port; public String mimeType; public String path; public String pathPattern; public String pathPrefix; @Override public String toString() { return String.format( "{scheme:%s, host:%s, mimeType:%s, path:%s, pathPattern:%s, pathPrefix:%s, port:%s}", scheme, host, mimeType, pathPattern, pathPrefix, path, port); } /** * 获得 path 匹配类型 */ public int getPatternMatcherType() { if (TextUtils.isEmpty(pathPattern) && TextUtils.isEmpty(pathPattern)) { return PatternMatcher.PATTERN_LITERAL; } else if (!TextUtils.isEmpty(pathPrefix)) { return PatternMatcher.PATTERN_PREFIX; } else { return PatternMatcher.PATTERN_SIMPLE_GLOB; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/svcmanager/ParcelBinder.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.svcmanager; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; /** * @author RePlugin Team */ class ParcelBinder implements Parcelable { private final IBinder mBinder; private ParcelBinder(Parcel source) { mBinder = source.readStrongBinder(); } public ParcelBinder(IBinder binder) { this.mBinder = binder; } public IBinder getIbinder() { return mBinder; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeStrongBinder(mBinder); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public ParcelBinder createFromParcel(Parcel source) { return new ParcelBinder(source); } @Override public ParcelBinder[] newArray(int size) { return new ParcelBinder[size]; } }; } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/svcmanager/PluginServiceManager.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.svcmanager; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import com.qihoo360.loader2.MP; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.replugin.helper.LogRelease; import java.util.HashMap; import java.util.Map; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 负责管理Plugin Service的管理器 * * @author RePlugin Team */ class PluginServiceManager { private static final boolean DEBUG = BuildConfig.DEBUG; private static final String TAG = DEBUG ? "PluginServiceManager" : PluginServiceManager.class.getSimpleName(); private static Map sRecordMap = new HashMap(); /** * 获取一个来自于Plugin的service实例 * * @param pluginName plugin的名称 * @param serviceName 所要获取的service的名称 * @param pid 发起请求的进程的pid * @param deathMonitor 请求方传过来的IBinder对象,仅用来监视请求进程死亡事件 * @return 所请求的service对象 * @throws RemoteException */ static IBinder getPluginService(String pluginName, String serviceName, int pid, IBinder deathMonitor) throws RemoteException { PluginServiceRecord pr; synchronized (sRecordMap) { final String key = generateMapKey(pluginName, serviceName); pr = sRecordMap.get(key); if (pr != null && !pr.isServiceAlive()) { pr = null; } if (pr == null) { pr = new PluginServiceRecord(pluginName, serviceName); sRecordMap.put(key, pr); } } return pr.getService(pid, deathMonitor); } /** * 处理之前被请求的一个service对象的引用已被释放 * * @param pluginName plugin的名称 * @param serviceName 获取的service的名称 * @param pid 发起请求的进程的pid */ static void onRefReleased(String pluginName, String serviceName, int pid) { synchronized (sRecordMap) { PluginServiceRecord pr = sRecordMap.get(generateMapKey(pluginName, serviceName)); if (pr != null) { int retCount = pr.decrementProcessRef(pid); if (DEBUG) { Log.d(TAG, "[onRefReleased] remaining ref count: " + retCount); } if (retCount <= 0) { removePluginServiceRecord(pr); } } } } /** * 处理之前有过请求纪录的一个进程死掉的事件 * * @param pluginName plugin的名称 * @param serviceName 获取的service的名称 * @param pid 发起请求的进程的pid */ static void onRefProcessDied(String pluginName, String serviceName, int pid) { synchronized (sRecordMap) { PluginServiceRecord pr = sRecordMap.get(generateMapKey(pluginName, serviceName)); if (pr != null) { int retCount = pr.refProcessDied(pid); if (DEBUG) { Log.d(TAG, "[onRefProcessDied] remaining ref count: " + retCount); } if (retCount <= 0) { removePluginServiceRecord(pr); } } } } /** * 当某个plugin service已经被完全释放,既没有任何引用,从缓存map中移除, 并且通知插件框架可以考虑释放此plugin的进程 */ private static void removePluginServiceRecord(PluginServiceRecord pr) { if (DEBUG) { Log.d(TAG, "[removePluginServiceRecord]: " + pr.mPluginName + ", " + pr.mServiceName); } synchronized (sRecordMap) { final String key = generateMapKey(pr.mPluginName, pr.mServiceName); // 通知框架接触对此service的引用 // binder有可能为空,做个保护 // Edited by Jiongxuan Zhang if (pr.mPluginBinder == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psm.rpsr: mpb nil"); } return; } MP.releasePluginBinder(pr.mPluginBinder); sRecordMap.remove(key); } } private static String generateMapKey(String pluginName, String serviceName) { return pluginName + "-" + serviceName; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/svcmanager/PluginServiceRecord.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.svcmanager; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import com.qihoo360.i.IPluginManager; import com.qihoo360.loader2.MP; import com.qihoo360.loader2.MP.PluginBinder; import com.qihoo360.mobilesafe.core.BuildConfig; import java.util.ArrayList; import java.util.concurrent.locks.ReentrantLock; /** * 每对plugin name和service name的组合(保证唯一性)都对应一个PluginServiceRecord实例。 * 此类提供了获取service的实现,并且在内部维护了一个请求过该服务的进程记录(ProcessRecord)的列表, * 当该列表为空时,通常是所有对此service的引用都已经释放了。 * * @author RePlugin Team */ class PluginServiceRecord extends ReentrantLock { private static final boolean DEBUG = BuildConfig.DEBUG; private static final String TAG = DEBUG ? "PluginServiceRecord" : PluginServiceRecord.class.getSimpleName(); private static final long serialVersionUID = 1964598149985081920L; /** * 每个ProcessRecord实例对应一个进程对此service的引用记录,同时监视着进程的死亡事件。 * 一个使用该service的进程可能有多份互不相干的引用,因此内部使用引用计数来实现。 */ class ProcessRecord implements IBinder.DeathRecipient { final int pid; final IBinder deathMonitor; private int refCount; private ProcessRecord(int pid, IBinder deathMonitor) { this.pid = pid; this.deathMonitor = deathMonitor; try { deathMonitor.linkToDeath(this, 0); } catch (RemoteException e) { if (DEBUG) { Log.d(TAG, "Error when linkToDeath: "); } } refCount = 1; } private int incrementRef() { return ++refCount; } private int decrementRef() { return --refCount; } @Override public void binderDied() { PluginServiceManager.onRefProcessDied(mPluginName, mServiceName, pid); } } final String mPluginName; final String mServiceName; PluginBinder mPluginBinder; // 多个进程同时使用一个plugin service的概率比较小,4个基本上足够,使用4为初始值以节省内存 ArrayList processRecords = new ArrayList(4); PluginServiceRecord(String pluginName, String serviceName) { mPluginName = pluginName; mServiceName = serviceName; } IBinder getService(int pid, IBinder deathMonitor) { lock(); try { if (mPluginBinder == null) { mPluginBinder = MP.fetchPluginBinder(mPluginName, IPluginManager.PROCESS_AUTO, mServiceName); } // MP.fetchPluginBinder有可能出现:目标进程被系统回收的情况,进而导致DeadObjectException的情况 // 这样其方法的返回值自然就是Null了 // Edited by Jiongxuan Zhang if (mPluginBinder == null) { return null; } addNewRecordInternal(pid, deathMonitor); return mPluginBinder.binder; } catch (Exception e) { if (DEBUG) { Log.d(TAG, "Error getting plugin service: ", e); } } finally { unlock(); } return null; } int decrementProcessRef(int pid) { lock(); try { ProcessRecord record = getProcessRecordInternal(pid); if (record != null) { int processRefCount = record.decrementRef(); if (processRefCount <= 0) { processRecords.remove(record); } } if (DEBUG) { Log.d(TAG, "[decrementProcessRef] remaining ref count: " + getTotalRefCountInternal()); } return getTotalRefCountInternal(); } catch (Exception e) { if (DEBUG) { Log.d(TAG, "Error decrement reference: ", e); } } finally { unlock(); } return -1; } int refProcessDied(int pid) { lock(); try { ProcessRecord record = getProcessRecordInternal(pid); if (record != null) { processRecords.remove(record); } return getTotalRefCountInternal(); } catch (Exception e) { if (DEBUG) { Log.d(TAG, "Error decrement reference: ", e); } } finally { unlock(); } return -1; } boolean isServiceAlive() { return mPluginBinder != null && mPluginBinder.binder != null && mPluginBinder.binder.isBinderAlive() && mPluginBinder.binder.pingBinder(); } // ==================================Internal=Methods=================================== private void addNewRecordInternal(int pid, IBinder deathMonitor) { ProcessRecord record = getProcessRecordInternal(pid); if (record != null) { // Already exists record.incrementRef(); } else { // New creation ProcessRecord pr = new ProcessRecord(pid, deathMonitor); processRecords.add(pr); } if (DEBUG) { Log.d(TAG, "[addNewRecordInternal] remaining ref count: " + getTotalRefCountInternal()); } } private ProcessRecord getProcessRecordInternal(int pid) { for (ProcessRecord record : processRecords) { if (record.pid == pid) { return record; } } return null; } private int getTotalRefCountInternal() { int totalRefCount = 0; for (ProcessRecord pr : processRecords) { totalRefCount += pr.refCount; } return totalRefCount; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/svcmanager/PluginServiceReferenceManager.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.svcmanager; import android.content.Context; import android.os.IBinder; import android.util.Log; import com.qihoo360.mobilesafe.core.BuildConfig; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; import java.util.ArrayList; /** * 注意此类的对象仅存活于请求plugin service的进程内,其作用是监视请求来的远端service引用何时被释放, 使用到了幽灵引用。 * * @author RePlugin Team */ class PluginServiceReferenceManager { private static final boolean DEBUG = BuildConfig.DEBUG; private static final String TAG = DEBUG ? "PluginServiceReferenceManager" : PluginServiceReferenceManager.class.getSimpleName(); private static class ServicePhantomRef extends PhantomReference { final String pluginName; final String serviceName; public ServicePhantomRef(String pluginName, String serviceName, IBinder r, ReferenceQueue q) { super(r, q); this.pluginName = pluginName; this.serviceName = serviceName; } } private static Context sAppContext = null; private static ArrayList sRefList = new ArrayList(); private static ReferenceQueue sRefQueue = new ReferenceQueue(); private static Thread sMonitorThread = null; /** * 一个新的plugin service引用被请求 */ static synchronized void onPluginServiceObtained(Context context, String pluginName, String serviceName, IBinder service) { sAppContext = context.getApplicationContext(); synchronized (sRefList) { sRefList.add(new ServicePhantomRef(pluginName, serviceName, service, sRefQueue)); } if (sMonitorThread == null) { startMonitoring(); } } /** * 启动一个后台线程见识是否还有未被释放的service引用,一旦没有了线程会推出。 */ private static synchronized void startMonitoring() { sMonitorThread = new Thread() { @Override public void run() { boolean quit = false; while (!quit) { synchronized (sRefList) { int remainedQueueCount = sRefList.size(); if (remainedQueueCount > 0) { ServicePhantomRef ref = (ServicePhantomRef) sRefQueue.poll(); while (ref != null) { if (DEBUG) { Log.d(TAG, "Plugin service ref released: " + ref.serviceName); } sRefList.remove(ref); remainedQueueCount--; QihooServiceManager.onPluginServiceReleased(sAppContext, ref.pluginName, ref.serviceName); ref = (ServicePhantomRef) sRefQueue.poll(); } } if (remainedQueueCount <= 0) { sMonitorThread = null; quit = true; } } if (!quit) { // 扫描间隔为5S try { Thread.sleep(5000); } catch (InterruptedException e) { if (DEBUG) { Log.d(TAG, "Thread sleeping interrupted: ", e); } } } } if (DEBUG) { Log.d(TAG, "sMonitorThread quits... "); } } }; if (DEBUG) { Log.d(TAG, "Start monitoring..."); } sMonitorThread.setPriority(Thread.NORM_PRIORITY); sMonitorThread.start(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/svcmanager/QihooServiceManager.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.svcmanager; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.replugin.IBinderGetter; import com.qihoo360.replugin.base.IPC; import java.lang.ref.SoftReference; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * 方便的以同步调用的方式获取一个服务实现的接口类 * * @author RePlugin Team */ public final class QihooServiceManager { private static final boolean DEBUG = BuildConfig.DEBUG; private static final String TAG = DEBUG ? "QihooServiceManager" : QihooServiceManager.class.getSimpleName(); private static Uri sServiceChannelUri = null; private static IServiceChannel sServerChannel; private static Map> sCache; private static final IBinder PROCESS_DEATH_AGENT = new Binder(); static { sCache = Collections.synchronizedMap(new HashMap>()); } /** * 获取已注册服务的IBinder对象,前提是该服务是静态服务,即默认一直存在,或者自己已经启动并且向我们注册过; * 注意不能通过此借口获取一个插件的服务,除非明确知道该插件的服务已经主动注册过,否则使用getPluginService() * * @param context * @param serviceName 请求获取的service名称 * @return 所请求的service实现对象 */ public static IBinder getService(Context context, String serviceName) { if (DEBUG) { Log.d(TAG, "[getService] begin = " + SystemClock.elapsedRealtime()); } IBinder service = null; /** * 先考虑本地缓存 */ SoftReference ref = sCache.get(serviceName); if (ref != null) { service = ref.get(); if (service != null) { if (service.isBinderAlive() && service.pingBinder()) { if (DEBUG) { Log.d(TAG, "[getService] Found service from cache: " + serviceName); Log.d(TAG, "[getService] end = " + SystemClock.elapsedRealtime()); } return service; } else { sCache.remove(serviceName); } } } IServiceChannel serviceChannel = getServerChannel(context); if (serviceChannel == null) { return null; } try { service = serviceChannel.getService(serviceName); if (service != null) { if (DEBUG) { Log.d(TAG, "[getService] Found service from remote service channel: " + serviceName); } service = ServiceWrapper.factory(context, serviceName, service); sCache.put(serviceName, new SoftReference(service)); } } catch (RemoteException e) { if (DEBUG) { Log.e(TAG, "[getService] Error when getting service from service channel...", e); } } if (DEBUG) { Log.d(TAG, "[getService] end = " + SystemClock.elapsedRealtime()); } return service; } /** * 动态添加(注册)一个服务 * * @param context * @param serviceName 请求添加的service名称 * @param service 请求添加的service实现对象 */ public static boolean addService(Context context, String serviceName, IBinder service) { IServiceChannel serviceChannel = getServerChannel(context); if (serviceChannel == null) { return false; } try { serviceChannel.addService(serviceName, service); } catch (RemoteException e) { if (DEBUG) { Log.e(TAG, "Add service failed...", e); } } return true; } /** * 动态添加(注册)一个服务 * * @param context * @param serviceName 请求添加的service名称 * @param getter 请求添加的service实现对象的Callback */ public static boolean addService(Context context, String serviceName, IBinderGetter getter) { IServiceChannel serviceChannel = getServerChannel(context); if (serviceChannel == null) { return false; } try { serviceChannel.addServiceDelayed(serviceName, getter); } catch (RemoteException e) { if (DEBUG) { Log.e(TAG, "Add service failed...", e); } } return true; } /** * 移除一个之前添加过的服务 * * @param context * @param serviceName 请求移除的service名称 * @return 请求移除的service实现对象 */ public static boolean removeService(Context context, String serviceName, IBinder service) { IServiceChannel serviceChannel = getServerChannel(context); if (serviceChannel == null) { return false; } try { serviceChannel.removeService(serviceName); } catch (RemoteException e) { if (DEBUG) { Log.e(TAG, "Remove service failed...", e); } } return true; } /** * 请求一个由plugin实现的service的实现对象,如果需要的话会启动该plugin的进程。 * 请求的过程由于可能初始化新的插件进程可能会比较耗时,因此不要再UI线程调用。 * * @param context * @param pluginName 实现了所请求的service的plugin的名称 * @param serviceName 所请求的service名称 * @return */ public static IBinder getPluginService(Context context, String pluginName, String serviceName) { IBinder service = getService(context, serviceName); if (service != null) { /** * 此Plugin的service已经主动注册 */ return service; } IServiceChannel serviceChannel = getServerChannel(context); if (serviceChannel == null) { return null; } try { /** * 这里面没有使用ServiceWrapper来包装plugin的service,因为一旦远程plugin * 进程死掉的话,重新获取其服务的过程又要花很多时间,在使用上会对使用者造成影响,因此不做Binder死掉自动重连的逻辑。 */ service = serviceChannel.getPluginService(pluginName, serviceName, PROCESS_DEATH_AGENT); PluginServiceReferenceManager.onPluginServiceObtained(context, pluginName, serviceName, service); } catch (RemoteException e) { if (DEBUG) { Log.e(TAG, "[getPluginService] Error when getting plugin service from service channel...", e); } } return service; } static IServiceChannel getServerChannel(Context context) { if (DEBUG) { Log.d(TAG, "[getServerChannel] begin = " + SystemClock.elapsedRealtime()); } if (sServerChannel != null && sServerChannel.asBinder().isBinderAlive() && sServerChannel.asBinder().pingBinder()) { return sServerChannel; } /* * In server process we can return * ServerChannelCreator.serviceChannelImpl directly instead of querying * it using content resolver */ if (IPC.isPersistentProcess()) { return ServiceChannelImpl.sServiceChannelImpl; } if (context == null) { return null; } IServiceChannel serviceChannel = null; Cursor cursor = null; try { cursor = context.getContentResolver().query(getServiceChannelUri(), null, null, null, null); IBinder binder = ServiceChannelCursor.getBinder(cursor); serviceChannel = IServiceChannel.Stub.asInterface(binder); sServerChannel = serviceChannel; } catch (Exception e) { if (DEBUG) { Log.e(TAG, "Error fetching service manager binder object using provider: ", e); } } finally { if (cursor != null) { try { cursor.close(); } catch (Exception e) { if (DEBUG) { Log.e(TAG, "Error closing cursor: ", e); } } } } if (DEBUG) { Log.d(TAG, "[getServerChannel] end = " + SystemClock.elapsedRealtime()); } return serviceChannel; } static Uri getServiceChannelUri() { if (sServiceChannelUri == null) { sServiceChannelUri = Uri.parse("content://" + ServiceProvider.AUTHORITY + "/" + ServiceProvider.PATH_SERVER_CHANNEL); } return sServiceChannelUri; } static void onPluginServiceReleased(Context context, String pluginName, String serviceName) { IServiceChannel serviceChannel = getServerChannel(context); if (serviceChannel != null) { try { serviceChannel.onPluginServiceRefReleased(pluginName, serviceName); } catch (RemoteException e) { if (DEBUG) { Log.d(TAG, "Error releaseing plugin service reference: ", e); } } } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/svcmanager/ServiceChannelCursor.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.svcmanager; import android.database.Cursor; import android.database.MatrixCursor; import android.os.Bundle; import android.os.IBinder; /** * A Matrix Cursor implementation that a binder object is embedded in its extra * data. * * @author RePlugin Team */ /* PACKAGE */class ServiceChannelCursor extends MatrixCursor { public static final String SERVER_CHANNEL_BUNDLE_KEY = "servicechannel"; /* PACKAGE */static final String[] DEFAULT_COLUMNS = { "s" }; static final ServiceChannelCursor makeCursor(IBinder binder) { return new ServiceChannelCursor(DEFAULT_COLUMNS, binder); } static final IBinder getBinder(Cursor cursor) { Bundle bundle = cursor.getExtras(); bundle.setClassLoader(ParcelBinder.class.getClassLoader()); ParcelBinder parcelBinder = bundle.getParcelable(SERVER_CHANNEL_BUNDLE_KEY); return parcelBinder.getIbinder(); } Bundle mBinderExtra = new Bundle(); public ServiceChannelCursor(String[] columnNames, IBinder binder) { super(columnNames); mBinderExtra.putParcelable(SERVER_CHANNEL_BUNDLE_KEY, new ParcelBinder(binder)); } @Override public Bundle getExtras() { return mBinderExtra; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/svcmanager/ServiceChannelImpl.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.svcmanager; import android.database.MatrixCursor; import android.os.DeadObjectException; import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.replugin.IBinderGetter; import java.util.concurrent.ConcurrentHashMap; /** * Service管理的后台实现,存活在常驻进程,其他进程的请求都是经过远端持有此实现Proxy把请求传递到常驻进程 再由此进行处理或者分发的。 * 此外,常驻进程的静态服务也是在此类的initServices()函数中添加的。 * * @author RePlugin Team */ /* PACKAGE */class ServiceChannelImpl { private static final boolean DEBUG = BuildConfig.DEBUG; private static final String TAG = DEBUG ? "ServiceChannelImpl" : ServiceChannelImpl.class.getSimpleName(); private static ConcurrentHashMap sServices = new ConcurrentHashMap(); // 添加延迟释放IBinder的Map // Added by Jiongxuan Zhang private static ConcurrentHashMap sDelayedServices = new ConcurrentHashMap<>(); /** * ServiceChannel的实现 */ static IServiceChannel.Stub sServiceChannelImpl = new IServiceChannel.Stub() { @Override public IBinder getService(String serviceName) throws RemoteException { if (DEBUG) { Log.d(TAG, "[getService] --> serviceName = " + serviceName); } if (TextUtils.isEmpty(serviceName)) { throw new IllegalArgumentException(); } IBinder service = sServices.get(serviceName); // 若没有注册此服务,则尝试从“延迟IBinder”中获取 // Added by Jiongxuan Zhang if (service == null) { return fetchFromDelayedMap(serviceName); } // 判断Binder是否挂掉 if (!service.isBinderAlive() || !service.pingBinder()) { if (DEBUG) { Log.d(TAG, "[getService] --> service died:" + serviceName); } sServices.remove(serviceName); return null; } else { return service; } } // 尝试从“延迟IBinder”中获取 // Added by Jiongxuan Zhang private IBinder fetchFromDelayedMap(String serviceName) { IBinderGetter sc = sDelayedServices.get(serviceName); if (sc == null) { return null; } try { IBinder s = sc.get(); addService(serviceName, s); return s; } catch (DeadObjectException e) { if (DEBUG) { e.printStackTrace(); } // 因为远端Callback所在进程已经挂掉,再使用它已没有意义,必须重新注册才行 sDelayedServices.remove(serviceName); } catch (RemoteException e) { if (DEBUG) { e.printStackTrace(); } // 可能不是因为远端进程挂掉,只是TransactionTooLarge等错误,故可不删除 } return null; } @Override public void addService(String serviceName, IBinder service) throws RemoteException { sServices.put(serviceName, service); } @Override public void addServiceDelayed(String serviceName, IBinderGetter getter) throws RemoteException { sDelayedServices.put(serviceName, getter); } @Override public void removeService(String serviceName) throws RemoteException { sServices.remove(serviceName); } @Override public IBinder getPluginService(String pluginName, String serviceName, IBinder deathMonitor) throws RemoteException { return PluginServiceManager.getPluginService(pluginName, serviceName, getCallingPid(), deathMonitor); } @Override public void onPluginServiceRefReleased(String pluginName, String serviceName) throws RemoteException { PluginServiceManager.onRefReleased(pluginName, serviceName, getCallingPid()); } }; static MatrixCursor sServiceChannelCursor = ServiceChannelCursor.makeCursor(sServiceChannelImpl); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/svcmanager/ServiceProvider.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.svcmanager; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.util.Log; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.replugin.base.IPC; /** * Provider used for getting the ServiceChannel from other process only * * @author RePlugin Team */ public class ServiceProvider extends ContentProvider { private static final boolean DEBUG = BuildConfig.DEBUG; private static final String TAG = "ServerProvider"; public static final String AUTHORITY = IPC.getPackageName() + ".svcmanager"; public static final String PATH_SERVER_CHANNEL = "severchannel"; @Override public boolean onCreate() { if (DEBUG) { Log.d(TAG, "[onCreate]" + " App = " + getContext().getApplicationContext()); } return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { if (DEBUG) { Log.d(TAG, "[query] uri = " + (uri == null ? "null" : uri.toString())); } return ServiceChannelImpl.sServiceChannelCursor; } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/mobilesafe/svcmanager/ServiceWrapper.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.mobilesafe.svcmanager; import android.content.Context; import android.os.IBinder; import android.os.IInterface; import android.os.Parcel; import android.os.RemoteException; import android.util.Log; import com.qihoo360.mobilesafe.core.BuildConfig; import java.io.FileDescriptor; /** * A Binder Wrapper class that monitors the death of the underlying remote * Binder and recovers it if needed. * * @author RePlugin Team */ /* PACKAGE */class ServiceWrapper implements IBinder, IBinder.DeathRecipient { private static final boolean DEBUG = BuildConfig.DEBUG; private static final String TAG = DEBUG ? "ServiceWrapper" : ServiceWrapper.class.getSimpleName(); private final Context mAppCpntext; private IBinder mRemote; private final String mName; public static IBinder factory(Context context, String name, IBinder binder) { String descriptor = null; try { descriptor = binder.getInterfaceDescriptor(); } catch (RemoteException e) { if (DEBUG) { Log.d(TAG, "getInterfaceDescriptor()", e); } } android.os.IInterface iin = binder.queryLocalInterface(descriptor); if (iin != null) { /** * If the requested interface has local implementation, meaning that * it's living in the same process as the one who requests for it, * return the binder directly since in such cases our wrapper does * not help in any way. */ return binder; } return new ServiceWrapper(context, name, binder); } private ServiceWrapper(Context context, String name, IBinder binder) { mAppCpntext = context.getApplicationContext(); mRemote = binder; mName = name; try { mRemote.linkToDeath(this, 0); } catch (RemoteException e) { if (DEBUG) { Log.d(TAG, "linkToDeath ex", e); } } } private IBinder getRemoteBinder() throws RemoteException { IBinder remote = mRemote; if (remote != null) { return remote; } IServiceChannel serviceChannel = QihooServiceManager.getServerChannel(mAppCpntext); if (serviceChannel == null) { // 在获取Cursor时,可能恰巧常驻进程被停止,则有可能出现获取失败的情况 // Added by Jiongxuan Zhang Log.e(TAG, "sw.grb: s is n"); throw new RemoteException(); } remote = serviceChannel.getService(mName); if (remote == null) { throw new RemoteException(); } mRemote = remote; return remote; } @Override public String getInterfaceDescriptor() throws RemoteException { return getRemoteBinder().getInterfaceDescriptor(); } @Override public boolean pingBinder() { try { return getRemoteBinder().pingBinder(); } catch (RemoteException e) { if (DEBUG) { Log.d(TAG, "getRemoteBinder()", e); } } return false; } @Override public boolean isBinderAlive() { try { return getRemoteBinder().isBinderAlive(); } catch (RemoteException e) { if (DEBUG) { Log.d(TAG, "isBinderAlive()", e); } } return false; } @Override public IInterface queryLocalInterface(String descriptor) { try { return getRemoteBinder().queryLocalInterface(descriptor); } catch (RemoteException e) { if (DEBUG) { Log.d(TAG, "queryLocalInterface()", e); } } return null; } @Override public void dump(FileDescriptor fd, String[] args) throws RemoteException { getRemoteBinder().dump(fd, args); } @Override public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { return getRemoteBinder().transact(code, data, reply, flags); } @Override public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException { /** * ServiceWrapper存在的意义在于能自动检测远程Binder进程死去并且在需要的时候重新获取, * 因此对于ServiceWrapper来说再设置DeathRecipient没有任何意义。 */ if (DEBUG) { throw new UnsupportedOperationException("ServiceWrapper does NOT support Death Recipient!"); } } @Override public boolean unlinkToDeath(DeathRecipient recipient, int flags) { /** * ServiceWrapper存在的意义在于能自动检测远程Binder进程死去并且在需要的时候重新获取, * 因此对于ServiceWrapper来说再设置DeathRecipient没有任何意义。 */ return false; } @Override public void binderDied() { if (DEBUG) { Log.d(TAG, "ServiceWrapper [binderDied]: " + mName); } mRemote = null; } // Override API 15 public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException { } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ContextInjector.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.content.Intent; import android.os.Bundle; /** * PluginContext 相关流程逻辑注入, 可通过回调自定义PluginContext(插件Context)中的某些行为逻辑 * * @author RePlugin Team */ public interface ContextInjector { /** * 回调时机:调用super.startActivity之前 *

* 可供业务层修改Intent参数或者加入自定义逻辑 * * @param intent */ void startActivityBefore(Intent intent); /** * 回调时机:调用super.startActivity之后 *

* 可供业务层修改Intent参数或者加入自定义逻辑 * * @param intent */ void startActivityAfter(Intent intent); /** * 回调时机:调用super.startActivity之前 *

* 可供业务层修改Intent参数或者加入自定义逻辑 * * @param intent */ void startActivityBefore(Intent intent, Bundle options); /** * 回调时机:调用super.startActivity之后 *

* 可供业务层修改Intent参数或者加入自定义逻辑 * * @param intent */ void startActivityAfter(Intent intent, Bundle options); /** * 回调时机:调用super.startService之前 *

* 可供业务层修改Intent参数或者加入自定义逻辑 * * @param service */ void startServiceBefore(Intent service); /** * 回调时机:调用super.startService之后 *

* 可供业务层修改Intent参数或者加入自定义逻辑 * * @param service */ void startServiceAfter(Intent service); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/DefaultRePluginCallbacks.java ================================================ package com.qihoo360.replugin; import android.content.Context; /** * @deprecated Use RePluginCallbacks instead * @see RePluginCallbacks * @author RePlugin Team */ public class DefaultRePluginCallbacks extends RePluginCallbacks { /** * @deprecated Use RePluginCallbacks instead * @see RePluginCallbacks#RePluginCallbacks(Context) */ public DefaultRePluginCallbacks(Context context) { super(context); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/DefaultRePluginEventCallbacks.java ================================================ package com.qihoo360.replugin; import android.content.Context; /** * @deprecated Use RePluginEventCallbacks instead * @see RePluginEventCallbacks * @author RePlugin Team */ public class DefaultRePluginEventCallbacks extends RePluginEventCallbacks { /** * @deprecated Use RePluginEventCallbacks instead * @see RePluginEventCallbacks#RePluginEventCallbacks(Context) */ public DefaultRePluginEventCallbacks(Context context) { super(context); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/IHostBinderFetcher.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.os.IBinder; /** * 用来实现主程序提供IBinder给其他插件 *

* 插件获取方法:Factory.query("main", "IShare"),返回值:IBinder *

* TODO 未来会废弃Factory类,并做些调整 * * @author RePlugin Team */ public interface IHostBinderFetcher { /** * 主程序需实现此方法,来返回一个IBinder对象,供插件使用 * * @param module 模块名 * @return 一个IBinder对象 */ IBinder query(String module); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/PluginDexClassLoader.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.os.Build; import com.qihoo360.loader2.PluginManager; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.utils.CloseableUtils; import com.qihoo360.replugin.utils.FileUtils; import com.qihoo360.replugin.utils.ReflectUtils; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import dalvik.system.DexClassLoader; /** * 插件的DexClassLoader。用来做一些“更高级”的特性,在RePluginConfig中可直接配置 *

注:原本只需要DexClassLoader即可,但若要支持一些高级特性(如可自由使用宿主的Class),则仍需实现相应方法 * * @author RePlugin Team */ public class PluginDexClassLoader extends DexClassLoader { private static final String TAG = "PluginDexClassLoader"; private final ClassLoader mHostClassLoader; private static Method sLoadClassMethod; private String mPluginName; /** * 初始化插件的DexClassLoader的构造函数。插件化框架会调用此函数。 * * @param pi the plugin's info,refer to {@link PluginInfo} * @param dexPath the list of jar/apk files containing classes and * resources, delimited by {@code File.pathSeparator}, which * defaults to {@code ":"} on Android * @param optimizedDirectory directory where optimized dex files * should be written; must not be {@code null} * @param librarySearchPath the list of directories containing native * libraries, delimited by {@code File.pathSeparator}; may be * {@code null} * @param parent the parent class loader */ public PluginDexClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, optimizedDirectory, librarySearchPath, parent); mPluginName = pi.getName(); installMultiDexesBeforeLollipop(pi, dexPath, parent); ClassLoader host = RePluginInternal.getAppClassLoader(); //防止再loader进程加载类出现死循环的问题 if (PluginManager.isPluginProcess() && host instanceof RePluginClassLoader) { mHostClassLoader = ((RePluginClassLoader) host).getOrig(); } else { mHostClassLoader = host; } initMethods(mHostClassLoader); } private static void initMethods(ClassLoader cl) { Class clz = cl.getClass(); if (sLoadClassMethod == null) { sLoadClassMethod = ReflectUtils.getMethod(clz, "loadClass", String.class, Boolean.TYPE); if (sLoadClassMethod == null) { throw new NoSuchMethodError("loadClass"); } } } @Override protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException { // 插件自己的Class。从自己开始一直到BootClassLoader,采用正常的双亲委派模型流程,读到了就直接返回 Class pc = null; ClassNotFoundException cnfException = null; try { pc = super.loadClass(className, resolve); if (pc != null) { // 只有开启“详细日志”才会输出,防止“刷屏”现象 if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.d(TAG, "loadClass: load plugin class, cn=" + className); } return pc; } } catch (ClassNotFoundException e) { // Do not throw "e" now cnfException = e; if (PluginDexClassLoaderPatch.need2LoadFromHost(className)) { try { return loadClassFromHost(className, resolve); } catch (ClassNotFoundException e1) { // Do not throw "e1" now cnfException = e1; if (LogDebug.LOG) { LogDebug.e(TAG, "loadClass ClassNotFoundException, from HostClassLoader&&PluginClassLoader, cn=" + className + ", pluginName=" + mPluginName); } } } else { if (LogDebug.LOG) { LogDebug.e(TAG, "loadClass ClassNotFoundException, from PluginClassLoader, cn=" + className + ", pluginName=" + mPluginName); } } } // 若插件里没有此类,则会从宿主ClassLoader中找,找到了则直接返回 // 注意:需要读取isUseHostClassIfNotFound开关。默认为关闭的。可参见该开关的说明 if (RePlugin.getConfig().isUseHostClassIfNotFound()) { try { return loadClassFromHost(className, resolve); } catch (ClassNotFoundException e) { // Do not throw "e" now cnfException = e; } } // At this point we can throw the previous exception if (cnfException != null) { throw cnfException; } return null; } private Class loadClassFromHost(String className, boolean resolve) throws ClassNotFoundException { Class c; try { c = (Class) sLoadClassMethod.invoke(mHostClassLoader, className, resolve); // 只有开启“详细日志”才会输出,防止“刷屏”现象 if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.w(TAG, "loadClass: load host class, cn=" + className + ", cz=" + c); } } catch (IllegalAccessException e) { // Just rethrow throw new ClassNotFoundException("Calling the loadClass method failed (IllegalAccessException)", e); } catch (InvocationTargetException e) { // Just rethrow throw new ClassNotFoundException("Calling the loadClass method failed (InvocationTargetException)", e); } return c; } /** * install extra dexes * * @param pi * @param dexPath * @param parent * @deprecated apply to ROM before Lollipop,may be deprecated */ private void installMultiDexesBeforeLollipop(PluginInfo pi, String dexPath, ClassLoader parent) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return; } try { // get paths of extra dex List dexFiles = getExtraDexFiles(pi, dexPath); if (dexFiles != null && dexFiles.size() > 0) { List allElements = new LinkedList<>(); // get dexElements of main dex Class clz = Class.forName("dalvik.system.BaseDexClassLoader"); Object pathList = ReflectUtils.readField(clz, this, "pathList"); Object[] mainElements = (Object[]) ReflectUtils.readField(pathList.getClass(), pathList, "dexElements"); allElements.add(mainElements); // get dexElements of extra dex (need to load dex first) String optimizedDirectory = pi.getExtraOdexDir().getAbsolutePath(); for (File file : dexFiles) { if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.d(TAG, "dex file:" + file.getName()); } DexClassLoader dexClassLoader = new DexClassLoader(file.getAbsolutePath(), optimizedDirectory, optimizedDirectory, parent); Object obj = ReflectUtils.readField(clz, dexClassLoader, "pathList"); Object[] dexElements = (Object[]) ReflectUtils.readField(obj.getClass(), obj, "dexElements"); allElements.add(dexElements); } // combine Elements Object combineElements = combineArray(allElements); // rewrite Elements combined to classLoader ReflectUtils.writeField(pathList.getClass(), pathList, "dexElements", combineElements); // delete extra dex, after optimized FileUtils.forceDelete(pi.getExtraDexDir()); //Test whether the Extra Dex is installed if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { Object object = ReflectUtils.readField(pathList.getClass(), pathList, "dexElements"); int length = Array.getLength(object); LogDebug.d(TAG, "dexElements length:" + length); } } } catch (Exception e) { e.printStackTrace(); } } /** * combine dexElements Array * * @param allElements all dexElements of dexes * @return the combined dexElements */ private Object combineArray(List allElements) { int startIndex = 0; int arrayLength = 0; Object[] originalElements = null; for (Object[] elements : allElements) { if (originalElements == null) { originalElements = elements; } arrayLength += elements.length; } Object[] combined = (Object[]) Array.newInstance( originalElements.getClass().getComponentType(), arrayLength); for (Object[] elements : allElements) { System.arraycopy(elements, 0, combined, startIndex, elements.length); startIndex += elements.length; } return combined; } /** * get paths of extra dex * * @param pi * @param dexPath * @return the File list of the extra dexes */ private List getExtraDexFiles(PluginInfo pi, String dexPath) { ZipFile zipFile = null; List files = null; try { if (pi != null) { zipFile = new ZipFile(dexPath); files = traverseExtraDex(pi, zipFile); } } catch (Exception e) { e.printStackTrace(); } finally { CloseableUtils.closeQuietly(zipFile); } return files; } /** * traverse extra dex files * * @param pi * @param zipFile * @return the File list of the extra dexes */ private static List traverseExtraDex(PluginInfo pi, ZipFile zipFile) { String dir = null; List files = new LinkedList<>(); Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); String name = entry.getName(); if (name.contains("../")) { // 过滤,防止被攻击 continue; } try { if (name.contains(".dex") && !name.equals("classes.dex")) { if (dir == null) { dir = pi.getExtraDexDir().getAbsolutePath(); } File file = new File(dir, name); extractFile(zipFile, entry, file); files.add(file); if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.d(TAG, "dex path:" + file.getAbsolutePath()); } } } catch (Exception e) { e.printStackTrace(); } } return files; } /** * extract File * * @param zipFile * @param ze * @param outFile * @throws IOException */ private static void extractFile(ZipFile zipFile, ZipEntry ze, File outFile) throws IOException { InputStream in = null; try { in = zipFile.getInputStream(ze); FileUtils.copyInputStreamToFile(in, outFile); if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.d(TAG, "extractFile(): Success! fn=" + outFile.getName()); } } finally { CloseableUtils.closeQuietly(in); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/PluginDexClassLoaderPatch.java ================================================ package com.qihoo360.replugin; /** * PluginDexClassLoader's Patch * * @author RePlugin Team */ public class PluginDexClassLoaderPatch { // org.apache.http.legacy.jar 中的包 private static final String[] APACHE_HTTP_LEGACY_PACKAGES = { "android.net.http", "android.net.compatibility", "com.android.internal.http.multipart", "org.apache.commons.codec", "org.apache.commons.logging", "org.apache.http" }; // OkHttp3 中的包 private static final String[] OKHTTP3_PACKAGES = { "okhttp3", "okio" }; /** * 当一个类,从插件中找不到时,是否需要再从宿主中找一找 * * @param className * @return */ public static boolean need2LoadFromHost(String className) { return isOkHttp3(className) || isApacheHttpLegacy(className) ; } /** * 是否为 org.apache.http.legacy.jar 中的类 *

* Android P 之前,org.apache.http.legacy.jar 是被 BootClassLoader 加载的。 * Android P, org.apache.http.legacy.jar 改成了被 PathClassLoader 加载。 *

* 带来的影响:插件ClassLoader,如果parent ClassLoader是BootClassLoader,就肯定找不到org.apache.http.legacy.jar中的类 * 这时候,需要使用hostClassLoader加载一下 * * @param className */ private static boolean isApacheHttpLegacy(String className) { for (String packagePrefix : APACHE_HTTP_LEGACY_PACKAGES) { if (className.startsWith(packagePrefix)) { return true; } } return false; } /** * 是否为 OkHttp3 中的类 * * @param className */ private static boolean isOkHttp3(String className) { for (String packagePrefix : OKHTTP3_PACKAGES) { if (className.startsWith(packagePrefix)) { return true; } } return false; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePlugin.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.app.Activity; import android.app.Application; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.qihoo360.loader.utils.LocalBroadcastManager; import com.qihoo360.i.Factory; import com.qihoo360.i.Factory2; import com.qihoo360.i.IPluginManager; import com.qihoo360.loader2.CertUtils; import com.qihoo360.loader2.DumpUtils; import com.qihoo360.loader2.MP; import com.qihoo360.loader2.PMF; import com.qihoo360.loader2.PluginStatusController; import com.qihoo360.mobilesafe.api.AppVar; import com.qihoo360.mobilesafe.api.Tasks; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.mobilesafe.svcmanager.QihooServiceManager; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.component.app.PluginApplicationClient; import com.qihoo360.replugin.debugger.DebuggerReceivers; import com.qihoo360.replugin.helper.HostConfigHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.packages.PluginFastInstallProviderProxy; import com.qihoo360.replugin.packages.PluginInfoUpdater; import com.qihoo360.replugin.packages.PluginManagerProxy; import com.qihoo360.replugin.packages.PluginRunningList; import com.qihoo360.replugin.packages.RePluginInstaller; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import static com.qihoo360.replugin.helper.LogDebug.LOG; /** * RePlugin的对外入口类

* 宿主App可直接调用此类中的方法,来使用插件化的几乎全部的逻辑。 * * @author RePlugin Team */ public class RePlugin { private static final String TAG = "RePlugin"; /** * 插件名为“宿主”。这样插件可以直接通过一些方法来使用“宿主”的接口 */ public static final String PLUGIN_NAME_MAIN = "main"; /** * 表示目标进程根据实际情况自动调配 */ public static final String PROCESS_AUTO = "" + IPluginManager.PROCESS_AUTO; /** * 表示目标为UI进程 */ public static final String PROCESS_UI = "" + IPluginManager.PROCESS_UI; /** * 表示目标为常驻进程(名字可变,见BuildConfig内字段) */ public static final String PROCESS_PERSIST = "" + IPluginManager.PROCESS_PERSIST; private static RePluginConfig sConfig; /** * 安装或升级此插件

* 注意:

* 1、这里只将APK移动(或复制)到“插件路径”下,不释放优化后的Dex和Native库,不会加载插件

* 2、支持“纯APK”和“p-n”(旧版,即将废弃)插件

* 3、此方法是【同步】的,耗时较少

* 4、不会触发插件“启动”逻辑,因此只要插件“当前没有被使用”,再次调用此方法则新插件立即生效 * * @param path 插件安装的地址。必须是“绝对路径”。通常可以用context.getFilesDir()来做 * @return 安装成功的插件信息,外界可直接读取 * @since 2.0.0 (1.x版本为installDelayed) */ public static PluginInfo install(String path) { if (TextUtils.isEmpty(path)) { throw new IllegalArgumentException(); } // 判断文件合法性 File file = new File(path); if (!file.exists()) { if (LogDebug.LOG) { LogDebug.e(TAG, "install: File not exists. path=" + path); } return null; } else if (!file.isFile()) { if (LogDebug.LOG) { LogDebug.e(TAG, "install: Not a valid file. path=" + path); } return null; } // 若为p-n开头的插件,则必须是从宿主设置的“插件安装路径”上(默认为files目录)才能安装,其余均不允许 if (path.startsWith("p-n-")) { String installPath = RePlugin.getConfig().getPnInstallDir().getAbsolutePath(); if (!path.startsWith(installPath)) { if (LogDebug.LOG) { LogDebug.e(TAG, "install: Must be installed from the specified path. Path=" + path + "; Allowed=" + installPath); } return null; } } return MP.pluginDownloaded(path); } /** * 卸载此插件

* 注意:

* 1、此卸载功能只针对"纯APK"插件方案

* 2、若插件正在运行,则直到下次重启进程后才生效 * * @param pluginName 待卸载插件名字 * @return 插件卸载是否成功 * @since 2.1.0 */ public static boolean uninstall(String pluginName) { if (TextUtils.isEmpty(pluginName)) { throw new IllegalArgumentException(); } return MP.pluginUninstall(pluginName); } /** * 预加载此插件。此方法会立即释放优化后的Dex和Native库,但不会运行插件代码。

* 具体用法可参见preload(PluginInfo)的说明 * * @param pluginName 要加载的插件名 * @return 预加载是否成功 * @see #preload(PluginInfo) * @since 2.0.0 */ public static boolean preload(String pluginName) { PluginInfo pi = getPluginInfo(pluginName); if (pi == null) { // 插件不存在,无法加载Dex if (LogDebug.LOG) { LogDebug.e(TAG, "preload: Plugin not found! pn=" + pluginName); } return false; } return preload(pi); } /** * 预加载此插件。此方法会立即释放优化后的Dex和Native库,但不会运行插件代码。

* 使用场景:在“安装”完成后“提前释放Dex”(时间算在“安装过程”中)。这样下次启动插件时则速度飞快

* 注意:

* 1、该方法非必须调用(见“使用场景”)。换言之,只要涉及到插件加载,就会自动完成preload操作,无需开发者关心

* 2、Dex和Native库会占用大量的“内部存储空间”。故除非插件是“确定要用的”,否则不必在安装完成后立即调用此方法

* 3、该方法为【同步】调用,且耗时较久(尤其是dex2oat的过程),建议在线程中使用

* 4、调用后将“启动”此插件,若再次升级,则必须重启进程后才生效 * * @param pi 要加载的插件信息 * @return 预加载是否成功 * @see #install(String) * @since 2.0.0 */ public static boolean preload(PluginInfo pi) { if (pi == null) { return false; } // 借助“UI进程”来快速释放Dex(见PluginFastInstallProviderProxy的说明) return PluginFastInstallProviderProxy.install(RePluginInternal.getAppContext(), pi); } /** * 是否启用调试器,Debug阶段建议开启,Release阶段建议关闭,默认为关闭状态 * * @param context Context对象 * @param enable true=开启 * @return 是否执行成功 * @since 2.0.0 */ public static boolean enableDebugger(Context context, boolean enable) { if ((null != context) && enable) { DebuggerReceivers debuggerReceivers = new DebuggerReceivers(); debuggerReceivers.registerReceivers(context); } return true; } /** * 开启一个插件的Activity

* 其中Intent的ComponentName的Key应为插件名(而不是包名),可使用createIntent方法来创建Intent对象 * * @param context Context对象 * @param intent 要打开Activity的Intent,其中ComponentName的Key必须为插件名 * @return 插件Activity是否被成功打开? * FIXME 是否需要Exception来做? * @see #createIntent(String, String) * @since 1.0.0 */ public static boolean startActivity(Context context, Intent intent) { // TODO 先用旧的开启Activity方案,以后再优化 ComponentName cn = intent.getComponent(); if (cn == null) { // TODO 需要支持Action方案 return false; } String plugin = cn.getPackageName(); String cls = cn.getClassName(); return Factory.startActivityWithNoInjectCN(context, intent, plugin, cls, IPluginManager.PROCESS_AUTO); } /** * 开启一个插件的Activity,无需调用createIntent或设置ComponentName来修改Intent * * @param context Context对象 * @param intent 要打开Activity的Intent,其中ComponentName的Key必须为插件名 * @param pluginName 插件名。稍后会填充到Intent中 * @param activity 插件的Activity。稍后会填充到Intent中 * @see #startActivity(Context, Intent) * @since 1.0.0 */ public static boolean startActivity(Context context, Intent intent, String pluginName, String activity) { // TODO 先用旧的开启Activity方案,以后再优化 return Factory.startActivity(context, intent, pluginName, activity, IPluginManager.PROCESS_AUTO); } /** * 通过 forResult 方式启动一个插件的 Activity * * @param activity 源 Activity * @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名 * @param requestCode 请求码 * @see #startActivityForResult(Activity, Intent, int, Bundle) * @since 2.1.3 */ public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode) { return Factory.startActivityForResult(activity, intent, requestCode, null); } /** * 通过 forResult 方式启动一个插件的 Activity * * @param activity 源 Activity * @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名 * @param requestCode 请求码 * @param options 附加的数据 * @see #startActivityForResult(Activity, Intent, int, Bundle) * @since 2.1.3 */ public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) { return Factory.startActivityForResult(activity, intent, requestCode, options); } /** * 创建一个用来定向到插件组件的Intent

*

* 推荐用法:

* * Intent in = RePlugin.createIntent("clean", "com.qihoo360.mobilesafe.clean.CleanActivity"); *

* 当然,也可以用标准的Android创建方法:

* * Intent in = new Intent();

* in.setComponent(new ComponentName("clean", "com.qihoo360.mobilesafe.clean.CleanActivity")); * * * @param pluginName 插件名 * @param cls 目标全名 * @return 可以被RePlugin识别的Intent * @since 1.0.0 */ public static Intent createIntent(String pluginName, String cls) { Intent in = new Intent(); in.setComponent(createComponentName(pluginName, cls)); return in; } /** * 创建一个用来定向到插件组件的ComponentName,其Key为插件名,Value为目标组件的类全名 * * @param pluginName 插件名 * @param cls 目标组件全名 * @return 一个修改过的ComponentName对象 * @since 1.0.0 */ public static ComponentName createComponentName(String pluginName, String cls) { return new ComponentName(pluginName, cls); } /** * 添加允许插件使用的签名指纹。一旦添加进来,则通过该签名制作的插件将允许被执行,否则将不能被执行

* 注意:请不要从Prefs中“缓存”签名信息,防止别他人篡改后,导致恶意插件被校验通过 * * @param sign 签名指纹 * @since 1.0.0 */ public static void addCertSignature(String sign) { if (TextUtils.isEmpty(sign)) { throw new IllegalArgumentException("arg is null"); } CertUtils.SIGNATURES.add(sign.toUpperCase()); } /** * 是否使用Dev版AAR?可支持一些"调试特性",但该AAR【千万不要用于发布环境】

* Dev版的AAR可支持如下特性:

* 1、插件签名不正确时仍允许被安装进来,这样利于调试(发布环境上则容易导致严重安全隐患)

* 2、可以打出一些完整的日志(发布环境上则容易被逆向,进而对框架稳定性、私密性造成严重影响) * * @return 是否使用Dev版的AAR? * @since 1.0.0 */ public static boolean isForDev() { return RePluginInternal.FOR_DEV; } /** * 获取当前版本 * * @return 版本号,如2.0.0等 * @since 2.0.0 */ public static String getVersion() { return BuildConfig.VERSION_NAME; } /** * 获取插件的组件列表(ComponentList)

* 注意:这里会尝试安装插件,但不会加载资源和代码,因此会有一些耗时的情况

* 性能消耗(小 → 大):ComponentList/PackageInfo(This) < Resources < ClassLoader < Context < Binder * * @param pluginName 要获取的插件名 * @return ComponentList对象 * @see ComponentList * @since 1.0.0 */ public static ComponentList fetchComponentList(String pluginName) { return Factory.queryPluginComponentList(pluginName); } /** * 加载插件,并获取插件的包信息

* 注意:这里会尝试加载插件,并释放其Jar包。但不会读取资源,也不会释放oat/odex

* 性能消耗(小 → 大):ComponentList/PackageInfo(This) < Resources < ClassLoader < Context < Binder * * @param pluginName 插件名 * @return PackageInfo对象 * @see PackageInfo * @since 1.0.0 */ public static PackageInfo fetchPackageInfo(String pluginName) { return Factory.queryPluginPackageInfo(pluginName); } /** * 加载插件,并获取插件的资源信息

* 注意:这里会尝试安装插件,并释放其Jar包,读取资源,但不会释放oat/odex。

* 性能消耗(小 → 大):ComponentList/PackageInfo < Resources(This) < ClassLoader < Context < Binder * * @param pluginName 插件名 * @return Resources对象 * @see Resources * @since 1.0.0 */ public static Resources fetchResources(String pluginName) { return Factory.queryPluginResouces(pluginName); } /** * 加载插件,并获取插件自身的ClassLoader对象,以调用插件内部的类

* 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久

* 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < ClassLoader(This) < Context < Binder * * @param pluginName 插件名 * @return 插件的ClassLoader对象 * @since 1.0.0 */ public static ClassLoader fetchClassLoader(String pluginName) { return Factory.queryPluginClassLoader(pluginName); } /** * 加载插件,并获取插件自身的Context对象,以获取资源等信息

* 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久

* 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < ClassLoader < Context(This) < Binder * * @param pluginName 插件名 * @return 插件的Context对象 * @since 1.0.0 */ public static Context fetchContext(String pluginName) { return Factory.queryPluginContext(pluginName); } /** * 加载插件,并通过插件里的Plugin类,获取插件定义的IBinder

* 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久

* 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < Context/ClassLoader < Binder(This)

*

* PluginBinder(如使用使用本方法)和GlobalBinder类方法(如getGlobalBinder)的不同:

* 1、PluginBinder需要指定插件;GlobalBinder无需指定

* 2、PluginBinder获取的是插件内部已定义好的Binder;GlobalBinder在获取时必须先在代码中注册 * * @param pluginName 插件名 * @param module 要加载的插件模块 * @param process 进程名 TODO 现阶段只能使用IPluginManager中的值,请务必使用它们,否则会出现问题 * @return 返回插件定义的IBinder对象,供外界使用 * @see #getGlobalBinder(String) * @since 1.0.0 */ public static IBinder fetchBinder(String pluginName, String module, String process) { return Factory.query(pluginName, module, Integer.parseInt(process)); } /** * 在当前进程加载插件,并通过插件里的Plugin类,获取插件定义的IBinder

* 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久

* 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < Context/ClassLoader < Binder(This)

*

* PluginBinder(如使用使用本方法)和GlobalBinder类方法(如getGlobalBinder)的不同:

* 1、PluginBinder需要指定插件;GlobalBinder无需指定

* 2、PluginBinder获取的是插件内部已定义好的Binder;GlobalBinder在获取时必须先在代码中注册 * * @param pluginName 插件名 * @param module 要加载的插件模块 * @return 返回插件定义的IBinder对象,供外界使用 * @see #getGlobalBinder(String) * @since 2.1.0 */ public static IBinder fetchBinder(String pluginName, String module) { return Factory.query(pluginName, module); } /** * 通过ClassLoader对象来获取该ClassLoader应属于哪个插件 *

* 该方法消耗非常小,可直接使用 * * @param cl ClassLoader对象 * @return 插件名 * @since 1.0.0 */ public static String fetchPluginNameByClassLoader(ClassLoader cl) { return Factory.fetchPluginName(cl); } /** * 通过资源名(包括前缀和具体名字),来获取指定插件里的资源的ID *

* 性能消耗:等同于 fetchResources * * @param pluginName 插件名 * @param resTypeAndName 要获取的“资源类型+资源名”,格式为:“[type]/[name]”。例如:

* → layout/common_title → 从“布局”里获取common_title的ID

* → drawable/common_bg → 从“可绘制图片”里获取common_bg的ID

* 详细见Android官方的说明 * @return 资源的ID。若为0,则表示资源没有找到,无法使用 * @since 2.2.0 */ public static int fetchResourceIdByName(String pluginName, String resTypeAndName) { PackageInfo pi = fetchPackageInfo(pluginName); if (pi == null) { // 插件没有找到 if (LogDebug.LOG) { LogDebug.e(TAG, "fetchResourceIdByName: Plugin not found. pn=" + pluginName + "; resName=" + resTypeAndName); } return 0; } Resources res = fetchResources(pluginName); if (res == null) { // 不太可能出现此问题,同样为插件没有找到 if (LogDebug.LOG) { LogDebug.e(TAG, "fetchResourceIdByName: Plugin not found (fetchResources). pn=" + pluginName + "; resName=" + resTypeAndName); } return 0; } // Identifier的第一个参数想要的是: // [包名]:[类型名]/[资源名]。其中[类型名]/[资源名]就是 resTypeAndName 参数 // 例如:com.qihoo360.replugin.sample.demo2:layout/from_demo1 String idKey = pi.packageName + ":" + resTypeAndName; return res.getIdentifier(idKey, null, null); } /** * 通过Layout名,来获取插件内的View,并自动做“强制类型转换”(也可直接使用View类型)

* 注意:若使用的是公共库,则务必按照Provided的形式引入,否则会出现“不同ClassLoader”导致的ClassCastException

* 当然,非公共库不受影响,但请务必使用Android Framework内的View(例如WebView、ViewGroup等),或索性直接使用View * * @param pluginName 插件名 * @param layoutName Layout名字 * @param root Optional view to be the parent of the generated hierarchy. * @return 插件的View。若为Null则表示获取失败 * @throws ClassCastException 若不是想要的那个View类型,或者ClassLoader不同,则可能会出现此异常。应确保View类型正确 * @since 2.2.0 */ public static T fetchViewByLayoutName(String pluginName, String layoutName, ViewGroup root) { Context context = fetchContext(pluginName); if (context == null) { // 插件没有找到 if (LogDebug.LOG) { LogDebug.e(TAG, "fetchViewByLayoutName: Plugin not found. pn=" + pluginName + "; layoutName=" + layoutName); } } String resTypeAndName = "layout/" + layoutName; int id = fetchResourceIdByName(pluginName, resTypeAndName); if (id <= 0) { // 无法拿到资源,可能是资源没有找到 if (LogDebug.LOG) { LogDebug.e(TAG, "fetchViewByLayoutName: fetch failed! pn=" + pluginName + "; layoutName=" + layoutName); } return null; } // TODO 可能要考虑WebView在API 19以上的特殊性 // 强制转换到T类型,一旦转换出错就抛出ClassCastException异常并告诉外界 // noinspection unchecked return (T) LayoutInflater.from(context).inflate(id, root); } /** * 获取所有插件的列表(指已安装的) * * @return PluginInfo的表 * @since 2.0.0(1.x版本为getExistPlugins) */ public static List getPluginInfoList() { return MP.getPlugins(true); } /** * 获取指定插件的信息 * * @param name 插件名 * @return PluginInfo对象 * @since 1.2.0 */ public static PluginInfo getPluginInfo(String name) { return MP.getPlugin(name, true); } /** * 获取当前插件的版本号,可以是VersionCode,也可以是meta-data中的ver。 * * @param name 插件名 * @return 插件版本号。若为-1则表示插件不存在 * @since 2.0.0 */ public static int getPluginVersion(String name) { PluginInfo pi = MP.getPlugin(name, false); if (pi == null) { return -1; } return pi.getVersion(); } /** * 判断插件是否已被安装(但不一定被使用过,如可能不会释放Dex、Native库等)

* 注意:RePlugin 1.x版本中,isPluginInstalled方法等于现在的isPluginUsed,故含义有变 * * @param pluginName 插件名 * @return 是否被安装 * @since 2.0.0 (1.x版本为isPluginExists) */ public static boolean isPluginInstalled(String pluginName) { PluginInfo pi = MP.getPlugin(pluginName, false); return pi != null; } /** * 判断插件是否曾被使用过。只要释放过Dex、Native的,就认为是“使用过”的

* 和isPluginDexExtracted的区别:插件会在升级完成后,会删除旧Dex。其isPluginDexExtracted为false,而isPluginUsed仍为true * * @param pluginName 插件名 * @return 插件是否已被使用过 * @since 2.0.0 */ public static boolean isPluginUsed(String pluginName) { PluginInfo pi = MP.getPlugin(pluginName, false); return pi != null && pi.isUsed(); } /** * 判断当前插件是否已释放了Dex、Native库等 * * @param pluginName 插件名 * @return 是否已被使用过 * @since 2.0.0 (原为isPluginInstalled) */ public static boolean isPluginDexExtracted(String pluginName) { PluginInfo pi = MP.getPlugin(pluginName, false); return pi != null && pi.isDexExtracted(); } /** * 当前插件是否在运行。只要任意进程在,就都属于此情况 * * @param pluginName 插件名 * @return 插件是否正在被运行 * @since 2.0.0 */ public static boolean isPluginRunning(String pluginName) { try { return PluginManagerProxy.isPluginRunning(pluginName); } catch (RemoteException e) { // 常驻进程中断,且当前进程也没有运行。先返回False if (LogRelease.LOGR) { e.printStackTrace(); } return false; } } /** * 当前插件是否在指定进程中运行 * * @param pluginName 插件名 * @param process 指定的进程名,必须为全名 * @return 插件是否在指定进程中运行 * @since 2.0.0 */ public static boolean isPluginRunningInProcess(String pluginName, String process) { try { return PluginManagerProxy.isPluginRunningInProcess(pluginName, process); } catch (RemoteException e) { // 常驻进程中断,且当前进程也没有运行。先返回False if (LogRelease.LOGR) { e.printStackTrace(); } return false; } } /** * 获取所有正在运行的插件列表 * * @return 所有正在运行的插件的List * @see PluginRunningList * @since 2.0.0 */ public static PluginRunningList getRunningPlugins() { return PluginManagerProxy.getRunningPluginsNoThrows(); } /** * 获取正在运行此插件的进程名列表

* 若要获取PID,可在拿到列表后,通过IPC.getPidByProcessName来反查 * * @param pluginName 要查询的插件名 * @return 正在运行此插件的进程名列表。一定不会为Null * @see IPC#getPidByProcessName(String) * @since 2.0.0 */ public static String[] getRunningProcessesByPlugin(String pluginName) { return PluginManagerProxy.getRunningProcessesByPluginNoThrows(pluginName); } /** * 当前是否处于"常驻进程"? * * @return 是否处于常驻进程 * @since 1.1.0 */ public static boolean isCurrentPersistentProcess() { return IPC.isPersistentProcess(); } /** * 获取RePlugin的Config对象。请参见RePluginConfig类的说明 * * @return RePluginConfig对象。注意,即便没有在attachBaseContext中自定义,也仍会有个默认的对象 * @see RePluginConfig * @see App#attachBaseContext(Application, RePluginConfig) * @since 1.2.0 */ public static RePluginConfig getConfig() { return sConfig; } /** * 注册“安装完成后的通知”广播

* 此为“本地”广播,插件内也可以接收到。开发者也可以自行注册,做法:

* * IntentFilter itf = new IntentFilter(MP.ACTION_NEW_PLUGIN);

* LocalBroadcastManager.getInstance(context).registerReceiver(r, itf); * * * @param context Context对象 * @param r 要绑定的BroadcastReceiver对象 * @since 1.0.0 */ public static void registerInstalledReceiver(Context context, BroadcastReceiver r) { IntentFilter itf = new IntentFilter(RePluginConstants.ACTION_NEW_PLUGIN); LocalBroadcastManager.getInstance(context).registerReceiver(r, itf); } /** * 在宿主内注册一个可被其它插件所获取的Binder的对象 * * @param hbf 一个IHostBinderFetcher对象 * @see #fetchBinder(String, String, String) * @since 1.0.0 */ public static void registerHostBinder(IHostBinderFetcher hbf) { MP.installBuiltinPlugin("main", hbf); } /** * 注册一个无需插件名,可被全局使用的Binder对象。Binder对象必须事先创建好

* 有关GlobalBinder的详细介绍,请参见getGlobalBinder的说明

* 有关此方法和registerGlobalBinderDelayed的区别,请参见其方法说明。 * * @param name Binder的描述名 * @param binder Binder对象 * @return 是否注册成功 * @see #getGlobalBinder(String) * @see #registerGlobalBinderDelayed(String, IBinderGetter) * @since 1.2.0 */ public static boolean registerGlobalBinder(String name, IBinder binder) { return QihooServiceManager.addService(RePluginInternal.getAppContext(), name, binder); } /** * 注册一个无需插件名,可被全局使用的Binder对象,但Binder对象只有在“用到时”才会被创建

* 有关GlobalBinder的详细介绍,请参见getGlobalBinder的说明

*

* 和registerGlobalBinder不同的是:

* 1、前者的binder对象必须事先创建好并传递到参数中

*   适用于Binder在注册时就立即创建(性能消耗小),或未来使用频率非常多的情况。如“用户账号服务”、“基础服务”等

* 2、后者会在getGlobalBinder指定的name被首次调用后,才会尝试获取Binder对象

*   适用于Binder只在使用时才被创建(确保启动性能快),或未来调用频率较少的情况。如“Root服务”、“特色功能服务”等

* * @param name Binder的描述名 * @param getter 当getGlobalBinder调用时匹配到name后,会调用getter.get()方法来获取IBinder对象 * @return 是否延迟注册成功 * @since 1.2.0 */ public static boolean registerGlobalBinderDelayed(String name, IBinderGetter getter) { return QihooServiceManager.addService(RePluginInternal.getAppContext(), name, getter); } /** * 取消全局Binder对象的注册。这样当调用getGlobalBinder时将不再返回结果

* 有关globalBinder的详细介绍,请参见registerGlobalBinder的说明 * * @param name Binder的描述名 * @return 是否取消成功 * @see #getGlobalBinder(String) * @since 1.2.0 */ public static boolean unregisterGlobalBinder(String name) { return QihooServiceManager.removeService(RePluginInternal.getAppContext(), name, null); } /** * 获取一个无需插件名,可被全局使用的Binder对象。必须先调用registerGlobalBinder注册后才能获得

* PluginBinder(如使用fetchBinder)和GlobalBinder类方法的不同:

* 1、PluginBinder需要指定插件;GlobalBinder无需指定

* 2、PluginBinder获取的是插件内部已定义好的Binder;GlobalBinder在获取时必须先在代码中注册 * * @param name Binder的描述名 * @return Binder对象。若之前从未注册过,或已被取消注册,则返回Null。 * @see #registerGlobalBinder(String, IBinder) * @see #unregisterGlobalBinder(String) * @see #fetchBinder(String, String, String) * @since 1.2.0 */ public static IBinder getGlobalBinder(String name) { return QihooServiceManager.getService(RePluginInternal.getAppContext(), name); } /** * 注册一个“跳转”类。一旦系统或自身想调用指定类时,将自动跳转到插件里的另一个类。

* 例如,系统想访问CallShowService类,但此类在宿主中不存在,只在callshow插件中有,则:

* 未注册“跳转类”时:直接到宿主中寻找CallShowService类,找到后就加载,找不到就崩溃(若不Catch)

* 注册“挑转类”后,直接将CallShowService的调用“跳转到”callshow插件的CallShowService2类中(名字可以不同)。这种情况下,需要调用:

* * RePlugin.registerHookingClass("com.qihoo360.mobilesafe.CallShowService",

*             RePlugin.createComponentName("callshow", "com.qihoo360.callshow.CallShowService2"),

*             DummyService.class); * *

* 该方法可以玩出很多【新花样】。如,可用于以下场景:

* * 已在宿主Manifest中声明了插件的四大组件,只是想借助插件化框架来找到该类并加载进来(如前述例子)。

* * 某些不方便(不好用,或需要云控)的类,想借机替换成插件里的。

*   如我们有一个LaunchUtils类,现在想使用Utils插件中的同样的类来替代。 * * @param source 要替换的类的全名 * @param target 要替换的类的目标,需要使用 createComponentName 方法来创建 * @param defClass 若要替换的类不存在,或插件不可用,则应该使用一个默认的Class。 *

* 可替换成如下的形式,也可以传Null。但若访问的是四大组件,传Null可能会导致出现App崩溃(且无法被Catch) *

* DummyService.class *

* DummyActivity.class *

* DummyProvider.class *

* DummyReceiver.class * @see com.qihoo360.replugin.component.dummy.DummyActivity * @see com.qihoo360.replugin.component.dummy.DummyService * @see com.qihoo360.replugin.component.dummy.DummyReceiver * @see com.qihoo360.replugin.component.dummy.DummyProvider * @since 1.0.0 */ public static void registerHookingClass(String source, ComponentName target, Class defClass) { Factory2.registerDynamicClass(source, target.getPackageName(), target.getClassName(), defClass); } /** * 查询某个 Component 是否是“跳转”类 * * @param component 要查询的组件信息,其中 packageName 为插件名称,className 为要查询的类名称 * @since 2.0.0 */ public static boolean isHookingClass(ComponentName component) { return Factory2.isDynamicClass(component.getPackageName(), component.getClassName()); } /** * 取消对某个“跳转”类的注册,恢复原状。

* 请参见 registerHookingClass 的详细说明 * * @param source 要替换的类的全名 * @see #registerHookingClass(String, ComponentName, Class) * @since 2.1.6 */ public static void unregisterHookingClass(String source) { Factory2.unregisterDynamicClass(source); } /** * 支持将APK转化成p-n-开头的插件(已经在360手机卫士80+个插件验证通过的方案),放入files目录,并返回其路径

* 注:由于目前卫士绝大多数插件还是p-n开头的,"纯APK"方案还没有经过大量测试,故这里将加入此接口。

* 具体做法:

* * String pnPath = RePlugin.convertToPnFile(apkPath); * RePlugin.install(pnPath); * * * @param path 要复制的路径 * @return 安装后的p-n的路径。如为Null表示转化成p-n时出现了问题 * @since 1.0.0 * @deprecated 临时方案,为360手机助手的早期而设计。正常情况下,请要么使用p-n方案,要么使用全新"纯APK"方案 */ public static String convertToPnFile(String path) { File f = RePluginInstaller.covertToPnFile(RePluginInternal.getAppContext(), path); if (f != null) { return f.getAbsolutePath(); } return null; } /** * dump RePlugin框架运行时的详细信息,包括:Activity 坑位映射表,正在运行的 Service,以及详细的插件信息 * * @param fd * @param writer * @param args */ public static void dump(FileDescriptor fd, PrintWriter writer, String[] args) { DumpUtils.dump(fd, writer, args); } /** * RePlugin中,针对Application的入口类

* 所有针对Application的调用应从此类开始 * * @author RePlugin Team */ public static class App { static boolean sAttached; static AtomicBoolean sCreated = new AtomicBoolean(false); /** * 当Application的attachBaseContext调用时需调用此方法

* 使用插件框架默认的方案 * * @param app Application对象 * @see Application#attachBaseContext(Context) */ public static void attachBaseContext(Application app) { attachBaseContext(app, new RePluginConfig()); } /** * 当Application的attachBaseContext调用时触发

* 可自定义插件框架的回调行为。参见RePluginCallbacks类的说明 * 此方法的定制性不如RePluginConfig的版本 * * @param app Application对象 * @param pc 可供外界使用的回调 * @see Application#attachBaseContext(Context) * @see RePluginCallbacks */ public static void attachBaseContext(Application app, RePluginCallbacks pc) { attachBaseContext(app, new RePluginConfig().setCallbacks(pc)); } /** * (推荐)当Application的attachBaseContext调用时需调用此方法

* 可自定义插件框架的行为。参见RePluginConfig类的说明 * * @param app Application对象 * @see Application#attachBaseContext(Context) * @see RePluginConfig * @since 1.2.0 */ public static void attachBaseContext(Application app, RePluginConfig config) { if (sAttached) { if (LogDebug.LOG) { LogDebug.d(TAG, "attachBaseContext: Already called"); } return; } RePluginInternal.init(app); sConfig = config; sConfig.initDefaults(app); IPC.init(app); // 打印当前内存占用情况 // 只有开启“详细日志”才会输出,防止“消耗性能” if (LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.printMemoryStatus(LogDebug.TAG, "act=, init, flag=, Start, pn=, framework, func=, attachBaseContext, lib=, RePlugin"); } // 初始化HostConfigHelper(通过反射HostConfig来实现) // NOTE 一定要在IPC类初始化之后才使用 HostConfigHelper.init(); // FIXME 此处需要优化掉 AppVar.sAppContext = app; // Plugin Status Controller PluginStatusController.setAppContext(app); PMF.init(app); PMF.callAttach(); sAttached = true; } /** * 当Application的onCreate调用时触发。

* 务必先调用attachBaseContext后,才能调用此方法 * * @throws IllegalStateException 若没有调用attachBaseContext,则抛出此异常 * @see Application#onCreate() */ public static void onCreate() { if (!sAttached) { throw new IllegalStateException(); } if (!sCreated.compareAndSet(false, true)) { return; } Tasks.init(); PMF.callAppCreate(); // 注册监听PluginInfo变化的广播以接受来自常驻进程的更新 if (!IPC.isPersistentProcess()) { PluginInfoUpdater.register(RePluginInternal.getAppContext()); } // 打印当前内存占用情况 // 只有开启“详细日志”才会输出,防止“消耗性能” if (LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.printMemoryStatus(LogDebug.TAG, "act=, init, flag=, End, pn=, framework, func=, onCreate, lib=, RePlugin"); } } /** * 当Application的onLowMemory调用时触发

* 除了插件化框架本身会做一些事情外,该方法也来通知插件onLowMemory的行为 *

* 如果App的minSdkVersion >= 14,该方法不用调用 * * @see Application#onLowMemory() */ public static void onLowMemory() { // API>14采用注册回调的方式执行插件中该方法 if (Build.VERSION.SDK_INT >= 14) { return; } // 遍历插件的Application对象,并调用其onLowMemory PluginApplicationClient.notifyOnLowMemory(); } /** * 当Application的onTrimMemory调用时触发

* 除了插件化框架本身会做一些事情外,该方法也来通知插件onTrimMemory的行为 *

* 如果App的minSdkVersion >= 14,该方法不用调用 * * @see Application#onTrimMemory(int) */ public static void onTrimMemory(int level) { // API>14采用注册回调的方式执行插件中该方法 if (Build.VERSION.SDK_INT >= 14) { return; } // 遍历插件的Application对象,并调用其onTrimMemory PluginApplicationClient.notifyOnTrimMemory(level); } /** * 当Application的onConfigurationChanged调用时触发

* 除了插件化框架本身会做一些事情外,该方法也来通知插件onConfigurationChanged的行为 *

* 如果App的minSdkVersion >= 14,该方法不用调用 * * @see Application#onConfigurationChanged(Configuration) */ public static void onConfigurationChanged(Configuration newConfig) { // API>14采用注册回调的方式执行插件中该方法 if (Build.VERSION.SDK_INT >= 14) { return; } // 遍历插件的Application对象,并调用其onConfigurationChanged PluginApplicationClient.notifyOnConfigurationChanged(newConfig); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginApplication.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.app.Application; import android.content.Context; import android.content.res.Configuration; /** * 方便宿主直接继承RePluginApplication来完成插件框架的注册

* 此外,也可以无需继承此Application,自己择机调用相关方法来使用 * * @author RePlugin Team */ public class RePluginApplication extends Application { /** * 子类可以复写此方法来自定义RePluginConfig。请参见 RePluginConfig 的说明 * * @see RePluginConfig * @return 新的RePluginConfig对象 */ protected RePluginConfig createConfig() { return new RePluginConfig(); } /** * 子类可以复写此方法来自定义RePluginCallbacks。请参见 RePluginCallbacks 的说明

* 注意:若在createConfig的RePluginConfig内同时也注册了Callbacks,则以这里创建出来的为准 * * @see RePluginCallbacks * @return 新的RePluginCallbacks对象,可以为空 */ protected RePluginCallbacks createCallbacks() { return null; } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); RePluginConfig c = createConfig(); if (c == null) { c = new RePluginConfig(); } RePluginCallbacks cb = createCallbacks(); if (cb != null) { c.setCallbacks(cb); } RePlugin.App.attachBaseContext(this, c); } @Override public void onCreate() { super.onCreate(); RePlugin.App.onCreate(); } @Override public void onLowMemory() { super.onLowMemory(); // 如果App的minSdkVersion >= 14,该方法可以不调用 RePlugin.App.onLowMemory(); } @Override public void onTrimMemory(int level) { super.onTrimMemory(level); // 如果App的minSdkVersion >= 14,该方法可以不调用 RePlugin.App.onTrimMemory(level); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // 如果App的minSdkVersion >= 14,该方法可以不调用 RePlugin.App.onConfigurationChanged(newConfig); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginCallbacks.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import com.qihoo360.loader2.PluginContext; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.utils.pkg.PackageFilesUtil; import java.io.InputStream; /** * 插件框架对外回调接口集 *

* 宿主需继承DefaultPluginCallbacks,并复写相应的方法来自定义插件框架 * * @author RePlugin Team */ public class RePluginCallbacks { protected final Context mContext; public RePluginCallbacks(Context context) { mContext = context; } /** * 创建【宿主用的】 RePluginClassLoader 对象以支持大多数插件化特征。默认为:RePluginClassLoader的实例 *

* 子类可复写此方法,来创建自己的ClassLoader,做相应的事情(如Hook宿主的ClassLoader做一些特殊的事) * * @param parent 该ClassLoader的父亲,通常为BootClassLoader * @param original 宿主的原ClassLoader,通常为PathClassLoader * @return 支持插件化方案的ClassLoader对象,可直接返回RePluginClassLoader * @see RePluginClassLoader */ public RePluginClassLoader createClassLoader(ClassLoader parent, ClassLoader original) { return new RePluginClassLoader(parent, original); } /** * 插件【插件用的】 ClassLoader对象。默认为:PluginDexClassLoader的实例 *

* 子类可复写此方法(虽然不建议),来创建插件自己需要用的DexClassLoader对象 *

* 注意:四个参数务必【透传】到要创建的ClassLoader中,以免出现意外。如应该这样做: * * return new MyDexClassLoader(dexPath, optimizedDirectory, librarySearchPath, parent); * * * @param pi 插件信息 * @param dexPath 插件APK所在路径 * @param optimizedDirectory 插件释放odex/oat的路径 * @param librarySearchPath 插件SO库所在路径 * @param parent 插件ClassLoader的父亲 * @return 插件自己可用的PluginDexClassLoader对象。 */ public PluginDexClassLoader createPluginClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { return new PluginDexClassLoader(pi, dexPath, optimizedDirectory, librarySearchPath, parent); } /** * 当要打开的Activity所对应的插件不存在时触发。通常在这里会触发“下载”逻辑 *

* 其中Intent信息很关键,当插件下载完成后,应使用其Intent来打开Activity。如为Null则只是下载,不打开Activity * * @param context Context对象 * @param plugin 要打开Activity的插件名,这样可知道要“下载”哪个插件 * @param intent 要打开的Activity的Intent信息 * @param process 要打开的Activity所在进程 * @return 若为true,则表示“我们已弹出下载界面”,则不会走后面的逻辑。若返回false则直接抛出ActivityNotFoundException */ public boolean onPluginNotExistsForActivity(Context context, String plugin, Intent intent, int process) { // Nothing return false; } /** * 当要打开的Activity所对应的插件过大,需要弹Loading窗提示时触发 *

* 其中Intent信息很关键,当插件安装完成后,应使用其Intent来打开Activity。如为Null则只是安装,不打开Activity * * @param context Context对象 * @param plugin 要打开Activity的插件名,这样可知道要“下载”哪个插件 * @param intent 要打开的Activity的Intent信息 * @param process 要打开的Activity所在进程 */ public boolean onLoadLargePluginForActivity(Context context, String plugin, Intent intent, int process) { // Nothing return false; } /** * 获取SharedPreferences对象 *

* 绝大多数情况下直接返回系统的即可,但如360手机卫士是实现了“跨进程SP”的功能,则需复写此方法 * * @param name Desired preferences file. If a preferences file by this name * does not exist, it will be created when you retrieve an * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()). * @param mode Operating mode. Use 0 or {@link Context#MODE_PRIVATE} for the * default operation. * @return The single {@link SharedPreferences} instance that can be used * to retrieve and modify the preference values. */ public SharedPreferences getSharedPreferences(Context context, String name, int mode) { return context.getSharedPreferences(name, mode); } /** * 打开一个可被云控的,插件框架方面的文件(如plugin-list.json等) *

* 既然文件可以被云控,那么通常会在Files和Assets上都有此文件 *

* 因此我们会和时间戳文件(或Prefs)进行对比,谁新就用谁的 *

*

* 宿主也可以自行实现相应逻辑。总之要实现“打开最新的文件”的逻辑即可 * * @param context Context对象 * @param filename 要打开的文件名 * @return InputStream对象 */ public InputStream openLatestFile(Context context, String filename) { return PackageFilesUtil.openLatestInputFile(context, filename); } /** * 获取业务层定义的ContextInjector实现对象,允许业务层对PluginContext中的startActivity等接口处进行自定义操作 *

* PluginContext {@link PluginContext} 是插件中使用的Context对象(Activity.mBase 和 Application.mBase) * * @return 可以允许返回null 表示无需注入自定义逻辑 * * @since 2.0.0 */ public ContextInjector createContextInjector() { // Nothing return null; } /** * 判断当前插件是否已经处于“禁用”状态,允许业务层自定义该禁用逻辑 * * @param pluginInfo 插件的信息 * @return 是否被禁用 * @since 2.1.0 */ public boolean isPluginBlocked(PluginInfo pluginInfo) { // Nothing, allow all return false; } /** * 为了p-n插件初始化PluginOverride逻辑,只有老插件方案使用 * 可以通过该回调,在进程初始化时,设置插件的override逻辑(每个进程都会调到) * * @since 2.2.2 */ public void initPnPluginOverride() { // default, do Nothing } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginClassLoader.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.os.Build; import com.qihoo360.replugin.utils.ReflectUtils; import com.qihoo360.loader.utils.StringUtils; import com.qihoo360.loader2.PMF; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.Enumeration; import dalvik.system.PathClassLoader; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 宿主的ClassLoader,插件框架的核心之一 *

* 注意:为了兼容Android 7.0以上的LoadedApk.updateApplicationInfo中,对addDexPath方法的依赖, * 特将继承关系调整到PathClassLoader,以前是ClassLoader * * @author RePlugin Team */ public class RePluginClassLoader extends PathClassLoader { private static final String TAG = "RePluginClassLoader"; private final ClassLoader mOrig; /** * 用load系列代替 */ //private Method findClassMethod; private Method findResourceMethod; private Method findResourcesMethod; private Method findLibraryMethod; private Method getPackageMethod; public RePluginClassLoader(ClassLoader parent, ClassLoader orig) { // 由于PathClassLoader在初始化时会做一些Dir的处理,所以这里必须要传一些内容进来 // 但我们最终不用它,而是拷贝所有的Fields super("", "", parent); mOrig = orig; // 将原来宿主里的关键字段,拷贝到这个对象上,这样骗系统以为用的还是以前的东西(尤其是DexPathList) // 注意,这里用的是“浅拷贝” // Added by Jiongxuan Zhang copyFromOriginal(orig); initMethods(orig); } public ClassLoader getOrig(){ return mOrig; } private void initMethods(ClassLoader cl) { Class c = cl.getClass(); findResourceMethod = ReflectUtils.getMethod(c, "findResource", String.class); findResourceMethod.setAccessible(true); findResourcesMethod = ReflectUtils.getMethod(c, "findResources", String.class); findResourcesMethod.setAccessible(true); findLibraryMethod = ReflectUtils.getMethod(c, "findLibrary", String.class); findLibraryMethod.setAccessible(true); getPackageMethod = ReflectUtils.getMethod(c, "getPackage", String.class); getPackageMethod.setAccessible(true); } private void copyFromOriginal(ClassLoader orig) { if (LOG && IPC.isPersistentProcess()) { LogDebug.d(TAG, "copyFromOriginal: Fields=" + StringUtils.toStringWithLines(ReflectUtils.getAllFieldsList(orig.getClass()))); } if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { // Android 2.2 - 2.3.7,有一堆字段,需要逐一复制 // 以下方法在较慢的手机上用时:8ms左右 copyFieldValue("libPath", orig); copyFieldValue("libraryPathElements", orig); copyFieldValue("mDexs", orig); copyFieldValue("mFiles", orig); copyFieldValue("mPaths", orig); copyFieldValue("mZips", orig); } else { // Android 4.0以上只需要复制pathList即可 // 以下方法在较慢的手机上用时:1ms copyFieldValue("pathList", orig); } } private void copyFieldValue(String field, ClassLoader orig) { try { Field f = ReflectUtils.getField(orig.getClass(), field); if (f == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "rpcl.cfv: null! f=" + field); } return; } // 删除final修饰符 ReflectUtils.removeFieldFinalModifier(f); // 复制Field中的值到this里 Object o = ReflectUtils.readField(f, orig); ReflectUtils.writeField(f, this, o); if (LOG) { Object test = ReflectUtils.readField(f, this); LogDebug.d(TAG, "copyFieldValue: Copied. f=" + field + "; actually=" + test + "; orig=" + o); } } catch (IllegalAccessException e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "rpcl.cfv: fail! f=" + field); } } } @Override protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException { // Class c = null; c = PMF.loadClass(className, resolve); if (c != null) { return c; } // try { c = mOrig.loadClass(className); // 只有开启“详细日志”才会输出,防止“刷屏”现象 if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.d(TAG, "loadClass: load other class, cn=" + className); } return c; } catch (Throwable e) { // } // return super.loadClass(className, resolve); } @Override protected Class findClass(String className) throws ClassNotFoundException { // INFO Never reach here since override loadClass , unless not found class if (LOGR) { LogRelease.w(PLUGIN_TAG, "NRH lcl.fc: c=" + className); } return super.findClass(className); } @Override protected URL findResource(String resName) { try { return (URL) findResourceMethod.invoke(mOrig, resName); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return super.findResource(resName); } @SuppressWarnings("unchecked") @Override protected Enumeration findResources(String resName) { try { return (Enumeration) findResourcesMethod.invoke(mOrig, resName); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return super.findResources(resName); } @Override public String findLibrary(String libName) { try { return (String) findLibraryMethod.invoke(mOrig, libName); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return super.findLibrary(libName); } @Override protected Package getPackage(String name) { // 金立手机的某些ROM(F103,F103L,F303,M3)代码ClassLoader.getPackage去掉了关键的保护和错误处理(2015.11~2015.12左右),会返回null // 悬浮窗某些draw代码触发getPackage(...).getName(),getName出现空指针解引,导致悬浮窗进程出现了大量崩溃 // 此处实现和AOSP一致确保不会返回null // SONGZHAOCHUN, 2016/02/29 if (name != null && !name.isEmpty()) { Package pack = null; try { pack = (Package) getPackageMethod.invoke(mOrig, name); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } if (pack == null) { if (LOGR) { LogRelease.w(PLUGIN_TAG, "NRH lcl.gp.1: n=" + name); } pack = super.getPackage(name); } if (pack == null) { if (LOGR) { LogRelease.w(PLUGIN_TAG, "NRH lcl.gp.2: n=" + name); } return definePackage(name, "Unknown", "0.0", "Unknown", "Unknown", "0.0", "Unknown", null); } return pack; } return null; } @Override public String toString() { return getClass().getName() + "[mBase=" + mOrig.toString() + "]"; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginConfig.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.content.Context; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import java.io.File; /** * 用来自定义RePlugin行为的类。

* 必须在RePlugin.App.attachBaseContext中被传递。一旦生效就无法再改变

* 具体用法:

* * RePlugin.App.attachBaseContext(this, "xxx", new RePluginConfig().setCallbacks(cb).setPersistentName(":srv2")); * * * @author RePlugin Team */ public final class RePluginConfig { private RePluginCallbacks callbacks; private RePluginEventCallbacks eventCallbacks; private File pnInstallDir; private boolean verifySign = false; private boolean persistentEnable = true; private boolean useHostClassIfNotFound = false; private boolean moveFileWhenInstalling = true; private boolean printDetailLog = false; private int defaultFrameworkVersion = 4; private String hostVersionName = ""; private String hostBuildID = ""; private boolean optimizeArtLoadDex = false; /** * 获取插件回调方法。通常无需调用此方法。 * * @return 可供外界使用的回调 */ public RePluginCallbacks getCallbacks() { return callbacks; } /** * 设置插件回调方法,可自定义插件框架的回调行为 * * @param callbacks 可供外界使用的回调 * @return RePluginConfig自己。这样可以连环调用set方法 */ public RePluginConfig setCallbacks(RePluginCallbacks callbacks) { if (!checkAllowModify()) { return this; } this.callbacks = callbacks; return this; } /** * 获取插件化框架的事件回调方法,通常无需调用此方法。 * * @return 可供外界使用的回调 */ public RePluginEventCallbacks getEventCallbacks() { return eventCallbacks; } /** * 设置插件化框架的事件回调方法,调用者可自定义插件框架的事件回调行为 * * @param eventCallbacks 可供外界使用的回调 * @return RePluginConfig自己。这样可以连环调用set方法 */ public RePluginConfig setEventCallbacks(RePluginEventCallbacks eventCallbacks) { if (!checkAllowModify()) { return this; } this.eventCallbacks = eventCallbacks; return this; } /** * 获取"p-n型插件安装的路径" * * @return 插件安装的路径 */ public File getPnInstallDir() { return pnInstallDir; } /** * 设置“p-n型插件安装的路径”。默认为宿主的files目录下

* 提示:"纯APK"方案不受此方法的影响,新方案可在任意目录上安装 * * @param pnInstallDir 插件安装的路径 * @return RePluginConfig自己。这样可以连环调用set方法 */ public RePluginConfig setPnInstallDir(File pnInstallDir) { if (!checkAllowModify()) { return this; } this.pnInstallDir = pnInstallDir; return this; } /** * 是否开启插件签名校验 * * @return 是否开启 */ public boolean getVerifySign() { return verifySign; } /** * 设置插件是否开启签名校验。默认为False。但强烈建议开启此开关。

* 此开关将必须和 RePlugin.addCertSignature 配合使用。

* 注意:该功能仅针对“纯APK”插件 * * @param verifySign * @return RePluginConfig自己。这样可以连环调用set方法 */ public RePluginConfig setVerifySign(boolean verifySign) { if (!checkAllowModify()) { return this; } this.verifySign = verifySign; return this; } /** * 是否当插件没有指定类时,使用宿主的类?

* 有关该开关的具体说明,请参见setUseHostClass方法 * * @return 是否使用宿主类 * @since 1.3.0 */ public boolean isUseHostClassIfNotFound() { return useHostClassIfNotFound; } /** * 当插件没有指定类时,是否允许使用宿主的类?若为true,则当插件内没有指定类时,将默认使用宿主的。

* 例如:插件中用反射使用A类(如通过UI的XML标签),但A在插件中不存在,则使用宿主中的相同的A类,若宿主也不存在,则抛出ClassNotFound异常

* 适用场景:宿主有FrescoView、Common View等 * * @param useHostClassIfNotFound 是否使用宿主类 * @return RePluginConfig自己。这样可以连环调用set方法 * @since 1.3.0 */ public RePluginConfig setUseHostClassIfNotFound(boolean useHostClassIfNotFound) { if (!checkAllowModify()) { return this; } this.useHostClassIfNotFound = useHostClassIfNotFound; return this; } /** * 在插件安装时,是否将文件“移动”到app_p_a目录下?默认为True。

* 有关该开关的具体说明,请参见setMoveFileWhenInstalling方法 * * @return 是否将文件“移动”到app_p_a目录下? * @since 2.0.0 */ public boolean isMoveFileWhenInstalling() { return moveFileWhenInstalling; } /** * 在插件安装时,是否将文件“移动”到app_p_a目录下?默认为True。

* 若为False,则表示只是“复制”到app_p_a目录下,原来安装前的APK文件还会保留。不推荐这么做,那样会浪费内部存储空间

* 注:只针对“纯APK”方案插件,对p-n无任何效果(因为p-n本身就不是一个标准的APK,必须“释放”而不能“移动”到app_plugin_v3目录下) * * @param moveFileWhenInstalling 是否将文件“移动”到app_p_a目录下? * @return RePluginConfig自己。这样可以连环调用set方法 * @since 2.0.0 */ public RePluginConfig setMoveFileWhenInstalling(boolean moveFileWhenInstalling) { if (!checkAllowModify()) { return this; } this.moveFileWhenInstalling = moveFileWhenInstalling; return this; } /** * 获取宿主的 BuildID * * @return 宿主的BuildID * @since 2.0.0 */ public String getHostBuildID() { return hostBuildID; } /** * 设置宿主的 BuildID

* BuildID 是一个比 VersionName 和 VersionCode 更细的维度(例如:服务器每 build 一次,版本号加 1) * * @param buildID 宿主的BuildID * @return RePluginConfig自己。这样可以连环调用set方法 * @since 2.0.0 */ public RePluginConfig setHostBuild(String buildID) { if (!checkAllowModify()) { return this; } hostBuildID = buildID; return this; } /** * 获取宿主的 VersionName * * @since 2.0.0 */ public String getHostVersionName() { return hostVersionName; } /** * 设置宿主的 VersionName * * @param versionName 宿主的VersionName * @return RePluginConfig自己。这样可以连环调用set方法 * @since 2.0.0 */ public RePluginConfig setHostVersionName(String versionName) { if (!checkAllowModify()) { return this; } hostVersionName = versionName; return this; } /** * 获取宿主的VersionBuild号 * * @return */ public String getHostVersionBuild() { return RePlugin.getConfig().getHostVersionName() + "." + RePlugin.getConfig().getHostBuildID(); } /** * 是否打印更详细的日志? * * @return 是否打印? * @since 2.0.0 */ public boolean isPrintDetailLog() { return printDetailLog; } /** * 是否打印更详细的日志?注意,可能会导致“刷屏”,以及因输出内存日志而出现一定的性能问题。

* 默认为:False。若为Release版AAR则此开关无效 * * @param printDetailLog 是否打印? * @return RePluginConfig自己。这样可以连环调用set方法 * @since 2.0.0 */ public RePluginConfig setPrintDetailLog(boolean printDetailLog) { this.printDetailLog = printDetailLog; return this; } /** * 获取框架默认版本号 * * @return defaultFrameworkVersion * @since 2.1.0 */ public int getDefaultFrameworkVersion() { return defaultFrameworkVersion; } /** * 设置框架默认版本号 * * @param defaultFrameworkVersion 框架默认版本号 * @return RePluginConfig自己。这样可以连环调用set方法 * @since 2.1.0 */ public RePluginConfig setDefaultFrameworkVersion(int defaultFrameworkVersion) { if (!checkAllowModify()) { return this; } this.defaultFrameworkVersion = defaultFrameworkVersion; return this; } // 针对RePlugin.App.AttachBaseContext的调用,初始化默认值 void initDefaults(Context context) { if (pnInstallDir == null) { pnInstallDir = context.getFilesDir(); } if (callbacks == null) { callbacks = new RePluginCallbacks(context); } if (eventCallbacks == null) { eventCallbacks = new RePluginEventCallbacks(context); } } // 不允许在attachBaseContext调用完成之后再来修改RePluginConfig对象中的内容 private boolean checkAllowModify() { if (RePlugin.App.sAttached) { // 不能在此处抛异常,因为个别情况下,宿主的attachBaseContext可能会被调用多次,导致最终出现异常。这里只打出日志即可。 // throw new IllegalStateException("Already called attachBaseContext. Do not modify!"); if (LogRelease.LOGR) { LogRelease.e(LogDebug.PLUGIN_TAG, "rpc.cam: do not modify", new Throwable()); } return false; } return true; } /** * 是否在Art上对首次加载插件速度做优化 * * @return */ public boolean isOptimizeArtLoadDex() { return optimizeArtLoadDex; } /** * 是否在Art上对首次加载插件速度做优化,默认为false * * @param optimizeArtLoadDex * @return * @since 2.2.2 */ public RePluginConfig setOptimizeArtLoadDex(boolean optimizeArtLoadDex) { if (!checkAllowModify()) { return this; } this.optimizeArtLoadDex = optimizeArtLoadDex; return this; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginConstants.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import com.qihoo360.replugin.model.PluginInfo; /** * RePlugin用到的通用的常量集合 * * @author RePlugin Team */ public class RePluginConstants { /** * 表示收到一个新的插件 *

* [LocalBroadcast,各进程] *

* 注意:为确保和卫士老插件兼容,请不要修改此值,以免影响插件的运行 * * @see RePlugin#registerInstalledReceiver(android.content.Context, android.content.BroadcastReceiver) */ public static final String ACTION_NEW_PLUGIN = "com.qihoo360.loader2.ACTION_NEW_PLUGIN"; /** * 安装插件的广播 */ public static final String ACTION_INSTALL_PLUGIN = "com.qihoo360.replugin.ACTION_INSTALL_PLUGIN"; /** * 启动 Activity 完成的广播 */ public static final String ACTION_START_ACTIVITY = "com.qihoo360.replugin.ACTION_START_ACTIVITY"; /** * 插件名称 */ public static final String KEY_PLUGIN_NAME = "plugin_name"; /** * 插件安装时的 path */ public static final String KEY_PLUGIN_PATH = "plugin_path"; /** * 插件版本 */ public static final String KEY_PLUGIN_VERSION = "plugin_ver"; /** * Activity 名称 */ public static final String KEY_PLUGIN_ACTIVITY = "plugin_activity"; /** * 安装插件 Activity 是否成功 */ public static final String KEY_INSTALL_PLUGIN_RESULT = "install_plugin_result"; /** * 启动插件 Activity 是否成功 */ public static final String KEY_START_ACTIVITY_RESULT = "start_activity_result"; /** * 插件信息 * * @see #ACTION_NEW_PLUGIN * @see PluginInfo */ public static final String KEY_PLUGIN_INFO = "plugin_info"; /** * 常驻进程是否需要重启 * * @see #ACTION_NEW_PLUGIN * @deprecated 应废弃此Key,或换用更合适的 */ public static final String KEY_PERSIST_NEED_RESTART = "persist_need_restart"; /** * 自己进程是否需要重启 * * @see #ACTION_NEW_PLUGIN * @deprecated 应废弃此Key,或换用更合适的 */ public static final String KEY_SELF_NEED_RESTART = "self_need_restart"; } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginEventCallbacks.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.app.Activity; import android.content.Context; import android.content.Intent; import com.qihoo360.replugin.model.PluginInfo; /** * 插件化框架对外事件回调接口集 *

* 宿主需继承此类,并复写相应的方法来自定义插件框架的事件处理机制 * * @author RePlugin Team */ public class RePluginEventCallbacks { protected final Context mContext; public RePluginEventCallbacks(Context context) { mContext = context; } /** * 安装插件失败 * * @param path 插件路径 * @param code 安装失败的原因 */ public void onInstallPluginFailed(String path, InstallResult code) { // Nothing } /** * 安装插件成功 * * @param info 插件信息 */ public void onInstallPluginSucceed(PluginInfo info) { // Nothing } /** * 启动 Activity 完成 * * @param plugin 插件名称 * @param activity 插件 Activity * @param result 启动是否成功 */ public void onStartActivityCompleted(String plugin, String activity, boolean result) { // Nothing } /** * 当插件Activity准备分配坑位时执行 * * @param intent 要打开的插件的Activity */ public void onPrepareAllocPitActivity(Intent intent) { // Nothing } /** * 当插件Activity即将被打开时执行,在onActivityPitAllocated之后被执行 * * @param context 要打开的Activity所在的Context * @param intent 原来要打开的插件的Activity * @param pittedIntent 目标坑位的Activity */ public void onPrepareStartPitActivity(Context context, Intent intent, Intent pittedIntent) { // Nothing } /** * 当插件Activity所在的坑位被执行“销毁”时被执行 * * @param activity 要销毁的Activity对象,通常是插件里的Activity */ public void onActivityDestroyed(Activity activity) { // Nothing } /** * 当插件Service的Binder被释放时被执行 */ public void onBinderReleased() { // Nothing } /** * 插件安装结果值 */ public enum InstallResult { SUCCEED, V5_FILE_BUILD_FAIL, V5_FILE_UPDATE_FAIL, READ_PKG_INFO_FAIL, VERIFY_SIGN_FAIL, VERIFY_VER_FAIL, COPY_APK_FAIL } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/RePluginInternal.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.app.Application; import android.content.Context; import com.qihoo360.mobilesafe.core.BuildConfig; /** * 对框架暴露的一些通用的接口。 *

* 注意:插件框架内部使用,外界请不要调用。 * * @author RePlugin Team */ public class RePluginInternal { public static final boolean FOR_DEV = BuildConfig.DEBUG; // FIXME 不建议缓存Application对象,容易导致InstantRun失效(警告中写着,具体原因待分析) static Context sAppContext; static void init(Application app) { sAppContext = app; } /** * 获取宿主注册时的Context对象 */ public static Context getAppContext() { return sAppContext; } /** * 获取宿主注册时的ClassLoader */ public static ClassLoader getAppClassLoader() { return getAppContext().getClassLoader(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/base/AMSUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.base; import android.app.ActivityManager; import android.content.Context; import java.util.List; /** * 和ActivityManagerService快速封装的一些接口 * * @author RePlugin Team */ public class AMSUtils { /** * 无需抛出异常而调用getRunningAppProcesses方法 * @param context context对象 * @return RunningAppProcessInfo列表 */ public static List getRunningAppProcessesNoThrows(Context context) { try { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); return am.getRunningAppProcesses(); } catch (Throwable e) { // 可能AMS挂了,但最多返回空列表即可。毕竟不是很重要的流程 e.printStackTrace(); } return null; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/base/IPC.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.base; import android.content.Context; import android.content.Intent; import android.os.Process; import android.os.RemoteException; import android.text.TextUtils; import com.qihoo360.loader.utils.SysUtils; import com.qihoo360.loader2.PluginProcessMain; import com.qihoo360.replugin.helper.HostConfigHelper; import com.qihoo360.replugin.helper.LogDebug; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 用于“进程间通信”的类。插件和宿主可使用此类来做一些跨进程发送广播、判断进程等工作。 * * @author RePlugin Team */ public class IPC { private static final String TAG = "IPC"; private static String sCurrentProcess; private static int sCurrentPid; private static String sPackageName; private static String sPersistentProcessName; private static boolean sIsPersistentProcess; private static boolean sIsUIProcess; /** * [HIDE] 外界请不要调用此方法 */ public static void init(Context context) { sCurrentProcess = SysUtils.getCurrentProcessName(); sCurrentPid = Process.myPid(); sPackageName = context.getApplicationInfo().packageName; // 设置最终的常驻进程名 if (HostConfigHelper.PERSISTENT_ENABLE) { String cppn = HostConfigHelper.PERSISTENT_NAME; if (!TextUtils.isEmpty(cppn)) { if (cppn.startsWith(":")) { sPersistentProcessName = sPackageName + cppn; } else { sPersistentProcessName = cppn; } } } else { sPersistentProcessName = sPackageName; } sIsUIProcess = sCurrentProcess.equals(sPackageName); sIsPersistentProcess = sCurrentProcess.equals(sPersistentProcessName); } /** * 获取当前进程名称(从缓存中) * * @return 当前进程名 */ public static String getCurrentProcessName() { return sCurrentProcess; } /** * 获取当前进程的ID(PID) * * @return 当前进程的ID */ public static int getCurrentProcessId() { return sCurrentPid; } /** * 获取常驻进程名 * * @return 常驻进程名 */ public static String getPersistentProcessName() { return sPersistentProcessName; } /** * 获取“插件处理逻辑所在进程名” * 若为“单进程模型”则返回UI进程,否则返回“常驻进程”名 * * @return 插件处理逻辑所在进程名 */ public static String getPluginHostProcessName() { return sPersistentProcessName; } /** * 是否为“插件处理逻辑所在进程”? * 若为“单进程模型”则判断当前是否在UI进程,否则判断是否在“常驻进程” * * @return 若为True,则表示当前正处于“插件处理逻辑所在进程” */ public static boolean isPluginHostProcess() { // FIXME 这块儿处理逻辑和原来不同,原来是endsWith,这里是Equals,需判断ROM是否做过什么修改 return TextUtils.equals(getCurrentProcessName(), getPluginHostProcessName()); } /** * 当前是否为“UI进程”(主进程)? * * @return 是否为UI进程 */ public static boolean isUIProcess() { return sIsUIProcess; } /** * 当前是否为“常驻进程”? * * @return 是否为常驻进程 */ public static boolean isPersistentProcess() { return sIsPersistentProcess; } /** * 是否支持在“常驻进程”中处理插件逻辑?若应用有常驻进程,则应将此设为True * 若为False,则处理插件的逻辑全部放在UI进程中(单进程) * * @return 是否支持? */ public static boolean isPersistentEnable() { return HostConfigHelper.PERSISTENT_ENABLE; } /** * 通过进程名来获取PID。仅允许获取应用自己的进程 * * @param processName 进程名 * @return PID,若为-1则表示没有此进程,或获取时出现问题 */ public static int getPidByProcessName(String processName) { if (TextUtils.isEmpty(processName)) { return -1; } // 拿的是自己?直接返回即可 if (TextUtils.equals(processName, getCurrentProcessName())) { return getCurrentProcessId(); } // 向常驻服务索要 try { return PluginProcessMain.getPluginHost().getPidByProcessName(processName); } catch (RemoteException e) { if (LOGR) { e.printStackTrace(); } return -1; } } /** * 通过PID来获取进程名。仅允许获取应用自己的进程 * * @param pid 进程PID * @return 进程名,若为null则表示没有此进程,或获取时出现问题 */ public static String getProcessNameByPid(int pid) { if (pid < 0) { return null; } // 拿的是自己?直接返回即可 if (pid == IPC.getCurrentProcessId()) { return getCurrentProcessName(); } try { return PluginProcessMain.getPluginHost().getProcessNameByPid(pid); } catch (RemoteException e) { if (LOGR) { e.printStackTrace(); } return null; } } /** * 获取当前宿主的包名(从缓存中) * @return 宿主的包名 */ public static String getPackageName() { return sPackageName; } /** * 多进程使用, 将intent送到目标插件所在进程,对方将受到Local Broadcast * 只有当目标进程存活时才能将数据送达 * 常驻进程通过Local Broadcast注册处理代码 * * @param target 插件名 * @param intent Intent对象 */ public static boolean sendLocalBroadcast2Plugin(Context c, String target, Intent intent) { if (LOG) { LogDebug.d(TAG, "sendLocalBroadcast2Plugin: target=" + target + " intent=" + intent); } if (TextUtils.isEmpty(target)) { return false; } try { PluginProcessMain.getPluginHost().sendIntent2Plugin(target, intent); return true; } catch (RemoteException e) { if (LOGR) { e.printStackTrace(); } } return false; } /** * 多进程使用, 将intent送到目标进程,对方将收到Local Broadcast广播 *

* 只有当目标进程存活时才能将数据送达 *

* 常驻进程通过Local Broadcast注册处理代码 * * @param target 目标进程名 * @param intent Intent对象 */ public static boolean sendLocalBroadcast2Process(Context c, String target, Intent intent) { if (LOG) { LogDebug.d(TAG, "sendLocalBroadcast2Process: target=" + target + " intent=" + intent); } if (TextUtils.isEmpty(target)) { return false; } try { PluginProcessMain.getPluginHost().sendIntent2Process(target, intent); return true; } catch (RemoteException e) { if (LOGR) { e.printStackTrace(); } } return false; } /** * 发送广播到所有卫士进程(底层已实现权限控制,不会被第三方APP污染) *

* 只有当目标进程存活时才能将数据送达 *

* 卫士任意进程(非single插件进程)只需要通过{@code LocalBroadcastManager}注册即可收到 * * @param intent Intent对象 */ public static boolean sendLocalBroadcast2All(Context c, Intent intent) { if (LOG) { LogDebug.d(TAG, "sendLocalBroadcast2All: intent=" + intent); } try { PluginProcessMain.getPluginHost().sendIntent2Process(null, intent); return true; } catch (RemoteException e) { if (LOGR) { e.printStackTrace(); } } return false; } /** * 多进程使用, 将intent送到目标插件所在进程,对方将收到Local Broadcast广播 *

* 只有当目标进程存活时才能将数据送达 *

* 常驻进程通过Local Broadcast注册处理代码 *

* 会【阻塞】直到所有消息处理完成后才能继续 * * @param target 插件名 * @param intent Intent对象 */ public static boolean sendLocalBroadcast2PluginSync(Context c, String target, Intent intent) { if (LOG) { LogDebug.d(TAG, "sendLocalBroadcast2PluginSync: target=" + target + " intent=" + intent); } if (TextUtils.isEmpty(target)) { return false; } try { PluginProcessMain.getPluginHost().sendIntent2PluginSync(target, intent); return true; } catch (RemoteException e) { if (LOGR) { e.printStackTrace(); } } return false; } /** * 多进程使用, 将intent送到目标进程,对方将收到Local Broadcast广播 *

* 只有当目标进程存活时才能将数据送达 *

* 常驻进程通过Local Broadcast注册处理代码 *

* 会【阻塞】直到所有消息处理完成后才能继续 * * @param target 目标进程名 * @param intent Intent对象 */ public static boolean sendLocalBroadcast2ProcessSync(Context c, String target, Intent intent) { if (LOG) { LogDebug.d(TAG, "sendLocalBroadcast2ProcessSync: target=" + target + " intent=" + intent); } if (TextUtils.isEmpty(target)) { return false; } try { PluginProcessMain.getPluginHost().sendIntent2ProcessSync(target, intent); return true; } catch (RemoteException e) { if (LOGR) { e.printStackTrace(); } } return false; } /** * 发送广播到所有卫士进程(底层已实现权限控制,不会被第三方APP污染) *

* 只有当目标进程存活时才能将数据送达 *

* 卫士任意进程(非single插件进程)只需要通过{@code LocalBroadcastManager}注册即可收到 *

* 会【阻塞】直到所有消息处理完成后才能继续 * * @param intent Intent对象 */ public static boolean sendLocalBroadcast2AllSync(Context c, Intent intent) { if (LOG) { LogDebug.d(TAG, "sendLocalBroadcast2AllSync: intent=" + intent); } try { PluginProcessMain.getPluginHost().sendIntent2ProcessSync(null, intent); return true; } catch (RemoteException e) { if (LOGR) { e.printStackTrace(); } } return false; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/base/LocalBroadcastHelper.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.base; import android.content.Context; import android.content.Intent; import com.qihoo360.loader.utils.LocalBroadcastManager; import java.util.concurrent.Callable; /** * 和LocalBroadcastManager有关的帮助类 * * @author RePlugin Team * @see androidx.localbroadcastmanager.content.LocalBroadcastManager */ public class LocalBroadcastHelper { /** * 和LocalBroadcastManager.sendBroadcastSync类似,唯一的区别是执行所在线程:前者只在调用所在线程,本方法则在UI线程。 *

* 可防止onReceiver在其它线程中被调用到。特别适用于AIDL、没有Looper的线程中调用此方法。 * * @param intent 要发送的Intent信息 * @see LocalBroadcastManager#sendBroadcastSync(Intent) */ public static void sendBroadcastSyncUi(final Context context, final Intent intent) { try { ThreadUtils.syncToMainThread(new Callable() { @Override public Void call() throws Exception { LocalBroadcastManager.getInstance(context).sendBroadcastSync(intent); return null; } }, 10000); } catch (Throwable t) { throw new RuntimeException(t); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/base/ThreadUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.base; import android.os.Handler; import android.os.Looper; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** * 和线程有关的帮助类 * * @author RePlugin Team */ public class ThreadUtils { private static Handler sHandler = new Handler(Looper.getMainLooper()); /** * 确保一定在主线程中使用 *

* 若当前处于主线程,则直接调用。若当前处于其它线程,则Post到主线程后等待结果 * * @param callable Callable对象 * @param wait 最长等待主线程的时间 * @param 任何Object子类均可以 * @return 主线程执行完方法后,返回的结果 */ public static T syncToMainThread(final Callable callable, int wait) throws Throwable { if (sHandler.getLooper() == Looper.myLooper()) { // 已在UI线程中使用,则直接调用它 return callable.call(); } else { // 不在UI线程,需尝试Post到UI线程并等待 return syncToMainThreadByOthers(callable, wait); } } private static T syncToMainThreadByOthers(final Callable callable, int wait) throws Throwable { final AtomicReference result = new AtomicReference<>(); final AtomicReference ex = new AtomicReference<>(); // 异步转同步 final CountDownLatch latch = new CountDownLatch(1); // 必须在主线程进行 sHandler.post(new Runnable() { @Override public void run() { try { result.set(callable.call()); } catch (Throwable e) { ex.set(e); } latch.countDown(); } }); try { latch.await(wait, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // ignore } // 若方法体有异常?直接抛出 Throwable exo = ex.get(); if (exo != null) { throw exo; } // 没有问题?则直接返回结果 return result.get(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/ComponentList.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.os.Environment; import android.text.TextUtils; import android.util.Pair; import com.qihoo360.i.Factory; import com.qihoo360.mobilesafe.parser.manifest.ManifestParser; import com.qihoo360.replugin.component.utils.ApkCommentReader; import com.qihoo360.replugin.component.utils.IntentMatcherHelper; import com.qihoo360.replugin.ext.parser.ApkParser; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * 用来快速获取四大组件和Application的系统Info的List *

* NOTE 每个Plugin对象维护一份ComponentList,且在第一次加载PackageInfo时被生成 * * @author RePlugin Team * @see android.content.pm.ActivityInfo * @see android.content.pm.ServiceInfo * @see android.content.pm.ProviderInfo * @see android.content.pm.ApplicationInfo */ public class ComponentList { /** * Class类名 - Activity的Map表 */ final HashMap mActivities = new HashMap<>(); /** * Class类名 - Provider的Map表 */ final HashMap mProvidersByName = new HashMap<>(); /** * Authority - Provider的Map表 */ final HashMap mProvidersByAuthority = new HashMap<>(); /** * Class类名 - Service的Map表 */ final HashMap mServices = new HashMap<>(); /** * Application对象 */ ApplicationInfo mApplication = null; /** * Class类名 - BroadcastReceiver的Map表 * 注意:是的,你没有看错,系统缓存Receiver就是用的ActivityInfo */ final HashMap mReceivers = new HashMap<>(); /** * 初始化ComponentList对象

* 注意:仅框架内部使用 */ public ComponentList(PackageInfo pi, String path, PluginInfo pli) { if (pi.activities != null) { for (ActivityInfo ai : pi.activities) { if (LOG) { LogDebug.d(PLUGIN_TAG, "activity=" + ai.name); } ai.applicationInfo.sourceDir = path; // todo extract to function if (ai.processName == null) { ai.processName = ai.applicationInfo.processName; } if (ai.processName == null) { ai.processName = ai.packageName; } mActivities.put(ai.name, ai); } } if (pi.providers != null) { for (ProviderInfo ppi : pi.providers) { if (LOG) { LogDebug.d(PLUGIN_TAG, "provider=" + ppi.name + "; auth=" + ppi.authority); } if (ppi.processName == null) { ppi.processName = ppi.applicationInfo.processName; } if (ppi.processName == null) { ppi.processName = ppi.packageName; } mProvidersByName.put(ppi.name, ppi); mProvidersByAuthority.put(ppi.authority, ppi); } } if (pi.services != null) { for (ServiceInfo si : pi.services) { if (LOG) { LogDebug.d(PLUGIN_TAG, "service=" + si.name); } if (si.processName == null) { si.processName = si.applicationInfo.processName; } if (si.processName == null) { si.processName = si.packageName; } mServices.put(si.name, si); } } if (pi.receivers != null) { for (ActivityInfo ri : pi.receivers) { if (LOG) { LogDebug.d(PLUGIN_TAG, "receiver=" + ri.name); } if (ri.processName == null) { ri.processName = ri.applicationInfo.processName; } if (ri.processName == null) { ri.processName = ri.packageName; } mReceivers.put(ri.name, ri); } } // 解析 Apk 中的 AndroidManifest.xml String manifest = getManifestFromApk(path); if (LOG) { LogDebug.d(PLUGIN_TAG, "\n解析插件 " + pli.getName() + " : " + path + "\nAndroidManifest: \n" + manifest); } // 生成组件与 IntentFilter 的对应关系 ManifestParser.INS.parse(pli, manifest); mApplication = pi.applicationInfo; if (mApplication.dataDir == null) { mApplication.dataDir = Environment.getDataDirectory() + File.separator + "data" + File.separator + mApplication.packageName; } if (LOG) { LogDebug.d(PLUGIN_TAG, "mApplication: " + mApplication); } } /** * 从 APK 中获取 Manifest 内容 * * @param apkFile apk 文件路径 * @return apk 中 AndroidManifest 中的内容 */ private static String getManifestFromApk(String apkFile) { // 先从 Apk comment 中解析 AndroidManifest String manifest = ApkCommentReader.readComment(apkFile); if (!TextUtils.isEmpty(manifest)) { if (LOG) { LogDebug.d(PLUGIN_TAG, "从 apk comment 中解析 xml:\n " + manifest); } return manifest; } // 解析失败时,再从 apk 中解析 ApkParser parser = null; try { parser = new ApkParser(apkFile); if (LOG) { long begin = System.currentTimeMillis(); manifest = parser.getManifestXml(); long end = System.currentTimeMillis(); LogDebug.d(PLUGIN_TAG, "从 apk 中解析 xml 耗时 " + (end - begin) + " 毫秒"); } else { manifest = parser.getManifestXml(); } return manifest; } catch (IOException t) { t.printStackTrace(); } finally { if (parser != null) { try { parser.close(); } catch (IOException e) { e.printStackTrace(); } } } return ""; } /** * 获取ServiceInfo对象 */ public ServiceInfo getService(String className) { return mServices.get(className); } /** * 获取该插件所有的ServiceInfo列表 */ public ServiceInfo[] getServices() { return mServices.values().toArray(new ServiceInfo[0]); } /** * 获取ServiceInfo对象 */ public ActivityInfo getActivity(String className) { return mActivities.get(className); } /** * 获取该插件所有的ActivityInfo列表 */ public ActivityInfo[] getActivities() { return mActivities.values().toArray(new ActivityInfo[0]); } /** * 获取 Receiver */ public ActivityInfo getReveiver(String className) { return mReceivers.get(className); } /** * 获取该插件所有的 Receiver 列表 */ public ActivityInfo[] getReceivers() { return mReceivers.values().toArray(new ActivityInfo[0]); } /** * 根据 Intent 匹配 Service *

* 遍历 plugin 插件中,所有保存的 Service 的 IntentFilter 数据,进行匹配, * 返回第一个符合条件的 ServiceInfo 对象. * * @param context Context * @param intent 调用方传来的 Intent * @return 匹配到的 ServiceInfo */ public Pair getServiceAndPluginByIntent(Context context, Intent intent) { String action = intent.getAction(); if (!TextUtils.isEmpty(action)) { Set plugins = ManifestParser.INS.getPluginsByActionWhenStartService(action); if (plugins != null) { for (String plugin : plugins) { // 获取 plugin 插件中,所有的 Service 和 IntentFilter 的对应关系 Map> filters = ManifestParser.INS.getServiceFilterMap(plugin); // 找到 plugin 插件中,IntentFilter 匹配成功的 Service String service = IntentMatcherHelper.doMatchIntent(context, intent, filters); ServiceInfo info = Factory.queryServiceInfo(plugin, service); if (info != null) { return new Pair<>(info, plugin); } } } } return null; } /** * 通过类名获取ProviderInfo对象 */ public ProviderInfo getProvider(String className) { return mProvidersByName.get(className); } /** * 通过Authority获取ProviderInfo对象 */ public ProviderInfo getProviderByAuthority(String authority) { return mProvidersByAuthority.get(authority); } /** * 获取该插件所有的ProviderInfo列表 */ public ProviderInfo[] getProviders() { return mProvidersByName.values().toArray(new ProviderInfo[0]); } /** * 获取Application对象 */ public ApplicationInfo getApplication() { return mApplication; } /** * 获取存储该插件所有的 ActivityInfo 的 Map */ public HashMap getActivityMap() { return mActivities; } /** * 获取存储该插件所有的 ServiceInfo 的 Map */ public HashMap getServiceMap() { return mServices; } /** * 获取存储该插件所有的 Receiver 的 Map */ public HashMap getReceiverMap() { return mReceivers; } /** * 获取存储该插件所有的 Provider 的 Map */ public HashMap getProviderMap() { return mProvidersByAuthority; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/activity/ActivityInjector.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.activity; import android.app.Activity; import android.app.ActivityManager; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.TextUtils; import com.qihoo360.loader2.MP; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import static com.qihoo360.replugin.helper.LogDebug.LOG; /** * 根据需要来Inject一些Activity的特性,使其更像一个App中的Activity * * @author RePlugin Team */ public class ActivityInjector { public static final String TAG = "activity-injector"; /** * 填充一些必要的东西到Activity中 * * @param activity Activity对象 * @param plugin 插件名 * @param realActivity 真实的(非坑位的)Activity名字 * @return 是否Inject成功 */ public static boolean inject(Activity activity, String plugin, String realActivity) { // 根据传进的参数来获取ActivityInfo PluginInfo pi = MP.getPlugin(plugin, false); if (pi == null) { return false; } ComponentList cl = RePlugin.fetchComponentList(plugin); if (cl == null) { return false; } ActivityInfo ai = cl.getActivity(realActivity); return ai != null && inject(activity, ai, pi.getFrameworkVersion()); } private static boolean inject(Activity activity, ActivityInfo ai, int frameworkVer) { // 可根据插件Activity的描述(android:label、android:icon)来设置Task在“最近应用程序”中的显示 // 注意:框架版本需 >= 4,否则仍沿用Application的Label和Icon if (frameworkVer >= 4) { injectTaskDescription(activity, ai); } return true; } /** * 可根据插件Activity的描述(android:label、android:icon)来设置Task在“最近应用程序”中的显示

* 注意:Android 4.x及以下暂不支持

* Author: Jiongxuan Zhang */ private static void injectTaskDescription(Activity activity, ActivityInfo ai) { // Android 4.x及以下暂不支持 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { return; } if (activity == null || ai == null) { return; } if (LOG) { LogDebug.d(TAG, "activity = " + activity); LogDebug.d(TAG, "ai = " + ai); } // 获取 activity label String label = getLabel(activity, ai); // 如果获取 label 失败(可能性极小),则不修改 TaskDescription if (TextUtils.isEmpty(label)) { return; } // 获取 ICON Bitmap bitmap = getIcon(activity, ai); // FIXME color的透明度需要在Theme中的colorPrimary中获取,先不实现 ActivityManager.TaskDescription td; if (bitmap != null) { td = new ActivityManager.TaskDescription(label, bitmap); } else { td = new ActivityManager.TaskDescription(label); } if (LOG) { LogDebug.d(TAG, "td = " + td); } activity.setTaskDescription(td); } /** * 获取 activity 的 label 属性 */ private static String getLabel(Activity activity, ActivityInfo ai) { String label; Resources res = activity.getResources(); // 获取 Activity label(如有) label = getLabelById(res, ai.labelRes); // 获取插件 Application Label(如有) if (TextUtils.isEmpty(label)) { label = getLabelById(res, ai.applicationInfo.labelRes); } // 获取宿主 App label if (TextUtils.isEmpty(label)) { Context appContext = RePluginInternal.getAppContext(); Resources appResource = appContext.getResources(); ApplicationInfo appInfo = appContext.getApplicationInfo(); label = getLabelById(appResource, appInfo.labelRes); } if (LOG) { LogDebug.d(TAG, "label = " + label); } return label; } private static String getLabelById(Resources res, int id) { if (id == 0) { return null; } try { return res.getString(id); } catch (Resources.NotFoundException e) { e.printStackTrace(); return null; } } /** * 获取 activity 的 icon 属性 */ private static Bitmap getIcon(Activity activity, ActivityInfo ai) { Drawable iconDrawable; Resources res = activity.getResources(); // 获取 Activity icon iconDrawable = getIconById(res, ai.icon); // 获取插件 Application Icon if (iconDrawable == null) { iconDrawable = getIconById(res, ai.applicationInfo.icon); } // 获取 App(Host) Icon if (iconDrawable == null) { Context appContext = RePluginInternal.getAppContext(); Resources appResource = appContext.getResources(); ApplicationInfo appInfo = appContext.getApplicationInfo(); iconDrawable = getIconById(appResource, appInfo.icon); } Bitmap bitmap = null; if (iconDrawable instanceof BitmapDrawable) { bitmap = ((BitmapDrawable) iconDrawable).getBitmap(); } if (LOG) { LogDebug.d(TAG, "bitmap = " + bitmap); } return bitmap; } private static Drawable getIconById(Resources res, int id) { if (id == 0) { return null; } try { return res.getDrawable(id); } catch (Resources.NotFoundException e) { e.printStackTrace(); return null; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/activity/DynamicClassProxyActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.activity; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import android.text.TextUtils; import com.qihoo360.i.Factory2; import com.qihoo360.i.IPluginManager; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.helper.LogDebug; /** * 若插件中的某个类是动态注册的,但是这个插件未下载,就调用此Activity做中转, * 解析完插件信息后,调用下载框下载插件;若解析失败,则调用 finish() * * @author RePlugin Team */ public class DynamicClassProxyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ComponentName cn = getIntent().getComponent(); if (cn != null) { // 获取 动态代理类名称 和 插件名称 String dynamicClassName = cn.getClassName(); String plugin = Factory2.getPluginByDynamicClass(dynamicClassName); if (LogDebug.LOG) { LogDebug.d("loadClass", "DynamicClassProxyActivity.onCreate(), plugin = " + plugin + ", class = " + dynamicClassName); } // 如果插件不存在,回调宿主接口。 if (!TextUtils.isEmpty(plugin) && !RePlugin.isPluginInstalled(plugin)) { Intent intent = new Intent(); intent.setComponent(new ComponentName(plugin, dynamicClassName)); RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(this, plugin, intent, IPluginManager.PROCESS_UI); } } finish(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/app/PluginApplicationClient.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.app; import android.app.Application; import android.content.ComponentCallbacks2; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.os.Build; import android.text.TextUtils; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.replugin.utils.basic.ArrayMap; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * 一种能处理【插件】的Application的类 * * @author RePlugin Team */ public class PluginApplicationClient { private static volatile boolean sInited; private static final byte[] LOCKER = new byte[0]; private static Method sAttachBaseContextMethod; private final ClassLoader mPlgClassLoader; private final ApplicationInfo mApplicationInfo; private Constructor mApplicationConstructor; private Application mApplication; private static ArrayMap> sRunningClients = new ArrayMap<>(); /** * 根据插件里的框架版本、Application等情况来创建PluginApplicationClient对象 * 若已经存在,则返回之前创建的ApplicationClient对象(此时Application不一定被加载进来) * 若不符合条件(如插件加载失败、版本不正确等),则会返回null * * @param pn 插件名 * @param plgCL 插件的ClassLoader * @param cl 插件的ComponentList * @param pi 插件的信息 */ public static PluginApplicationClient getOrCreate(String pn, ClassLoader plgCL, ComponentList cl, PluginInfo pi) { if (pi.getFrameworkVersion() <= 1) { // 仅框架版本为2及以上的,才支持Application的加载 if (LOG) { LogDebug.d(PLUGIN_TAG, "PAC.create(): FrameworkVer less than 1. cl=" + plgCL); } return null; } PluginApplicationClient pac = getRunning(pn); if (pac != null) { // 已经初始化过Application?直接返回 if (LOG) { LogDebug.d(PLUGIN_TAG, "PAC.create(): Already Loaded." + plgCL); } return pac; } if (LOG) { LogDebug.d(PLUGIN_TAG, "PAC.create(): Create and load Application. cl=" + plgCL); } // 初始化所有需要反射的方法 try { initMethods(); } catch (Throwable e) { if (BuildConfig.DEBUG) { e.printStackTrace(); } return null; } final PluginApplicationClient pacNew = new PluginApplicationClient(plgCL, cl, pi); if (pacNew.isValid()) { sRunningClients.put(pn, new WeakReference<>(pacNew)); if (Build.VERSION.SDK_INT >= 14) { RePluginInternal.getAppContext().registerComponentCallbacks(new ComponentCallbacks2() { @Override public void onTrimMemory(int level) { pacNew.callOnTrimMemory(level); } @Override public void onConfigurationChanged(Configuration newConfig) { pacNew.callOnConfigurationChanged(newConfig); } @Override public void onLowMemory() { pacNew.callOnLowMemory(); } }); } return pacNew; } else { // Application对象没有初始化出来,则直接按失败处理 return null; } } public static void notifyOnLowMemory() { for (WeakReference pacw : sRunningClients.values()) { PluginApplicationClient pac = pacw.get(); if (pac == null) { continue; } pac.callOnLowMemory(); } } public static void notifyOnTrimMemory(int level) { for (WeakReference pacw : sRunningClients.values()) { PluginApplicationClient pac = pacw.get(); if (pac == null) { continue; } pac.callOnTrimMemory(level); } } public static void notifyOnConfigurationChanged(Configuration newConfig) { for (WeakReference pacw : sRunningClients.values()) { PluginApplicationClient pac = pacw.get(); if (pac == null) { continue; } pac.callOnConfigurationChanged(newConfig); } } public static PluginApplicationClient getRunning(String pn) { WeakReference w = sRunningClients.get(pn); if (w == null) { return null; } return w.get(); } private static void initMethods() throws NoSuchMethodException { if (sInited) { return; } synchronized (LOCKER) { if (sInited) { return; } // NOTE getDeclaredMethod只能获取当前类声明的方法,无法获取继承到的,而getMethod虽可以获取继承方法,但又不能获取非Public的方法 // NOTE 权衡利弊,还是仅构造函数用反射类,其余用它明确声明的类来做 sAttachBaseContextMethod = Application.class.getDeclaredMethod("attach", Context.class); sAttachBaseContextMethod.setAccessible(true); // Protected 修饰 sInited = true; } } private PluginApplicationClient(ClassLoader plgCL, ComponentList cl, PluginInfo pi) { mPlgClassLoader = plgCL; mApplicationInfo = cl.getApplication(); try { // 尝试使用自定义Application(如有) if (mApplicationInfo != null && !TextUtils.isEmpty(mApplicationInfo.className)) { initCustom(); } // 若自定义有误(或没有),且框架版本为3及以上的,则可以创建空Application对象,方便插件getApplicationContext到自己 if (!isValid() && pi.getFrameworkVersion() >= 3) { mApplication = new Application(); } } catch (Throwable e) { // 出现异常,表示Application有问题 if (BuildConfig.DEBUG) { e.printStackTrace(); } mApplication = new Application(); } } public void callAttachBaseContext(Context c) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PAC.callAttachBaseContext(): Call attachBaseContext(), cl=" + mPlgClassLoader); } try { sAttachBaseContextMethod.setAccessible(true); // Protected 修饰 sAttachBaseContextMethod.invoke(mApplication, c); } catch (Throwable e) { if (BuildConfig.DEBUG) { e.printStackTrace(); } } } public void callOnCreate() { if (LOG) { LogDebug.d(PLUGIN_TAG, "PAC.callOnCreate(): Call onCreate(), cl=" + mPlgClassLoader); } mApplication.onCreate(); } public void callOnLowMemory() { if (LOG) { LogDebug.d(PLUGIN_TAG, "PAC.callOnLowMemory(): Call onLowMemory(), cl=" + mPlgClassLoader); } mApplication.onLowMemory(); } public void callOnTrimMemory(int level) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { return; } if (LOG) { LogDebug.d(PLUGIN_TAG, "PAC.callOnLowMemory(): Call onTrimMemory(), cl=" + mPlgClassLoader + "; lv=" + level); } mApplication.onTrimMemory(level); } public void callOnConfigurationChanged(Configuration newConfig) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PAC.callOnLowMemory(): Call onConfigurationChanged(), cl=" + mPlgClassLoader + "; nc=" + newConfig); } mApplication.onConfigurationChanged(newConfig); } public Application getObj() { return mApplication; } private boolean initCustom() { try { initCustomConstructor(); initCustomObject(); // 看mApplication是否被初始化成功 return mApplication != null; } catch (Throwable e) { // 出现异常,表示自定义Application有问题 if (BuildConfig.DEBUG) { e.printStackTrace(); } } return false; } private void initCustomConstructor() throws ClassNotFoundException, NoSuchMethodException { String aic = mApplicationInfo.className; Class psc = mPlgClassLoader.loadClass(aic); mApplicationConstructor = psc.getConstructor(); } private void initCustomObject() throws IllegalAccessException, InvocationTargetException, InstantiationException { Object appObj = mApplicationConstructor.newInstance(); if (appObj instanceof Application) { mApplication = (Application) appObj; } } private boolean isValid() { return mApplication != null; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/dummy/DummyActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.dummy; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import com.qihoo360.replugin.helper.LogRelease; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 表示一个“仿造的”Activity,打开后直接退出。

* 此类可防止系统调用插件时因类找不到而崩溃。请参见 registerHookingClass 的说明 * * @see com.qihoo360.replugin.RePlugin#registerHookingClass(String, ComponentName, Class) * @author RePlugin Team */ public class DummyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // INFO dummy activity on create finish if (LOGR) { LogRelease.i(PLUGIN_TAG, "d.a o.c f"); } // 之所以传Null,是因为系统会直接解析savedInstanceState // 这时如果常驻进程已被杀,这时立即恢复后,由于插件还没有准备好,故会出现崩溃情况 // 详细见:Crash Hash = 5C863A3E0CACDAEA9DBD05B9A7D353FE super.onCreate(null); //设置空的intent,防止解包时反序列化失败 setIntent(new Intent()); try { finish(); } catch (Exception ignored) { } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/dummy/DummyProvider.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.dummy; import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; /** * 表示一个“仿造的”Provider,什么都不做。

* 此类可防止系统调用插件时因类找不到而崩溃。请参见 registerHookingClass 的说明 * * @see com.qihoo360.replugin.RePlugin#registerHookingClass(String, ComponentName, Class) * @author RePlugin Team */ public class DummyProvider extends ContentProvider { @Override public boolean onCreate() { return false; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/dummy/DummyReceiver.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.dummy; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import com.qihoo360.replugin.helper.LogRelease; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 表示一个“仿造的”BroadcastReceiver,收到消息后什么事情也不做

* 此类可防止系统调用插件时因类找不到而崩溃。请参见 registerHookingClass 的说明 * * @see com.qihoo360.replugin.RePlugin#registerHookingClass(String, ComponentName, Class) * @author RePlugin Team */ public class DummyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // INFO dummy receiver on receive occur if (LOGR) { LogRelease.i(PLUGIN_TAG, "d.r o.c f"); } // Nothing } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/dummy/DummyService.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.dummy; import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; /** * 表示一个“仿造的”Service,启动后什么事情也不做

* 此类可防止系统调用插件时因类找不到而崩溃。请参见 registerHookingClass 的说明 * * @see com.qihoo360.replugin.RePlugin#registerHookingClass(String, ComponentName, Class) * @author RePlugin Team */ public class DummyService extends Service { @Override public void onCreate() { super.onCreate(); stopSelf(); } @Override public IBinder onBind(Intent intent) { return null; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/dummy/ForwardActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.dummy; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import com.qihoo360.loader2.PMF; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.utils.FixOTranslucentOrientation; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 若坑位出现丢失或错乱,则通过读取Intent.Category来做个中转 * * @author RePlugin Team */ public class ForwardActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(null); // INFO forward activity on Create if (LOGR) { LogRelease.i(PLUGIN_TAG, "f.a: o.c"); } FixOTranslucentOrientation.fix(this); Intent intent = getIntent(); if (intent == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "f.a: nul i"); } } PMF.forward(this, intent); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/process/PluginProcessHost.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.process; import android.util.SparseArray; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.provider.PluginPitProviderBase; import java.util.HashMap; import java.util.Map; /** * 用于在宿主中,处理插件自定义多进程相关业务 * * @author RePlugin Team */ public class PluginProcessHost { /** * 自定义插件的数量,暂时只支持3个自定义进程 */ public static final int PROCESS_COUNT = 3; /** * 自定义进程,int 标识,从 -100 开始,每次加 1; * 目前只支持 3 个进程,即到 -98 */ public static final int PROCESS_INIT = -100; /** * 插件中,进程名称后缀, * 进程名称类似 xxx.xx:p0, xxx.xx:p1, xxx.xx:p2 */ public static final String PROCESS_PLUGIN_SUFFIX = "p"; /** * 插件中,进程名称后缀(带冒号) */ public static final String PROCESS_PLUGIN_SUFFIX2 = ":" + PROCESS_PLUGIN_SUFFIX; /** * 保存进程后缀和其 Int 值 * 如:{":p1":-99, ":p2":-98} */ public static final Map PROCESS_INT_MAP = new HashMap<>(); /** * 保存进程映射时,符号和实际进程名称的关系 * 如:{"$p1":"com.xx.xxx:p1", "$p2":"com.xx.xxx:p2"} */ public static final Map PROCESS_ADJUST_MAP = new HashMap<>(); /** * 保存进程 Int 值与对应 Provider 的 Authority 的关系 * 如:{-99:"com.qihoo360.mobilesafe.Plugin.NP.1", -98:"com.qihoo360.mobilesafe.Plugin.NP.2"} */ public static final SparseArray PROCESS_AUTHORITY_MAP = new SparseArray<>(); static { for (int i = 0; i < PROCESS_COUNT; i++) { PROCESS_INT_MAP.put(PROCESS_PLUGIN_SUFFIX2 + i, PROCESS_INIT + i); PROCESS_ADJUST_MAP.put("$" + PROCESS_PLUGIN_SUFFIX + i, IPC.getPackageName() + ":" + PROCESS_PLUGIN_SUFFIX + i); PROCESS_AUTHORITY_MAP.put(PROCESS_INIT + i, PluginPitProviderBase.AUTHORITY_PREFIX + i); } } /** * 取进程名称中,冒号及后面的部分,如果进程名中无冒号,则返回原值。 * * @param processName 进程名称 */ public static String processTail(String processName) { int indexOfColon = processName.indexOf(':'); if (indexOfColon >= 0) { processName = processName.toLowerCase(); return processName.substring(indexOfColon); } else { return processName; } } /** * 是否是用户自定义的进程 */ public static boolean isCustomPluginProcess(int index) { return index >= PROCESS_INIT && index < PROCESS_INIT + PROCESS_COUNT; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/process/ProcessPitProviderBase.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.process; import android.annotation.SuppressLint; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.util.Log; import com.qihoo360.loader2.PluginProviderStub; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.replugin.base.IPC; import java.io.FileDescriptor; import java.io.PrintWriter; /** * @author RePlugin Team */ public class ProcessPitProviderBase extends ContentProvider { private static final String TAG = "ProviderBase"; public static final String AUTHORITY_PREFIX = IPC.getPackageName() + ".loader.p.main"; public static final Uri buildUri(int index) { String str = ""; if (index < 0) { str += "N"; index *= -1; } str += index; Uri uri = Uri.parse("content://" + AUTHORITY_PREFIX + str + "/main"); if (BuildConfig.DEBUG) { Log.d(TAG, "buildUri: uri=" + uri); } return uri; } /** * API Level 18 Override有效 * @param fd * @param writer * @param args */ @SuppressLint("Override") public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { // TODO Jiongxuan Zhang // if (RePluginInternal.FOR_DEV) { // MobileSafeApplication.getInstance().dump(fd, writer, args); // // // } } @Override public boolean onCreate() { return true; } @Override public String getType(Uri uri) { return null; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } @Override public Uri insert(Uri uri, ContentValues values) { return PluginProviderStub.stubPlugin(uri, values); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/process/ProcessPitProviderLoader0.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.process; /** * @author RePlugin Team */ public class ProcessPitProviderLoader0 extends ProcessPitProviderBase { } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/process/ProcessPitProviderLoader1.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.process; /** * @author RePlugin Team */ public class ProcessPitProviderLoader1 extends ProcessPitProviderBase { } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/process/ProcessPitProviderP0.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.process; /** * @author RePlugin Team */ public class ProcessPitProviderP0 extends ProcessPitProviderBase { } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/process/ProcessPitProviderP1.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.process; /** * @author RePlugin Team */ public class ProcessPitProviderP1 extends ProcessPitProviderBase { } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/process/ProcessPitProviderP2.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.process; /** * @author RePlugin Team */ public class ProcessPitProviderP2 extends ProcessPitProviderBase { } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/process/ProcessPitProviderPersist.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.process; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import com.qihoo360.loader2.PluginProviderStub; import com.qihoo360.replugin.base.IPC; /** * @author RePlugin Team */ public class ProcessPitProviderPersist extends ContentProvider { private static final String TAG = "ProcessPitProviderPersist"; private static final String AUTHORITY_PREFIX = IPC.getPackageName() + ".loader.p.main"; public static final Uri URI = Uri.parse("content://" + AUTHORITY_PREFIX + "/main"); public static boolean sInvoked; @Override public boolean onCreate() { return true; } @Override public String getType(Uri uri) { return null; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { sInvoked = true; return PluginProviderStub.stubMain(uri, projection, selection, selectionArgs, sortOrder); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/process/ProcessPitProviderUI.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.process; /** * @author RePlugin Team */ public class ProcessPitProviderUI extends ProcessPitProviderBase { } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/provider/PluginPitProviderBase.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.provider; import android.annotation.TargetApi; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.CancellationSignal; import com.qihoo360.replugin.base.IPC; /** * 所有插件的Provider均由此处分发 * * @author RePlugin Team */ public abstract class PluginPitProviderBase extends ContentProvider { PluginProviderHelper mHelper; public static final String AUTHORITY_PREFIX = IPC.getPackageName() + ".Plugin.NP."; protected PluginPitProviderBase(String authority) { mHelper = new PluginProviderHelper(authority); } @Override public boolean onCreate() { // Nothing,不需要做任何事情 return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { PluginProviderHelper.PluginUri pu = mHelper.toPluginUri(uri); if (pu == null) { return null; } ContentProvider cp = mHelper.getProvider(pu); if (cp == null) { return null; } return cp.query(pu.transferredUri, projection, selection, selectionArgs, sortOrder); } @Override @TargetApi(16) public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { PluginProviderHelper.PluginUri pu = mHelper.toPluginUri(uri); if (pu == null) { return null; } ContentProvider cp = mHelper.getProvider(pu); if (cp == null) { return null; } return cp.query(pu.transferredUri, projection, selection, selectionArgs, sortOrder, cancellationSignal); } @Override public String getType(Uri uri) { PluginProviderHelper.PluginUri pu = mHelper.toPluginUri(uri); if (pu == null) { return null; } ContentProvider cp = mHelper.getProvider(pu); if (cp == null) { return null; } return cp.getType(pu.transferredUri); } @Override public Uri insert(Uri uri, ContentValues values) { PluginProviderHelper.PluginUri pu = mHelper.toPluginUri(uri); if (pu == null) { return null; } ContentProvider cp = mHelper.getProvider(pu); if (cp == null) { return null; } return cp.insert(pu.transferredUri, values); } @Override public int bulkInsert(Uri uri, ContentValues[] values) { PluginProviderHelper.PluginUri pu = mHelper.toPluginUri(uri); if (pu == null) { return -1; } ContentProvider cp = mHelper.getProvider(pu); if (cp == null) { return -1; } return cp.bulkInsert(pu.transferredUri, values); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { PluginProviderHelper.PluginUri pu = mHelper.toPluginUri(uri); if (pu == null) { return -1; } ContentProvider cp = mHelper.getProvider(pu); if (cp == null) { return -1; } return cp.delete(pu.transferredUri, selection, selectionArgs); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { PluginProviderHelper.PluginUri pu = mHelper.toPluginUri(uri); if (pu == null) { return -1; } ContentProvider cp = mHelper.getProvider(pu); if (cp == null) { return -1; } return cp.update(pu.transferredUri, values, selection, selectionArgs); } @Override public void onLowMemory() { for (ContentProvider cp : mHelper.mProviderAuthorityMap.values()) { cp.onLowMemory(); } super.onLowMemory(); } @Override @TargetApi(14) public void onTrimMemory(int level) { for (ContentProvider cp : mHelper.mProviderAuthorityMap.values()) { cp.onTrimMemory(level); } super.onTrimMemory(level); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/provider/PluginPitProviderP0.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.provider; /** * ProcessPitProviderLoader0 * * @author RePlugin Team */ public class PluginPitProviderP0 extends PluginPitProviderBase { public static final String AUTHORITY = AUTHORITY_PREFIX + "0"; public PluginPitProviderP0() { super(AUTHORITY); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/provider/PluginPitProviderP1.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.provider; /** * ProcessPitProviderLoader1 * * @author RePlugin Team */ public class PluginPitProviderP1 extends PluginPitProviderBase { public static final String AUTHORITY = AUTHORITY_PREFIX + "1"; public PluginPitProviderP1() { super(AUTHORITY); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/provider/PluginPitProviderP2.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.provider; /** * ProcessProviderLoader2 * * @author RePlugin Team */ public class PluginPitProviderP2 extends PluginPitProviderBase { public static final String AUTHORITY = AUTHORITY_PREFIX + "2"; public PluginPitProviderP2() { super(AUTHORITY); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/provider/PluginPitProviderPersist.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.provider; /** * @author RePlugin Team */ public class PluginPitProviderPersist extends PluginPitProviderBase { public static final String AUTHORITY = AUTHORITY_PREFIX + "PSP"; public PluginPitProviderPersist() { super(AUTHORITY); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/provider/PluginPitProviderUI.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.provider; /** * @author RePlugin Team */ public class PluginPitProviderUI extends PluginPitProviderBase { public static final String AUTHORITY = AUTHORITY_PREFIX + "UIP"; public PluginPitProviderUI() { super(AUTHORITY); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/provider/PluginProviderClient.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.provider; import android.annotation.TargetApi; import android.content.ContentProviderClient; import android.content.ContentValues; import android.content.Context; import android.content.pm.ProviderInfo; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.text.TextUtils; import android.util.Log; import com.qihoo360.i.Factory; import com.qihoo360.i.IPluginManager; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.component.process.PluginProcessHost; import com.qihoo360.replugin.component.utils.PluginClientHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import java.io.InputStream; import java.io.OutputStream; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 一种能够对【插件】的Provider做增加、删除、改变、查询的接口。 * 就像使用ContentResolver一样 * * @author RePlugin Team */ public class PluginProviderClient { private static final String TAG = "PluginProviderClient"; private static final int PROCESS_UNKNOWN = Integer.MAX_VALUE; /** * 调用插件里的Provider * @see android.content.ContentResolver#acquireContentProviderClient(String) */ public static ContentProviderClient acquireContentProviderClient(Context c, String name) { // fixme 如何判断应该使用哪个进程的 provider 呢? return c.getContentResolver().acquireContentProviderClient(PluginPitProviderP0.AUTHORITY); } /** * 调用插件里的Provider * @see android.content.ContentResolver#query(Uri, String[], String, String[], String) */ public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Uri turi = toCalledUri(c, uri); return c.getContentResolver().query(turi, projection, selection, selectionArgs, sortOrder); } /** * 调用插件里的Provider * @see android.content.ContentResolver#query(Uri, String[], String, String[], String, CancellationSignal) */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { Uri turi = toCalledUri(c, uri); return c.getContentResolver().query(turi, projection, selection, selectionArgs, sortOrder, cancellationSignal); } /** * 调用插件里的Provider * @see android.content.ContentResolver#getType(Uri) */ public static String getType(Context c, Uri uri) { Uri turi = toCalledUri(c, uri); return c.getContentResolver().getType(turi); } /** * 调用插件里的Provider * @see android.content.ContentResolver#insert(Uri, ContentValues) */ public static Uri insert(Context c, Uri uri, ContentValues values) { Uri turi = toCalledUri(c, uri); return c.getContentResolver().insert(turi, values); } /** * 调用插件里的Provider * @see android.content.ContentResolver#bulkInsert(Uri, ContentValues[]) */ public static int bulkInsert(Context c, Uri uri, ContentValues[] values) { Uri turi = toCalledUri(c, uri); return c.getContentResolver().bulkInsert(turi, values); } /** * 调用插件里的Provider * @see android.content.ContentResolver#delete(Uri, String, String[]) */ public static int delete(Context c, Uri uri, String selection, String[] selectionArgs) { Uri turi = toCalledUri(c, uri); return c.getContentResolver().delete(turi, selection, selectionArgs); } /** * 调用插件里的Provider * @see android.content.ContentResolver#update(Uri, ContentValues, String, String[]) */ public static int update(Context c, Uri uri, ContentValues values, String selection, String[] selectionArgs) { Uri turi = toCalledUri(c, uri); return c.getContentResolver().update(turi, values, selection, selectionArgs); } /** * 调用插件里的Provider * * @see android.content.ContentResolver#openInputStream(Uri) */ public static InputStream openInputStream(Context c, Uri uri) { try { Uri turi = toCalledUri(c, uri); return c.getContentResolver().openInputStream(turi); } catch (Throwable e) { if (LOGR) { e.printStackTrace(); } } return null; } /** * 调用插件里的Provider * * @see android.content.ContentResolver#openOutputStream(Uri) */ public static OutputStream openOutputStream(Context c, Uri uri) { try { Uri turi = toCalledUri(c, uri); return c.getContentResolver().openOutputStream(turi); } catch (Throwable e) { if (LOGR) { e.printStackTrace(); } } return null; } /** * 调用插件里的Provider * * @see android.content.ContentResolver#openOutputStream(Uri, String) */ public static OutputStream openOutputStream(Context c, Uri uri, String mode) { try { Uri turi = toCalledUri(c, uri); return c.getContentResolver().openOutputStream(turi, mode); } catch (Throwable e) { if (LOGR) { e.printStackTrace(); } } return null; } /** * 调用插件里的Provider * * @see android.content.ContentResolver#openFileDescriptor(Uri, String) */ public static ParcelFileDescriptor openFileDescriptor(Context c, Uri uri, String mode) { try { Uri turi = toCalledUri(c, uri); return c.getContentResolver().openFileDescriptor(turi, mode); } catch (Throwable e) { if (LOGR) { e.printStackTrace(); } } return null; } /** * 调用插件里的Provider * * @see android.content.ContentResolver#openFileDescriptor(Uri, String, CancellationSignal) */ @TargetApi(Build.VERSION_CODES.KITKAT) public static ParcelFileDescriptor openFileDescriptor(Context c, Uri uri, String mode, CancellationSignal cancellationSignal) { try { Uri turi = toCalledUri(c, uri); return c.getContentResolver().openFileDescriptor(turi, mode, cancellationSignal); } catch (Throwable e) { if (LOGR) { e.printStackTrace(); } } return null; } /** * 调用插件里的Provider * * @see android.content.ContentResolver#registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer) */ public static void registerContentObserver(Context c, Uri uri, boolean notifyForDescendents, ContentObserver observer) { try { Uri turi = toCalledUri(c, uri); c.getContentResolver().registerContentObserver(turi, notifyForDescendents, observer); } catch (Throwable e) { if (LOGR) { e.printStackTrace(); } } } /** * 调用插件里的Provider * * @see android.content.ContentResolver#notifyChange(Uri, ContentObserver) */ public static void notifyChange(Context c, Uri uri, ContentObserver observer) { Uri turi = toCalledUri(c, uri); c.getContentResolver().notifyChange(turi, observer); } /** * 调用插件里的Provider * * @see android.content.ContentResolver#notifyChange(Uri, ContentObserver, boolean) */ public static void notifyChange(Context c, Uri uri, ContentObserver observer, boolean b) { Uri turi = toCalledUri(c, uri); c.getContentResolver().notifyChange(turi, observer, b); } /** * 将从【当前】插件或主程序里的URI转化成系统传过来的URI,且由插件Manifest来指定进程。例如: * Before: content://com.qihoo360.contacts.abc/people (Contacts插件,UI) * After: content://com.qihoo360.mobilesafe.PluginUIP/contacts/com.qihoo360.mobilesafe.contacts.abc/people * * @param c 当前的Context对象。若传递主程序的Context,则直接返回Uri,不作处理。否则就做Uri转换 * @param uri URI对象 * @return 转换后可直接在ContentResolver使用的URI */ public static Uri toCalledUri(Context c, Uri uri) { String pn = fetchPluginByContext(c, uri); if (pn == null) { return uri; } return toCalledUri(c, pn, uri, IPluginManager.PROCESS_AUTO); } /** * 将从插件里的URI转化成系统传过来的URI。可自由指定在哪个进程启动。例如: * Before: content://com.qihoo360.contacts.abc/people (Contacts插件,UI) * After: content://com.qihoo360.mobilesafe.PluginUIP/contacts/com.qihoo360.mobilesafe.contacts.abc/people * * @param context 当前的Context对象,目前暂无用 * @param plugin 要使用的插件 * @param uri URI对象 * @param process 进程信息,若为PROCESS_AUTO,则根据插件Manifest来指定进程 * @return 转换后可直接在ContentResolver使用的URI */ public static Uri toCalledUri(Context context, String plugin, Uri uri, int process) { if (TextUtils.isEmpty(plugin)) { throw new IllegalArgumentException(); } if (uri == null) { throw new IllegalArgumentException(); } if (uri.getAuthority().startsWith(PluginPitProviderBase.AUTHORITY_PREFIX)) { // 自己已填好了要使用的插件名(以PluginUIProvider及其它为开头),这里不做处理 return uri; } // content://com.qihoo360.mobilesafe.PluginUIP if (process == IPluginManager.PROCESS_AUTO) { // 直接从插件的Manifest中获取 process = getProcessByAuthority(plugin, uri.getAuthority()); if (process == PROCESS_UNKNOWN) { // 可能不是插件里的,而是主程序的,直接返回Uri即可 return uri; } } String au; if (process == IPluginManager.PROCESS_PERSIST) { au = PluginPitProviderPersist.AUTHORITY; } else if (PluginProcessHost.isCustomPluginProcess(process)) { au = PluginProcessHost.PROCESS_AUTHORITY_MAP.get(process); } else { au = PluginPitProviderUI.AUTHORITY; } // from => content:// com.qihoo360.contacts.abc/people?id=9 // to => content://com.qihoo360.mobilesafe.Plugin.NP.UIP/plugin_name/com.qihoo360.contacts.abc/people?id=9 String newUri = String.format("content://%s/%s/%s", au, plugin, uri.toString().replace("content://", "")); return Uri.parse(newUri); } // 根据Context所带的插件信息,来获取插件名。若获取不到,或者为主程序,则返回Null private static String fetchPluginByContext(Context c, Uri uri) { // 根据Context的ClassLoader来看到底属于哪个插件,还是只是主程序 ClassLoader cl = c.getClassLoader(); String pn = Factory.fetchPluginName(cl); if (TextUtils.isEmpty(pn)) { // 获得了无效的插件信息,这种情况很少见,故打出错误信息,什么也不做 if (LOGR) { LogRelease.e(PLUGIN_TAG, "ppc.fubc: pn is n. u=" + uri); } return null; } else if (TextUtils.equals(pn, RePlugin.PLUGIN_NAME_MAIN)) { // 此Context属于主工程,则也什么都不做。稍后会直接走“主程序的Context”来做处理 if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginProviderClient.fubc(): Call Main! u=" + uri); } return null; } else { // 返回这个Plugin名字 if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginProviderClient.fubc(): Call Plugin! u=" + uri); } return pn; } } private static int getProcessByAuthority(String pn, String authority) { // 开始尝试获取插件的ServiceInfo ComponentList col = Factory.queryPluginComponentList(pn); if (col == null) { if (LogDebug.LOG) { Log.e(TAG, "getProcessByAuthority(): Fetch Component List Error! pn=" + pn + "; au=" + authority); } return PROCESS_UNKNOWN; } ProviderInfo si = col.getProviderByAuthority(authority); if (si == null) { if (LogDebug.LOG) { Log.e(TAG, "getProcessByAuthority(): Not register! pn=" + pn + "; au=" + authority); } return PROCESS_UNKNOWN; } int p = PluginClientHelper.getProcessInt(si.processName); if (LogDebug.LOG) { Log.d(TAG, "getProcessByAuthority(): Okay! Process=" + p + "; pn=" + pn); } return p; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/provider/PluginProviderClient2.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.provider; import android.annotation.TargetApi; import android.content.ContentProviderClient; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.CancellationSignal; import android.os.RemoteException; import android.util.Log; import com.qihoo360.replugin.helper.LogDebug; import static com.qihoo360.replugin.component.provider.PluginProviderClient.toCalledUri; /** * 此工具类为插件提供 ContentProviderClient 的转换接口 * * @author RePlugin Team */ public class PluginProviderClient2 { private static final String TAG = "PluginProviderClient2"; /** * 调用插件里的Provider * * @see android.content.ContentProviderClient#query(Uri, String[], String, String[], String) */ public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ContentProviderClient client = PluginProviderClient.acquireContentProviderClient(c, ""); if (client != null) { try { Uri toUri = toCalledUri(c, uri); return client.query(toUri, projection, selection, selectionArgs, sortOrder); } catch (RemoteException e) { if (LogDebug.LOG) { Log.d(TAG, e.toString()); } } } if (LogDebug.LOG) { Log.d(TAG, String.format("call query1 %s fail", uri.toString())); } return null; } /** * 调用插件里的Provider * * @see android.content.ContentProviderClient#query(Uri, String[], String, String[], String, CancellationSignal) */ @TargetApi(16) public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { ContentProviderClient client = PluginProviderClient.acquireContentProviderClient(c, ""); if (client != null) { try { Uri toUri = toCalledUri(c, uri); return client.query(toUri, projection, selection, selectionArgs, sortOrder, cancellationSignal); } catch (RemoteException e) { if (LogDebug.LOG) { Log.d(TAG, e.toString()); } } } if (LogDebug.LOG) { Log.d(TAG, String.format("call query2 %s fail", uri.toString())); } return null; } /** * 调用插件里的Provider * * @see android.content.ContentProviderClient#update(Uri, ContentValues, String, String[]) */ public static int update(Context c, Uri uri, ContentValues values, String selection, String[] selectionArgs) { ContentProviderClient client = PluginProviderClient.acquireContentProviderClient(c, ""); if (client != null) { try { Uri toUri = toCalledUri(c, uri); return client.update(toUri, values, selection, selectionArgs); } catch (RemoteException e) { if (LogDebug.LOG) { Log.d(TAG, e.toString()); } } } if (LogDebug.LOG) { Log.d(TAG, String.format("call update %s", uri.toString())); } return -1; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/provider/PluginProviderHelper.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.provider; import android.content.ContentProvider; import android.content.Context; import android.content.pm.ProviderInfo; import android.net.Uri; import android.text.TextUtils; import android.util.Log; import com.qihoo360.i.Factory; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.helper.LogDebug; import java.util.concurrent.ConcurrentHashMap; import java.util.List; import java.util.Map; /** * 和安装、加载、缓存插件Provider有关的帮助类。 * * @author RePlugin Team */ public class PluginProviderHelper { private static final String TAG = "PluginProviderHelper"; private static final String SCHEME_AND_SSP = "content://"; private final String mAuthority; PluginProviderHelper(String authority) { mAuthority = authority; } Map mProviderAuthorityMap = new ConcurrentHashMap<>(); // 将从系统传过来的URI转化成插件里的URI。例如: // Before: content://com.qihoo360.mobilesafe.PluginTransferP/contacts/com.qihoo360.contacts.abc/people // After : content:// com.qihoo360.contacts.abc/people (从contacts插件中解析并寻找) public PluginUri toPluginUri(Uri uri) { if (LogDebug.LOG) { Log.i(TAG, "toPluginUri(): Start... Uri=" + uri); } // Authority正确 if (!TextUtils.equals(uri.getAuthority(), mAuthority)) { if (LogDebug.LOG) { Log.e(TAG, "toPluginUri(): Authority error! auth=" + uri.getAuthority()); } return null; } // 至少两个元素(排除掉authority) List fs = uri.getPathSegments(); if (fs.size() < 2) { if (LogDebug.LOG) { Log.e(TAG, "toPluginUri(): Less than 2 fragments, size=" + fs.size()); } return null; } // 获取插件名(第一个元素) String pn = fs.get(0); // 看这个Plugin是否可以被打开 if (!RePlugin.isPluginInstalled(pn)) { if (LogDebug.LOG) { Log.e(TAG, "toPluginUri(): Plugin not exists! pn=" + pn); } return null; } // 剔除Uri中开头的内容 String ut = uri.toString(); String tut = removeHostAuthorityAndInfo(ut, pn); PluginUri pu = new PluginUri(); pu.plugin = pn; pu.transferredUri = Uri.parse(tut); if (LogDebug.LOG) { Log.i(TAG, "toPluginUri(): End! t-uri=" + pu); } return pu; } public ContentProvider getProvider(PluginUri pu) { if (LogDebug.LOG) { Log.i(TAG, "getProvider(): Start... pu=" + pu); } String auth = pu.transferredUri.getAuthority(); // 已有缓存?直接返回! ContentProvider cp = mProviderAuthorityMap.get(auth); if (cp != null) { if (LogDebug.LOG) { Log.i(TAG, "getProvider(): Exists! Return now. cp=" + cp); } return cp; } // 开始构建插件里的ContentProvider对象 cp = installProvider(pu, auth); if (cp == null) { if (LogDebug.LOG) { Log.e(TAG, "getProvider(): Install fail!"); } return null; } // 加入列表。下次直接读缓存 mProviderAuthorityMap.put(auth, cp); if (LogDebug.LOG) { Log.i(TAG, "getProvider(): Okay! pu=" + pu + "; cp=" + cp); } return cp; } private String removeHostAuthorityAndInfo(String uri, String plugin) { // content:// --- com.qihoo360.mobilesafe.PluginTransferP --- /(1) --- shakeoff --- /(1) --- int startsWith = SCHEME_AND_SSP.length() + mAuthority.length() + 1 + plugin.length() + 1; return SCHEME_AND_SSP + uri.substring(startsWith, uri.length()); } private ContentProvider installProvider(PluginUri pu, String auth) { // 开始尝试获取插件的ProviderInfo ComponentList col = Factory.queryPluginComponentList(pu.plugin); if (col == null) { if (LogDebug.LOG) { Log.e(TAG, "installProvider(): Fetch Component List Error! auth=" + auth); } return null; } ProviderInfo pi = col.getProviderByAuthority(auth); if (pi == null) { if (LogDebug.LOG) { Log.e(TAG, "installProvider(): Not register! auth=" + auth); } return null; } // 通过ProviderInfo创建ContentProvider对象 Context plgc = Factory.queryPluginContext(pu.plugin); if (plgc == null) { if (LogDebug.LOG) { Log.e(TAG, "installProvider(): Fetch Context Error! auth=" + auth); } return null; } ClassLoader cl = plgc.getClassLoader(); if (cl == null) { if (LogDebug.LOG) { Log.e(TAG, "installProvider(): ClassLoader is Null!"); } return null; } ContentProvider cp; try { cp = (ContentProvider) cl.loadClass(pi.name).newInstance(); } catch (Throwable e) { if (LogDebug.LOG) { Log.e(TAG, "installProvider(): New instance fail!", e); } return null; } // 调用attachInfo方法(内部会调用onCreate) try { cp.attachInfo(plgc, pi); } catch (Throwable e) { // 有两种可能: // 1、第三方ROM修改了ContentProvider.attachInfo的实现 // 2、开发者自己覆写了attachInfo方法,其中有Bug // 故暂时先Try-Catch,这样若插件的Provider没有使用Context对象,则也不会出现问题 if (LogDebug.LOG) { Log.e(TAG, "installProvider(): Attach info fail!", e); } return null; } return cp; } static class PluginUri { Uri transferredUri; String plugin; @Override public String toString() { return transferredUri + " [" + plugin + "]"; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/receiver/PluginReceiverHelper.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.text.TextUtils; import com.qihoo360.i.Factory; import com.qihoo360.mobilesafe.api.Tasks; import com.qihoo360.replugin.helper.LogDebug; import java.util.HashMap; import static com.qihoo360.replugin.component.receiver.PluginReceiverProxy.loadClassSafety; import static com.qihoo360.replugin.helper.LogDebug.LOG; /** * @author RePlugin Team */ public class PluginReceiverHelper { /** * 插件静态注册的广播,从常驻代理到插件时,调用此方法 */ public static void onPluginReceiverReceived(final String plugin, final String receiverName, final HashMap receivers, final Intent intent) { if (TextUtils.isEmpty(plugin) || TextUtils.isEmpty(receiverName)) { if (LOG) { LogDebug.d(PluginReceiverProxy.TAG, "plugin or receiver or intent is null, return."); } return; } // 使用插件的 Context 对象 final Context pContext = Factory.queryPluginContext(plugin); if (pContext == null) { return; } String key = String.format("%s-%s", plugin, receiverName); BroadcastReceiver receiver = null; if (receivers == null || !receivers.containsKey(key)) { try { // 使用插件的 ClassLoader 加载 BroadcastReceiver Class c = loadClassSafety(pContext.getClassLoader(), receiverName); if (c != null) { receiver = (BroadcastReceiver) c.newInstance(); if (receivers != null) { receivers.put(key, receiver); } if (LOG) { LogDebug.d(PluginReceiverProxy.TAG, String.format("反射创建 Receiver 实例 %s", receiverName)); } } } catch (Throwable e) { if (LOG) { LogDebug.d(PluginReceiverProxy.TAG, e.toString()); } } } else { receiver = receivers.get(key); } if (receiver != null) { final BroadcastReceiver finalReceiver = receiver; // 转到 ui 线程 Tasks.post2UI(new Runnable() { @Override public void run() { if (LOG) { LogDebug.d(PluginReceiverProxy.TAG, String.format("调用 %s.onReceive()", receiverName)); } finalReceiver.onReceive(pContext, intent); } }); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/receiver/PluginReceiverProxy.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.text.TextUtils; import android.util.Log; import com.qihoo360.i.Factory; import com.qihoo360.i.IPluginManager; import com.qihoo360.loader2.IPluginClient; import com.qihoo360.loader2.IPluginHost; import com.qihoo360.loader2.MP; import com.qihoo360.loader2.PluginBinderInfo; import com.qihoo360.loader2.PluginProcessMain; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.component.utils.PluginClientHelper; import com.qihoo360.replugin.helper.LogDebug; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.qihoo360.replugin.helper.LogDebug.LOG; /** * @author RePlugin Team */ public class PluginReceiverProxy extends BroadcastReceiver { public static final String TAG = "ms-receiver"; private HashMap>> mActionPluginComponents; /** * 保存 Receiver 与 process 的关系 */ private final HashMap mReceiverProcess = new HashMap<>(); @Override public void onReceive(Context context, Intent intent) { if (intent == null || mActionPluginComponents == null) { return; } String action = intent.getAction(); if (!TextUtils.isEmpty(action)) { if (LOG) { LogDebug.d(TAG, String.format("代理 Receiver 收到 action: %s ", action)); } // 根据 action 取得 map> HashMap> pc = mActionPluginComponents.get(action); if (pc != null) { // 遍历每一个插件 for (HashMap.Entry> entry : pc.entrySet()) { String plugin = entry.getKey(); if (entry.getValue() == null) { continue; } // 拷贝数据,防止多线程问题 List receivers = new ArrayList<>(entry.getValue()); // 此插件所有声明的 receiver for (String receiver : receivers) { try { // 在对应进程接收广播, 如果进程未启动,则拉起之 int process = getProcessOfReceiver(plugin, receiver); // todo 合并 IPluginClient 和 IPluginHost if (process == IPluginManager.PROCESS_PERSIST) { IPluginHost host = PluginProcessMain.getPluginHost(); host.onReceive(plugin, receiver, intent); } else { IPluginClient client = MP.startPluginProcess(plugin, process, new PluginBinderInfo(PluginBinderInfo.NONE_REQUEST)); client.onReceive(plugin, receiver, intent); } } catch (Throwable e) { if (LOG) { Log.d(TAG, e.toString()); } } } } } } } public static Class loadClassSafety(ClassLoader classLoader, String className) throws ClassNotFoundException { return classLoader.loadClass(className); } /** * 获取插件 plugin 的 receiver 所在进程 ID * * @param plugin 插件名称 * @param receiver Receiver 名称 * @return Receiver 声明的进程ID */ private int getProcessOfReceiver(String plugin, String receiver) { String key = plugin + "-" + receiver; if (!mReceiverProcess.containsKey(key)) { // 获得 ActivityInfo ComponentList components = Factory.queryPluginComponentList(plugin); if (components != null) { Map receiverMap = components.getReceiverMap(); if (receiverMap != null) { ActivityInfo ai = receiverMap.get(receiver); if (ai != null) { mReceiverProcess.put(key, PluginClientHelper.getProcessInt(ai.processName)); } } } } if (mReceiverProcess.containsKey(key)) { return mReceiverProcess.get(key); } return IPluginManager.PROCESS_UI; } public void setActionPluginMap(HashMap>> map) { mActionPluginComponents = map; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/PluginServiceClient.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.service; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ServiceInfo; import android.os.Handler; import android.os.Looper; import android.os.Messenger; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import com.qihoo360.i.Factory; import com.qihoo360.loader2.PMF; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.component.service.server.IPluginServiceServer; import com.qihoo360.replugin.component.utils.PluginClientHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 一种能够对【插件】的服务进行:启动、停止、绑定、解绑等功能的类 * 所有针对插件命令的操作,均从此类开始。 * * @author RePlugin Team */ public class PluginServiceClient { static final int PROCESS_UNKNOWN = Integer.MAX_VALUE; private static final String TAG = "PluginServiceClient"; private static PluginServiceServerFetcher sServerFetcher = new PluginServiceServerFetcher(); private static PluginServiceDispatcherManager sDispatcherManager = new PluginServiceDispatcherManager(); private static Handler sClientHandler = new Handler(Looper.getMainLooper()); private static Messenger sClientMessenger = new Messenger(sClientHandler); /** * 开启指定插件的服务。近似于Context.startService方法 * * @param context Context对象 * @param intent 要打开的服务名。如何填写请参见类的说明 * @return 最终打开哪个服务的ComponentName * @see android.content.Context#startService(Intent) */ public static ComponentName startService(Context context, Intent intent) { return startService(context, intent, false); } // @hide public static ComponentName startService(Context context, Intent intent, boolean throwOnFail) { // 从 Intent 中获取 ComponentName ComponentName cn = getServiceComponentFromIntent(context, intent); // 获取将要使用服务的进程ID,并在稍后获取PSS对象 int process = getProcessByComponentName(cn); if (process == PROCESS_UNKNOWN) { // 有可能是不支持的插件,也有可能本意就是想调用Main工程的服务。则直接调用系统方法来做 if (LOG) { LogDebug.d(PLUGIN_TAG, "PSS.startService(): Call SystemAPI: in=" + intent); } if (throwOnFail) { throw new PluginClientHelper.ShouldCallSystem(); } else { return context.startService(intent); } } // 既然确认是插件,则将之前获取的插件信息填入 intent.setComponent(cn); IPluginServiceServer pss = sServerFetcher.fetchByProcess(process); if (pss == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.ss: pss n"); } return null; } // 开启服务 try { return pss.startService(intent, sClientMessenger); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.ss: pss e", e); } } return null; } /** * 停止指定插件的服务。近似于Context.stopService * * @param context Context对象 * @param intent 要打开的服务名。如何填写请参见类的说明 * @return 是否成功停止服务。大于0表示成功 * @see android.content.Context#stopService(Intent) */ public static boolean stopService(Context context, Intent intent) { return stopService(context, intent, false); } // @hide public static boolean stopService(Context context, Intent intent, boolean throwOnFail) { // 根据Context所带的插件信息,来填充Intent的ComponentName对象。具体见方法说明 ComponentName cn = PluginClientHelper.getComponentNameByContext(context, intent.getComponent()); // 获取将要使用服务的进程ID,并在稍后获取PSS对象 int process = getProcessByComponentName(cn); if (process == PROCESS_UNKNOWN) { // 有可能是不支持的插件,也有可能本意就是想调用Main工程的服务。则直接调用系统方法来做 if (LOG) { LogDebug.d(PLUGIN_TAG, "PSS.stopService(): Call SystemAPI: in=" + intent); } if (throwOnFail) { throw new PluginClientHelper.ShouldCallSystem(); } else { return context.stopService(intent); } } // 既然确认是插件,则将之前获取的插件信息填入 intent.setComponent(cn); IPluginServiceServer pss = sServerFetcher.fetchByProcess(process); if (pss == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.sts: pss n"); } return false; } // 停止服务 try { return pss.stopService(intent, sClientMessenger) != 0; } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.sts: pss e", e); } } return false; } /** * 绑定插件服务,获取其AIDL。近似于Context.bindService * * @param context Context对象 * @param intent 要打开的服务名。如何填写请参见类的说明 * @param sc ServiceConnection对象(等同于系统) * @param flags flags对象。目前仅支持BIND_AUTO_CREATE标志 * @return 是否成功绑定服务。大于0表示成功 * @see android.content.Context#bindService(Intent, ServiceConnection, int) */ public static boolean bindService(Context context, Intent intent, ServiceConnection sc, int flags) { return bindService(context, intent, sc, flags, false); } // @hide public static boolean bindService(Context context, Intent intent, ServiceConnection sc, int flags, boolean throwOnFail) { // 根据Context所带的插件信息,来填充Intent的ComponentName对象。具体见方法说明 ComponentName cn = PluginClientHelper.getComponentNameByContext(context, intent.getComponent()); // 获取将要使用服务的进程ID,并在稍后获取PSS对象 int process = getProcessByComponentName(cn); if (process == PROCESS_UNKNOWN) { // 有可能是不支持的插件,也有可能本意就是想调用Main工程的服务。则直接调用系统方法来做 if (LOG) { LogDebug.d(PLUGIN_TAG, "PSS.bindService(): Call SystemAPI: in=" + intent); } if (throwOnFail) { throw new PluginClientHelper.ShouldCallSystem(); } else { return context.bindService(intent, sc, flags); } } // 既然确认是插件,则将之前获取的插件信息填入 intent.setComponent(cn); IPluginServiceServer pss = sServerFetcher.fetchByProcess(process); if (pss == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.bs: pss n"); } return false; } // 开始绑定服务 try { ServiceDispatcher sd = sDispatcherManager.get(sc, context, sClientHandler, flags, process); int r = pss.bindService(intent, sd.getIServiceConnection(), flags, sClientMessenger); // TODO 返回值处理 return r != 0; } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.bs: pss e", e); } } return false; } /** * 解除对插件服务的绑定 * * @param context Context对象 * @param sc ServiceConnection对象(等同于系统) * @return 是否成功解除绑定 */ public static boolean unbindService(Context context, ServiceConnection sc) { return unbindService(context, sc, true); } // @hide public static boolean unbindService(Context context, ServiceConnection sc, boolean callSysFirst) { // 由于我们不确定ServiceConnection是否在系统中注册,所以先尝试走系统unbind流程,然后再走我们的 if (callSysFirst) { try { if (LOG) { LogDebug.d(PLUGIN_TAG, "PSS.unbindService(): First, We call SystemAPI: sc=" + sc); } context.unbindService(sc); } catch (Throwable e) { // Ignore } } // 获取服务分发器 ServiceDispatcher sd = sDispatcherManager.forget(context, sc); if (sd == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.us: sd n"); } return false; } IPluginServiceServer pss = sServerFetcher.fetchByProcess(sd.getProcess()); if (pss == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.us: pss n"); } return false; } try { return pss.unbindService(sd.getIServiceConnection()); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.us: pss e", e); } } return false; } /** * 在插件服务中停止服务。近似于Service.stopSelf * 注意:此方法应在插件服务中被调用 * * @param s 要停止的插件服务 * @see android.app.Service#stopSelf() */ public static void stopSelf(Service s) { Intent intent = new Intent(s, s.getClass()); try { PMF.stopService(intent); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "pss.ss: pf f", e); } } } private static int getProcessByComponentName(ComponentName cn) { if (cn == null) { // 如果Intent里面没有带ComponentName,则我们不支持此特性,直接返回null // 外界会直接调用其系统的对应方法 return PROCESS_UNKNOWN; } String pn = cn.getPackageName(); // 开始尝试获取插件的ServiceInfo ComponentList col = Factory.queryPluginComponentList(pn); if (col == null) { if (LogDebug.LOG) { Log.e(TAG, "getProcessByComponentName(): Fetch Component List Error! pn=" + pn); } return PROCESS_UNKNOWN; } ServiceInfo si = col.getService(cn.getClassName()); if (si == null) { if (LogDebug.LOG) { Log.e(TAG, "getProcessByComponentName(): Not register! pn=" + pn); } return PROCESS_UNKNOWN; } int p = PluginClientHelper.getProcessInt(si.processName); if (LogDebug.LOG) { Log.d(TAG, "getProcessByComponentName(): Okay! Process=" + p + "; pn=" + pn); } return p; } /** * 启动 Service 时,从 Intent 中获取 ComponentName */ private static ComponentName getServiceComponentFromIntent(Context context, Intent intent) { ClassLoader cl = context.getClassLoader(); String plugin = Factory.fetchPluginName(cl); /* Intent 中已指定 ComponentName */ if (intent.getComponent() != null) { // 根据 Context 所带的插件信息,来填充 Intent 的 ComponentName 对象。具体见方法说明 return PluginClientHelper.getComponentNameByContext(context, intent.getComponent()); } else { /* Intent 中已指定 Action,根据 action 解析出 ServiceInfo */ if (!TextUtils.isEmpty(intent.getAction())) { ComponentList componentList = Factory.queryPluginComponentList(plugin); if (componentList != null) { // 返回 ServiceInfo 和 Service 所在的插件 Pair pair = componentList.getServiceAndPluginByIntent(context, intent); if (pair != null) { return new ComponentName(pair.second, pair.first.name); } } } else { if (LOG) { LogDebug.d(PLUGIN_TAG, "PSS.startService(): No Component and no Action"); } } } return null; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/PluginServiceDispatcherManager.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.service; import android.content.Context; import android.content.ServiceConnection; import android.os.Handler; import android.util.Log; import com.qihoo360.replugin.utils.basic.ArrayMap; import static com.qihoo360.replugin.helper.LogDebug.LOG; /** * 用来管理ServiceDispatcher的类 * * @author RePlugin Team */ public class PluginServiceDispatcherManager { private static final String TAG = "PSDM"; private ArrayMap> mServices = new ArrayMap<>(); private ArrayMap> mUnboundServices = new ArrayMap<>(); private static final byte[] SERVICES_LOCKER = new byte[0]; public ServiceDispatcher get(ServiceConnection c, Context context, Handler handler, int flags, int process) { synchronized (SERVICES_LOCKER) { ServiceDispatcher sd = null; ArrayMap map = mServices.get(context); if (map != null) { sd = map.get(c); } if (sd == null) { sd = new ServiceDispatcher(c, context, handler, flags, process); if (map == null) { map = new ArrayMap<>(); mServices.put(context, map); } map.put(c, sd); } else { sd.validate(context, handler); } return sd; } } public ServiceDispatcher forget(Context context, ServiceConnection c) { synchronized (SERVICES_LOCKER) { ArrayMap map = mServices.get(context); ServiceDispatcher sd = null; if (map != null) { sd = map.get(c); if (sd != null) { map.remove(c); sd.doForget(); if (map.size() == 0) { mServices.remove(context); } if ((sd.getFlags() & Context.BIND_DEBUG_UNBIND) != 0) { ArrayMap holder = mUnboundServices.get(context); if (holder == null) { holder = new ArrayMap<>(); mUnboundServices.put(context, holder); } RuntimeException ex = new IllegalArgumentException( "Originally unbound here:"); ex.fillInStackTrace(); sd.setUnbindLocation(ex); holder.put(c, sd); } return sd; } } ArrayMap holder = mUnboundServices.get(context); if (holder != null) { sd = holder.get(c); if (sd != null) { // NOTE 和系统不同的是,这里我们不会那么激进的抛出异常,而是打一个Error。 // NOTE 主要是因为插件的加载方式不同于系统,若有问题则不应抛异常提示。 // NOTE 下同。 by Jiongxuan Zhang RuntimeException ex = sd.getUnbindLocation(); Exception e = new IllegalArgumentException( "Unbinding Service " + c + " that was already unbound", ex); if (LOG) { Log.e(TAG, "forgetServiceDispatcher(): Unbind Error!", e); } return null; } } if (context == null) { Exception e = new IllegalStateException("Unbinding Service " + c + " from Context that is no longer in use"); if (LOG) { Log.e(TAG, "forgetServiceDispatcher(): Unbind Error!", e); } return null; } else { Exception e = new IllegalArgumentException("Service not registered: " + c); if (LOG) { Log.e(TAG, "forgetServiceDispatcher(): Unbind Error!", e); } return null; } } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/PluginServiceServerFetcher.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.service; import android.os.IBinder; import com.qihoo360.i.IPluginManager; import com.qihoo360.loader2.IPluginClient; import com.qihoo360.loader2.IPluginHost; import com.qihoo360.loader2.MP; import com.qihoo360.loader2.PluginBinderInfo; import com.qihoo360.loader2.PluginProcessMain; import com.qihoo360.replugin.utils.basic.ArrayMap; import com.qihoo360.replugin.component.service.server.IPluginServiceServer; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 用来获取PluginServiceServer服务的类 * * @author RePlugin Team */ public class PluginServiceServerFetcher { private ArrayMap mServiceManagerByProcessMap = new ArrayMap<>(); private static final byte[] PSS_LOCKER = new byte[0]; public IPluginServiceServer fetchByProcess(int process) { if (process == PluginServiceClient.PROCESS_UNKNOWN) { return null; } // 取之前的缓存 IPluginServiceServer pss; synchronized (PSS_LOCKER) { pss = mServiceManagerByProcessMap.get(process); if (pss != null) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginServiceClient.fsmbp(): Exists! p=" + process); } return pss; } } // 缓存没有?则去目标进程获取新的 if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginServiceClient.fsmbp(): Create a new one! p=" + process); } try { if (process == IPluginManager.PROCESS_PERSIST) { IPluginHost ph = PluginProcessMain.getPluginHost(); pss = ph.fetchServiceServer(); } else { PluginBinderInfo pbi = new PluginBinderInfo(PluginBinderInfo.NONE_REQUEST); IPluginClient pc = MP.startPluginProcess(null, process, pbi); pss = pc.fetchServiceServer(); } // 挂死亡周期,如果出问题了就置空重来,防止外界调用psm出现DeadObject问题 pss.asBinder().linkToDeath(new PSSDeathMonitor(process, pss.asBinder()), 0); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.fsm: e", e); } } if (pss != null) { synchronized (PSS_LOCKER) { mServiceManagerByProcessMap.put(process, pss); } } return pss; } // Plugin Service Server(目标进程)的死亡周期监听 private final class PSSDeathMonitor implements IBinder.DeathRecipient { PSSDeathMonitor(int process, IBinder service) { mProcess = process; mService = service; } public void binderDied() { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psc.dm: d, rm p " + mProcess); } synchronized (PSS_LOCKER) { mServiceManagerByProcessMap.remove(mProcess); } } final int mProcess; final IBinder mService; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/ServiceDispatcher.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.service; import android.content.ComponentName; import android.content.Context; import android.content.ServiceConnection; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.util.AndroidRuntimeException; import com.qihoo360.loader2.mgr.IServiceConnection; import com.qihoo360.replugin.utils.basic.ArrayMap; import java.lang.ref.WeakReference; /** * 用来在Client端管理、调度Service的类。仅Client端可用 * 负责同目标进程(Server)进行通信,以及关联“死亡周期”,缓存等功能。 * * NOTE 类似于Android的ServiceDispatcher * * @hide 框架内部使用 * @author RePlugin Team */ final class ServiceDispatcher { private final ServiceDispatcher.InnerConnection mIServiceConnection; private final ServiceConnection mConnection; private final Context mContext; private final Handler mActivityThread; private final ServiceConnectionLeaked mLocation; private final int mFlags; // 指定要绑定的进程 // Added by Jiongxuan Zhang private final int mProcess; private RuntimeException mUnbindLocation; // private boolean mDied; private boolean mForgotten; private static class ConnectionInfo { IBinder binder; IBinder.DeathRecipient deathMonitor; } private static class InnerConnection extends IServiceConnection.Stub { final WeakReference mDispatcher; InnerConnection(ServiceDispatcher sd) { mDispatcher = new WeakReference(sd); } public void connected(ComponentName name, IBinder service) throws RemoteException { ServiceDispatcher sd = mDispatcher.get(); if (sd != null) { sd.connected(name, service); } } } private final ArrayMap mActiveConnections = new ArrayMap<>(); ServiceDispatcher(ServiceConnection conn, Context context, Handler activityThread, int flags, int process) { mIServiceConnection = new InnerConnection(this); mConnection = conn; mContext = context; mActivityThread = activityThread; mLocation = new ServiceConnectionLeaked(null); mLocation.fillInStackTrace(); mFlags = flags; mProcess = process; } void validate(Context context, Handler activityThread) { if (mContext != context) { throw new RuntimeException( "ServiceConnection " + mConnection + " registered with differing Context (was " + mContext + " now " + context + ")"); } if (mActivityThread != activityThread) { throw new RuntimeException( "ServiceConnection " + mConnection + " registered with differing handler (was " + mActivityThread + " now " + activityThread + ")"); } } void doForget() { synchronized (this) { for (int i = 0; i < mActiveConnections.size(); i++) { ServiceDispatcher.ConnectionInfo ci = mActiveConnections.valueAt(i); ci.binder.unlinkToDeath(ci.deathMonitor, 0); } mActiveConnections.clear(); mForgotten = true; } } ServiceConnectionLeaked getLocation() { return mLocation; } ServiceConnection getServiceConnection() { return mConnection; } IServiceConnection getIServiceConnection() { return mIServiceConnection; } int getFlags() { return mFlags; } void setUnbindLocation(RuntimeException ex) { mUnbindLocation = ex; } RuntimeException getUnbindLocation() { return mUnbindLocation; } int getProcess() { return mProcess; } public void connected(ComponentName name, IBinder service) { if (mActivityThread != null) { mActivityThread.post(new RunConnection(name, service, 0)); } else { doConnected(name, service); } } public void death(ComponentName name, IBinder service) { ServiceDispatcher.ConnectionInfo old; synchronized (this) { // mDied = true; old = mActiveConnections.remove(name); if (old == null || old.binder != service) { // Death for someone different than who we last // reported... just ignore it. return; } old.binder.unlinkToDeath(old.deathMonitor, 0); } if (mActivityThread != null) { mActivityThread.post(new RunConnection(name, service, 1)); } else { doDeath(name, service); } } public void doConnected(ComponentName name, IBinder service) { ServiceDispatcher.ConnectionInfo old; ServiceDispatcher.ConnectionInfo info; synchronized (this) { if (mForgotten) { // We unbound before receiving the connection; ignore // any connection received. return; } old = mActiveConnections.get(name); if (old != null && old.binder == service) { // Huh, already have this one. Oh well! return; } if (service != null) { // A new service is being connected... set it all up. // mDied = false; info = new ConnectionInfo(); info.binder = service; info.deathMonitor = new DeathMonitor(name, service); try { service.linkToDeath(info.deathMonitor, 0); mActiveConnections.put(name, info); } catch (RemoteException e) { // This service was dead before we got it... just // don't do anything with it. mActiveConnections.remove(name); return; } } else { // The named service is being disconnected... clean up. mActiveConnections.remove(name); } if (old != null) { old.binder.unlinkToDeath(old.deathMonitor, 0); } } // If there was an old service, it is not disconnected. if (old != null) { mConnection.onServiceDisconnected(name); } // If there is a new service, it is now connected. if (service != null) { mConnection.onServiceConnected(name, service); } } public void doDeath(ComponentName name, IBinder service) { mConnection.onServiceDisconnected(name); } private final class RunConnection implements Runnable { RunConnection(ComponentName name, IBinder service, int command) { mName = name; mService = service; mCommand = command; } public void run() { if (mCommand == 0) { doConnected(mName, mService); } else if (mCommand == 1) { doDeath(mName, mService); } } final ComponentName mName; final IBinder mService; final int mCommand; } private final class DeathMonitor implements IBinder.DeathRecipient { DeathMonitor(ComponentName name, IBinder service) { mName = name; mService = service; } public void binderDied() { death(mName, mService); } final ComponentName mName; final IBinder mService; } } final class ServiceConnectionLeaked extends AndroidRuntimeException { public ServiceConnectionLeaked(String msg) { super(msg); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/ConnectionBindRecord.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.service.server; import android.content.Context; import com.qihoo360.loader2.mgr.IServiceConnection; /** * 用来表示一个BindService的连接 * 存储了IServiceConnection对象,被其它几个Record引用 * * @author RePlugin Team */ class ConnectionBindRecord { final ProcessBindRecord binding; // 谁绑定了这个链接? final IServiceConnection conn; // IServiceConnection(ServiceConnection for Client)对象 final int flags; // 绑定的Flags boolean serviceDead; // 连接即将被unbind,标记此开关是防止重复unbind private String stringName; ConnectionBindRecord(ProcessBindRecord abr, IServiceConnection sc, int f) { binding = abr; conn = sc; flags = f; } public String toString() { if (stringName != null) { return stringName; } StringBuilder sb = new StringBuilder(128); sb.append("ConnectionBindRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" p"); sb.append(binding.client.pid); sb.append(' '); if ((flags & Context.BIND_AUTO_CREATE) != 0) { sb.append("CR "); } if ((flags & Context.BIND_DEBUG_UNBIND) != 0) { sb.append("DBG "); } if ((flags & Context.BIND_NOT_FOREGROUND) != 0) { sb.append("!FG "); } if ((flags & Context.BIND_ABOVE_CLIENT) != 0) { sb.append("ABCLT "); } if ((flags & Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) { sb.append("OOM "); } if ((flags & Context.BIND_WAIVE_PRIORITY) != 0) { sb.append("WPRI "); } if ((flags & Context.BIND_IMPORTANT) != 0) { sb.append("IMP "); } if ((flags & Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { sb.append("WACT "); } if (serviceDead) { sb.append("DEAD "); } sb.append(binding.service.shortName); sb.append(":@"); sb.append(Integer.toHexString(System.identityHashCode(conn.asBinder()))); sb.append('}'); stringName = sb.toString(); return stringName; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/IntentBindRecord.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.service.server; import android.content.Context; import android.content.Intent; import android.os.IBinder; import com.qihoo360.replugin.utils.basic.ArrayMap; import com.qihoo360.replugin.utils.basic.ArraySet; /** * 用来表示每个Intent所代表的绑定信息 * 一个Intent可以被多个Process(Client)绑定,但只调用一次onBind方法。像系统那样 * * @author RePlugin Team */ class IntentBindRecord { /** * Intent对应的Service信息 */ final ServiceRecord service; /** * Intent信息 */ final Intent.FilterComparison intent; /** * 哪些进程(Client)的Intent,绑定了这些服务 */ final ArrayMap apps = new ArrayMap<>(); /** * onBind返回的方法 */ IBinder binder; /** * 是否已绑定 */ boolean hasBound; String stringName; IntentBindRecord(ServiceRecord service, Intent.FilterComparison intent) { this.service = service; this.intent = intent; } int collectFlags() { int flags = 0; for (int i = apps.size() - 1; i >= 0; i--) { final ArraySet connections = apps.valueAt(i).connections; for (int j = connections.size() - 1; j >= 0; j--) { flags |= connections.valueAt(j).flags; } } return flags; } @Override public String toString() { if (stringName != null) { return stringName; } StringBuilder sb = new StringBuilder(128); sb.append("IntentBindRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); if ((collectFlags() & Context.BIND_AUTO_CREATE) != 0) { sb.append("CR "); } sb.append(service.shortName); sb.append(':'); if (intent != null) { sb.append(intent.getIntent().toString()); } // 添加Process记录信息 sb.append(':'); if (apps.size() > 0) { sb.append(apps.toString()); } sb.append('}'); stringName = sb.toString(); return stringName; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/PluginPitService.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.service.server; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.IBinder; import com.qihoo360.i.IPluginManager; import java.lang.ref.WeakReference; /** * 表示一个“坑位”服务。一个进程放着一个坑位,且不做什么实际意义的事情

* 注意:

* 1、不能直接在AndroidManifest.xml中注册,而是通过RePluginClassLoader做中转

* 2、仅为防止进程杀掉,以及获取共用Token而设计。其余均在PluginServiceServer中实现 * * @author RePlugin Team */ public class PluginPitService extends Service { private static WeakReference sService; public PluginPitService() { sService = new WeakReference<>(this); } @Override public IBinder onBind(Intent intent) { return null; } public static PluginPitService get() { return sService.get(); } /** * 创建一个可供使用的ComponentName * * @param process Process代号(注意,不是ProcessID) * @return 一个新的CN对象 */ public static ComponentName makeComponentName(Context c, int process) { String key = c.getPackageName(); String prefix = PluginPitService.class.getName(); String value; if (process == IPluginManager.PROCESS_UI) { value = prefix + "UI"; } else if (process == IPluginManager.PROCESS_PERSIST) { value = prefix + "Guard"; } else { value = prefix + "P" + (100 + process); // TODO 因为process可能是负数,所以这里需要+100,将来会优化这里 } return new ComponentName(key, value); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/PluginServiceServer.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.service.server; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.util.Log; import com.qihoo360.i.Factory; import com.qihoo360.loader2.mgr.IServiceConnection; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.base.ThreadUtils; import com.qihoo360.replugin.component.ComponentList; import com.qihoo360.replugin.component.utils.PluginClientHelper; import com.qihoo360.replugin.helper.JSONHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.utils.basic.ArrayMap; import org.json.JSONArray; import org.json.JSONObject; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Map; import java.util.concurrent.Callable; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 负责Server端的服务调度、提供等工作,是服务的提供方,核心类之一 * * @author RePlugin Team */ public class PluginServiceServer { private static final String TAG = "PluginServiceServer"; private static final byte[] LOCKER = new byte[0]; /** * Service onStartCommand */ private static final int WHAT_ON_START_COMMAND = 1; /** * Service onCreate */ private static final int WHAT_ON_CREATE = 2; /** * Service onCreate onBind */ private static final int WHAT_ON_CREATE_AND_BIND = 3; private final Context mContext; private final Stub mStub; private Method mAttachBaseContextMethod; /** * PID -> ProcessRecord对象 */ final ArrayMap mProcesses = new ArrayMap<>(); /** * K:IServiceConnection(ServiceConnect)对象 * V:此SC旗下的所有Binder连接。有可能一个IServiceConnection就连接了多个服务 */ final ArrayMap> mServiceConnections = new ArrayMap<>(); private final ArrayMap mServicesByName = new ArrayMap<>(); private final ArrayMap mServicesByIntent = new ArrayMap<>(); private Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); int what = msg.what; try { if (what == WHAT_ON_CREATE_AND_BIND){ if (msg.obj == null || !(msg.obj instanceof OnCreateAndBindMsg)){ if (LOG) { LogDebug.e(PLUGIN_TAG, handlerErrorMsg(what)); } return; } handleOnCreateAndBind((OnCreateAndBindMsg) msg.obj); return; } if (what != WHAT_ON_CREATE && what != WHAT_ON_START_COMMAND){ return; } Bundle data = msg.getData(); if (data == null){ if (LOG) { LogDebug.e(PLUGIN_TAG, handlerErrorMsg(what)); } return; } Intent intent = data.getParcelable("intent"); if (intent == null){ if (LOG) { LogDebug.e(PLUGIN_TAG, handlerErrorMsg(what)); } return; } ServiceRecord sr = (ServiceRecord)msg.obj; if (sr == null){ if (LOG) { LogDebug.e(PLUGIN_TAG, handlerErrorMsg(what)); } return; } switch (msg.what) { case WHAT_ON_CREATE: handleOnCreate(intent, sr); break; case WHAT_ON_START_COMMAND: intent.setExtrasClassLoader(sr.service.getClassLoader()); sr.service.onStartCommand(intent, 0, 0); break; } }catch (Exception e){ if (LOG) { LogDebug.e(PLUGIN_TAG, handlerErrorMsg(what), e); } } } }; private String handlerErrorMsg(int what){ switch (what){ case WHAT_ON_CREATE: return "pss.onStartCommand fail."; case WHAT_ON_START_COMMAND: return "pss.onCreate fail."; case WHAT_ON_CREATE_AND_BIND: return "pss.onCreate pss.onBind fail"; } return ""; } public PluginServiceServer(Context context) { mContext = context; mStub = new Stub(); } // 启动插件Service。说明见PluginServiceClient的定义 ComponentName startServiceLocked(Intent intent, Messenger client) { intent = cloneIntentLocked(intent); ComponentName cn = intent.getComponent(); // ProcessRecord callerPr = retrieveProcessRecordLocked(client); final ServiceRecord sr = retrieveServiceLocked(intent); if (sr == null) { return null; } if (!handleOnCreate(intent, sr)){ return null; } return cn; } private boolean handleOnCreate(Intent intent, ServiceRecord sr){ if (Looper.myLooper() != Looper.getMainLooper()){ if (sr.service == null){ sendStartServiceMsg(intent, sr, WHAT_ON_CREATE); return true; } handleOnStartCommand(intent, sr); return true; } if (!installServiceIfNeededLocked(sr)) { return false; } handleOnStartCommand(intent, sr); return true; } private void handleOnStartCommand(Intent intent, ServiceRecord sr){ ComponentName cn = intent.getComponent(); sr.startRequested = true; // 加入到列表中,统一管理 mServicesByName.put(cn, sr); if (LOG) { LogDebug.i(PLUGIN_TAG, "PSM.startService(): Start! in=" + intent + "; sr=" + sr); } sendStartServiceMsg(intent, sr, WHAT_ON_START_COMMAND); } private void sendStartServiceMsg(Intent intent, ServiceRecord sr, int what){ // 从binder线程post到ui线程,去执行Service的onCreate, onStartCommand操作 Message message = mHandler.obtainMessage(what); Bundle data = new Bundle(); data.putParcelable("intent", intent); message.setData(data); message.obj = sr; mHandler.sendMessage(message); } // 停止插件的Service。说明见PluginServiceClient的定义 int stopServiceLocked(Intent intent) { intent = cloneIntentLocked(intent); ServiceRecord sr = getServiceLocked(intent); if (sr == null) { return 0; } sr.startRequested = false; recycleServiceIfNeededLocked(sr); if (LOG) { LogDebug.i(PLUGIN_TAG, "PSM.stopService(): Stop! in=" + intent + "; sr=" + sr); } return 1; } // 绑定插件Service。说明见PluginServiceClient的定义 int bindServiceLocked(Intent intent, IServiceConnection connection, int flags, Messenger client) { OnCreateAndBindMsg msg = new OnCreateAndBindMsg(); msg.intent = cloneIntentLocked(intent); msg.cn = intent.getComponent(); msg.callerPr = retrieveProcessRecordLocked(client); msg.sr = retrieveServiceLocked(intent); msg.connection = connection; msg.flags = flags; if (msg.sr == null) { return 0; } if (!handleOnCreateAndBind(msg)) { return 0; } return 1; } private boolean handleOnCreateAndBind(OnCreateAndBindMsg msg){ if (msg == null){ return false; } if (Looper.myLooper() != Looper.getMainLooper()){ if (msg.sr.service == null){ Message message = mHandler.obtainMessage(WHAT_ON_CREATE_AND_BIND); message.obj = msg; mHandler.sendMessage(message); return true; } handleOnBind(msg); return true; } if (!installServiceIfNeededLocked(msg.sr)) { return false; } handleOnBind(msg); return true; } private void handleOnBind(OnCreateAndBindMsg msg){ // 将ServiceConnection连接加入各种表中 ProcessBindRecord b = msg.sr.retrieveAppBindingLocked(msg.intent, msg.callerPr); insertConnectionToRecords(msg.sr, b, msg.connection, msg.flags); // 判断是否已经绑定过 if (b.intent.hasBound) { // 之前此Intent已绑定过,则直接返回。像系统那样 // 注意:不管哪个进程,只要第一次绑定过了,其后直接返回。像系统那样 callConnectedMethodLocked(msg.connection, msg.cn, b.intent.binder); } else { // 没有绑定,则直接调用onBind,且记录绑定状态 if (b.intent.apps.size() > 0) { IBinder bd = msg.sr.service.onBind(msg.intent); b.intent.hasBound = true; b.intent.binder = bd; if (bd != null) { // 为空就不会回调,但仍算绑定成功。像系统那样 callConnectedMethodLocked(msg.connection, msg.cn, bd); } } } if (LOG) { LogDebug.i(PLUGIN_TAG, "PSM.bindService(): Bind! inb=" + b + "; fl=" + msg.flags + "; sr=" + msg.sr); } } private void insertConnectionToRecords(ServiceRecord sr, ProcessBindRecord b, IServiceConnection connection, int flags) { ConnectionBindRecord c = new ConnectionBindRecord(b, connection, flags); IBinder binder = connection.asBinder(); // ServiceRecord.connections ArrayList clist = sr.connections.get(binder); if (clist == null) { clist = new ArrayList<>(); sr.connections.put(binder, clist); } clist.add(c); // ProcessBindRecord.connections b.connections.add(c); // ProcessRecord.connections b.client.connections.add(c); // PluginServiceServer.mServiceConnections clist = mServiceConnections.get(binder); if (clist == null) { clist = new ArrayList<>(); mServiceConnections.put(binder, clist); } clist.add(c); } // 取消插件Service的绑定。说明见PluginServiceClient的定义 boolean unbindServiceLocked(IServiceConnection connection) { // ServiceConnection可以绑定多个服务,这次需逐一解绑 IBinder binder = connection.asBinder(); ArrayList clist = mServiceConnections.get(binder); if (clist == null) { if (LOG) { LogDebug.i(PLUGIN_TAG, "PSM.unbindService(): clist is null!"); } return false; } while (clist.size() > 0) { ConnectionBindRecord r = clist.get(0); removeConnectionLocked(r); if (clist.size() > 0 && clist.get(0) == r) { // In case it didn't get removed above, do it now. clist.remove(0); } } return true; } // 将此Connection(ServiceConnection)从各种表中清除,并按需调用onUnbind方法 // NOTE 有点像ActiveServices.removeConnectionLocked,但在处理逻辑上有不同 private void removeConnectionLocked(ConnectionBindRecord c) { IBinder binder = c.conn.asBinder(); ProcessBindRecord b = c.binding; ServiceRecord s = b.service; // ServiceRecord.connections ArrayList clist = s.connections.get(binder); if (clist != null) { clist.remove(c); if (clist.size() == 0) { s.connections.remove(binder); } } // ProcessBindRecord.connections b.connections.remove(c); // ProcessRecord.connections b.client.connections.remove(c); // PluginServiceServer.mServiceConnections clist = mServiceConnections.get(binder); if (clist != null) { clist.remove(c); if (clist.size() == 0) { mServiceConnections.remove(binder); } } // 若所有BindConnection都已不再连接,则清除其Map if (b.connections.size() == 0) { b.intent.apps.remove(b.client); } // 之前已经打算解绑了?无需再做 if (c.serviceDead) { return; } // 当所有应用都已解绑后,则直接调用onUnbind if (b.intent.apps.size() == 0 && b.intent.hasBound) { b.intent.hasBound = false; s.service.onUnbind(b.intent.intent.getIntent()); if (LOG) { LogDebug.i(PLUGIN_TAG, "PSM.removeConnectionLocked(): boundRef is 0, call onUnbind(), sr=" + s); } // 尝试释放Service对象 if ((c.flags & Context.BIND_AUTO_CREATE) != 0) { recycleServiceIfNeededLocked(s); } } else { if (LOG) { LogDebug.i(PLUGIN_TAG, "PSM.removeConnectionLocked(): Not unbind, sr=" + s); } } } public IPluginServiceServer getService() { return mStub; } // 若Client和Server在同一进程,则两者的intent对象完全相同 // 换言之,如果Client端修改了intent对象,则对应的,server端也会被修改,这不符合预期 // 故,所有的Intent操作都必须Clone一份 private Intent cloneIntentLocked(Intent intent) { return new Intent(intent); } // 通过Intent获取ServiceRecord服务,如无则直接返回Null private ServiceRecord getServiceLocked(Intent service) { ComponentName cn = service.getComponent(); return mServicesByName.get(cn); } // 通过Intent对象创建或获取ServiceRecord服务。涉及到插件信息的获取 private ServiceRecord retrieveServiceLocked(Intent service) { ComponentName cn = service.getComponent(); ServiceRecord sr = mServicesByName.get(cn); if (sr != null) { return sr; } Intent.FilterComparison fi = new Intent.FilterComparison(service); sr = mServicesByIntent.get(fi); if (sr != null) { return sr; } String pn = cn.getPackageName(); String name = cn.getClassName(); // 看这个Plugin是否可以被打开 if (!RePlugin.isPluginInstalled(pn)) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psm.is: p n ex " + name); } return null; } // 开始尝试获取插件的ServiceInfo ComponentList col = Factory.queryPluginComponentList(pn); if (col == null) { if (LOG) { Log.e(TAG, "installServiceLocked(): Fetch Component List Error! pn=" + pn); } return null; } ServiceInfo si = col.getService(cn.getClassName()); if (si == null) { if (LOG) { Log.e(TAG, "installServiceLocked(): Not register! pn=" + pn); } return null; } // 构建,放入表中 sr = new ServiceRecord(cn, fi, si); mServicesByName.put(cn, sr); mServicesByIntent.put(fi, sr); return sr; } // 判断是否已加载过Service对象,如无则创建它 private boolean installServiceIfNeededLocked(final ServiceRecord sr) { if (sr.service != null) { return true; } try { Boolean result = ThreadUtils.syncToMainThread(new Callable() { @Override public Boolean call() { return installServiceLocked(sr); } }, 6000); if (result == null) { return false; } return result; } catch (Throwable e) { if (LOG) { LogDebug.e(PLUGIN_TAG, "pss.isinl e:", e); } return false; } } // 加载插件,获取Service对象,并将其缓存起来 private boolean installServiceLocked(ServiceRecord sr) { // 通过ServiceInfo创建Service对象 Context plgc = Factory.queryPluginContext(sr.plugin); if (plgc == null) { if (LogDebug.LOG) { Log.e(TAG, "installServiceLocked(): Fetch Context Error! pn=" + sr.plugin); } return false; } ClassLoader cl = plgc.getClassLoader(); if (cl == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psm.is: cl n " + sr.className); } return false; } // 构建Service对象 Service s; try { s = (Service) cl.loadClass(sr.serviceInfo.name).newInstance(); } catch (Throwable e) { if (LOGR) { LogRelease.e(TAG, "isl: ni f " + sr.plugin, e); } return false; } // 只复写Context,别的都不做 try { attachBaseContextLocked(s, plgc); } catch (Throwable e) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "psm.is: abc e", e); } return false; } s.onCreate(); sr.service = s; // 开启“坑位”服务,防止进程被杀 ComponentName pitCN = getPitComponentName(); sr.pitComponentName = pitCN; startPitService(pitCN); return true; } // 最终会调用Client端的ServiceConnection private void callConnectedMethodLocked(IServiceConnection conn, ComponentName cn, IBinder b) { try { conn.connected(cn, b); } catch (RemoteException e) { if (BuildConfig.DEBUG) { e.printStackTrace(); } } } // 判断是否绑定或启动服务,根据情况来释放Service // NOTE 有点像ActiveServices.bringDownServiceIfNeededLocked,但在处理逻辑上有不同 private void recycleServiceIfNeededLocked(ServiceRecord sr) { // 是否已被startService开启? if (sr.startRequested) { if (LOG) { LogDebug.i(PLUGIN_TAG, "PSM.recycleServiceIfNeededLocked(): Not Recycle because startRequested is true! sr=" + sr); } return; } // 服务是否有被绑定的? boolean hasConn = sr.hasAutoCreateConnections(); if (hasConn) { if (LOG) { LogDebug.i(PLUGIN_TAG, "PSM.recycleServiceIfNeededLocked(): Not Recycle because bindingCount > 0! sr=" + sr); } return; } // 开始回收服务 recycleServiceLocked(sr); } // 释放Service的全部资源,调用onDestroy方法,并停止“坑位服务” // NOTE 有点像ActiveServices.bringDownServiceLocked,但在处理逻辑上有不同 private void recycleServiceLocked(ServiceRecord r) { if (LOG) { LogDebug.i(PLUGIN_TAG, "PSM.recycleServiceLocked(): Recycle Now!"); } // Report to all of the connections that the service is no longer // available. for (int conni = r.connections.size() - 1; conni >= 0; conni--) { ArrayList c = r.connections.valueAt(conni); for (int i = 0; i < c.size(); i++) { ConnectionBindRecord cr = c.get(i); // There is still a connection to the service that is // being brought down. Mark it as dead. cr.serviceDead = true; callConnectedMethodLocked(cr.conn, r.name, null); } } mServicesByName.remove(r.name); mServicesByIntent.remove(r.intent); if (r.bindings.size() > 0) { r.bindings.clear(); } r.service.onDestroy(); // 停止“坑位”服务,系统可以根据需要来回收了 ComponentName pitCN = getPitComponentName(); r.pitComponentName = pitCN; stopPitService(pitCN); } // 通过反射调用Service.attachBaseContext方法(Protected的) private void attachBaseContextLocked(ContextWrapper cw, Context c) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException { if (mAttachBaseContextMethod == null) { mAttachBaseContextMethod = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class); mAttachBaseContextMethod.setAccessible(true); } mAttachBaseContextMethod.invoke(cw, c); // init Application Field applicationField = Service.class.getDeclaredField("mApplication"); if (applicationField != null) { applicationField.setAccessible(true); applicationField.set(cw, c.getApplicationContext()); } } class Stub extends IPluginServiceServer.Stub { @Override public ComponentName startService(Intent intent, Messenger client) throws RemoteException { synchronized (LOCKER) { return PluginServiceServer.this.startServiceLocked(intent, client); } } @Override public int stopService(Intent intent, Messenger client) throws RemoteException { synchronized (LOCKER) { return PluginServiceServer.this.stopServiceLocked(intent); } } @Override public int bindService(Intent intent, IServiceConnection conn, int flags, Messenger client) throws RemoteException { synchronized (LOCKER) { return PluginServiceServer.this.bindServiceLocked(intent, conn, flags, client); } } @Override public boolean unbindService(IServiceConnection conn) throws RemoteException { synchronized (LOCKER) { return PluginServiceServer.this.unbindServiceLocked(conn); } } @Override public String dump() throws RemoteException { synchronized (LOCKER) { return PluginServiceServer.this.dump(); } } } // 通过Client端传来的IBinder(Messenger)来获取Pid,以及进程信息 private ProcessRecord retrieveProcessRecordLocked(Messenger client) { int callerPid = Binder.getCallingPid(); ProcessRecord pr = mProcesses.get(callerPid); if (pr == null) { pr = new ProcessRecord(callerPid, client); mProcesses.put(callerPid, pr); } return pr; } // 构建一个占坑服务 private ComponentName getPitComponentName() { String pname = IPC.getCurrentProcessName(); int process = PluginClientHelper.getProcessInt(pname); return PluginPitService.makeComponentName(mContext, process); } // 开启“坑位”服务,防止进程被杀 private void startPitService(ComponentName pitCN) { // TODO 其实,有一种更好的办法……敬请期待 if (LOG) { LogDebug.d(TAG, "startPitService: Start " + pitCN); } Intent intent = new Intent(); intent.setComponent(pitCN); try { mContext.startService(intent); } catch (Exception e) { // 就算AMS出了问题(如system_server挂了,概率极低,和低配ROM有关),最多也就是服务容易被系统回收,但不能让它“不干活” e.printStackTrace(); } } // 停止“坑位”服务,系统可以根据需要来回收了 private void stopPitService(ComponentName pitCN) { if (LOG) { LogDebug.d(TAG, "stopPitService: Stop " + pitCN); } Intent intent = new Intent(); intent.setComponent(pitCN); try { mContext.stopService(intent); } catch (Exception e) { // 就算AMS出了问题(如system_server挂了,概率极低,和低配ROM有关),最多也就是服务容易被系统回收,但不能让它“不干活” e.printStackTrace(); } } /** * dump当前进程中运行的service详细信息,供client端使用 * * @return */ private String dump() { if (mServicesByName == null || mServicesByName.isEmpty()) { return null; } JSONArray jsonArray = new JSONArray(); JSONObject serviceObj; for (Map.Entry entry : mServicesByName.entrySet()) { ComponentName key = entry.getKey(); ServiceRecord value = entry.getValue(); serviceObj = new JSONObject(); JSONHelper.putNoThrows(serviceObj, "className", key.getClassName()); JSONHelper.putNoThrows(serviceObj, "process", value.getServiceInfo().processName); JSONHelper.putNoThrows(serviceObj, "plugin", value.getPlugin()); JSONHelper.putNoThrows(serviceObj, "pitClassName", value.getPitComponentName().getClassName()); jsonArray.put(serviceObj); } return jsonArray.toString(); } private static class OnCreateAndBindMsg{ private Intent intent; private IServiceConnection connection; private int flags; private ServiceRecord sr; private ComponentName cn; private ProcessRecord callerPr; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/ProcessBindRecord.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.service.server; import com.qihoo360.replugin.utils.basic.ArraySet; /** * 用来表示每个进程(Client端)与Service的绑定关系 * * NOTE 类似于Android的AppBindRecord * * @author RePlugin Team */ class ProcessBindRecord { // 被绑定的Service信息 final ServiceRecord service; // 绑定此Service所用的Intent信息 final IntentBindRecord intent; // 哪个进程(Client)建立了绑定关系 final ProcessRecord client; // 在此Service、此进程、此Intent下面的所有连接 final ArraySet connections = new ArraySet<>(); ProcessBindRecord(ServiceRecord service, IntentBindRecord intent, ProcessRecord client) { this.service = service; this.intent = intent; this.client = client; } public String toString() { return "ProcessBindRecord{" + Integer.toHexString(System.identityHashCode(this)) + " " + service.shortName + ":" + client.pid + "}"; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/ProcessRecord.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.service.server; import android.os.Messenger; import java.util.ArrayList; /** * 表示一个Client信息。每个Client是一个进程 * * NOTE 类似于Android的ProcessRecord * * @author RePlugin Team */ class ProcessRecord { // 该Client进程的PID final int pid; // 该Client传递而来的IBinder(Messenger)对象 final Messenger client; // Client进程绑定到此(Server)进程的所有连接信息 final ArrayList connections = new ArrayList<>(); private String stringName; ProcessRecord(int p, Messenger c) { pid = p; client = c; } public String toString() { if (stringName != null) { return stringName; } StringBuilder sb = new StringBuilder(128); sb.append("ProcessRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" p"); sb.append(pid); sb.append('}'); stringName = sb.toString(); return stringName; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/service/server/ServiceRecord.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.service.server; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ServiceInfo; import android.os.IBinder; import com.qihoo360.replugin.utils.basic.ArrayMap; import java.util.ArrayList; /** * 用来表示一个Service对象的信息 * 包含绑定此Service的连接信息、Intent绑定信息等 *

* NOTE 类似于Android的ServiceRecord * * @author RePlugin Team */ class ServiceRecord { // Service的ComponentName final ComponentName name; // 插件名 final String plugin; // Service类名 final String className; // Intent过滤器,方便直接获取Service对象 final Intent.FilterComparison intent; // 可用来创建Service的ServiceInfo对象 final ServiceInfo serviceInfo; // Service对象 Service service; // 替当前 "插件服务" 在AMS中占坑的组件 ComponentName pitComponentName; // 是否调用过startService且没有停止 boolean startRequested; // 每个Intent对应一个IntentBindRecord缓存 final ArrayMap bindings = new ArrayMap<>(); // 每个IBinder(IServiceConnection)对应一个连接信息的缓存 final ArrayMap> connections = new ArrayMap<>(); final String shortName; ServiceRecord(ComponentName cn, Intent.FilterComparison fi, ServiceInfo si) { name = cn; plugin = cn.getPackageName(); className = cn.getClassName(); shortName = name.flattenToShortString(); intent = fi; serviceInfo = si; } public ProcessBindRecord retrieveAppBindingLocked(Intent intent, ProcessRecord app) { Intent.FilterComparison filter = new Intent.FilterComparison(intent); IntentBindRecord i = bindings.get(filter); if (i == null) { i = new IntentBindRecord(this, filter); bindings.put(filter, i); } ProcessBindRecord a = i.apps.get(app); if (a != null) { return a; } a = new ProcessBindRecord(this, i, app); i.apps.put(app, a); return a; } // 是否有Flag为AUTO_CREATE的绑定链接 public boolean hasAutoCreateConnections() { // XXX should probably keep a count of the number of auto-create // connections directly in the service. for (int conni = connections.size() - 1; conni >= 0; conni--) { ArrayList cr = connections.valueAt(conni); for (int i = 0; i < cr.size(); i++) { if ((cr.get(i).flags & Context.BIND_AUTO_CREATE) != 0) { return true; } } } return false; } @Override public String toString() { return "[srv=" + service == null ? "null" : service.getClass().getName() + "; startRequested=" + startRequested + "; bindings=(" + bindings.size() + ") " + bindings + "]"; } public String getPlugin() { return plugin; } public ComponentName getPitComponentName() { return pitComponentName; } public ServiceInfo getServiceInfo() { return serviceInfo; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/utils/ApkCommentReader.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.utils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Arrays; import java.util.zip.GZIPInputStream; /** * ApkCommentReader *

* 读取 apk 文件的 comment。 * apk 的 comment 段为【内容(content)+内容长度(LITTLE_ENDIAN两字节)+魔法标记(MAGIC)】, * * @author RePlugin Team */ public class ApkCommentReader { /** * 魔法标记 */ private static final byte[] MAGIC = new byte[]{0x28, 0x4d, 0x53, 0x2d, 0x50, 0x4c, 0x47, 0x29}; // (MS-PLG) /** * 读取 apk 的注释 * * @param path apk 文件路径 * @return comment 内容 */ public static String readComment(String path) { RandomAccessFile raf = null; try { raf = new RandomAccessFile(path, "r"); return decompress(getComment(raf)); } catch (IOException e) { e.printStackTrace(); } finally { if (raf != null) { try { raf.close(); } catch (IOException e) { e.printStackTrace(); } } } return ""; } /** * 获取解压前的注释内容 */ private static byte[] getComment(RandomAccessFile raf) { if (null == raf) { return null; } try { /* 判断文件结尾是否具有魔法标记,没有就直接返回 */ long index = raf.length(); index -= MAGIC.length; raf.seek(index); byte[] magicBuffer = new byte[MAGIC.length]; raf.readFully(magicBuffer); if (!Arrays.equals(magicBuffer, MAGIC)) { return null; } /* 读取内容, 直接滑动文件索引到读取“内容长度”位置,长度和 zip 定义一致,为2字节 */ index -= 2; raf.seek(index); byte[] contentLen = new byte[2]; raf.readFully(contentLen); // 2byte 转无符号 short int length = (contentLen[1] << 8 & 0xFF00) | (contentLen[0] & 0xFF); if (length > 0) { index -= length; raf.seek(index); byte[] bytes = new byte[length]; raf.readFully(bytes); return bytes; } } catch (IOException e) { e.printStackTrace(); } return null; } /** * 对读取到了字节数据进行是解压,得到最终注释内容 */ private static String decompress(byte[] str) { if (str == null) { return ""; } ByteArrayOutputStream out = null; try { out = new ByteArrayOutputStream(); ByteArrayInputStream bis = new ByteArrayInputStream(str); GZIPInputStream gzip = new GZIPInputStream(bis); byte[] buffer = new byte[256]; int n; while ((n = gzip.read(buffer)) >= 0) { out.write(buffer, 0, n); } return out.toString("utf-8"); } catch (IOException e) { e.printStackTrace(); } finally { if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } return ""; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/utils/IntentMatcherHelper.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.utils; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.net.Uri; import com.qihoo360.i.Factory; import com.qihoo360.mobilesafe.parser.manifest.ManifestParser; import com.qihoo360.replugin.helper.LogDebug; import java.util.List; import java.util.Map; import java.util.Set; import static com.qihoo360.mobilesafe.parser.manifest.ManifestParser.TAG; import static com.qihoo360.replugin.helper.LogDebug.LOG; /** * 和Intent匹配有关的类 * * @author RePlugin Team */ public class IntentMatcherHelper { /** * 根据 Intent 以及 plugin 匹配 Activity *

* 遍历plugin插件中,保存的 Activity 的 IntentFilter 数据,进行匹配, * 返回第一个符合条件的 ActivityInfo 对象. * * @param context Context * @param plugin 插件名称 * @param intent 调用方传来的 Intent * @return 匹配到的 ActivityInfo 和 插件名称 */ public static ActivityInfo getActivityInfo(Context context, String plugin, Intent intent) { if (plugin == null) { return null; } String activity = doMatchIntent(context, intent, ManifestParser.INS.getActivityFilterMap(plugin)); return Factory.queryActivityInfo(plugin, activity); } /** * 根据 Intent 匹配组件 * * @param context Context * @param intent 调用方传来的 Intent * @param filtersMap 插件中声明的所有组件和 IntentFilter * @return ComponentInfo */ public static String doMatchIntent(Context context, Intent intent, Map> filtersMap) { if (filtersMap == null) { return null; } final String action = intent.getAction(); final String type = intent.resolveTypeIfNeeded(context.getContentResolver()); final Uri data = intent.getData(); final String scheme = intent.getScheme(); final Set categories = intent.getCategories(); for (Map.Entry> entry : filtersMap.entrySet()) { String pluginName = entry.getKey(); List filters = entry.getValue(); if (filters == null) { continue; } for (IntentFilter filter : filters) { int match = filter.match(action, type, scheme, data, categories, "ComponentList"); if (match >= 0) { if (LOG) { LogDebug.d(TAG, "IntentFilter 匹配成功: " + entry.getKey()); } return pluginName; } else { if (LOG) { String reason; switch (match) { case IntentFilter.NO_MATCH_ACTION: reason = "action"; break; case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break; case IntentFilter.NO_MATCH_DATA: reason = "data"; break; case IntentFilter.NO_MATCH_TYPE: reason = "type"; break; default: reason = "unknown reason"; break; } LogDebug.d(TAG, " Filter did not match: " + reason); } } } } return ""; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/component/utils/PluginClientHelper.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.component.utils; import android.content.ComponentName; import android.content.Context; import android.content.pm.ComponentInfo; import android.text.TextUtils; import com.qihoo360.i.Factory; import com.qihoo360.i.IPluginManager; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.component.process.PluginProcessHost; import com.qihoo360.replugin.helper.HostConfigHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import static com.qihoo360.replugin.component.process.PluginProcessHost.PROCESS_INT_MAP; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 和插件Client有关的帮助类 * * @author RePlugin Team */ public class PluginClientHelper { /** * 根据Context所带的插件信息,来填充Intent的ComponentName对象 * 若外界将Context.xxxService方法用PluginServiceClient代替(如编译工具所做的),则这里会做如下事情: * 1、将外界传递的Intent为com.qihoo360.mobilesafe的包名,变成一个特定插件的包名 * 2、这样交给外界的时候,其ComponentName已变成“插件名-类名”的形式,可以做下一步处理 * 3、若其后处理失败,则仍会调用系统的相应方法(但不是该函数的职责) * * @param c 根据Context内容来决定如何填充Intent,此为那个Context对象 * @param from ComponentName * @return 新的ComponentName。或者如果插件有问题,则返回from */ public static ComponentName getComponentNameByContext(Context c, ComponentName from) { if (from == null) { // 如果Intent里面没有带ComponentName,则我们不支持此特性,直接返回null // 外界会直接调用其系统的对应方法 return null; } String appPackage = IPC.getPackageName(); if (!TextUtils.equals(from.getPackageName(), appPackage)) { // 自己已填好了要使用的插件名(作为CN的Key),这里不做处理 return from; } // 根据Context的ClassLoader来看到底属于哪个插件,还是只是主程序 ClassLoader cl = c.getClassLoader(); String pn = Factory.fetchPluginName(cl); if (TextUtils.isEmpty(pn)) { // 获得了无效的插件信息,这种情况很少见,故打出错误信息,什么也不做 if (LOGR) { LogRelease.e(PLUGIN_TAG, "pch.iibc: pn is n. n=" + from); } } else if (TextUtils.equals(pn, RePlugin.PLUGIN_NAME_MAIN)) { // 此Context属于主工程,则也什么都不做。稍后会直接走“主程序的Context”来做处理 if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginClientHelper.iibc(): Call Main! n=" + from); } } else { // 将Context所在的插件名写入CN中,待后面的方法去处理 if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginClientHelper.iibc(): Call Plugin! n=" + from); } return new ComponentName(pn, from.getClassName()); } return from; } /** * 根据进程名称获取进程ID * * @param processName 进程名称 */ public static Integer getProcessInt(String processName) { if (!TextUtils.isEmpty(processName)) { // 插件若想将组件定义成在"常驻进程"中运行,则可以在android:process中定义: // 1. (推荐)":GuardService"。这样无论宿主的常驻进程名是什么,都会定向到"常驻进程" String pntl = processName.toLowerCase(); String ppdntl = HostConfigHelper.PERSISTENT_NAME.toLowerCase(); if (pntl.contains(ppdntl)) { return IPluginManager.PROCESS_PERSIST; } // 2. 和宿主常驻进程名相同,这样也会定向到"常驻进程",但若移植到其它宿主上则会出现问题 String ppntl = IPC.getPersistentProcessName().toLowerCase(); if (TextUtils.equals(pntl, ppntl)) { return IPluginManager.PROCESS_PERSIST; } // 3. 用户自定义进程时,从 Map 中取数据 // (根据冒号之后的名称来判断是否是自定义进程) processName = PluginProcessHost.processTail(processName.toLowerCase()); if (PROCESS_INT_MAP.containsKey(processName)) { return PROCESS_INT_MAP.get(processName); } } return IPluginManager.PROCESS_UI; } /** * 从 ComponentInfo 获取 插件名称 */ public static String getPluginName(ComponentInfo ci) { if (ci != null && ci.packageName != null) { int indexOfLastDot = ci.packageName.lastIndexOf("."); if (indexOfLastDot > 0) { return ci.packageName.substring(indexOfLastDot + 1); } } return ""; } /** * 用来告诉外界,可以直接调用系统方法来处理

* 注意:仅框架内部使用 * * @author RePlugin Team */ public static class ShouldCallSystem extends RuntimeException { private static final long serialVersionUID = -2987516993124234548L; public ShouldCallSystem() { } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/debugger/DebuggerReceivers.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.debugger; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; import android.text.TextUtils; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; /** * 用来接收一些Debug有关的Receivers,如快速安装插件等

* * @author RePlugin Team */ public class DebuggerReceivers { private static final String TAG = "DebugReceivers"; private final String BR_LOGO = ".replugin"; private final String BR_POSTFIX_INSTALL = ".install"; private final String BR_POSTFIX_INSTALL_WITH_PN = ".install_with_pn"; private final String BR_POSTFIX_UNINSTALL = ".uninstall"; private final String BR_POSTFIX_ACTIVITY = ".start_activity"; private final String PARAM_PATH = "path"; private final String PARAM_IMMEDIATELY = "immediately"; private final String PARAM_PLUGIN = "plugin"; private final String PARAM_ACTIVITY = "activity"; private String packageName; // 安装"纯APK"插件 // 举例:adb shell am broadcast -a com.qihoo360.repluginapp.replugin.install -e path [Path_In_SDCard] private String actionInstall; // 卸载"纯APK"插件 // 举例:adb shell am broadcast -a com.qihoo360.repluginapp.replugin.uninstall -e plugin [Name] private String actionUninstall; // 安装"p-n-"开头的插件 // 举例:adb shell am broadcast -a com.qihoo360.repluginapp.replugin.install_with_pn -e path [Path_In_SDCard] private String actionInstallWithPN; // 启动插件的Activity,如果activity为空,则启动默认Activity // 举例:adb shell am broadcast -a com.qihoo360.repluginapp.replugin.start_activity -e plugin [Name] -e activity [Class] private String actionStartActivity; private BroadcastReceiver sDebugerReceiver; /** * 注册一系列用于快速调试的广播 * * @param context * @return 是否注册成功 */ public boolean registerReceivers(Context context) { if (sDebugerReceiver != null) { return true; } if (null == context) { return false; } packageName = context.getPackageName(); actionInstall = packageName + BR_LOGO + BR_POSTFIX_INSTALL; actionUninstall = packageName + BR_LOGO + BR_POSTFIX_UNINSTALL; actionInstallWithPN = packageName + BR_LOGO + BR_POSTFIX_INSTALL_WITH_PN; actionStartActivity = packageName + BR_LOGO + BR_POSTFIX_ACTIVITY; sDebugerReceiver = new DebugerReceiver(); IntentFilter itf = new IntentFilter(); itf.addAction(actionInstall); itf.addAction(actionUninstall); itf.addAction(actionInstallWithPN); itf.addAction(actionStartActivity); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.registerReceiver(sDebugerReceiver, itf, Context.RECEIVER_EXPORTED); } else { context.registerReceiver(sDebugerReceiver, itf); } return true; } class DebugerReceiver extends BroadcastReceiver { /** * 安装"纯APK"插件 * * @param context * @param intent * @return 执行是否成功 */ private boolean doActionInstall(final Context context, final Intent intent) { String path = intent.getStringExtra(PARAM_PATH); String immediatelyText = intent.getStringExtra(PARAM_IMMEDIATELY); boolean immediately = false; if (TextUtils.equals(immediatelyText, "true")) { immediately = true; } onInstallByApk(path, immediately); return true; } /** * 卸载"纯APK"插件 * * @param context * @param intent * @return 执行是否成功 */ private boolean doActionUninstall(final Context context, final Intent intent) { String plugin = intent.getStringExtra(PARAM_PLUGIN); if (TextUtils.isEmpty(plugin)){ return false; } return RePlugin.uninstall(plugin); } /** * 安装"p-n-"开头的插件 * * @param context * @param intent * @return 执行是否成功 */ private boolean doActionInstallWithPN(final Context context, final Intent intent) { String path = intent.getStringExtra(PARAM_PATH); String immediatelyText = intent.getStringExtra(PARAM_IMMEDIATELY); boolean immediately = false; if (TextUtils.equals(immediatelyText, "true")) { immediately = true; } onInstallByPn(path, immediately); return true; } /** * @param context * @param intent * @return 执行是否成功 */ private boolean doActionStartActivity(final Context context, final Intent intent) { String plugin = intent.getStringExtra(PARAM_PLUGIN); if (TextUtils.isEmpty(plugin)) { return false; } String activity = intent.getStringExtra(PARAM_ACTIVITY); return onStartActivity(context, plugin, activity); } @Override public void onReceive(Context context, Intent intent) { String act = intent.getAction(); if (TextUtils.isEmpty(act)) { return; } // 只在常驻进程中处理Debug逻辑 if (!RePlugin.isCurrentPersistentProcess()) { //留待扩展其他功能,暂时直接返回 return; } if (act.equals(actionInstall)) { doActionInstall(context, intent); } else if (act.equals(actionUninstall)) { doActionUninstall(context, intent); } else if (act.equals(actionInstallWithPN)) { doActionInstallWithPN(context, intent); } else if (act.equals(actionStartActivity)) { doActionStartActivity(context, intent); } } private boolean onInstallByApk(String path, boolean immediately) { return onInstall(path, immediately); } private boolean onInstallByPn(String path, boolean imeediately) { path = RePlugin.convertToPnFile(path); if (TextUtils.isEmpty(path)) { if (LogDebug.LOG) { LogDebug.e(TAG, "onInstallByPn: Error! path=" + path); } return false; } return onInstall(path, imeediately); } private boolean onInstall(String path, boolean immediately) { PluginInfo pi = RePlugin.install(path); // Okay if (pi != null) { if (LogDebug.LOG) { LogDebug.i(TAG, "onInstall: Install Success! cur=" + RePlugin.getPluginInfo(pi.getName())); } if (immediately) { if (RePlugin.preload(pi)) { if (LogDebug.LOG) { LogDebug.i(TAG, "onInstall: Preload Success! pn=" + pi.getName()); } return true; } else { if (LogDebug.LOG) { LogDebug.e(TAG, "onInstall: Preload Error! pn=" + pi.getName()); } } } } else { if (LogDebug.LOG) { LogDebug.e(TAG, "onInstall: Install Error! path=" + path); } } return false; } private boolean onStartActivity(Context context, String plugin, String activity) { if (TextUtils.isEmpty(activity)) { //启动默认的activity Intent intent1 = new Intent(Intent.ACTION_MAIN); intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return RePlugin.startActivity(context, intent1, plugin, null); } return RePlugin.startActivity(context, RePlugin.createIntent(plugin, activity)); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/AbstractApkParser.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser; import com.qihoo360.replugin.ext.parser.parser.XmlStreamer; import com.qihoo360.replugin.ext.parser.exception.ParserException; import com.qihoo360.replugin.ext.parser.parser.BinaryXmlParser; import com.qihoo360.replugin.ext.parser.parser.XmlTranslator; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; /** * Common Apk Parser methods. * This Class is not thread-safe * * @author Liu Dong */ public abstract class AbstractApkParser implements Closeable { private static final String MANIFEST_FILE = "AndroidManifest.xml"; private String manifestXml; /** * return decoded AndroidManifest.xml * * @return decoded AndroidManifest.xml */ public String getManifestXml() throws IOException { if (this.manifestXml == null) { parseManifestXml(); } return this.manifestXml; } /** * parse manifest.xml, get manifestXml as xml text. */ private void parseManifestXml() throws IOException { XmlTranslator xmlTranslator = new XmlTranslator(); byte[] data = getFileData(MANIFEST_FILE); if (data == null) { throw new ParserException("Manifest file not found"); } transBinaryXml(data, xmlTranslator); this.manifestXml = xmlTranslator.getXml(); } /** * read file in apk into bytes */ public abstract byte[] getFileData(String path) throws IOException; private void transBinaryXml(byte[] data, XmlStreamer xmlStreamer) throws IOException { ByteBuffer buffer = ByteBuffer.wrap(data); BinaryXmlParser binaryXmlParser = new BinaryXmlParser(buffer); binaryXmlParser.setXmlStreamer(xmlStreamer); binaryXmlParser.parse(); } @Override public void close() throws IOException { } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/ApkParser.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * ApkParser and result holder. * This class is not thread-safe. * * @author dongliu */ public class ApkParser extends AbstractApkParser implements Closeable { private final ZipFile zf; public ApkParser(File apkFile) throws IOException { // create zip file cost time, use one zip file for apk parser life cycle this.zf = new ZipFile(apkFile); } public ApkParser(String filePath) throws IOException { this(new File(filePath)); } @Override public byte[] getFileData(String path) throws IOException { ZipEntry entry = zf.getEntry(path); if (entry == null) { return null; } InputStream inputStream = zf.getInputStream(entry); return toByteArray(inputStream); } @Override public void close() throws IOException { super.close(); zf.close(); } private static byte[] toByteArray(InputStream in) throws IOException { try { byte[] buf = new byte[1024 * 8]; ByteArrayOutputStream bos = null; try { bos = new ByteArrayOutputStream(); int len; while ((len = in.read(buf)) != -1) { bos.write(buf, 0, len); } return bos.toByteArray(); } finally { if (bos != null) { bos.close(); } } } finally { if (in != null) { in.close(); } } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/exception/ParserException.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.exception; /** * thrown when parse failed. * * @author dongliu */ public class ParserException extends RuntimeException { public ParserException(String msg) { super(msg); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/parser/BinaryXmlParser.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.parser; import com.qihoo360.replugin.ext.parser.struct.ChunkHeader; import com.qihoo360.replugin.ext.parser.struct.ChunkType; import com.qihoo360.replugin.ext.parser.struct.StringPool; import com.qihoo360.replugin.ext.parser.struct.StringPoolHeader; import com.qihoo360.replugin.ext.parser.struct.xml.Attribute; import com.qihoo360.replugin.ext.parser.struct.xml.Attributes; import com.qihoo360.replugin.ext.parser.struct.xml.NullHeader; import com.qihoo360.replugin.ext.parser.struct.xml.XmlHeader; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNamespaceEndTag; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNamespaceStartTag; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNodeHeader; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNodeStartTag; import com.qihoo360.replugin.ext.parser.struct.xml.XmlResourceMapHeader; import com.qihoo360.replugin.ext.parser.utils.ParseUtils; import com.qihoo360.replugin.ext.parser.exception.ParserException; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNodeEndTag; import com.qihoo360.replugin.ext.parser.utils.Buffers; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * Android Binary XML format * see http://justanapplication.wordpress.com/category/android/android-binary-xml/ * * @author dongliu */ public class BinaryXmlParser { /** * By default the data buffer Chunks is buffer little-endian byte order both at runtime and when stored buffer * files. */ private ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN; private StringPool stringPool; // some attribute name stored by resource id private String[] resourceMap; private ByteBuffer buffer; private XmlStreamer xmlStreamer; public BinaryXmlParser(ByteBuffer buffer) { this.buffer = buffer.duplicate(); this.buffer.order(byteOrder); } /** * Parse binary xml. */ public void parse() { ChunkHeader chunkHeader = readChunkHeader(); if (chunkHeader == null) { return; } if (chunkHeader.getChunkType() != ChunkType.XML && chunkHeader.getChunkType() != ChunkType.NULL) { // notice that some apk mark xml header type as 0, really weird // see https://github.com/clearthesky/apk-parser/issues/49#issuecomment-256852727 return; } // read string pool chunk chunkHeader = readChunkHeader(); if (chunkHeader == null) { return; } ParseUtils.checkChunkType(ChunkType.STRING_POOL, chunkHeader.getChunkType()); stringPool = ParseUtils.readStringPool(buffer, (StringPoolHeader) chunkHeader); // read on chunk, check if it was an optional XMLResourceMap chunk chunkHeader = readChunkHeader(); if (chunkHeader == null) { return; } if (chunkHeader.getChunkType() == ChunkType.XML_RESOURCE_MAP) { long[] resourceIds = readXmlResourceMap((XmlResourceMapHeader) chunkHeader); resourceMap = new String[resourceIds.length]; chunkHeader = readChunkHeader(); } while (chunkHeader != null) { /*if (chunkHeader.chunkType == ChunkType.XML_END_NAMESPACE) { break; }*/ long beginPos = buffer.position(); switch (chunkHeader.getChunkType()) { case ChunkType.XML_END_NAMESPACE: XmlNamespaceEndTag xmlNamespaceEndTag = readXmlNamespaceEndTag(); xmlStreamer.onNamespaceEnd(xmlNamespaceEndTag); break; case ChunkType.XML_START_NAMESPACE: XmlNamespaceStartTag namespaceStartTag = readXmlNamespaceStartTag(); xmlStreamer.onNamespaceStart(namespaceStartTag); break; case ChunkType.XML_START_ELEMENT: readXmlNodeStartTag(); break; case ChunkType.XML_END_ELEMENT: readXmlNodeEndTag(); break; case ChunkType.XML_CDATA: break; default: if (chunkHeader.getChunkType() >= ChunkType.XML_FIRST_CHUNK && chunkHeader.getChunkType() <= ChunkType.XML_LAST_CHUNK) { Buffers.skip(buffer, chunkHeader.getBodySize()); } else { throw new ParserException("Unexpected chunk type:" + chunkHeader.getChunkType()); } } buffer.position((int) (beginPos + chunkHeader.getBodySize())); chunkHeader = readChunkHeader(); } } private XmlNodeEndTag readXmlNodeEndTag() { XmlNodeEndTag xmlNodeEndTag = new XmlNodeEndTag(); int nsRef = buffer.getInt(); int nameRef = buffer.getInt(); if (nsRef > 0) { xmlNodeEndTag.setNamespace(stringPool.get(nsRef)); } xmlNodeEndTag.setName(stringPool.get(nameRef)); if (xmlStreamer != null) { xmlStreamer.onEndTag(xmlNodeEndTag); } return xmlNodeEndTag; } private XmlNodeStartTag readXmlNodeStartTag() { int nsRef = buffer.getInt(); int nameRef = buffer.getInt(); XmlNodeStartTag xmlNodeStartTag = new XmlNodeStartTag(); if (nsRef > 0) { xmlNodeStartTag.setNamespace(stringPool.get(nsRef)); } xmlNodeStartTag.setName(stringPool.get(nameRef)); // read attributes. // attributeStart and attributeSize are always 20 (0x14) int attributeStart = Buffers.readUShort(buffer); int attributeSize = Buffers.readUShort(buffer); int attributeCount = Buffers.readUShort(buffer); int idIndex = Buffers.readUShort(buffer); int classIndex = Buffers.readUShort(buffer); int styleIndex = Buffers.readUShort(buffer); // read attributes Attributes attributes = new Attributes(attributeCount); for (int count = 0; count < attributeCount; count++) { Attribute attribute = readAttribute(); if (xmlStreamer != null) { String value = attribute.getRawValue(); attribute.setValue(value); attributes.set(count, attribute); } } xmlNodeStartTag.setAttributes(attributes); if (xmlStreamer != null) { xmlStreamer.onStartTag(xmlNodeStartTag); } return xmlNodeStartTag; } private Attribute readAttribute() { int nsRef = buffer.getInt(); int nameRef = buffer.getInt(); Attribute attribute = new Attribute(); if (nsRef > 0) { attribute.setNamespace(stringPool.get(nsRef)); } attribute.setName(stringPool.get(nameRef)); if (attribute.getName().isEmpty() && resourceMap != null && nameRef < resourceMap.length) { // some processed apk file make the string pool value empty, if it is a xmlmap attr. attribute.setName(resourceMap[nameRef]); //TODO: how to get the namespace of attribute } int rawValueRef = buffer.getInt(); if (rawValueRef > 0) { attribute.setRawValue(stringPool.get(rawValueRef)); } ParseUtils.readResValue(buffer, stringPool); return attribute; } private XmlNamespaceStartTag readXmlNamespaceStartTag() { int prefixRef = buffer.getInt(); int uriRef = buffer.getInt(); XmlNamespaceStartTag nameSpace = new XmlNamespaceStartTag(); if (prefixRef > 0) { nameSpace.setPrefix(stringPool.get(prefixRef)); } if (uriRef > 0) { nameSpace.setUri(stringPool.get(uriRef)); } return nameSpace; } private XmlNamespaceEndTag readXmlNamespaceEndTag() { int prefixRef = buffer.getInt(); int uriRef = buffer.getInt(); XmlNamespaceEndTag nameSpace = new XmlNamespaceEndTag(); if (prefixRef > 0) { nameSpace.setPrefix(stringPool.get(prefixRef)); } if (uriRef > 0) { nameSpace.setUri(stringPool.get(uriRef)); } return nameSpace; } private long[] readXmlResourceMap(XmlResourceMapHeader chunkHeader) { int count = chunkHeader.getBodySize() / 4; long[] resourceIds = new long[count]; for (int i = 0; i < count; i++) { resourceIds[i] = Buffers.readUInt(buffer); } return resourceIds; } private ChunkHeader readChunkHeader() { // finished if (!buffer.hasRemaining()) { return null; } long begin = buffer.position(); int chunkType = Buffers.readUShort(buffer); int headerSize = Buffers.readUShort(buffer); long chunkSize = Buffers.readUInt(buffer); switch (chunkType) { case ChunkType.XML: return new XmlHeader(chunkType, headerSize, chunkSize); case ChunkType.STRING_POOL: StringPoolHeader stringPoolHeader = new StringPoolHeader(chunkType, headerSize, chunkSize); stringPoolHeader.setStringCount(Buffers.readUInt(buffer)); stringPoolHeader.setStyleCount(Buffers.readUInt(buffer)); stringPoolHeader.setFlags(Buffers.readUInt(buffer)); stringPoolHeader.setStringsStart(Buffers.readUInt(buffer)); stringPoolHeader.setStylesStart(Buffers.readUInt(buffer)); buffer.position((int) (begin + headerSize)); return stringPoolHeader; case ChunkType.XML_RESOURCE_MAP: buffer.position((int) (begin + headerSize)); return new XmlResourceMapHeader(chunkType, headerSize, chunkSize); case ChunkType.XML_START_NAMESPACE: case ChunkType.XML_END_NAMESPACE: case ChunkType.XML_START_ELEMENT: case ChunkType.XML_END_ELEMENT: case ChunkType.XML_CDATA: XmlNodeHeader header = new XmlNodeHeader(chunkType, headerSize, chunkSize); header.setLineNum((int) Buffers.readUInt(buffer)); header.setCommentRef((int) Buffers.readUInt(buffer)); buffer.position((int) (begin + headerSize)); return header; case ChunkType.NULL: return new NullHeader(chunkType, headerSize, chunkSize); default: throw new ParserException("Unexpected chunk type:" + chunkType); } } public void setXmlStreamer(XmlStreamer xmlStreamer) { this.xmlStreamer = xmlStreamer; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/parser/StringPoolEntry.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.parser; /** * class for sort string pool indexes */ public class StringPoolEntry { private int idx; private long offset; public StringPoolEntry(int idx, long offset) { this.idx = idx; this.offset = offset; } public int getIdx() { return idx; } public void setIdx(int idx) { this.idx = idx; } public long getOffset() { return offset; } public void setOffset(long offset) { this.offset = offset; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/parser/XmlNamespaces.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.parser; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNamespaceEndTag; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNamespaceStartTag; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * the xml file's namespaces. * * @author dongliu */ class XmlNamespaces { private List namespaces; private List newNamespaces; public XmlNamespaces() { this.namespaces = new ArrayList<>(); this.newNamespaces = new ArrayList<>(); } public void addNamespace(XmlNamespaceStartTag tag) { XmlNamespace namespace = new XmlNamespace(tag.getPrefix(), tag.getUri()); namespaces.add(namespace); newNamespaces.add(namespace); } public void removeNamespace(XmlNamespaceEndTag tag) { XmlNamespace namespace = new XmlNamespace(tag.getPrefix(), tag.getUri()); namespaces.remove(namespace); newNamespaces.remove(namespace); } public String getPrefixViaUri(String uri) { if (uri == null) { return null; } for (XmlNamespace namespace : namespaces) { if (namespace.uri.equals(uri)) { return namespace.prefix; } } return null; } public List consumeNameSpaces() { if (!newNamespaces.isEmpty()) { List xmlNamespaces = new ArrayList<>(); xmlNamespaces.addAll(newNamespaces); newNamespaces.clear(); return xmlNamespaces; } else { return Collections.emptyList(); } } /** * one namespace */ public static class XmlNamespace { private String prefix; private String uri; private XmlNamespace(String prefix, String uri) { this.prefix = prefix; this.uri = uri; } public String getPrefix() { return prefix; } public String getUri() { return uri; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } XmlNamespace namespace = (XmlNamespace) o; if (prefix == null && namespace.prefix != null) { return false; } if (uri == null && namespace.uri != null) { return false; } if (prefix != null && !prefix.equals(namespace.prefix)) { return false; } if (uri != null && !uri.equals(namespace.uri)) { return false; } return true; } @Override public int hashCode() { int result = prefix.hashCode(); result = 31 * result + uri.hashCode(); return result; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/parser/XmlStreamer.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.parser; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNamespaceEndTag; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNamespaceStartTag; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNodeEndTag; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNodeStartTag; /** * callback interface for parse binary xml file. * * @author dongliu */ public interface XmlStreamer { void onStartTag(XmlNodeStartTag xmlNodeStartTag); void onEndTag(XmlNodeEndTag xmlNodeEndTag); void onNamespaceStart(XmlNamespaceStartTag tag); void onNamespaceEnd(XmlNamespaceEndTag tag); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/parser/XmlTranslator.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.parser; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNamespaceEndTag; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNamespaceStartTag; import com.qihoo360.replugin.ext.parser.struct.xml.Attribute; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNodeEndTag; import com.qihoo360.replugin.ext.parser.struct.xml.XmlNodeStartTag; import com.qihoo360.replugin.ext.parser.utils.xml.XmlEscaper; import java.util.List; /** * trans to xml text when parse binary xml file. * * @author dongliu */ public class XmlTranslator implements XmlStreamer { private StringBuilder sb; private int shift = 0; private XmlNamespaces namespaces; private boolean isLastStartTag; public XmlTranslator() { sb = new StringBuilder(); sb.append("\n"); this.namespaces = new XmlNamespaces(); } @Override public void onStartTag(XmlNodeStartTag xmlNodeStartTag) { if (isLastStartTag) { sb.append(">\n"); } appendShift(shift++); sb.append('<'); if (xmlNodeStartTag.getNamespace() != null) { String prefix = namespaces.getPrefixViaUri(xmlNodeStartTag.getNamespace()); if (prefix != null) { sb.append(prefix).append(":"); } else { sb.append(xmlNodeStartTag.getNamespace()).append(":"); } } sb.append(xmlNodeStartTag.getName()); List nps = namespaces.consumeNameSpaces(); if (!nps.isEmpty()) { for (XmlNamespaces.XmlNamespace np : nps) { sb.append(" xmlns:").append(np.getPrefix()).append("=\"") .append(np.getUri()) .append("\""); } } isLastStartTag = true; for (Attribute attribute : xmlNodeStartTag.getAttributes().value()) { onAttribute(attribute); } } private void onAttribute(Attribute attribute) { sb.append(" "); String namespace = this.namespaces.getPrefixViaUri(attribute.getNamespace()); if (namespace == null) { namespace = attribute.getNamespace(); } if (namespace != null && !namespace.isEmpty()) { sb.append(namespace).append(':'); } String escapedFinalValue = XmlEscaper.escapeXml10(attribute.getValue()); sb.append(attribute.getName()).append('=').append('"') .append(escapedFinalValue).append('"'); } @Override public void onEndTag(XmlNodeEndTag xmlNodeEndTag) { --shift; if (isLastStartTag) { sb.append(" />\n"); } else { appendShift(shift); sb.append("\n"); } isLastStartTag = false; } @Override public void onNamespaceStart(XmlNamespaceStartTag tag) { this.namespaces.addNamespace(tag); } @Override public void onNamespaceEnd(XmlNamespaceEndTag tag) { this.namespaces.removeNamespace(tag); } private void appendShift(int shift) { for (int i = 0; i < shift; i++) { sb.append("\t"); } } public String getXml() { return sb.toString(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/ChunkHeader.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct; /** * A Chunk is just a piece of memory split into two parts, a header and a body. * The exact structure of the header and the body of a given Chunk is determined by its type. *

 * chunk header struct.
 * struct ResChunk_header {
 * uint16_t type;
 * uint16_t headerSize;
 * uint32_t size;
 * }
 * 
* * @author dongliu */ public class ChunkHeader { // Type identifier for this chunk. The meaning of this value depends // on the containing chunk. private int chunkType; // Size of the chunk header (in bytes). Adding this value to // the address of the chunk allows you to find its associated data // (if any). private int headerSize; // Total size of this chunk (in bytes). This is the chunkSize plus // the size of any data associated with the chunk. Adding this value // to the chunk allows you to completely skip its contents (including // any child chunks). If this value is the same as chunkSize, there is // no data associated with the chunk. private long chunkSize; public ChunkHeader(int chunkType, int headerSize, long chunkSize) { this.chunkType = chunkType; this.headerSize = headerSize; this.chunkSize = chunkSize; } public int getBodySize() { return (int) (this.chunkSize - this.headerSize); } public int getChunkType() { return chunkType; } public void setChunkType(int chunkType) { this.chunkType = chunkType; } public int getHeaderSize() { return headerSize; } public void setHeaderSize(int headerSize) { this.headerSize = headerSize; } public long getChunkSize() { return chunkSize; } public void setChunkSize(long chunkSize) { this.chunkSize = chunkSize; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/ChunkType.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct; /** * Resource type * see https://github.com/android/platform_frameworks_base/blob/master/include/androidfw/ResourceTypes.h * * @author dongliu */ public class ChunkType { public static final int NULL = 0x0000; public static final int STRING_POOL = 0x0001; public static final int TABLE = 0x0002; public static final int XML = 0x0003; // Chunk types in XML public static final int XML_FIRST_CHUNK = 0x0100; public static final int XML_START_NAMESPACE = 0x0100; public static final int XML_END_NAMESPACE = 0x0101; public static final int XML_START_ELEMENT = 0x0102; public static final int XML_END_ELEMENT = 0x0103; public static final int XML_CDATA = 0x0104; public static final int XML_LAST_CHUNK = 0x017f; // This contains a uint32_t array mapping strings in the string // pool back to resource identifiers. It is optional. public static final int XML_RESOURCE_MAP = 0x0180; // Chunk types in RES_TABLE_TYPE public static final int TABLE_PACKAGE = 0x0200; public static final int TABLE_TYPE = 0x0201; public static final int TABLE_TYPE_SPEC = 0x0202; // android5.0+ // DynamicRefTable public static final int TABLE_LIBRARY = 0x0203; } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/ResourceValue.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct; /** * Resource entity, may be one entry in resource table, or string value * A apk only has one resource table. * * @author dongliu */ public abstract class ResourceValue { protected final int value; protected ResourceValue(int value) { this.value = value; } public static ResourceValue string(int value, StringPool stringPool) { return new StringResourceValue(value, stringPool); } public static ResourceValue raw(int value, short type) { return new RawValue(value, type); } public abstract String toStringValue(); private static class StringResourceValue extends ResourceValue { private final StringPool stringPool; private StringResourceValue(int value, StringPool stringPool) { super(value); this.stringPool = stringPool; } @Override public String toStringValue() { if (value >= 0) { return stringPool.get(value); } else { return null; } } } private static class RawValue extends ResourceValue { private final short dataType; private RawValue(int value, short dataType) { super(value); this.dataType = dataType; } @Override public String toStringValue() { return "{" + dataType + ":" + (value & 0xFFFFFFFFL) + "}"; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/StringPool.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct; /** * String pool. * * @author dongliu */ public class StringPool { private String[] pool; public StringPool(int poolSize) { pool = new String[poolSize]; } public String get(int idx) { return pool[idx]; } public void set(int idx, String value) { pool[idx] = value; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/StringPoolHeader.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct; /** * String pool chunk header. * * @author dongliu */ public class StringPoolHeader extends ChunkHeader { // If set, the string index is sorted by the string values (based on strcmp16()). public static final int SORTED_FLAG = 1; // String pool is encoded in UTF-8 public static final int UTF8_FLAG = 1 << 8; // Number of style span arrays in the pool (number of uint32_t indices // follow the string indices). private long stringCount; // Number of style span arrays in the pool (number of uint32_t indices // follow the string indices). private long styleCount; private long flags; // Index from header of the string data. private long stringsStart; // Index from header of the style data. private long stylesStart; public StringPoolHeader(int chunkType, int headerSize, long chunkSize) { super(chunkType, headerSize, chunkSize); } public long getStringCount() { return stringCount; } public void setStringCount(long stringCount) { this.stringCount = stringCount; } public long getStyleCount() { return styleCount; } public void setStyleCount(long styleCount) { this.styleCount = styleCount; } public long getFlags() { return flags; } public void setFlags(long flags) { this.flags = flags; } public long getStringsStart() { return stringsStart; } public void setStringsStart(long stringsStart) { this.stringsStart = stringsStart; } public long getStylesStart() { return stylesStart; } public void setStylesStart(long stylesStart) { this.stylesStart = stylesStart; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/xml/Attribute.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct.xml; /** * xml node attribute * * @author dongliu */ public class Attribute { private String namespace; private String name; // The original raw string value of this private String rawValue; // Processed typed value of this // the final value as string private String value; public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getRawValue() { return rawValue; } public void setRawValue(String rawValue) { this.rawValue = rawValue; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public String toString() { return "Attribute{" + "name='" + name + '\'' + ", namespace='" + namespace + '\'' + '}'; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/xml/Attributes.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct.xml; /** * xml node attributes * * @author dongliu */ public class Attributes { private final Attribute[] attributes; public Attributes(int size) { this.attributes = new Attribute[size]; } public void set(int i, Attribute attribute) { attributes[i] = attribute; } public String get(String name) { for (Attribute attribute : attributes) { if (attribute.getName().equals(name)) { return attribute.getValue(); } } return null; } public int size() { return attributes.length; } public boolean getBoolean(String name, boolean b) { String value = get(name); return value == null ? b : Boolean.parseBoolean(value); } public Integer getInt(String name) { String value = get(name); if (value == null) { return null; } if (value.startsWith("0x")) { return Integer.valueOf(value.substring(2), 16); } return Integer.valueOf(value); } public Long getLong(String name) { String value = get(name); if (value == null) { return null; } if (value.startsWith("0x")) { return Long.valueOf(value.substring(2), 16); } return Long.valueOf(value); } public Attribute[] value() { return this.attributes; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/xml/NullHeader.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct.xml; import com.qihoo360.replugin.ext.parser.struct.ChunkHeader; /** * Null header. * * @author dongliu */ public class NullHeader extends ChunkHeader { public NullHeader(int chunkType, int headerSize, long chunkSize) { super(chunkType, headerSize, chunkSize); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/xml/XmlHeader.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct.xml; import com.qihoo360.replugin.ext.parser.struct.ChunkHeader; /** * Binary XML header. It is simply a struct ResChunk_header. * The header.type is always 0×0003 (XML). * * @author dongliu */ public class XmlHeader extends ChunkHeader { public XmlHeader(int chunkType, int headerSize, long chunkSize) { super(chunkType, headerSize, chunkSize); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/xml/XmlNamespaceEndTag.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct.xml; /** * @author dongliu */ public class XmlNamespaceEndTag { private String prefix; private String uri; public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } @Override public String toString() { return prefix + "=" + uri; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/xml/XmlNamespaceStartTag.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct.xml; /** * @author dongliu */ public class XmlNamespaceStartTag { private String prefix; private String uri; public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } @Override public String toString() { return prefix + "=" + uri; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/xml/XmlNodeEndTag.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct.xml; /** * @author dongliu */ public class XmlNodeEndTag { private String namespace; private String name; public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("'); return sb.toString(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/xml/XmlNodeHeader.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct.xml; import com.qihoo360.replugin.ext.parser.struct.ChunkHeader; /** * @author dongliu */ public class XmlNodeHeader extends ChunkHeader { // Line number in original source file at which this element appeared. private int lineNum; // Optional XML comment string pool ref, -1 if none private int commentRef; public XmlNodeHeader(int chunkType, int headerSize, long chunkSize) { super(chunkType, headerSize, chunkSize); } public int getLineNum() { return lineNum; } public void setLineNum(int lineNum) { this.lineNum = lineNum; } public int getCommentRef() { return commentRef; } public void setCommentRef(int commentRef) { this.commentRef = commentRef; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/xml/XmlNodeStartTag.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct.xml; /** * @author dongliu */ public class XmlNodeStartTag { private String namespace; private String name; // Byte offset from the start of this structure where the attributes start. uint16 //public int attributeStart; // Size of the ResXMLTree_attribute structures that follow. unit16 //public int attributeSize; // Number of attributes associated with an ELEMENT. uint 16 // These are available as an array of ResXMLTree_attribute structures immediately following this node. //public int attributeCount; // Index (1-based) of the "id" attribute. 0 if none. uint16 //public short idIndex; // Index (1-based) of the "style" attribute. 0 if none. uint16 //public short styleIndex; private Attributes attributes; public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Attributes getAttributes() { return attributes; } public void setAttributes(Attributes attributes) { this.attributes = attributes; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append('<'); if (namespace != null) { sb.append(namespace).append(":"); } sb.append(name); sb.append('>'); return sb.toString(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/struct/xml/XmlResourceMapHeader.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.struct.xml; import com.qihoo360.replugin.ext.parser.struct.ChunkHeader; /** * @author dongliu */ public class XmlResourceMapHeader extends ChunkHeader { public XmlResourceMapHeader(int chunkType, int headerSize, long chunkSize) { super(chunkType, headerSize, chunkSize); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/utils/Buffers.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.utils; import java.nio.ByteBuffer; /** * utils method for byte buffer * * @author Liu Dong im@dongliu.net */ public class Buffers { /** * get one unsigned byte as short type */ public static short readUByte(ByteBuffer buffer) { byte b = buffer.get(); return (short) (b & 0xff); } /** * get one unsigned short as int type */ public static int readUShort(ByteBuffer buffer) { short s = buffer.getShort(); return s & 0xffff; } /** * get one unsigned int as long type */ public static long readUInt(ByteBuffer buffer) { int i = buffer.getInt(); return i & 0xffffffffL; } /** * get bytes */ public static byte[] readBytes(ByteBuffer buffer, int size) { byte[] bytes = new byte[size]; buffer.get(bytes); return bytes; } /** * read utf16 strings, use strLen, not ending 0 char. */ public static String readString(ByteBuffer buffer, int strLen) { StringBuilder sb = new StringBuilder(strLen); for (int i = 0; i < strLen; i++) { sb.append(buffer.getChar()); } return sb.toString(); } /** * skip count bytes */ public static void skip(ByteBuffer buffer, int count) { buffer.position(buffer.position() + count); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/utils/ParseUtils.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.utils; import com.qihoo360.replugin.ext.parser.parser.StringPoolEntry; import com.qihoo360.replugin.ext.parser.struct.ResourceValue; import com.qihoo360.replugin.ext.parser.struct.StringPool; import com.qihoo360.replugin.ext.parser.struct.StringPoolHeader; import com.qihoo360.replugin.ext.parser.exception.ParserException; import java.nio.ByteBuffer; import java.nio.charset.Charset; /** * @author dongliu */ public class ParseUtils { public static Charset charsetUTF8 = Charset.forName("UTF-8"); /** * read string from input buffer. if get EOF before read enough data, throw IOException. */ public static String readString(ByteBuffer buffer, boolean utf8) { if (utf8) { // The lengths are encoded in the same way as for the 16-bit format // but using 8-bit rather than 16-bit integers. int strLen = readLen(buffer); int bytesLen = readLen(buffer); byte[] bytes = Buffers.readBytes(buffer, bytesLen); String str = new String(bytes, charsetUTF8); // zero int trailling = Buffers.readUByte(buffer); return str; } else { // The length is encoded as either one or two 16-bit integers as per the commentRef... int strLen = readLen16(buffer); String str = Buffers.readString(buffer, strLen); // zero int trailling = Buffers.readUShort(buffer); return str; } } /** * read encoding len. * see StringPool.cpp ENCODE_LENGTH */ private static int readLen(ByteBuffer buffer) { int len = 0; int i = Buffers.readUByte(buffer); if ((i & 0x80) != 0) { //read one more byte. len |= (i & 0x7f) << 7; len += Buffers.readUByte(buffer); } else { len = i; } return len; } /** * read encoding len. * see Stringpool.cpp ENCODE_LENGTH */ private static int readLen16(ByteBuffer buffer) { int len = 0; int i = Buffers.readUShort(buffer); if ((i & 0x8000) != 0) { len |= (i & 0x7fff) << 15; len += Buffers.readUShort(buffer); } else { len = i; } return len; } /** * read String pool, for apk binary xml file and resource table. */ public static StringPool readStringPool(ByteBuffer buffer, StringPoolHeader stringPoolHeader) { long beginPos = buffer.position(); long[] offsets = new long[(int) stringPoolHeader.getStringCount()]; // read strings offset if (stringPoolHeader.getStringCount() > 0) { for (int idx = 0; idx < stringPoolHeader.getStringCount(); idx++) { offsets[idx] = Buffers.readUInt(buffer); } } // read flag // the string index is sorted by the string values if true boolean sorted = (stringPoolHeader.getFlags() & StringPoolHeader.SORTED_FLAG) != 0; // string use utf-8 format if true, otherwise utf-16 boolean utf8 = (stringPoolHeader.getFlags() & StringPoolHeader.UTF8_FLAG) != 0; // read strings. the head and metas have 28 bytes long stringPos = beginPos + stringPoolHeader.getStringsStart() - stringPoolHeader.getHeaderSize(); buffer.position((int) stringPos); StringPoolEntry[] entries = new StringPoolEntry[offsets.length]; for (int i = 0; i < offsets.length; i++) { entries[i] = new StringPoolEntry(i, stringPos + offsets[i]); } String lastStr = null; long lastOffset = -1; StringPool stringPool = new StringPool((int) stringPoolHeader.getStringCount()); for (StringPoolEntry entry : entries) { if (entry.getOffset() == lastOffset) { stringPool.set(entry.getIdx(), lastStr); continue; } buffer.position((int) entry.getOffset()); lastOffset = entry.getOffset(); String str = ParseUtils.readString(buffer, utf8); lastStr = str; stringPool.set(entry.getIdx(), str); } // read styles if (stringPoolHeader.getStyleCount() > 0) { // now we just skip it } buffer.position((int) (beginPos + stringPoolHeader.getBodySize())); return stringPool; } /** * read res value, convert from different types to string. */ public static ResourceValue readResValue(ByteBuffer buffer, StringPool stringPool) { // ResValue resValue = new ResValue(); int size = Buffers.readUShort(buffer); short res0 = Buffers.readUByte(buffer); short dataType = Buffers.readUByte(buffer); return ResourceValue.raw(buffer.getInt(), dataType); } public static void checkChunkType(int expected, int real) { if (expected != real) { throw new ParserException("Expect chunk type:" + Integer.toHexString(expected) + ", but got:" + Integer.toHexString(real)); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/utils/xml/AggregateTranslator.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.utils.xml; import java.io.IOException; import java.io.Writer; /** * Executes a sequence of translators one after the other. Execution ends whenever * the first translator consumes codepoints from the input. */ class AggregateTranslator extends CharSequenceTranslator { private final CharSequenceTranslator[] translators; /** * Specify the translators to be used at creation time. * * @param translators CharSequenceTranslator array to aggregate */ public AggregateTranslator(final CharSequenceTranslator... translators) { this.translators = translators; } /** * The first translator to consume codepoints from the input is the 'winner'. * Execution stops with the number of consumed codepoints being returned. * {@inheritDoc} */ @Override public int translate(final CharSequence input, final int index, final Writer out) throws IOException { for (final CharSequenceTranslator translator : translators) { final int consumed = translator.translate(input, index, out); if (consumed != 0) { return consumed; } } return 0; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/utils/xml/CharSequenceTranslator.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.utils.xml; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.Locale; /** * An API for translating text. * Its core use is to escape and unescape text. Because escaping and unescaping * is completely contextual, the API does not present two separate signatures. */ abstract class CharSequenceTranslator { /** *

Returns an upper case hexadecimal String for the given * character.

* * @param codepoint The codepoint to convert. * @return An upper case hexadecimal String */ public static String hex(final int codepoint) { return Integer.toHexString(codepoint).toUpperCase(Locale.ENGLISH); } /** * Translate a set of codepoints, represented by an int index into a CharSequence, * into another set of codepoints. The number of codepoints consumed must be returned, * and the only IOExceptions thrown must be from interacting with the Writer so that * the top level API may reliably ignore StringWriter IOExceptions. * * @param input CharSequence that is being translated * @param index int representing the current point of translation * @param out Writer to translate the text to * @return int count of codepoints consumed * @throws IOException if and only if the Writer produces an IOException */ public abstract int translate(CharSequence input, int index, Writer out) throws IOException; /** * Helper for non-Writer usage. * * @param input CharSequence to be translated * @return String output of translation */ public final String translate(final CharSequence input) { if (input == null) { return null; } try { final StringWriter writer = new StringWriter(input.length() * 2); translate(input, writer); return writer.toString(); } catch (final IOException ioe) { // this should never ever happen while writing to a StringWriter throw new RuntimeException(ioe); } } /** * Translate an input onto a Writer. This is intentionally final as its algorithm is * tightly coupled with the abstract method of this class. * * @param input CharSequence that is being translated * @param out Writer to translate the text to * @throws IOException if and only if the Writer produces an IOException */ public final void translate(final CharSequence input, final Writer out) throws IOException { if (out == null) { throw new IllegalArgumentException("The Writer must not be null"); } if (input == null) { return; } int pos = 0; final int len = input.length(); while (pos < len) { final int consumed = translate(input, pos, out); if (consumed == 0) { final char[] c = Character.toChars(Character.codePointAt(input, pos)); out.write(c); pos += c.length; continue; } // contract with translators is that they have to understand codepoints // and they just took care of a surrogate pair for (int pt = 0; pt < consumed; pt++) { pos += Character.charCount(Character.codePointAt(input, pos)); } } } /** * Helper method to create a merger of this translator with another set of * translators. Useful in customizing the standard functionality. * * @param translators CharSequenceTranslator array of translators to merge with this one * @return CharSequenceTranslator merging this translator with the others */ public final CharSequenceTranslator with(final CharSequenceTranslator... translators) { final CharSequenceTranslator[] newArray = new CharSequenceTranslator[translators.length + 1]; newArray[0] = this; System.arraycopy(translators, 0, newArray, 1, translators.length); return new AggregateTranslator(newArray); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/utils/xml/CodePointTranslator.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.utils.xml; import java.io.IOException; import java.io.Writer; /** * Helper subclass to CharSequenceTranslator to allow for translations that * will replace up to one character at a time. */ abstract class CodePointTranslator extends CharSequenceTranslator { /** * Implementation of translate that maps onto the abstract translate(int, Writer) method. * {@inheritDoc} */ @Override public final int translate(final CharSequence input, final int index, final Writer out) throws IOException { final int codepoint = Character.codePointAt(input, index); final boolean consumed = translate(codepoint, out); return consumed ? 1 : 0; } /** * Translate the specified codepoint into another. * * @param codepoint int character input to translate * @param out Writer to optionally push the translated output to * @return boolean as to whether translation occurred or not * @throws IOException if and only if the Writer produces an IOException */ public abstract boolean translate(int codepoint, Writer out) throws IOException; } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/utils/xml/EntityArrays.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.utils.xml; /** * Class holding various entity data for HTML and XML - generally for use with * the LookupTranslator. * All arrays are of length [*][2]. */ public class EntityArrays { private static final String[][] BASIC_ESCAPE = { {"\"", """}, // " - double-quote {"&", "&"}, // & - ampersand {"<", "<"}, // < - less-than {">", ">"}, // > - greater-than }; private static final String[][] APOS_ESCAPE = { {"'", "'"}, // XML apostrophe }; /** * Mapping to escape the basic XML and HTML character entities. *

* Namely: {@code " & < >} * * @return the mapping table */ public static String[][] BASIC_ESCAPE() { return BASIC_ESCAPE.clone(); } /** * Mapping to escape the apostrophe character to its XML character entity. * * @return the mapping table */ public static String[][] APOS_ESCAPE() { return APOS_ESCAPE.clone(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/utils/xml/LookupTranslator.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.utils.xml; import java.io.IOException; import java.io.Writer; import java.util.HashMap; /** * Translates a value using a lookup table. */ class LookupTranslator extends CharSequenceTranslator { private final HashMap lookupMap; private final int shortest; private final int longest; /** * Define the lookup table to be used in translation *

* Note that, as of Lang 3.1, the key to the lookup table is converted to a * java.lang.String, while the value remains as a java.lang.CharSequence. * This is because we need the key to support hashCode and equals(Object), * allowing it to be the key for a HashMap. See LANG-882. * * @param lookup CharSequence[][] table of size [*][2] */ public LookupTranslator(final CharSequence[]... lookup) { lookupMap = new HashMap<>(); int shortest = Integer.MAX_VALUE; int longest = 0; if (lookup != null) { for (final CharSequence[] seq : lookup) { this.lookupMap.put(seq[0].toString(), seq[1]); final int sz = seq[0].length(); if (sz < shortest) { shortest = sz; } if (sz > longest) { longest = sz; } } } this.shortest = shortest; this.longest = longest; } /** * {@inheritDoc} */ @Override public int translate(final CharSequence input, final int index, final Writer out) throws IOException { int max = longest; if (index + longest > input.length()) { max = input.length() - index; } // descend so as to get a greedy algorithm for (int i = max; i >= shortest; i--) { final CharSequence subSeq = input.subSequence(index, index + i); final CharSequence result = lookupMap.get(subSeq.toString()); if (result != null) { out.write(result.toString()); return i; } } return 0; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/utils/xml/NumericEntityEscaper.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.utils.xml; import java.io.IOException; import java.io.Writer; /** * Translates codepoints to their XML numeric entity escaped value. */ class NumericEntityEscaper extends CodePointTranslator { private final int below; private final int above; private final boolean between; /** *

Constructs a NumericEntityEscaper for the specified range. This is * the underlying method for the other constructors/builders. The below * and above boundaries are inclusive when between is * true and exclusive when it is false.

* * @param below int value representing the lowest codepoint boundary * @param above int value representing the highest codepoint boundary * @param between whether to escape between the boundaries or outside them */ private NumericEntityEscaper(final int below, final int above, final boolean between) { this.below = below; this.above = above; this.between = between; } /** *

Constructs a NumericEntityEscaper for all characters.

*/ public NumericEntityEscaper() { this(0, Integer.MAX_VALUE, true); } /** *

Constructs a NumericEntityEscaper below the specified value (exclusive).

* * @param codepoint below which to escape * @return the newly created {@code NumericEntityEscaper} instance */ public static NumericEntityEscaper below(final int codepoint) { return outsideOf(codepoint, Integer.MAX_VALUE); } /** *

Constructs a NumericEntityEscaper above the specified value (exclusive).

* * @param codepoint above which to escape * @return the newly created {@code NumericEntityEscaper} instance */ public static NumericEntityEscaper above(final int codepoint) { return outsideOf(0, codepoint); } /** *

Constructs a NumericEntityEscaper between the specified values (inclusive).

* * @param codepointLow above which to escape * @param codepointHigh below which to escape * @return the newly created {@code NumericEntityEscaper} instance */ public static NumericEntityEscaper between(final int codepointLow, final int codepointHigh) { return new NumericEntityEscaper(codepointLow, codepointHigh, true); } /** *

Constructs a NumericEntityEscaper outside of the specified values (exclusive).

* * @param codepointLow below which to escape * @param codepointHigh above which to escape * @return the newly created {@code NumericEntityEscaper} instance */ public static NumericEntityEscaper outsideOf(final int codepointLow, final int codepointHigh) { return new NumericEntityEscaper(codepointLow, codepointHigh, false); } /** * {@inheritDoc} */ @Override public boolean translate(final int codepoint, final Writer out) throws IOException { if (between) { if (codepoint < below || codepoint > above) { return false; } } else { if (codepoint >= below && codepoint <= above) { return false; } } out.write("&#"); out.write(Integer.toString(codepoint, 10)); out.write(';'); return true; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/utils/xml/UnicodeUnpairedSurrogateRemover.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.utils.xml; import java.io.IOException; import java.io.Writer; /** * Helper subclass to CharSequenceTranslator to remove unpaired surrogates. */ class UnicodeUnpairedSurrogateRemover extends CodePointTranslator { /** * Implementation of translate that throws out unpaired surrogates. * {@inheritDoc} */ @Override public boolean translate(int codepoint, Writer out) throws IOException { if (codepoint >= Character.MIN_SURROGATE && codepoint <= Character.MAX_SURROGATE) { // It's a surrogate. Write nothing and say we've translated. return true; } else { // It's not a surrogate. Don't translate it. return false; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/ext/parser/utils/xml/XmlEscaper.java ================================================ /* * Copyright (c) 2016, Liu Dong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 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. * * 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. */ package com.qihoo360.replugin.ext.parser.utils.xml; /** * Utils method to escape xml string, copied from apache commons lang3 * * @author Liu Dong {@literal } */ public class XmlEscaper { public static final CharSequenceTranslator ESCAPE_XML10 = new AggregateTranslator( new LookupTranslator(EntityArrays.BASIC_ESCAPE()), new LookupTranslator(EntityArrays.APOS_ESCAPE()), new LookupTranslator( new String[][]{ {"\u0000", ""}, {"\u0001", ""}, {"\u0002", ""}, {"\u0003", ""}, {"\u0004", ""}, {"\u0005", ""}, {"\u0006", ""}, {"\u0007", ""}, {"\u0008", ""}, {"\u000b", ""}, {"\u000c", ""}, {"\u000e", ""}, {"\u000f", ""}, {"\u0010", ""}, {"\u0011", ""}, {"\u0012", ""}, {"\u0013", ""}, {"\u0014", ""}, {"\u0015", ""}, {"\u0016", ""}, {"\u0017", ""}, {"\u0018", ""}, {"\u0019", ""}, {"\u001a", ""}, {"\u001b", ""}, {"\u001c", ""}, {"\u001d", ""}, {"\u001e", ""}, {"\u001f", ""}, {"\ufffe", ""}, {"\uffff", ""} }), NumericEntityEscaper.between(0x7f, 0x84), NumericEntityEscaper.between(0x86, 0x9f), new UnicodeUnpairedSurrogateRemover() ); /** *

Escapes the characters in a {@code String} using XML entities.

*/ public static String escapeXml10(final String input) { return ESCAPE_XML10.translate(input); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/helper/HostConfigHelper.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.helper; import com.qihoo360.replugin.utils.ReflectUtils; /** * 从宿主的 RePluginHostConfig 中获取一些字段值, * RepluginHostConfig 文件由 replugin-host-gradle 自动生成 * * @author RePlugin Team */ public class HostConfigHelper { private static final String HOST_CONFIG_FILE_PATH = "com.qihoo360.replugin.gen."; private static final String HOST_CONFIG_FILE_NAME = "RePluginHostConfig"; private static Class HOST_CONFIG_CLASS; //------------------------------------------------------------ // RePlugin 坑位默认配置项 // 注意:以下配置项必须和 replugin-host-gradle 插件中的配置相同 //------------------------------------------------------------ // 是否使用“常驻进程”(见PERSISTENT_NAME)作为插件的管理进程 public static boolean PERSISTENT_ENABLE = true; // 常驻进程名 public static String PERSISTENT_NAME = ":GuardService"; // 背景透明的坑的数量(每种 launchMode 不同) public static int ACTIVITY_PIT_COUNT_TS_STANDARD = 2; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TOP = 2; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TASK = 2; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE = 3; // 背景透明的坑的数量(每种 launchMode 不同)横屏 public static int ACTIVITY_PIT_COUNT_TS_STANDARD_LAND = 1; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TOP_LAND = 1; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TASK_LAND = 1; public static int ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE_LAND = 1; // 背景不透明的坑的数量(每种 launchMode 不同) public static int ACTIVITY_PIT_COUNT_NTS_STANDARD = 6; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP = 2; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK = 3; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE = 2; // 背景不透明的坑的数量(每种 launchMode 不同)横屏 public static int ACTIVITY_PIT_COUNT_NTS_STANDARD_LAND = 1; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP_LAND = 1; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK_LAND = 1; public static int ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE_LAND = 1; // TaskAffinity 组数 public static int ACTIVITY_PIT_COUNT_TASK = 2; // 是否使用 AppCompat 库 public static boolean ACTIVITY_PIT_USE_APPCOMPAT = false; // host 是否支持了androidx库 public static boolean HOST_USE_ANDROIDX = false; // 是否使用 横屏坑位 public static boolean HOST_USE_OCCUPYLAND = false; //------------------------------------------------------------ // 主程序支持的插件版本范围 //------------------------------------------------------------ // HOST 向下兼容的插件版本 public static int ADAPTER_COMPATIBLE_VERSION = 10; // HOST 插件版本 public static int ADAPTER_CURRENT_VERSION = 12; static { try { HOST_CONFIG_CLASS = ReflectUtils.getClass(HOST_CONFIG_FILE_PATH + HOST_CONFIG_FILE_NAME); } catch (ClassNotFoundException e) { // Ignore, Just use default value } try { PERSISTENT_ENABLE = readField("PERSISTENT_ENABLE"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { PERSISTENT_NAME = readField("PERSISTENT_NAME"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_USE_APPCOMPAT = readField("ACTIVITY_PIT_USE_APPCOMPAT"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { HOST_USE_ANDROIDX = readField("HOST_USE_ANDROIDX"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { HOST_USE_OCCUPYLAND = readField("HOST_USE_OCCUPYLAND"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_TS_STANDARD = readField("ACTIVITY_PIT_COUNT_TS_STANDARD"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_TS_STANDARD_LAND = readField("ACTIVITY_PIT_COUNT_TS_STANDARD_LAND"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_TS_SINGLE_TOP = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_TOP"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_TS_SINGLE_TOP_LAND = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_TOP_LAND"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_TS_SINGLE_TASK = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_TASK"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_TS_SINGLE_TASK_LAND = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_TASK_LAND"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE_LAND = readField("ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE_LAND"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_NTS_STANDARD = readField("ACTIVITY_PIT_COUNT_NTS_STANDARD"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_NTS_STANDARD_LAND = readField("ACTIVITY_PIT_COUNT_NTS_STANDARD_LAND"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP_LAND = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP_LAND"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK_LAND = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK_LAND"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE_LAND = readField("ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE_LAND"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ACTIVITY_PIT_COUNT_TASK = readField("ACTIVITY_PIT_COUNT_TASK"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ADAPTER_COMPATIBLE_VERSION = readField("ADAPTER_COMPATIBLE_VERSION"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } try { ADAPTER_CURRENT_VERSION = readField("ADAPTER_CURRENT_VERSION"); } catch (NoSuchFieldException e) { // Ignore, Just use default value } } private static T readField(String name) throws NoSuchFieldException { try { // 就是要强转 //noinspection unchecked return (T) ReflectUtils.readStaticField(HOST_CONFIG_CLASS, name); } catch (IllegalAccessException e) { // 此Field可能为非Public权限,不过由于我们做了Accessible处理,可能性非常低 // NOTE 因为类型转换发生在readField返回值之后才做,故“ClassCastException”只会出现在static方法块内 // NOTE 故在此处做Catch ClassCastException是无效的 throw new IllegalStateException(e); } // NOTE 不需要Catch NoSuchFieldException,因为只要此Field找不到就抛,符合预期 } public static void init() { // Nothing, Just init on "static" block } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/helper/JSONHelper.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.helper; import android.os.Build; import com.qihoo360.replugin.RePluginInternal; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.lang.reflect.Field; import java.util.List; /** * 和JSON操作有关的帮助类 * * @author RePlugin Team */ public class JSONHelper { private static final boolean LOG = RePluginInternal.FOR_DEV; /** * 不抛出异常,直接Put * * @param jo JSONObject对象 * @param key 键 * @param value 值 */ public static void putNoThrows(JSONObject jo, String key, T value) { try { jo.put(key, value); } catch (JSONException e) { if (LOG) { e.printStackTrace(); } } } /** * 克隆一份JSON对象 * * @param from JSON对象 * @return 克隆后的JSON对象 */ public static JSONObject cloneNoThrows(JSONObject from) { try { // 不能用new JsonObject(JSONObject, String[])版本,因为不是深拷贝 return new JSONObject(from.toString()); } catch (JSONException e) { // 不太可能走到这里 if (LOG) { e.printStackTrace(); } return null; } } /** * 根据Android API的版本,来决定是使用Remove方法(API 19),还是通过反射来做(低于API 19) */ public static void remove(JSONArray jsonArray, int index) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { jsonArray.remove(index); } else { removeBelowAPI19(jsonArray, index); } } private static void removeBelowAPI19(JSONArray jsonArray, int index) { if (index < 0) { return; } try { Field valuesField = JSONArray.class.getDeclaredField("values"); valuesField.setAccessible(true); @SuppressWarnings("unchecked") List values = (List) valuesField.get(jsonArray); if (index >= values.size()) { return; } values.remove(index); } catch (Exception e) { if (LogRelease.LOGR) { e.printStackTrace(); } } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/helper/LogDebug.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.helper; import android.os.Build; import android.os.Debug; import android.util.Log; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.model.PluginInfo; /** * 只在Debug环境下才输出的各种日志,只有当setDebug为true时才会出来 *

* 注意:Release版不会输出,而且会被Proguard删除掉 * * @author RePlugin Team */ public class LogDebug { public static final String TAG = "RePlugin"; private static final String TAG_PREFIX = TAG + "."; /** * 是否输出日志?若用的是nolog编译出的AAR,则这里为False * 注意:所有使用LogDebug前,必须先用此字段来做判断。这样Release阶段才会被彻底删除掉 * 如: * * if (LogDebug.LOG) { * LogDebug.v("xxx", "yyy"); * } * */ public static final boolean LOG = RePluginInternal.FOR_DEV; /** * 允许Dump出一些内容 */ public static final boolean DUMP_ENABLED = LOG; /** * Send a verbose log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int v(String tag, String msg) { if (RePluginInternal.FOR_DEV) { return Log.v(TAG_PREFIX + tag, msg); } return -1; } /** * Send a verbose log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int v(String tag, String msg, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.v(TAG_PREFIX + tag, msg, tr); } return -1; } /** * Send a debug log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int d(String tag, String msg) { if (RePluginInternal.FOR_DEV) { return Log.d(TAG_PREFIX + tag, msg); } return -1; } /** * Send a debug log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int d(String tag, String msg, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.d(TAG_PREFIX + tag, msg, tr); } return -1; } /** * Send an info log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int i(String tag, String msg) { if (RePluginInternal.FOR_DEV) { return Log.i(TAG_PREFIX + tag, msg); } return -1; } /** * Send a inifo log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int i(String tag, String msg, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.i(TAG_PREFIX + tag, msg, tr); } return -1; } /** * Send a warning log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int w(String tag, String msg) { if (RePluginInternal.FOR_DEV) { return Log.w(TAG_PREFIX + tag, msg); } return -1; } /** * Send a warning log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int w(String tag, String msg, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.w(TAG_PREFIX + tag, msg, tr); } return -1; } /** * Send a warning log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param tr An exception to log */ public static int w(String tag, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.w(TAG_PREFIX + tag, tr); } return -1; } /** * Send an error log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int e(String tag, String msg) { if (RePluginInternal.FOR_DEV) { return Log.e(TAG_PREFIX + tag, msg); } return -1; } /** * Send a error log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int e(String tag, String msg, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.e(TAG_PREFIX + tag, msg, tr); } return -1; } /** * 打印当前内存占用日志,方便外界诊断。注意,这会显著消耗性能(约50ms左右) * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int printMemoryStatus(String tag, String msg) { if (RePluginInternal.FOR_DEV) { Debug.MemoryInfo mi = new Debug.MemoryInfo(); Debug.getMemoryInfo(mi); String mit = "desc=, memory_v_0_0_1, process=, " + IPC.getCurrentProcessName() + ", totalPss=, " + mi.getTotalPss() + ", dalvikPss=, " + mi.dalvikPss + ", nativeSize=, " + mi.nativePss + ", otherPss=, " + mi.otherPss + ", "; return Log.i(tag + "-MEMORY", mit + msg); } return -1; } /** * 打印当前内存占用日志,方便外界诊断。注意,这会显著消耗性能(约50ms左右) * * @param pi * @param load * @return */ public static int printPluginInfo(PluginInfo pi, int load) { long apk = pi.getApkFile().length(); long dex = pi.getDexFile().length(); return printMemoryStatus(TAG, "act=, loadLocked, flag=, Start, pn=, " + pi.getName() + ", type=, " + load + ", apk=, " + apk + ", odex=, " + dex + ", sys_api=, " + Build.VERSION.SDK_INT); } /** * @deprecated 为兼容卫士,以后干掉 */ public static final String PLUGIN_TAG = "ws001"; /** * @deprecated 为兼容卫士,以后干掉 */ public static final String MAIN_TAG = "ws000"; /** * @deprecated 为兼容卫士,以后干掉 */ public static final String MISC_TAG = "ws002"; public static final String LOADER_TAG = "createClassLoader"; /** * 去掉pn逻辑的tag */ public static final String TAG_NO_PN = "nopn"; } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/helper/LogRelease.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.helper; import android.util.Log; /** * 可同时在Debug和Release上输出的日志 * * @author RePlugin Team */ public class LogRelease { /** * 是否输出日志?因为这里的日志都是“必须要输出的”,故默认均为true(除非有极特殊需要) * 注意:所有使用LogRelease前,必须先用此字段来做判断 * 如: * * if (LogRelease.LOGR) { * LogRelease.v("xxx", "yyy"); * } * */ public static final boolean LOGR = true; /** * Send a verbose log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int v(String tag, String msg) { return Log.v(tag, msg); } /** * Send a verbose log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int v(String tag, String msg, Throwable tr) { return Log.v(tag, msg, tr); } /** * Send a debug log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int d(String tag, String msg) { return Log.d(tag, msg); } /** * Send a debug log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int d(String tag, String msg, Throwable tr) { return Log.d(tag, msg, tr); } /** * Send an info log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int i(String tag, String msg) { return Log.i(tag, msg); } /** * Send a inifo log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int i(String tag, String msg, Throwable tr) { return Log.i(tag, msg, tr); } /** * Send a warning log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int w(String tag, String msg) { return Log.w(tag, msg); } /** * Send a warning log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int w(String tag, String msg, Throwable tr) { return Log.w(tag, msg, tr); } /** * Send a warning log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param tr An exception to log */ public static int w(String tag, Throwable tr) { return Log.w(tag, tr); } /** * Send an error log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int e(String tag, String msg) { return Log.e(tag, msg); } /** * Send a error log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int e(String tag, String msg, Throwable tr) { return Log.e(tag, msg, tr); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfo.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.model; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.database.Cursor; import android.database.MatrixCursor; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import com.qihoo360.loader2.Constant; import com.qihoo360.loader2.PluginNativeLibsHelper; import com.qihoo360.loader2.V5FileInfo; import com.qihoo360.loader2.VMRuntimeCompat; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.utils.FileUtils; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.Serializable; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * 用来描述插件的描述信息。以Json来封装 * * @author RePlugin Team */ public class PluginInfo implements Serializable, Parcelable, Cloneable { private static final long serialVersionUID = -6531475023210445876L; private static final String TAG = "PluginInfo"; /** * 表示一个尚未安装的"纯APK"插件,其path指向下载完成后APK所在位置 */ public static final int TYPE_NOT_INSTALL = 10; /** * 表示一个释放过的"纯APK"插件,其path指向释放后的那个APK包

*

* 注意:此时可能还并未安装,仅仅是将APK拷贝到相应目录而已。例如,若是通过RePlugin.installDelayed时即为如此 */ public static final int TYPE_EXTRACTED = 11; /** * 表示为P-n已安装,其path指向释放后的那个Jar包(存在于app_plugins_v3,如clean-10-10-102.jar,一个APK) * * @deprecated 只用于旧的P-n插件,可能会废弃 */ public static final int TYPE_PN_INSTALLED = 1; /** * 内置插件 */ public static final int TYPE_BUILTIN = 2; /** * 表示为P-n还未安装,其path指向释放前的那个Jar包(在Files目录下,如p-n-clean.jar,有V5文件头) * * @deprecated 只用于旧的P-n插件,可能会废弃 */ public static final int TYPE_PN_JAR = 3; /** * 表示“不确定的框架版本号”,只有旧P-n插件才需要在Load期间得到框架版本号 * * @deprecated 只用于旧的P-n插件,可能会废弃 */ public static final int FRAMEWORK_VERSION_UNKNOWN = 0; public static final String PI_PKGNAME = "pkgname"; // √ public static final String PI_ALI = "ali"; // √ public static final String PI_LOW = "low"; // √ public static final String PI_HIGH = "high"; // √ public static final String PI_VER = "ver"; // √ public static final String PI_PATH = "path"; public static final String PI_TYPE = "type"; public static final String PI_NAME = "name"; // √ public static final String PI_UPINFO = "upinfo"; public static final String PI_DELINFO = "delinfo"; public static final String PI_COVERINFO = "coverinfo"; public static final String PI_COVER = "cover"; public static final String PI_VERV = "verv"; public static final String PI_USED = "used"; public static final String PI_FRM_VER = "frm_ver"; private transient final Map mJson = new ConcurrentHashMap(1 << 4); // 若插件需要更新,则会有此值 private PluginInfo mPendingUpdate; // 若插件需要卸载,则会有此值 private PluginInfo mPendingDelete; // 若插件需要同版本覆盖安装更新,则会有此值 private PluginInfo mPendingCover; private boolean mIsPendingCover; // 若当前为“新的PluginInfo”且为“同版本覆盖”,则为了能区分路径,则需要将此字段同步到Json文件中 // 若当前为“新的PluginInfo”,则其“父Info”是什么? // 通常当前这个Info会包裹在“mPendingUpdate/mPendingDelete/mPendingCover”内 // 此信息【不会】做持久化工作。下次重启进程后会消失 private PluginInfo mParentInfo; private PluginInfo(JSONObject jo) { initPluginInfo(jo); } private PluginInfo(String name, int low, int high, int ver) { put(PI_NAME, name); put(PI_LOW, low); put(PI_HIGH, high); put(PI_VER, ver); } private PluginInfo(String pkgName, String alias, int low, int high, int version, String path, int type) { // 如Low、High不正确,则给个默认值(等于应用的“最小支持协议版本”) if (low <= 0) { low = Constant.ADAPTER_COMPATIBLE_VERSION; } if (high <= 0) { high = Constant.ADAPTER_COMPATIBLE_VERSION; } put(PI_PKGNAME, pkgName); put(PI_ALI, alias); put(PI_NAME, makeName(pkgName, alias)); put(PI_LOW, low); put(PI_HIGH, high); setVersion(version); setPath(path); setType(type); } private void initPluginInfo(JSONObject jo) { final Iterator keys = jo.keys(); while (keys.hasNext()) { final String k = keys.next(); put(k, jo.opt(k)); } // 缓存“待更新”的插件信息 final JSONObject ujo = jo.optJSONObject(PI_UPINFO); if (ujo != null) { setPendingUpdate(new PluginInfo(ujo)); } // 缓存“待卸载”的插件信息 final JSONObject djo = jo.optJSONObject(PI_DELINFO); if (djo != null) { setPendingDelete(new PluginInfo(djo)); } // 缓存"待覆盖安装"的插件信息 final JSONObject cjo = jo.optJSONObject(PI_COVERINFO); if (cjo != null) { setPendingCover(new PluginInfo(cjo)); } // 缓存"待覆盖安装"的插件覆盖字段 setIsPendingCover(jo.optBoolean(PI_COVER)); } // 通过别名和包名来最终确认插件名 // 注意:老插件会用到"name"字段,同时出于性能考虑,故必须写在Json中。见调用此方法的地方 private String makeName(String pkgName, String alias) { if (!TextUtils.isEmpty(alias)) { return alias; } if (!TextUtils.isEmpty(pkgName)) { return pkgName; } return ""; } /** * 通过插件APK的MetaData来初始化PluginInfo

* 注意:框架内部接口,外界请不要直接使用 */ public static PluginInfo parseFromPackageInfo(PackageInfo pi, String path) { ApplicationInfo ai = pi.applicationInfo; String pn = pi.packageName; String alias = null; int low = 0; int high = 0; int ver = 0; Bundle metaData = ai.metaData; // 优先读取MetaData中的内容(如有),并覆盖上面的默认值 if (metaData != null) { // 获取插件别名(如有),如无则将"包名"当做插件名 alias = metaData.getString("com.qihoo360.plugin.name"); // 获取最低/最高协议版本(默认为应用的最小支持版本,以保证一定能在宿主中运行) low = metaData.getInt("com.qihoo360.plugin.version.low"); high = metaData.getInt("com.qihoo360.plugin.version.high"); // 获取插件的版本号。优先从metaData中读取,如无则使用插件的VersionCode ver = metaData.getInt("com.qihoo360.plugin.version.ver"); } // 针对有问题的字段做除错处理 if (low <= 0) { low = Constant.ADAPTER_COMPATIBLE_VERSION; } if (high <= 0) { high = Constant.ADAPTER_COMPATIBLE_VERSION; } if (ver <= 0) { ver = pi.versionCode; } PluginInfo pli = new PluginInfo(pn, alias, low, high, ver, path, PluginInfo.TYPE_NOT_INSTALL); // 获取插件的框架版本号 pli.setFrameworkVersionByMeta(metaData); return pli; } /** * (框架内部接口)通过传入的JSON的字符串来创建PluginInfo对象

* 注意:框架内部接口,外界请不要直接使用 */ public static PluginInfo parseFromJsonText(String joText) { JSONObject jo; try { jo = new JSONObject(joText); } catch (JSONException e) { if (LOG) { e.printStackTrace(); } return null; } // 三个字段是必备的,其余均可 if (jo.has(PI_PKGNAME) && jo.has(PI_TYPE) && jo.has(PI_VER)) { return new PluginInfo(jo); } else { return null; } } /** * 获取插件名,如果有别名,则返回别名,否则返回插件包名

* (注意:旧插件"p-n"的"别名"就是插件名) */ public String getName() { return get(PI_NAME, ""); } /** * 获取插件包名 */ public String getPackageName() { return get(PI_PKGNAME, ""); } /** * 获取插件别名 */ public String getAlias() { return get(PI_ALI, ""); } /** * 获取插件的版本 */ public int getVersion() { return get(PI_VER, 0); } /** * 获取最新的插件,目前所在的位置 */ public String getPath() { return get(PI_PATH, ""); } /** * 设置最新的插件,目前所在的位置

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 */ public void setPath(String path) { put(PI_PATH, path); } /** * 插件是否被使用过?只要释放过Dex的都认为是true * * @return 插件是否使用过? */ public boolean isUsed() { // 注意:该方法不单纯获取JSON中的值,而是根据插件类型(p-n、纯APK)、所处环境(新插件、当前插件)而定 if (getParentInfo() != null) { // 若PluginInfo是其它PluginInfo中的PendingUpdate,则返回那个PluginInfo的Used即可 return getParentInfo().isUsed(); } else { // 若是纯APK,且不是PendingUpdate,则直接从Json中获取 return get(PI_USED, false); } } /** * 设置插件是否被使用过

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 * * @param used 插件是否被使用过 */ public void setIsUsed(boolean used) { put(PI_USED, used); } /** * 获取Long型的,可用来对比的版本号 */ public long getVersionValue() { return get(PI_VERV, 0L); } /** * 插件的Dex是否已被优化(释放)了? * * @return */ public boolean isDexExtracted() { File f = getDexFile(); // 文件存在,且大小不为 0 时,才返回 true。 return f.exists() && FileUtils.sizeOf(f) > 0; } /** * 获取APK存放的文件信息

* 若为"纯APK"插件,则会位于app_p_a中;若为"p-n"插件,则会位于"app_plugins_v3"中

* 注意:若支持同版本覆盖安装的话,则会位于app_p_c中;

* * @return Apk所在的File对象 */ public File getApkFile() { return new File(getApkDir(), makeInstalledFileName() + ".jar"); } /** * 获取APK存放目录 * * @return */ public String getApkDir() { // 必须使用宿主的Context对象,防止出现“目录定位到插件内”的问题 Context context = RePluginInternal.getAppContext(); File dir; if (getIsPendingCover()) { dir = context.getDir(Constant.LOCAL_PLUGIN_APK_COVER_DIR, 0); } else { dir = context.getDir(Constant.LOCAL_PLUGIN_APK_SUB_DIR, 0); } return dir.getAbsolutePath(); } /** * 获取或创建(如果需要)某个插件的Dex目录,用于放置dex文件 * 注意:仅供框架内部使用;仅适用于Android 4.4.x及以下 * * @param dirSuffix 目录后缀 * @return 插件的Dex所在目录的File对象 */ private File getDexDir(File dexDir, String dirSuffix) { File dir = new File(dexDir, makeInstalledFileName() + dirSuffix); if (!dir.exists()) { dir.mkdir(); } return dir; } /** * 获取Extra Dex(优化前)生成时所在的目录

* 若为"纯APK"插件,则会位于app_p_od/xx_ed中;若为"p-n"插件,则会位于"app_plugins_v3_odex/xx_ed"中

* 若支持同版本覆盖安装的话,则会位于app_p_c/xx_ed中;

* 注意:仅供框架内部使用;仅适用于Android 4.4.x及以下 * * @return 优化前Extra Dex所在目录的File对象 */ public File getExtraDexDir() { return getDexDir(getDexParentDir(), Constant.LOCAL_PLUGIN_INDEPENDENT_EXTRA_DEX_SUB_DIR); } /** * 获取Extra Dex(优化后)生成时所在的目录

* 若为"纯APK"插件,则会位于app_p_od/xx_eod中;若为"p-n"插件,则会位于"app_plugins_v3_odex/xx_eod"中

* 若支持同版本覆盖安装的话,则会位于app_p_c/xx_eod中;

* 注意:仅供框架内部使用;仅适用于Android 4.4.x及以下 * * @return 优化后Extra Dex所在目录的File对象 */ public File getExtraOdexDir() { return getDexDir(getDexParentDir(), Constant.LOCAL_PLUGIN_INDEPENDENT_EXTRA_ODEX_SUB_DIR); } /** * 获取Dex(优化后)生成时所在的目录

* * Android O之前: * 若为"纯APK"插件,则会位于app_p_od中;若为"p-n"插件,则会位于"app_plugins_v3_odex"中

* 若支持同版本覆盖安装的话,则会位于app_p_c中;

* * Android O: * APK存放目录/oat/{cpuType} * * 注意:仅供框架内部使用 * @return 优化后Dex所在目录的File对象 */ public File getDexParentDir() { // 必须使用宿主的Context对象,防止出现“目录定位到插件内”的问题 Context context = RePluginInternal.getAppContext(); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { return new File(getApkDir() + File.separator + "oat" + File.separator + VMRuntimeCompat.getArtOatCpuType()); } else { if (getIsPendingCover()) { return context.getDir(Constant.LOCAL_PLUGIN_APK_COVER_DIR, 0); } else { return context.getDir(Constant.LOCAL_PLUGIN_APK_ODEX_SUB_DIR, 0); } } } /** * 获取Dex(优化后)所在的文件信息

* * Android O 之前: * 若为"纯APK"插件,则会位于app_p_od中;若为"p-n"插件,则会位于"app_plugins_v3_odex"中

* * Android O: * APK存放目录/oat/{cpuType}/XXX.odex * * 注意:仅供框架内部使用 * * @return 优化后Dex所在文件的File对象 */ public File getDexFile() { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { File dir = getDexParentDir(); return new File(dir, makeInstalledFileName() + ".odex"); } else { File dir = getDexParentDir(); return new File(dir, makeInstalledFileName() + ".dex"); } } /** * 根据类型来获取SO释放的路径

* 若为"纯APK"插件,则会位于app_p_n中;若为"p-n"插件,则会位于"app_plugins_v3_libs"中

* 若支持同版本覆盖安装的话,则会位于app_p_c中;

* 注意:仅供框架内部使用 * * @return SO释放路径所在的File对象 */ public File getNativeLibsDir() { File oldDir = getOldNativeLibsDir(); return new File(oldDir.getAbsolutePath(), VMRuntimeCompat.getArtOatCpuType()); } @Deprecated public File getOldNativeLibsDir(){ // 必须使用宿主的Context对象,防止出现“目录定位到插件内”的问题 Context context = RePluginInternal.getAppContext(); File dir; if (getIsPendingCover()) { dir = context.getDir(Constant.LOCAL_PLUGIN_APK_COVER_DIR, 0); } else { dir = context.getDir(Constant.LOCAL_PLUGIN_APK_LIB_DIR, 0); } return new File(dir, makeInstalledFileName()); } /** * 获取插件当前所处的类型。详细见TYPE_XXX常量 */ public int getType() { return get(PI_TYPE, 0); } /** * 设置插件当前所处的类型。详细见TYPE_XXX常量

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 */ public void setType(int type) { put(PI_TYPE, type); } /** * 是否已准备好了新版本? * * @return 是否已准备好 */ public boolean isNeedUpdate() { return mPendingUpdate != null; } /** * 获取将来要更新的插件的信息,将会在下次启动时才能被使用 * * @return 插件更新信息 */ public PluginInfo getPendingUpdate() { return mPendingUpdate; } /** * 设置插件的更新信息。此信息有可能等到下次才能被使用

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 * * @param info 插件的更新信息 */ public void setPendingUpdate(PluginInfo info) { mPendingUpdate = info; if (info != null) { put(PI_UPINFO, info.getJSON()); } else { mJson.remove(PI_UPINFO); } } /** * 是否需要删除插件? * * @return 是否需要卸载插件 */ public boolean isNeedUninstall() { return mPendingDelete != null; } /** * 获取将来要卸载的插件的信息,将会在下次启动时才能被使用 * * @return 插件卸载信息 */ public PluginInfo getPendingDelete() { return mPendingDelete; } /** * 设置插件的卸载信息。此信息有可能等到下次才能被使用

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 * * @param info 插件的卸载信息 */ public void setPendingDelete(PluginInfo info) { mPendingDelete = info; if (info != null) { put(PI_DELINFO, info.getJSON()); } else { mJson.remove(PI_DELINFO); } } /** * 是否已准备好了新待覆盖的版本? * * @return 是否已准备好 */ public boolean isNeedCover() { return mPendingCover != null; } /** * 获取将来要覆盖更新的插件的信息,将会在下次启动时才能被使用 * * @return 插件覆盖安装信息 */ public PluginInfo getPendingCover() { return mPendingCover; } /** * 设置插件的覆盖更新信息。此信息有可能等到下次才能被使用

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 * * @param info 插件覆盖安装信息 */ public void setPendingCover(PluginInfo info) { mPendingCover = info; if (info != null) { put(PI_COVERINFO, info.getJSON()); } else { mJson.remove(PI_COVERINFO); } } /** * 此PluginInfo是否包含同版本覆盖的字段?只在调用RePlugin.install方法才能看到

* 注意:仅框架内部使用 * * @return 是否包含同版本覆盖字段 */ public boolean getIsPendingCover() { return mIsPendingCover; } /** * 设置PluginInfo的同版本覆盖的字段

* 注意:仅框架内部使用 */ public void setIsPendingCover(boolean coverInfo) { mIsPendingCover = coverInfo; if (coverInfo) { put(PI_COVER, true); } else { mJson.remove(PI_COVER); } } /** * 获取最小支持宿主API的版本 */ public int getLowInterfaceApi() { return get(PI_LOW, Constant.ADAPTER_COMPATIBLE_VERSION); } /** * 获取最高支持宿主API的版本 * * @deprecated 可能会废弃 */ public int getHighInterfaceApi() { return get(PI_HIGH, Constant.ADAPTER_COMPATIBLE_VERSION); } /** * 获取框架的版本号

* 此版本号不同于“协议版本”。这直接关系到四大组件和其它模块的加载情况 */ public int getFrameworkVersion() { // 仅p-n插件在用 // 之所以默认为FRAMEWORK_VERSION_UNKNOWN,是因为在这里还只是读取p-n文件头,框架版本需要在loadDex阶段获得 return get(PI_FRM_VER, FRAMEWORK_VERSION_UNKNOWN); } /** * 设置框架的版本号

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 * * @param version 框架版本号 */ public void setFrameworkVersion(int version) { put(PI_FRM_VER, version); } /** * 根据MetaData来设置框架版本号

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 * * @param meta MetaData数据 */ public void setFrameworkVersionByMeta(Bundle meta) { int dfv = RePlugin.getConfig().getDefaultFrameworkVersion(); int frameVer = 0; if (meta != null) { frameVer = meta.getInt("com.qihoo360.framework.ver", dfv); } if (frameVer < 1) { frameVer = dfv; } setFrameworkVersion(frameVer); } // @hide public JSONObject getJSON() { return new JSONObject(mJson); } /** * 生成用于放入app_plugin_v3(app_p_n)等目录下的插件的文件名,其中:

* 1、“纯APK”方案:得到混淆后的文件名(规则见代码内容)

* 2、“旧p-n”和“内置插件”(暂定)方案:得到类似 shakeoff_10_10_103 这样的比较规范的文件名

* 3、只获取文件名,其目录和扩展名仍需在外面定义 * * @return 文件名(不含扩展名) */ public String makeInstalledFileName() { // 混淆插件名字,做法: // 1. 生成最初的名字:[插件包名(小写)][协议最低版本][协议最高版本][插件版本][ak] // 必须用小写和数字、无特殊字符,否则hashCode后会有一定的重复率 // 2. 将其生成出hashCode // 3. 将整体数字 - 88 String n = getPackageName().toLowerCase() + getLowInterfaceApi() + getHighInterfaceApi() + getVersion() + "ak"; int h = n.hashCode() - 88; return Integer.toString(h); } /** * 更新插件信息。通常是在安装完新插件后调用此方法

* 只更新一些必要的方法,如插件版本、路径、时间等。 * * @param info 新版本插件信息 */ public void update(PluginInfo info) { // TODO low high setVersion(info.getVersion()); setPath(info.getPath()); setType(info.getType()); setPackageName(info.getPackageName()); setAlias(info.getAlias()); } /** * 更新插件的所有信息 * @param info */ public void updateAll(PluginInfo info) { synchronized (mJson) { mJson.clear(); for (String key : info.mJson.keySet()) { mJson.put(key, info.mJson.get(key)); } } } /** * 若此Info为“新PluginInfo”,则这里返回的是“其父Info”的内容。通常和PendingUpdate有关 * * @return 父PluginInfo */ public PluginInfo getParentInfo() { return mParentInfo; } // @hide public void setParentInfo(PluginInfo parent) { mParentInfo = parent; } static PluginInfo createByJO(JSONObject jo) { if (jo == null || jo.length() == 0) return null; PluginInfo pi = new PluginInfo(jo); // 必须有包名或别名 if (TextUtils.isEmpty(pi.getName())) { return null; } return pi; } private void setPackageName(String pkgName) { if (!TextUtils.equals(pkgName, getPackageName())) { put(PI_PKGNAME, pkgName); } } private void setAlias(String alias) { if (!TextUtils.equals(alias, getAlias())) { put(PI_ALI, alias); } } private void setVersion(int version) { put(PI_VER, version); put(PI_VERV, buildCompareValue()); } // ------------------------- // Parcelable and Cloneable // ------------------------- public static final Creator CREATOR = new Creator() { @Override public PluginInfo createFromParcel(Parcel source) { return new PluginInfo(source); } @Override public PluginInfo[] newArray(int size) { return new PluginInfo[size]; } }; private PluginInfo(Parcel source) { JSONObject jo = null; String txt = null; try { txt = source.readString(); jo = new JSONObject(txt); } catch (JSONException e) { if (LogDebug.LOG) { LogDebug.e(TAG, "PluginInfo: mJson error! s=" + txt, e); } jo = new JSONObject(); } initPluginInfo(jo); } @Override public Object clone() { try { final String jsonText = getJSON().toString(); return new PluginInfo(new JSONObject(jsonText)); } catch (JSONException e) { e.printStackTrace(); } return null; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(getJSON().toString()); } @Override public String toString() { final StringBuilder b = new StringBuilder(); b.append("PInfo { "); toContentString(b); b.append(" }"); return b.toString(); } private void toContentString(StringBuilder b) { // 插件名 + 版本 + 框架版本 { b.append('<'); b.append(getName()).append(':').append(getVersion()); b.append('(').append(getFrameworkVersion()).append(')'); b.append("> "); } // 当前是否为PendingUpdate的信息 if (mParentInfo != null) { b.append("[HAS_PARENT] "); } // 插件类型 { if (getType() == TYPE_BUILTIN) { b.append("[BUILTIN] "); } else { b.append("[APK] "); } } // 插件是否已释放 if (isDexExtracted()) { b.append("[DEX_EXTRACTED] "); } // 插件是否“正在使用” if (RePlugin.isPluginRunning(getName())) { b.append("[RUNNING] "); } // 哪些进程使用 String[] processes = RePlugin.getRunningProcessesByPlugin(getName()); if (processes != null) { b.append("processes=").append(Arrays.toString(processes)).append(' '); } // 插件基本信息 if (mJson != null) { b.append("js=").append(mJson).append(' '); } // 和插件路径有关(除APK路径以外) { b.append("dex=").append(getDexFile()).append(' '); b.append("nlib=").append(getNativeLibsDir()); } } @Override public int hashCode() { return mJson.hashCode(); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (this.getClass() != obj.getClass()) { return false; } PluginInfo pluginInfo = (PluginInfo) obj; try { return pluginInfo.mJson.toString().equals(mJson.toString()); } catch (Exception e) { return false; } } // ------------------------- // FIXME 兼容老的P-n插件 // ------------------------- public static final String QUERY_COLUMNS[] = { PI_NAME, PI_LOW, PI_HIGH, PI_VER, PI_TYPE, "v5type", PI_PATH, "v5index", "v5offset", "v5length", "v5md5" }; private static final Pattern REGEX; static { REGEX = Pattern.compile(Constant.LOCAL_PLUGIN_FILE_PATTERN); } /** * */ public static final Comparator VERSION_COMPARATOR = new Comparator() { @Override public int compare(PluginInfo lhs, PluginInfo rhs) { long diff = lhs.getVersionValue() - rhs.getVersionValue(); if (diff > 0) { return 1; } else if (diff < 0) { return -1; } return 0; } }; public static final String format(String name, int low, int high, int ver) { return name + "-" + low + "-" + high + "-" + ver; } public static final PluginInfo build(File f) { Matcher m = REGEX.matcher(f.getName()); if (m == null || !m.matches()) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginInfo.build: skip, no match1, file=" + f.getAbsolutePath()); } return null; } MatchResult r = m.toMatchResult(); if (r == null || r.groupCount() != 4) { if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginInfo.build: skip, no match2, file=" + f.getAbsolutePath()); } return null; } String name = r.group(1); int low = Integer.parseInt(r.group(2)); int high = Integer.parseInt(r.group(3)); int ver = Integer.parseInt(r.group(4)); String path = f.getPath(); PluginInfo info = new PluginInfo(name, low, high, ver, TYPE_PN_INSTALLED, V5FileInfo.NONE_PLUGIN, path, -1, -1, -1, null); if (LOG) { LogDebug.d(PLUGIN_TAG, "PluginInfo.build: found plugin, name=" + info.getName() + " low=" + info.getLowInterfaceApi() + " high=" + info.getHighInterfaceApi() + " ver=" + info.getVersion()); } return info; } public static final PluginInfo buildFromBuiltInJson(JSONObject jo) { String pkgName = jo.optString("pkg"); String name = jo.optString(PI_NAME); String assetName = jo.optString(PI_PATH); if (TextUtils.isEmpty(name) || TextUtils.isEmpty(pkgName) || TextUtils.isEmpty(assetName)) { if (LogDebug.LOG) { LogDebug.d(TAG, "buildFromBuiltInJson: Invalid json. j=" + jo); } return null; } int low = jo.optInt(PI_LOW, Constant.ADAPTER_COMPATIBLE_VERSION); // Low应指向最低兼容版本 int high = jo.optInt(PI_HIGH, Constant.ADAPTER_COMPATIBLE_VERSION); // High同上 int ver = jo.optInt(PI_VER); PluginInfo info = new PluginInfo(pkgName, name, low, high, ver, assetName, TYPE_BUILTIN); // 从 json 中读取 frameVersion(可选) int frameVer = jo.optInt("frm"); if (frameVer < 1) { frameVer = RePlugin.getConfig().getDefaultFrameworkVersion(); } info.setFrameworkVersion(frameVer); return info; } public static final PluginInfo buildV5(String name, int low, int high, int ver, int v5Type, String v5Path, int v5index, int v5offset, int v5length, String v5md5) { return new PluginInfo(name, low, high, ver, TYPE_PN_JAR, v5Type, v5Path, v5index, v5offset, v5length, v5md5); } public static final PluginInfo build(Cursor cursor) { String name = cursor.getString(0); int v1 = cursor.getInt(1); int v2 = cursor.getInt(2); int v3 = cursor.getInt(3); int type = cursor.getInt(4); int v5Type = cursor.getInt(5); String path = cursor.getString(6); int v5index = cursor.getInt(7); int v5offset = cursor.getInt(8); int v5length = cursor.getInt(9); String v5md5 = cursor.getString(10); return new PluginInfo(name, v1, v2, v3, type, v5Type, path, v5index, v5offset, v5length, v5md5); } public static final PluginInfo build(String name, int low, int high, int ver) { return new PluginInfo(name, low, high, ver); } // Old Version private PluginInfo(String name, int low, int high, int ver, int type, int v5Type, String path, int v5index, int v5offset, int v5length, String v5md5) { this(name, name, low, high, ver, path, type); put("v5type", v5Type); put("v5index", v5index); put("v5offset", v5offset); put("v5length", v5length); put("v5md5", v5md5); } private String formatName() { return format(getName(), getLowInterfaceApi(), getHighInterfaceApi(), getVersion()); } final void to(MatrixCursor cursor) { cursor.newRow().add(getName()).add(getLowInterfaceApi()).add(getHighInterfaceApi()) .add(getVersion()).add(getType()).add(getV5Type()).add(getPath()) .add(getV5Index()).add(getV5Offset()).add(getV5Length()).add(getV5MD5()); } public final void to(Intent intent) { intent.putExtra(PI_NAME, getName()); intent.putExtra(PI_LOW, getLowInterfaceApi()); intent.putExtra(PI_HIGH, getHighInterfaceApi()); intent.putExtra(PI_VER, getVersion()); intent.putExtra(PI_TYPE, getType()); intent.putExtra("v5type", getV5Type()); intent.putExtra(PI_PATH, getPath()); intent.putExtra("v5index", getV5Index()); intent.putExtra("v5offset", getV5Offset()); intent.putExtra("v5length", getV5Length()); intent.putExtra("v5md5", getV5MD5()); } public final boolean deleteObsolote(Context context) { if (getType() != TYPE_PN_INSTALLED) { return true; } if (TextUtils.isEmpty(getPath())) { return true; } boolean rc = new File(getPath()).delete(); // 同时清除旧的SO库文件 // Added by Jiongxuan Zhang File libDir = getNativeLibsDir(); PluginNativeLibsHelper.clear(libDir); return rc; } /** * 判断是否可以替换(将NOT_INSTALLED变为INSTALLED)

* 条件:目前是BUILT_IN或NOT_INSTALLED,插件基本信息相同 * * @param info 要替换的Info对象 * @return 是否可以替换 * @deprecated 只用于旧的P-n插件,可能会废弃 */ public final boolean canReplaceForPn(PluginInfo info) { if (getType() != TYPE_EXTRACTED && info.getType() == TYPE_EXTRACTED && getName().equals(info.getName()) && getLowInterfaceApi() == info.getLowInterfaceApi() && getHighInterfaceApi() == info.getHighInterfaceApi() && getVersion() == info.getVersion()) { return true; } return false; } public final boolean match() { boolean isBlocked = RePlugin.getConfig().getCallbacks().isPluginBlocked(this); if (LOG) { if (isBlocked) { LogDebug.d(PLUGIN_TAG, "match result: plugin is blocked"); } } return !isBlocked; } private final long buildCompareValue() { long x; x = getHighInterfaceApi() & 0x7fff; long v1 = x << (32 + 16); x = getLowInterfaceApi() & 0xffff; long v2 = x << 32; long v3 = getVersion(); return v1 | v2 | v3; } /** * 判断是否为p-n类型的插件? * * @return 是否为p-n类型的插件 * @deprecated 只用于旧的P-n插件,可能会废弃 */ public boolean isPnPlugin() { int type = getType(); return type == TYPE_PN_INSTALLED || type == TYPE_PN_JAR || type == TYPE_BUILTIN; } /** * V5类型 * * @deprecated 只用于旧的P-n插件,可能会废弃 */ public int getV5Type() { return get("v5type", V5FileInfo.NONE_PLUGIN); } /** * V5类型:复合插件文件索引 * * @deprecated 只用于旧的P-n插件,可能会废弃 */ public int getV5Index() { return get("v5index", -1); } /** * V5类型:复合插件文件偏移 * * @deprecated 只用于旧的P-n插件,可能会废弃 */ public int getV5Offset() { return get("v5offset", -1); } /** * V5类型:复合插件文件长度 * * @deprecated 只用于旧的P-n插件,可能会废弃 */ public int getV5Length() { return get("v5length", -1); } /** * V5类型:复合插件文件MD5 * * @deprecated 只用于旧的P-n插件,可能会废弃 */ public String getV5MD5() { return get("v5md5", ""); } //// private T get(String name, T def) { synchronized (mJson) { final Object obj = mJson.get(name); return (def.getClass().isInstance(obj)) ? (T) obj : def; } } public void put(String key, T value) { if (key == null || value == null) return; mJson.put(key, value); //value & key must not null } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/model/PluginInfoList.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.model; import android.content.Context; import android.text.TextUtils; import android.util.Log; import com.qihoo360.loader2.Constant; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.utils.Charsets; import com.qihoo360.replugin.utils.FileUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** * @author RePlugin Team */ public class PluginInfoList implements Iterable { private static final String TAG = "PluginInfoList"; private final ConcurrentHashMap mMap = new ConcurrentHashMap<>(); public void add(PluginInfo pi) { addToMap(pi); } /** * 强制更新内存列表,比如安装后需要立即更新p.l的情况 * @param pi */ public void addForce(PluginInfo pi) { if (pi == null) return; if (!TextUtils.isEmpty(pi.getName())) mMap.put(pi.getName(), pi); if (!TextUtils.isEmpty(pi.getAlias())) mMap.put(pi.getAlias(), pi); } public void remove(String pn) { mMap.remove(pn); } public PluginInfo get(String pn) { return pn != null ? mMap.get(pn) : null; } public List cloneList() { return new ArrayList<>(getCopyValues()); } public boolean load(Context context) { try { // 1. 读出字符串 final File f = getFile(context); final String result = FileUtils.readFileToString(f, Charsets.UTF_8); if (TextUtils.isEmpty(result)) { if (LogDebug.LOG) { LogDebug.e(TAG, "load: Read Json error!"); } return false; } // 2. 解析出JSON final JSONArray jArr = new JSONArray(result); for (int i = 0; i < jArr.length(); i++) { final JSONObject jo = jArr.optJSONObject(i); final PluginInfo pi = PluginInfo.createByJO(jo); if (pi == null) { if (LogDebug.LOG) { LogDebug.e(TAG, "load: PluginInfo Invalid. Ignore! jo=" + jo); } continue; } //block状态的插件丢弃 if (RePlugin.getConfig().getCallbacks().isPluginBlocked(pi)) { continue; } addToMap(pi); } return true; } catch (IOException e) { if (LogDebug.LOG) { LogDebug.e(TAG, "load: Load error!", e); } } catch (JSONException e) { if (LogDebug.LOG) { LogDebug.e(TAG, "load: Parse Json Error!", e); } } return false; } public boolean save(Context context) { try { final File f = getFile(context); final JSONArray jsonArr = new JSONArray(); for (PluginInfo i : getCopyValues()) jsonArr.put(i.getJSON()); if (LogDebug.LOG) { Log.d(LogDebug.TAG_NO_PN, "save json into p.l=" + jsonArr.toString()); } FileUtils.writeStringToFile(f, jsonArr.toString(), Charsets.UTF_8); return true; } catch (IOException e) { if (LogDebug.LOG) { e.printStackTrace(); } return false; } } @Override public Iterator iterator() { return getCopyValues().iterator(); } /// private Collection getCopyValues() { return new HashSet(mMap.values()); //是否有必要去重??? } private void addToMap(PluginInfo pi) { if (pi == null) return; if (!TextUtils.isEmpty(pi.getName())) updateMap(pi.getName(), pi); if (!TextUtils.isEmpty(pi.getAlias())) updateMap(pi.getAlias(), pi); } private void updateMap(String name, PluginInfo info) { if (mMap.contains(info)) { return; } if (LogDebug.LOG) { Log.d(TAG, "updateMap=" + info + ",address=" + System.identityHashCode(info)); } //解决其他进程重启后,重新加载,导致内存中的PluginInfo对象被替换的问题 if (mMap.containsKey(name)) { PluginInfo oriInfo = mMap.get(name); if (oriInfo != null) { oriInfo.updateAll(info); mMap.put(name, oriInfo); } else { mMap.put(name, info); } } else { mMap.put(name, info); } } private File getFile(Context context) { final File d = context.getDir(Constant.LOCAL_PLUGIN_APK_SUB_DIR, 0); return new File(d, "p.l"); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginFastInstallProvider.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.packages; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; import com.qihoo360.loader2.PMF; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; /** * 在UI进程中安装插件的Provider。有关具体说明,请参见PluginInstallProviderProxy的说明 * * @author RePlugin Team * @see PluginFastInstallProviderProxy */ public class PluginFastInstallProvider extends ContentProvider { private static final String TAG = "PluginFastInstallPv"; public static final String AUTHORITY = IPC.getPackageName() + ".loader.p.pip"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); public static final String SELECTION_INSTALL = "inst"; public static final String KEY_PLUGIN_INFO = "pi"; // 此类不是个标准的Provider(只是命令接受),不需要URI_MATCHER这么复杂的处理形式 // private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); static ContentValues makeInstallValues(PluginInfo pi) { ContentValues cv = new ContentValues(); cv.put(KEY_PLUGIN_INFO, pi.getJSON().toString()); return cv; } @Override public boolean onCreate() { // Nothing return true; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { if (LogDebug.LOG) { LogDebug.d(TAG, "update: cv=" + values); } if (TextUtils.isEmpty(selection)) { return 0; } switch (selection) { case SELECTION_INSTALL: { return install(values); } } return 0; } private int install(ContentValues cv) { if (cv == null) { return 0; } String pit = cv.getAsString(KEY_PLUGIN_INFO); if (TextUtils.isEmpty(pit)) { return 0; } PluginInfo pi = PluginInfo.parseFromJsonText(pit); // 开始加载ClassLoader ClassLoader cl = PMF.getLocal().loadPluginClassLoader(pi); if (cl != null) { return 1; } else { return 0; } } @Override public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Nothing return null; } @Override public String getType( Uri uri) { // Nothing return null; } @Override public Uri insert( Uri uri, ContentValues values) { // Nothing return null; } @Override public int delete( Uri uri, String selection, String[] selectionArgs) { // Nothing return 0; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginFastInstallProviderProxy.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.packages; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.os.RemoteException; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; /** * 在UI进程中安装插件。借助其Provider来做。

* 这里有两个好处:

* 1、Android 7.0及以上,若在UI进程中优化Dex,则比非UI进程要快4~5倍(如loan插件从4800ms到900ms)

* 这和“JIT/AOT”和“空闲时编译”的机制有关,参见Google官方说明,不在此赘述。

* 2、系统在加载Dex后会将其mmap到内存中,若仅是为了“安装”而不运行,则完全没必要在“当前进程”中去占用这些内容

* 而更应该放到容易释放的UI进程中来做。 * * @author RePlugin Team */ public class PluginFastInstallProviderProxy { private static final String TAG = "PluginFastInstallPr"; private static final byte[] LOCK = new byte[0]; private static ContentProviderClient sProvider; /** * 根据PluginInfo的信息来通知UI进程去“安装”插件,包括释放Dex等。 * * @param context Context对象 * @param pi PluginInfo对象 * @return 安装是否成功 */ public static boolean install(Context context, PluginInfo pi) { // 若Dex已经释放,则无需处理,直接返回 if (pi.isDexExtracted()) { if (LogDebug.LOG) { LogDebug.w(TAG, "install: Already loaded, no need to install. pi=" + pi); } return true; } ContentProviderClient cpc = getProvider(context); if (cpc == null) { return false; } try { int r = cpc.update(PluginFastInstallProvider.CONTENT_URI, PluginFastInstallProvider.makeInstallValues(pi), PluginFastInstallProvider.SELECTION_INSTALL, null); if (LogDebug.LOG) { LogDebug.i(TAG, "install: Install. pi=" + pi + "; result=" + r); } return r > 0; } catch (RemoteException e) { e.printStackTrace(); } return false; } private static ContentProviderClient getProvider(Context context) { if (sProvider != null) { return sProvider; } synchronized (LOCK) { if (sProvider != null) { return sProvider; } ContentResolver cr = context.getContentResolver(); if (cr == null) { // 不太可能,但保险起见还是返回 if (LogRelease.LOGR) { LogRelease.e(LogDebug.PLUGIN_TAG, "pipp.gp: cr n"); } return null; } ContentProviderClient cpc = cr.acquireContentProviderClient(PluginFastInstallProvider.CONTENT_URI); if (cpc == null) { // 获取Provider失败,可能性不大,先返回空 if (LogRelease.LOGR) { LogRelease.e(LogDebug.PLUGIN_TAG, "pipp.gp: cpc n"); } return null; } // 缓存下来,以备后用 sProvider = cpc; return cpc; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginInfoUpdater.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.packages; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.text.TextUtils; import com.qihoo360.loader.utils.LocalBroadcastManager; import com.qihoo360.loader2.MP; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; /** * 当有插件信息需要通知各进程更新时触发 * * @author RePlugin Team */ public class PluginInfoUpdater { private static final String TAG = "PluginInfoUpdater"; private static final String ACTION_UPDATE_INFO = "com.qihoo360.replugin.pms.ACTION_UPDATE_INFO"; public static final String ACTION_UNINSTALL_PLUGIN = "ACTION_UNINSTALL_PLUGIN"; public static void register(Context context) { IntentFilter filter = new IntentFilter(ACTION_UPDATE_INFO); LocalBroadcastManager.getInstance(context).registerReceiver(new UpdateReceiver(), filter); } static void updateIsUsed(Context context, String pluginName, boolean used) { if (LogDebug.LOG) { LogDebug.i(TAG, "updateIsUsed: Prepare to send broadcast, pn=" + pluginName + "; used=" + used); } Intent intent = new Intent(ACTION_UPDATE_INFO); intent.putExtra("pn", pluginName); intent.putExtra("used", used); IPC.sendLocalBroadcast2All(context, intent); } private static class UpdateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (TextUtils.equals(intent.getAction(), ACTION_UPDATE_INFO)) { PluginInfoUpdater.onReceiveUpdateInfo(intent); } } } private static boolean onReceiveUpdateInfo(Intent intent) { if (LogDebug.LOG) { LogDebug.i(TAG, "onReceiveUpdateInfo: in=" + intent); } String pn = intent.getStringExtra("pn"); if (TextUtils.isEmpty(pn)) { return false; } // 获取“不经过Clone”的PluginInfo,因为要修改 PluginInfo pi = MP.getPlugin(pn, false); if (pi == null) { return false; } // 若填写了used,则修改它 if (intent.hasExtra("used")) { boolean used = intent.getBooleanExtra("used", false); if (LogDebug.LOG) { LogDebug.i(TAG, "onReceiveUpdateInfo: pn=" + pn + "; setIsUsed=" + used); } pi.setIsUsed(used); } return true; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginManagerProxy.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.packages; import android.os.RemoteException; import android.text.TextUtils; import com.qihoo360.loader2.IPluginHost; import com.qihoo360.loader2.MP; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import java.util.List; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.PLUGIN_TAG; /** * 用于各进程(包括常驻自己)缓存 PluginManagerServer 的Binder实现 * * @author RePlugin Team * @see PluginManagerServer */ public class PluginManagerProxy { private static final String TAG = "PluginManagerClient"; private static IPluginManagerServer sRemote; private static boolean sRunningSynced; private static PluginRunningList sRunningList = new PluginRunningList(); static { sRunningList.setProcessInfo(IPC.getCurrentProcessName(), IPC.getCurrentProcessId()); } /** * 连接到常驻进程,并缓存IPluginManagerServer对象 * * @param host IPluginHost对象 * @throws RemoteException 和常驻进程通讯出现异常 */ public static void connectToServer(IPluginHost host) throws RemoteException { if (sRemote != null) { if (LogDebug.LOG) { LogDebug.e(TAG, "connectToServer: Already connected! host=" + sRemote); } return; } sRemote = host.fetchManagerServer(); } /** * 当常驻进程出现异常时,断开当前进程与其的缓存的链接 */ public static void disconnect() { sRemote = null; // 表示常驻挂掉,下回需同步 sRunningSynced = false; // 不要清除"正在运行插件"的列表,毕竟插件还在该进程中运行着。在下次常驻启动时会自动同步过去 // sRunningList.clear(); } /** * 调用常驻进程的Server端去加载插件列表,方便之后使用 * * @return PluginInfo的列表 * @throws RemoteException 和常驻进程通讯出现异常 */ public static List load() throws RemoteException { // 不判断sRemote在不在,因为本应该在sRemote获取后就马上调用 return sRemote.load(); } /** * 预安装内置插件到app_p_a目录。因为考虑到内置插件的特殊性和缓存中针对内置插件需要做一些处理,最开始我们需要认为内置插件就已经属于安装了,读取列表应该能读取到才行。 * 所以需要把插件信息写入到p.l中,以便于后续的加载和更新都可以读到。 * 注意:只会在首次启动应用的时候做这个事 * @param builtins 内置插件列表 * @return 安装完后的插件列表,理论上就是内置插件 * @throws RemoteException */ public static List preInstallBuiltins(List builtins) throws RemoteException { return sRemote.preInstallBuiltins(builtins); } /** * 调用常驻进程的Server端去更新插件列表 * * @return PluginInfo的列表 * @throws RemoteException 和常驻进程通讯出现异常 */ public static List updateAllPlugins() throws RemoteException { // 不判断sRemote在不在,因为本应该在sRemote获取后就马上调用 return sRemote.load(); } /** * 去常驻进程更新isUsed状态,并发送到所有进程中更新 * * @param name 插件名字 * @param path 插件释放的路径 * @param type 插件状态类型 * @param used 插件是否已被使用 */ public static void updateUsedIfNeeded(String name, String path, int type, boolean used) throws RemoteException { PluginInfo pi = MP.getPlugin(name, false); if (pi == null) { // 不太可能到这里 return; } if (pi.isUsed() == used) { // 已经改变了?那就不做处理 if (LOG) { LogDebug.i(TAG, "updateUsedIfNeeded: pi.isUsed == used, ignore. used=" + used + "; pn=" + name); } return; } if (sRemote == null) { // 常驻已挂掉,可以认为无需处理 if (LogRelease.LOGR) { LogRelease.e(PLUGIN_TAG, "pmc.uuin: s=null"); } return; } // 去常驻进程更新状态 sRemote.updateUsedNew(name, path, type, used); } /** * 更新插件信息到常驻进程和p.l文件,一般是首次内置插件加载完毕后,需要调用 * * @param plugin 插件名字 * @param type 插件的类型 * @param path 插件的路径 * @throws RemoteException */ public static void updateTP(String plugin, int type, String path) throws RemoteException { sRemote.updateTP(plugin, type, path); } /** * 首先检查本地进程是否使用,然后再调用常驻进程的Server端去判断 * * @param pluginName 插件名 * @return 是否运行 * @throws RemoteException 和常驻进程通讯出现异常 */ public static boolean isPluginRunning(String pluginName) throws RemoteException { if (sRunningList.isRunning(pluginName)) { // 当前进程就已经运行了,直接返回 return true; } if (sRemote == null) { // 常驻已挂掉,可以认为先返回False if (LogRelease.LOGR) { LogRelease.e(PLUGIN_TAG, "pmp.ipr: s=null"); } return false; } // 去常驻进程查其它进程是否运行 return sRemote.isPluginRunning(pluginName, null); } /** * 首先检查指定进程是否和本地相同,然后再调用常驻进程的Server端去判断 * * @param pluginName 插件名 * @param process 指定进程 * @return 是否运行 * @throws RemoteException 和常驻进程通讯出现异常 */ public static boolean isPluginRunningInProcess(String pluginName, String process) throws RemoteException { if (TextUtils.equals(process, IPC.getCurrentProcessName())) { // 要查的就是当前所在进程?那直接从当前进程中取表即可 return sRunningList.isRunning(pluginName); } else { // 要查的不在当前进程?则通过远端去查 if (sRemote == null) { // 常驻已挂掉,可以认为先返回False if (LogRelease.LOGR) { LogRelease.e(PLUGIN_TAG, "pmp.iprip: s=null"); } return false; } // 去常驻进程查其它进程是否运行 return sRemote.isPluginRunning(pluginName, process); } } /** * 同步当前进程"已加载插件列表"到常驻进程的Server端中 * * @throws RemoteException 和常驻进程通讯出现异常 */ public static void syncRunningPlugins() throws RemoteException { if (sRunningSynced) { // 已经同步过,无需再次同步,直到常驻进程挂掉 return; } if (!sRunningList.hasRunning()) { // 没有正在运行的插件,无需同步。若该进程被刚刚加载时会出现此情况 return; } // 不判断sRemote在不在,因为本应该在sRemote获取后就马上调用 sRemote.syncRunningPlugins(sRunningList); sRunningSynced = true; } /** * 添加插件到"当前进程的正在运行插件列表",并同步到Server端 * * @param pluginName 插件名 */ public static void addToRunningPluginsNoThrows(String pluginName) { // 本地先加一份 sRunningList.add(pluginName); // 通知常驻在总表中也加一份 if (sRemote != null) { // 有可能常驻进程已经被干掉,那就等下次调用syncRunningPlugins时才同步。 // 而当它存在时就直接加入列表即可 try { sRemote.addToRunningPlugins(sRunningList.mProcessName, sRunningList.mPid, pluginName); } catch (RemoteException e) { // 常驻进程出现问题,先不管,等下次启动时再同步 if (LogRelease.LOGR) { e.printStackTrace(); } } } } /** * 调用常驻进程的Server端去拉取正在运行的插件列表。如有异常,则只获取本地即可 * * @return 正在运行的插件列表 */ public static PluginRunningList getRunningPluginsNoThrows() { PluginRunningList rl = null; // 只有常驻进程在时才获取 if (sRemote != null) { try { rl = new PluginRunningList(sRemote.getRunningPlugins()); } catch (RemoteException e) { // 常驻进程出现问题 if (LogRelease.LOGR) { e.printStackTrace(); } } } // 没有获取到?则获取本地运行插件列表 if (rl == null) { rl = new PluginRunningList(sRunningList); } return rl; } /** * 获取正在运行此插件的进程名列表。若常驻进程无法使用,则只获取当前进程的运行情况 * * @param pluginName 要查询的插件名 * @return 正在运行此插件的进程名列表。一定不会为Null */ public static String[] getRunningProcessesByPluginNoThrows(String pluginName) { // 只有常驻进程在时才获取 if (sRemote != null) { try { return sRemote.getRunningProcessesByPlugin(pluginName); } catch (RemoteException e) { // 常驻进程出现问题 if (LogRelease.LOGR) { e.printStackTrace(); } } } // 没有获取到?则只判断本地是否运行即可 String[] r; if (sRunningList.isRunning(pluginName)) { r = new String[]{sRunningList.mProcessName}; } else { r = new String[0]; } return r; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginManagerServer.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.packages; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import com.qihoo360.loader2.CertUtils; import com.qihoo360.loader2.MP; import com.qihoo360.loader2.PluginNativeLibsHelper; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginEventCallbacks; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.model.PluginInfoList; import com.qihoo360.replugin.utils.FileUtils; import com.qihoo360.replugin.utils.pkg.PackageFilesUtil; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogDebug.TAG_NO_PN; /** * 插件管理器。用来控制插件的安装、卸载、获取等。运行在常驻进程中

* 补充:涉及到插件交互、运行机制有关的管理器,在IPluginHost中

* TODO 待p-n型插件逐渐变少后,将涉及到存储等逻辑,从PmHostSvc中重构后移到这里

*

* 注意:插件框架内部使用,外界请不要调用。 * * @author RePlugin Team */ public class PluginManagerServer { private static final String TAG = "PluginManagerServer"; private static final byte[] LOCKER_PROCESS_KILLED = new byte[0]; private static final byte[] LOCKER = new byte[0]; private Context mContext; // 存储所有插件的信息 // TODO 目前这里只存新插件信息,不做额外的处理。除此之外,在PmHostSvc和PmBase中存放着所有插件信息,将来会优化这里 private PluginInfoList mList = new PluginInfoList(); private Map mProcess2PluginsMap = new ConcurrentHashMap<>(); private IPluginManagerServer mStub; public PluginManagerServer(Context context) { mContext = context; mStub = new Stub(); } public IPluginManagerServer getService() { return mStub; } /** * 若某个客户端进程(除常驻进程外的进程)被干掉时调用此方法 * * @param processName 被干掉的进程名 */ public void onClientProcessKilled(String processName) { synchronized (LOCKER_PROCESS_KILLED) { mProcess2PluginsMap.remove(processName); if (LogDebug.LOG) { LogDebug.d(TAG, "onClientProcessKilled: Killed! process=" + processName + "; remains=" + mProcess2PluginsMap); } } } private List loadLocked() { if (!mList.load(mContext)) { return null; } // 执行“更新或删除Pending”插件,并返回结果 return updateAllLocked(); } private List installBuiltins(List builtins) { if (builtins != null) { for (PluginInfo builtin : builtins) { if (LogDebug.LOG) { Log.d(LogDebug.TAG_NO_PN, "installBuiltins, plugin=" + builtin + ",address=" + System.identityHashCode(builtin)); } mList.add(builtin); } } mList.save(mContext); return mList.cloneList(); } private List updateAllLocked() { // 判断是否需要更新插件(只有未运行的才可以) updateAllIfNeeded(); // TODO 扫描一下,看看文件在不在 return mList.cloneList(); } private PluginInfo installLocked(String path) { final boolean verifySignEnable = RePlugin.getConfig().getVerifySign(); final int flags = verifySignEnable ? PackageManager.GET_META_DATA | PackageManager.GET_SIGNATURES : PackageManager.GET_META_DATA; // 1. 读取APK内容 PackageInfo pi = mContext.getPackageManager().getPackageArchiveInfo(path, flags); if (pi == null) { if (LogDebug.LOG) { LogDebug.e(TAG, "installLocked: Not a valid apk. path=" + path); } RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.READ_PKG_INFO_FAIL); return null; } // 2. 校验插件签名 if (verifySignEnable) { if (!verifySignature(pi, path)) { return null; } } // 3. 解析出名字和三元组 PluginInfo instPli = PluginInfo.parseFromPackageInfo(pi, path); if (LogDebug.LOG) { LogDebug.i(TAG, "installLocked: Info=" + instPli); } instPli.setType(PluginInfo.TYPE_NOT_INSTALL); // 若要安装的插件版本小于或等于当前版本,则安装失败 // NOTE 绝大多数情况下,应该在调用RePlugin.install方法前,根据云端回传的信息来判断,以防止下载旧插件,浪费流量 // NOTE 这里仅做双保险,或通过特殊渠道安装时会有用 // 注意:这里必须用“非Clone过的”PluginInfo,因为要修改里面的内容 PluginInfo curPli = MP.getPlugin(instPli.getName(), false); if (curPli != null) { if (LogDebug.LOG) { LogDebug.i(TAG, "installLocked: Has installed plugin. current=" + curPli); } // 版本较老?直接返回 final int checkResult = checkVersion(instPli, curPli); if (checkResult < 0) { RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.VERIFY_VER_FAIL); return null; } else if (checkResult == 0){ instPli.setIsPendingCover(true); } } // 4. 将合法的APK改名后,移动(或复制,见RePluginConfig.isMoveFileWhenInstalling)到新位置 // 注意:不能和p-n的最终释放位置相同,因为管理方式不一样 if (!copyOrMoveApk(path, instPli)) { RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.COPY_APK_FAIL); return null; } // 5. 从插件中释放 So 文件 PluginNativeLibsHelper.install(instPli.getPath(), instPli.getNativeLibsDir()); // 6. 若已经安装旧版本插件,则尝试更新插件信息,否则直接加入到列表中 if (curPli != null) { if (LogDebug.LOG) { Log.d(TAG_NO_PN, "cur exist pinfo,curinfo=" + curPli + ",address=" + System.identityHashCode(curPli)); } updateOrLater(curPli, instPli); Log.d(TAG_NO_PN, "updateOrLater,mList.get=" + mList.get(curPli.getName()) + ",address=" + System.identityHashCode(mList.get(curPli.getName()))); } else { if (LogDebug.LOG) { Log.d(TAG_NO_PN, "new install,pinfo=" + instPli + ",address=" + System.identityHashCode(instPli)); } mList.add(instPli); } // 7. 保存插件信息到文件中,下次可直接使用 mList.save(mContext); return instPli; } private boolean verifySignature(PackageInfo pi, String path) { if (!CertUtils.isPluginSignatures(pi)) { if (LogDebug.LOG) { LogDebug.d(TAG, "verifySignature: invalid cert: " + " name=" + pi); } RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.VERIFY_SIGN_FAIL); return false; } if (LogDebug.LOG) { LogDebug.d(TAG, "verifySignature: valid cert: " + " name=" + pi); } return true; } private int checkVersion(PluginInfo instPli, PluginInfo curPli) { // 支持插件同版本覆盖安装? // 若现在要安装的,与之前的版本相同,则覆盖掉之前的版本; if (instPli.getVersion() == curPli.getVersion()) { if (LogDebug.LOG) { LogDebug.d(TAG, "isSameVersion: same version. " + "inst_ver=" + instPli.getVersion() + "; cur_ver=" + curPli.getVersion()); } return 0; } // 若现在要安装的,比之前的版本还要旧,则忽略更新; if (instPli.getVersion() < curPli.getVersion()) { if (LogDebug.LOG) { LogDebug.e(TAG, "checkVersion: Older than current, install fail. pn=" + curPli.getName() + "; inst_ver=" + instPli.getVersion() + "; cur_ver=" + curPli.getVersion()); } return -1; } // 已有“待更新版本”? // 若现在要安装的,比“待更新版本”还要旧,则也可以忽略 PluginInfo curUpdatePli = curPli.getPendingUpdate(); if (curUpdatePli != null && instPli.getVersion() < curUpdatePli.getVersion()) { if (LogDebug.LOG) { LogDebug.e(TAG, "checkVersion: Older than updating plugin. Ignore. pn=" + curPli.getName() + "; " + "cur_ver=" + curPli.getVersion() + "; old_ver=" + curUpdatePli.getVersion() + "; new_ver=" + instPli.getVersion()); } return -1; } return 1; } private boolean copyOrMoveApk(String path, PluginInfo instPli) { File srcFile = new File(path); File newFile = instPli.getApkFile(); // 插件已被释放过一次?通常“同版本覆盖安装”时,覆盖次数超过2次的会出现此问题 // 此时,直接删除安装路径下的文件即可,这样就可以直接Move/Copy了 if (newFile.exists()) { FileUtils.deleteQuietly(newFile); } // 将源APK文件移动/复制到安装路径下 try { if (RePlugin.getConfig().isMoveFileWhenInstalling()) { FileUtils.moveFile(srcFile, newFile); } else { FileUtils.copyFile(srcFile, newFile); } } catch (IOException e) { if (LogRelease.LOGR) { LogRelease.e(TAG, "copyOrMoveApk: Copy/Move Failed! src=" + srcFile + "; dest=" + newFile, e); } return false; } instPli.setPath(newFile.getAbsolutePath()); instPli.setType(PluginInfo.TYPE_EXTRACTED); return true; } private void updateOrLater(PluginInfo curPli, PluginInfo instPli) { if (LogDebug.LOG) { LogDebug.d(TAG, "updateOrLater: Need update. pn=" + curPli.getName() + "; cur_ver=" + curPli.getVersion() + "; update_ver=" + instPli.getVersion()); } // 已有“待更新版本”? PluginInfo curUpdatePli = curPli.getPendingUpdate(); if (curUpdatePli != null) { updatePendingUpdate(curPli, instPli, curUpdatePli); // 由于"打算要更新"的前提是插件正在被运行,且下次重启时会清空这个信息,既然这次只是替换"打算要更新"的插件信息 // 则不必再做后面诸如"插件是否存在"等判断,直接返回即可 return; } // 正在运行?Later到下次使用时再释放。否则直接开始更新 if (RePlugin.isPluginRunning(curPli.getName())) { if (LogDebug.LOG) { LogDebug.w(TAG, "updateOrLater: Plugin is running. Later. pn=" + curPli.getName()); } if (instPli.getVersion() > curPli.getVersion()) { // 高版本升级 curPli.setPendingUpdate(instPli); curPli.setPendingDelete(null); curPli.setPendingCover(null); if (LogDebug.LOG) { LogDebug.w(TAG, "updateOrLater: Plugin need update high version. clear PendingDelete and PendingCover."); } } else if (instPli.getVersion() == curPli.getVersion()){ // 同版本覆盖 curPli.setPendingCover(instPli); curPli.setPendingDelete(null); // 注意:并不需要对PendingUpdate信息做处理,因为此前的updatePendingUpdate方法时就已经返回了 if (LogDebug.LOG) { LogDebug.w(TAG, "updateOrLater: Plugin need update same version. clear PendingDelete."); } } // 设置其Parent为curPli,在PmBase.newPluginFound时会用到 instPli.setParentInfo(curPli); } else { if (LogDebug.LOG) { LogDebug.i(TAG, "updateOrLater: Not running. Update now! pn=" + curPli.getName()); } updateNow(curPli, instPli); mList.addForce(instPli); } } private void updatePendingUpdate(PluginInfo curPli, PluginInfo instPli, PluginInfo curUpdatePli) { if (curUpdatePli.getVersion() < instPli.getVersion()) { // 现在的版本比之前"打算要更新"的版本还要新(形象的称之为“夹心层”),则删除掉该“夹心层”的版本,然后换成这个更新的 // 例如:原插件版本为101,原本打算更新的是102,现在遇到了103,显然102的就变成了“夹心层”版本了,直接用103的更好 if (LogDebug.LOG) { LogDebug.i(TAG, "updatePendingUpdate: Found newer plugin, replace. pn=" + curPli.getName() + "; " + "cur_ver=" + curPli.getVersion() + "; old_ver=" + curUpdatePli.getVersion() + "; new_ver=" + instPli.getVersion()); } // 设置待更新版本至最大版本 curPli.setPendingUpdate(instPli); instPli.setParentInfo(curPli); // 删除“夹心层”插件文件 try { FileUtils.forceDelete(new File(curUpdatePli.getPath())); } catch (IOException e) { if (LogRelease.LOGR) { e.printStackTrace(); } } } else { // 由于已经在installLocked中做过判断了,故不太可能走到这里。不过仍可以忽略 if (LogDebug.LOG) { LogDebug.e(TAG, "updatePendingUpdate: Older than updating plugin. But..."); } } } private void updateAllIfNeeded() { // FIXME 务必保证sync方法被调用,否则有可能判断不准确 int updateNum = 0; for (PluginInfo pi : mList) { if (updateIfNeeded(pi)) { updateNum++; } } if (LogDebug.LOG) { LogDebug.d(TAG, "updateAllIfNeeded: Updated " + updateNum + " plugins"); } if (updateNum > 0) { mList.save(mContext); } } // NOTE 调用此方法后,务必最终调用sList.save(),不然会丢失改动 private boolean updateIfNeeded(PluginInfo curInfo) { if (isPluginRunningLocked(curInfo.getName(), null)) { // 插件正在被使用,不能贸然升级或者卸载 if (LogDebug.LOG) { LogDebug.w(TAG, "updateIfNeeded: Plugin is running. pn=" + curInfo.getName()); } return false; } // 更新插件前,首先判断该插件是否需要卸载,若是则直接删除,不再做更新操作 if (curInfo.isNeedUninstall()) { if (LogDebug.LOG) { LogDebug.d(TAG, "updateIfNeeded: delete plugin. pn=" + curInfo.getName()); } // 移除插件及其已释放的Dex、Native库等文件并向各进程发送广播,同步更新 return uninstallNow(curInfo.getPendingDelete()); } else if (curInfo.isNeedUpdate()) { // 需要更新插件?那就直接来 updateNow(curInfo, curInfo.getPendingUpdate()); return true; } else if (curInfo.isNeedCover()) { updateNow(curInfo, curInfo.getPendingCover()); return true; } else { // 既不需要删除也不需要更新 if (LogDebug.LOG) { LogDebug.d(TAG, "updateIfNeeded: Not need to update. pn=" + curInfo.getName()); } return false; } } private void updateNow(PluginInfo curInfo, PluginInfo newInfo) { final boolean covered = newInfo.getIsPendingCover(); if (covered) { move(curInfo, newInfo); } else { // 删除旧版本插件,不管是不是p-n的,且清掉Dex和Native目录 delete(curInfo); } newInfo.setType(PluginInfo.TYPE_EXTRACTED); if (LogDebug.LOG) { LogDebug.i(TAG, "updateNow: Update. pn=" + curInfo.getVersion() + "; cur_ver=" + curInfo.getVersion() + "; update_ver=" + newInfo.getVersion()); } if (covered) { curInfo.setPendingCover(null); newInfo.setIsPendingCover(false); //修改isPendingCover属性后必须同时修改json中的path路径 newInfo.setPath(newInfo.getApkFile().getPath()); } else { curInfo.update(newInfo); curInfo.setPendingUpdate(null); } } private void move(PluginInfo curPi, PluginInfo newPi) { if (LogDebug.LOG) { LogDebug.i(TAG, "move. curPi=" + curPi.getPath() + "; newPi=" + newPi.getPath()); } try { FileUtils.copyFile(newPi.getApkFile(), curPi.getApkFile()); if (newPi.getDexFile().exists()) { FileUtils.copyFile(newPi.getDexFile(), curPi.getDexFile()); } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { FileUtils.copyDir(newPi.getExtraOdexDir(), curPi.getExtraOdexDir()); } if (newPi.getNativeLibsDir().exists()) { FileUtils.copyDir(newPi.getNativeLibsDir(), curPi.getNativeLibsDir()); } } catch (IOException e) { if (LogRelease.LOGR) { e.printStackTrace(); } } finally { try { File parentDir = newPi.getApkFile().getParentFile(); FileUtils.forceDelete(parentDir); } catch (IOException e) { if (LogRelease.LOGR) { e.printStackTrace(); } } catch (IllegalArgumentException e2) { if (LogRelease.LOGR) { e2.printStackTrace(); } } } } private void delete(PluginInfo pi) { try { FileUtils.forceDelete(new File(pi.getPath())); FileUtils.forceDelete(pi.getDexFile()); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { FileUtils.forceDelete(pi.getExtraOdexDir()); } FileUtils.forceDelete(pi.getNativeLibsDir()); } catch (IOException e) { if (LogRelease.LOGR) { e.printStackTrace(); } } catch (IllegalArgumentException e2) { if (LogRelease.LOGR) { e2.printStackTrace(); } } } /** * 去掉pn之后,此方法不再使用,应该及时更新p.l中的Path和type * @deprecated * * @param name * @param used */ private void updateUsedLocked(String name, boolean used) { final PluginInfo pi = MP.getPlugin(name, false); if (LogDebug.LOG) { Log.d(LogDebug.TAG_NO_PN, "prepare to update used for :" + name + ",pi=" + pi); } if (pi == null) { return; } // 1. 设置状态并保存 pi.setIsUsed(used); mList.save(mContext); // 2. 给各进程发送广播,要求更新Used状态(同步) PluginInfoUpdater.updateIsUsed(RePluginInternal.getAppContext(), name, used); } private void updateUsedLocked(String name, String path, int type, boolean used) { final PluginInfo pi = MP.getPlugin(name, false); if (LogDebug.LOG) { Log.d(LogDebug.TAG_NO_PN, "prepare to update used for :" + name + ",pi=" + pi + ",address=" + System.identityHashCode(pi)); } if (pi == null) { return; } // 1. 设置状态并保存 pi.setIsUsed(used); pi.setPath(path); pi.setType(type); if (LogDebug.LOG) { Log.d(LogDebug.TAG_NO_PN, "plugin in list ,pi=" + mList.get(name) + ",address=" + System.identityHashCode(mList.get(name))); } mList.save(mContext); // 2. 给各进程发送广播,要求更新Used状态(同步) PluginInfoUpdater.updateIsUsed(RePluginInternal.getAppContext(), name, used); } private boolean uninstallLocked(PluginInfo pi) { if (pi == null) { return false; } // 插件正在运行? 记录“卸载状态”,推迟到到常驻进程重启的时执行卸载 if (RePlugin.isPluginRunning(pi.getName())) { return uninstallLater(pi); } // 插件未在运行,直接卸载 return uninstallNow(pi); } private boolean uninstallLater(PluginInfo info) { if (LOG) { LogDebug.d(TAG, "Is running. Uninstall later! pn=" + info.getName()); } PluginInfo pi = MP.getPlugin(info.getName(), false); if (pi == null) { return false; } pi.setPendingDelete(info); // 保存插件卸载状态到文件中,下次可直接使用 mList.save(mContext); return false; } private boolean uninstallNow(PluginInfo info) { if (LOG) { LogDebug.i(TAG, "Not running. Uninstall now! pn=" + info.getName()); } // 1. 移除插件及其已释放的Dex、Native库等文件 PackageFilesUtil.forceDelete(info); // 2. 保存插件信息到文件中 mList.remove(info.getName()); mList.save(mContext); return true; } private PluginRunningList getRunningPluginsLocked() { PluginRunningList l = new PluginRunningList(); for (PluginRunningList ps : mProcess2PluginsMap.values()) { for (String p : ps) { if (!l.isRunning(p)) { l.add(p); } } } return l; } private boolean isPluginRunningLocked(String pluginName, String process) { if (TextUtils.isEmpty(process)) { // 没有明确目标进程,只要找到了就返回 for (PluginRunningList ps : mProcess2PluginsMap.values()) { if (ps.isRunning(pluginName)) { return true; } } } else { // 有明确目标,则直接获取即可 PluginRunningList ps = mProcess2PluginsMap.get(process); if (ps != null) { if (ps.isRunning(pluginName)) { return true; } } } return false; } private void syncRunningPluginsLocked(PluginRunningList list) { // 复制一份List,这样无论是否为跨进程,都不会因客户端对List的修改而产生影响 PluginRunningList newList = new PluginRunningList(list); mProcess2PluginsMap.put(list.mProcessName, newList); if (LogDebug.LOG) { LogDebug.d(TAG, "syncRunningPluginsLocked: Synced! pl=" + list + "; map=" + mProcess2PluginsMap); } } private void addToRunningPluginsLocked(String processName, int pid, String pluginName) { PluginRunningList l = mProcess2PluginsMap.get(processName); if (l == null) { l = new PluginRunningList(); mProcess2PluginsMap.put(processName, l); } // 不管是从缓存中获取,还是新创建的,都应该重新“刷新”一下进程信息,再将其Add到表中 l.setProcessInfo(processName, pid); l.add(pluginName); if (LogDebug.LOG) { LogDebug.d(TAG, "addToRunningPluginsLocked: Added! pl =" + l +"; map=" + mProcess2PluginsMap); } } private String[] getRunningProcessesByPluginLocked(String pluginName) { ArrayList l = new ArrayList<>(); for (PluginRunningList prl : mProcess2PluginsMap.values()) { if (prl.isRunning(pluginName)) { l.add(prl.mProcessName); } } return l.toArray(new String[0]); } private void updateTP(String name, int type, String path) { PluginInfo pi = MP.getPlugin(name, false); if (pi == null) { return; } pi.setType(type); pi.setPath(path); mList.add(pi); mList.save(mContext); } private class Stub extends IPluginManagerServer.Stub { @Override public PluginInfo install(String path) throws RemoteException { synchronized (LOCKER) { return PluginManagerServer.this.installLocked(path); } } @Override public List load() throws RemoteException { synchronized (LOCKER) { return PluginManagerServer.this.loadLocked(); } } @Override public List preInstallBuiltins(List builtins) throws RemoteException { synchronized (LOCKER) { return PluginManagerServer.this.installBuiltins(builtins); } } @Override public List updateAll() throws RemoteException { synchronized (LOCKER) { return PluginManagerServer.this.updateAllLocked(); } } @Override public void updateUsed(String name, boolean used) throws RemoteException { synchronized (LOCKER) { PluginManagerServer.this.updateUsedLocked(name, used); } } @Override public void updateUsedNew(String name, String path, int type, boolean used) throws RemoteException { synchronized (LOCKER) { PluginManagerServer.this.updateUsedLocked(name, path, type, used); } } @Override public boolean uninstall(PluginInfo info) throws RemoteException { synchronized (LOCKER) { return PluginManagerServer.this.uninstallLocked(info); } } @Override public PluginRunningList getRunningPlugins() throws RemoteException { synchronized (LOCKER) { return PluginManagerServer.this.getRunningPluginsLocked(); } } @Override public boolean isPluginRunning(String pluginName, String process) throws RemoteException { synchronized (LOCKER) { return PluginManagerServer.this.isPluginRunningLocked(pluginName, process); } } @Override public void syncRunningPlugins(PluginRunningList list) throws RemoteException { synchronized (LOCKER) { PluginManagerServer.this.syncRunningPluginsLocked(list); } } @Override public void addToRunningPlugins(String processName, int pid, String pluginName) throws RemoteException { synchronized (LOCKER) { PluginManagerServer.this.addToRunningPluginsLocked(processName, pid, pluginName); } } @Override public String[] getRunningProcessesByPlugin(String pluginName) throws RemoteException { synchronized (LOCKER) { return PluginManagerServer.this.getRunningProcessesByPluginLocked(pluginName); } } @Override public void updateTP(String plugin, int type, String path) throws RemoteException { synchronized (LOCKER) { PluginManagerServer.this.updateTP(plugin, type, path); } } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginPublishFileGenerator.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.packages; import android.text.TextUtils; import com.qihoo360.replugin.utils.CloseableUtils; import com.qihoo360.replugin.utils.basic.SecurityUtil; import com.qihoo360.replugin.utils.FileUtils; import com.qihoo360.replugin.utils.IOUtils; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; /** * 将原来的APK转换成卫士主程序能支持的“可发布插件”格式 *

* 即:在APK之前添加一段“文件头”信息,包括插件版本、MD5等 *

* 注意:内部框架使用 * * @author RePlugin Team */ class PluginPublishFileGenerator { /** * 开始写入 * * @param srcPath 原APK路径 * @param outPath 目标Jar包路径 * @param low 最小协议版本 * @param high 当前协议版本 * @param ver 插件版本 * @return 是否写入成功 */ static boolean write(String srcPath, String outPath, int low, int high, int ver) { FileOutputStream os = null; FileInputStream is = null; DataOutputStream dis = null; try { is = FileUtils.openInputStream(new File(srcPath)); os = FileUtils.openOutputStream(new File(outPath)); dis = new DataOutputStream(os); // 插件基础字段 dis.writeInt(low); dis.writeInt(high); dis.writeInt(ver); // md5 String md5 = SecurityUtil.getFileMD5(srcPath); if (TextUtils.isEmpty(md5)) { return false; } dis.writeUTF(md5); // 扩展字段(custom) dis.writeInt(0); // 文件长度 dis.writeInt((int) new File(srcPath).length()); // Jar包内容 IOUtils.copy(is, dis); return true; } catch (Throwable e) { e.printStackTrace(); } finally { CloseableUtils.closeQuietly(dis); CloseableUtils.closeQuietly(os); CloseableUtils.closeQuietly(is); } return false; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/PluginRunningList.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.packages; import android.os.Parcel; import android.os.Parcelable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * 所有正在运行的插件列表 * * @author RePlugin Team */ public class PluginRunningList implements Parcelable, Iterable, Cloneable { private final ArrayList mList; String mProcessName; int mPid = Integer.MIN_VALUE; PluginRunningList() { mList = new ArrayList<>(); } PluginRunningList(PluginRunningList list) { // 复制一份,而不是复用以前的,避免外界影响到这里 mProcessName = list.mProcessName; mPid = list.mPid; mList = new ArrayList<>(list.getList()); } void setProcessInfo(String processName, int pid) { mProcessName = processName; mPid = pid; } void add(String s) { synchronized (this) { if (!isRunning(s)) { mList.add(s); } } } boolean isRunning(String pluginName) { return mList.contains(pluginName); } boolean hasRunning() { return !mList.isEmpty(); } // 获取List内部对象。使用此方法时务必小心,确保外界不会影响这里后才能使用 List getList() { return mList; } @Override public Iterator iterator() { return mList.iterator(); } @Override public String toString() { StringBuilder b = new StringBuilder(); b.append("PRunningL{ "); // 进程名+PID if (mPid == Integer.MIN_VALUE) { b.append(""); } else { b.append('<'); b.append(mProcessName); b.append(':'); b.append(mPid); b.append("> "); } // 进程运行列表 b.append(mList); b.append(" }"); return b.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PluginRunningList strings = (PluginRunningList) o; if (mPid != strings.mPid) return false; if (!mList.equals(strings.mList)) return false; return mProcessName != null ? mProcessName.equals(strings.mProcessName) : strings.mProcessName == null; } @Override public int hashCode() { int result = mList.hashCode(); result = 31 * result + (mProcessName != null ? mProcessName.hashCode() : 0); result = 31 * result + mPid; return result; } @Override protected Object clone() throws CloneNotSupportedException { return new PluginRunningList(this); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mProcessName); dest.writeInt(mPid); dest.writeSerializable(mList); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public PluginRunningList createFromParcel(Parcel in) { return new PluginRunningList(in); } public PluginRunningList[] newArray(int size) { return new PluginRunningList[size]; } }; private PluginRunningList(Parcel in) { mProcessName = in.readString(); mPid = in.readInt(); mList = (ArrayList) in.readSerializable(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/packages/RePluginInstaller.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.packages; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.utils.FileUtils; import java.io.File; import java.io.IOException; /** * 和插件安装有关的入口类 * * @author RePlugin Team */ public class RePluginInstaller { private static final String TAG = "RePluginInstaller"; // 将插件从APK -> p-n-xxx.jar,并放入files目录下 public static File covertToPnFile(Context context, String path) { File filesDir = RePlugin.getConfig().getPnInstallDir(); // 已经是p-n开头的了?直接返回 File f = new File(path); if (f.getName().startsWith("p-n-")) { return copyPnToInstallPathIfNeeded(f, filesDir); } // 1. 读取APK内容 PackageInfo pi = context.getPackageManager().getPackageArchiveInfo(path, PackageManager.GET_META_DATA); if (pi == null) { if (LogDebug.LOG) { LogDebug.e(TAG, "covertToPnFile: Not a valid apk. path=" + path); } return null; } // 2. 解析出名字和三元组 PluginInfo pli = PluginInfo.parseFromPackageInfo(pi, path); if (pli == null) { if (LogDebug.LOG) { LogDebug.e(TAG, "covertToPnFile: MetaData Invalid! Are you define com.qihoo360.plugin.name and others? path=" + path); } return null; } // 3. 转化为p-n-xxx.jar文件,并写入到Files目录下 File publishFile = new File(filesDir, "p-n-" + pli.getName() + ".jar"); boolean r = PluginPublishFileGenerator.write(path, publishFile.getAbsolutePath(), pli.getLowInterfaceApi(), pli.getHighInterfaceApi(), pli.getVersion()); if (!r) { if (LogDebug.LOG) { LogDebug.e(TAG, "covertToPnFile: Write to publish file error! path=" + path + "; publish=" + publishFile.getAbsolutePath()); } return null; } // 返回给外界,开始直接安装这个Files目录下的文件 return publishFile; } private static File copyPnToInstallPathIfNeeded(File f, File filesDir) { if (f.getParentFile().equals(filesDir)) { // 该p-n已在安装目录上?直接忽略,返回即可 if (LogDebug.LOG) { LogDebug.i(TAG, "copyPnToInstallPathIfNeeded: Already p-n file in install path. Ignore. path=" + f.getAbsolutePath()); } return f; } else { // 将p-n文件复制到安装目录下,然后再返回 File df = new File(filesDir, f.getName()); if (LogDebug.LOG) { LogDebug.i(TAG, "copyPnToInstallPathIfNeeded: Already p-n file, copy to install path. src=" + f.getAbsolutePath() + "; dest=" + df.getAbsolutePath()); } try { FileUtils.copyFile(f, df); } catch (IOException e) { if (LogDebug.LOG) { LogDebug.e(TAG, "copyPnToInstallPathIfNeeded: Copy fail!", e); } return null; } return df; } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/AssetsUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils; import android.content.Context; import android.util.Log; import com.qihoo360.loader2.PluginNativeLibsHelper; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import java.io.File; import java.io.IOException; import java.io.InputStream; import static com.qihoo360.replugin.helper.LogDebug.LOG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team */ public class AssetsUtils { private static final String TAG = LogDebug.PLUGIN_TAG; /** * 提取文件到目标位置 * @param context * @param name asset名称(asset的相对路径,可包含子路径) * @param dir 目标文件夹(asset的输出目录) * @return */ public static final boolean extractTo(Context context, final String name, final String dir, final String dstName) { File file = new File(dir + "/" + dstName); InputStream is = FileUtils.openInputStreamFromAssetsQuietly(context, name); if (is == null) { if (LOG) { LogDebug.e(TAG, "extractTo: Fail to open " + name + "from Assets"); } return false; } try { FileUtils.copyInputStreamToFile(is, file); return true; } catch (IOException e) { if (LOGR) { e.printStackTrace(); } } finally { CloseableUtils.closeQuietly(is); } return false; } /** * 提取文件到目标位置,并处理文件夹是否存在,是否校验,是否强制覆盖,是否需要释放SO库 * @param context * @param info PluginInfo对象(asset的相对路径,可包含子路径) * @param dir 目标文件夹(asset的输出目录) * @param dexOutputDir 成功提取该文件时,是否删除同名的DEX文件 * @return */ public static final boolean quickExtractTo(Context context, final PluginInfo info, final String dir, final String dstName, String dexOutputDir) { QuickExtractResult result = quickExtractTo(context, info.getPath(), dir, dstName, dexOutputDir); // 释放失败 || 被释放的文件已经存在 switch (result) { case FAIL: return false; case EXISTED: return true; default: // 释放插件里的Native(SO)库文件 // Added by Jiongxuan Zhang File file = new File(dir + "/" + dstName); File libDir = info.getNativeLibsDir(); boolean rc = PluginNativeLibsHelper.install(file.getAbsolutePath(), libDir); if (!rc) { if (LOGR) { LogRelease.e(TAG, "a u e rc f so " + file.getPath()); } return rc; } return true; } } /** * 提取文件到目标位置,并处理文件夹是否存在,是否校验,是否强制覆盖。不会释放SO库 * @param context * @param name asset名称(asset的相对路径,可包含子路径) * @param dir 目标文件夹(asset的输出目录) * @param dexOutputDir 成功提取该文件时,是否删除同名的DEX文件 * @return 释放文件的结果 */ public static final QuickExtractResult quickExtractTo(Context context, final String name, final String dir, final String dstName, String dexOutputDir) { File file = new File(dir + "/" + dstName); // 建立子目录 File d = file.getParentFile(); if (!d.exists()) { if (!d.mkdirs()) { if (LOGR) { Log.e(TAG, "can't create: " + d.getPath()); } return QuickExtractResult.FAIL; } } // 检查创建是否成功 if (!d.exists() || !d.isDirectory()) { if (LOGR) { Log.e(TAG, "can't create dir: " + d.getPath()); } return QuickExtractResult.FAIL; } // 文件已存在,直接返回 if (file.exists()) { return QuickExtractResult.EXISTED; } boolean rc = extractTo(context, name, dir, dstName); if (LOG) { Log.d(TAG, "create new: " + file.getPath() + (rc ? " ok" : " error")); } if (!rc) { if (LOGR) { Log.e(TAG, "a u e rc f " + file.getPath()); } return QuickExtractResult.FAIL; } return QuickExtractResult.SUCCESS; } /** * 调用 QuickExtract 的结果值 */ public enum QuickExtractResult { SUCCESS, // 释放文件成功 FAIL, // 释放文件失败 EXISTED // 文件已存在(之前释放过了) } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/Charsets.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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. */ package com.qihoo360.replugin.utils; import java.nio.charset.Charset; /** * Charsets required of every implementation of the Java platform. * * From the Java documentation * Standard charsets: *

* Every implementation of the Java platform is required to support the following character encodings. Consult * the release documentation for your implementation to see if any other encodings are supported. Consult the release * documentation for your implementation to see if any other encodings are supported. *

* *
    *
  • US-ASCII
    * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
  • *
  • ISO-8859-1
    * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
  • *
  • UTF-8
    * Eight-bit Unicode Transformation Format.
  • *
  • UTF-16BE
    * Sixteen-bit Unicode Transformation Format, big-endian byte order.
  • *
  • UTF-16LE
    * Sixteen-bit Unicode Transformation Format, little-endian byte order.
  • *
  • UTF-16
    * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order * accepted on input, big-endian used on output.)
  • *
* * @see Standard charsets * @since 2.3 * @version $Id: Charsets.java 1686747 2015-06-21 18:44:49Z krosenvold $ */ public class Charsets { /** * Returns the given Charset or the default Charset if the given Charset is null. * * @param charset * A charset or null. * @return the given Charset or the default Charset if the given Charset is null */ public static Charset toCharset(final Charset charset) { return charset == null ? Charset.defaultCharset() : charset; } /** * Returns a Charset for the named charset. If the name is null, return the default Charset. * * @param charset * The name of the requested charset, may be null. * @return a Charset for the named charset * @throws java.nio.charset.UnsupportedCharsetException * If the named charset is unavailable */ public static Charset toCharset(final String charset) { return charset == null ? Charset.defaultCharset() : Charset.forName(charset); } /** *

* Eight-bit Unicode Transformation Format. *

*

* Every implementation of the Java platform is required to support this character encoding. *

* * @see Standard charsets * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} */ @Deprecated public static final Charset UTF_8 = Charset.forName("UTF-8"); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/CloseableUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils; import android.database.Cursor; import java.io.Closeable; import java.io.IOException; import java.util.zip.ZipFile; /** * 和关闭流有关的帮助类 * * @author RePlugin Team */ public class CloseableUtils { /** * 大部分Close关闭流,以及实现Closeable的功能可使用此方法 * * @param c Closeable对象,包括Stream等 */ public static void closeQuietly(Closeable c) { try { if (c != null) { c.close(); } } catch (final IOException ioe) { // ignore } } /** * 允许“一口气”关闭多个Closeable的方法 * * @param closeables 多个Closeable对象 */ public static void closeQuietly(final Closeable... closeables) { if (closeables == null) { return; } for (final Closeable closeable : closeables) { closeQuietly(closeable); } } /** * 解决API 15及以下的Cursor都没有实现Closeable接口,因此调用Closeable参数会出现转换异常的问题 * java.lang.IncompatibleClassChangeError: interface not implemented, * * @param c Cursor对象 */ public static void closeQuietly(Cursor c) { try { if (c != null) { c.close(); } } catch (Exception e) { // ignore } } /** * 解决API 18及以下的ZipFile都没有实现Closeable接口,因此调用Closeable参数会出现转换异常的问题 * java.lang.IncompatibleClassChangeError: interface not implemented, * * @param c Cursor对象 */ public static void closeQuietly(ZipFile c) { try { if (c != null) { c.close(); } } catch (Exception e) { // ignore } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/Dex2OatUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils; import android.os.Build; import android.util.Log; import com.qihoo360.replugin.RePluginInternal; import java.io.File; import java.io.IOException; /** * @author RePlugin Team * * Art Dex2Oat utils */ public class Dex2OatUtils { public static final String TAG = "Dex2Oat"; private static final boolean FOR_DEV = RePluginInternal.FOR_DEV; /** * 判断当前是否Art模式 * * @return */ public static boolean isArtMode() { return System.getProperty("java.vm.version", "").startsWith("2"); } /** * 在真正loadDex之前 inject * * @param dexPath * @param optimizedDirectory * @param optimizedFileName * @return */ public static void injectLoadDex(String dexPath, String optimizedDirectory, String optimizedFileName) { if (Dex2OatUtils.isArtMode()) { File odexFile = new File(optimizedDirectory, optimizedFileName); if (!odexFile.exists() || odexFile.length() <= 0) { if (FOR_DEV) { Log.d(Dex2OatUtils.TAG, optimizedFileName + " 文件不存在"); } long being = System.currentTimeMillis(); boolean injectLoadDex = innerInjectLoadDex(dexPath, optimizedDirectory, optimizedFileName); if (FOR_DEV) { Log.d(Dex2OatUtils.TAG, "injectLoadDex use:" + (System.currentTimeMillis() - being)); Log.d(Dex2OatUtils.TAG, "injectLoadDex result:" + injectLoadDex); } } else { if (FOR_DEV) { Log.d(Dex2OatUtils.TAG, optimizedFileName + " 文件存在, 不需要inject,size:" + odexFile.length()); } } } } private static boolean innerInjectLoadDex(String dexPath, String optimizedDirectory, String optimizedFileName) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { if (FOR_DEV) { Log.d(TAG, "before Android L, do nothing."); } return false; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) { return injectLoadDex4Art(dexPath, optimizedDirectory, optimizedFileName); } else { return injectLoadDex4More(); } } private static boolean injectLoadDexBeforeN() { if (isArtMode()) { long begin = System.currentTimeMillis(); if (FOR_DEV) { Log.d(TAG, "Art before Android N, try 2 hook."); } try { // ArtAdapter.setDex2oatEnabledNative(true); } catch (Throwable e) { if (FOR_DEV) { e.printStackTrace(); Log.e(TAG, "hook error"); } } if (FOR_DEV) { Log.d(TAG, "hook end, use:" + (System.currentTimeMillis() - begin)); } return true; } if (FOR_DEV) { Log.d(TAG, "not Art, do nothing."); } return false; } private static boolean injectLoadDex4Art(String dexPath, String optimizedDirectory, String optimizedFileName) { if (FOR_DEV) { Log.d(TAG, "Andorid Art, try 2 interpretDex2Oat, interpret-only."); } String odexAbsolutePath = (optimizedDirectory + File.separator + optimizedFileName); long begin = System.currentTimeMillis(); try { InterpretDex2OatHelper.interpretDex2Oat(dexPath, odexAbsolutePath); } catch (IOException e) { if (FOR_DEV) { e.printStackTrace(); Log.e(TAG, "interpretDex2Oat Error"); } return false; } if (FOR_DEV) { Log.d(TAG, "interpretDex2Oat use:" + (System.currentTimeMillis() - begin)); Log.d(TAG, "interpretDex2Oat odexSize:" + InterpretDex2OatHelper.getOdexSize(odexAbsolutePath)); } return true; } private static boolean injectLoadDex4More() { return false; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/FileUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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. */ package com.qihoo360.replugin.utils; import android.content.Context; import android.content.res.AssetManager; import android.text.TextUtils; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.channels.FileChannel; import java.nio.charset.Charset; /** * 一些和文件操作相关的类。大部分摘自 Apache IO Library * * @author RePlugin Team, Apache IO Library */ public class FileUtils { /** * The number of bytes in a kilobyte. */ public static final long ONE_KB = 1024; /** * The number of bytes in a megabyte. */ public static final long ONE_MB = ONE_KB * ONE_KB; /** * The file copy buffer size (30 MB) */ private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30; //----------------------------------------------------------------------- /** * Opens a {@link FileInputStream} for the specified file, providing better * error messages than simply calling new FileInputStream(file). *

* At the end of the method either the stream will be successfully opened, * or an exception will have been thrown. *

* An exception is thrown if the file does not exist. * An exception is thrown if the file object exists but is a directory. * An exception is thrown if the file exists but cannot be read. * * @param file the file to open for input, must not be {@code null} * @return a new {@link FileInputStream} for the specified file * @throws FileNotFoundException if the file does not exist * @throws IOException if the file object is a directory * @throws IOException if the file cannot be read */ public static FileInputStream openInputStream(final File file) throws IOException { if (file.exists()) { if (file.isDirectory()) { throw new IOException("File '" + file + "' exists but is a directory"); } if (file.canRead() == false) { throw new IOException("File '" + file + "' cannot be read"); } } else { throw new FileNotFoundException("File '" + file + "' does not exist"); } return new FileInputStream(file); } /** * 不抛出任何异常,直接尝试打开一个Assets下的文件。若无法打开则直接返回Null * @param context Context对象 * @param fileName Assets文件所在路径 * @return Assets文件的输入流 */ public static InputStream openInputStreamFromAssetsQuietly(final Context context, final String fileName) { AssetManager a = context.getAssets(); if (a == null) { // Never be here return null; } try { return a.open(fileName); } catch (IOException e) { e.printStackTrace(); } return null; } //----------------------------------------------------------------------- /** * Opens a {@link FileOutputStream} for the specified file, checking and * creating the parent directory if it does not exist. *

* At the end of the method either the stream will be successfully opened, * or an exception will have been thrown. *

* The parent directory will be created if it does not exist. * The file will be created if it does not exist. * An exception is thrown if the file object exists but is a directory. * An exception is thrown if the file exists but cannot be written to. * An exception is thrown if the parent directory cannot be created. * * @param file the file to open for output, must not be {@code null} * @return a new {@link FileOutputStream} for the specified file * @throws IOException if the file object is a directory * @throws IOException if the file cannot be written to * @throws IOException if a parent directory needs creating but that fails */ public static FileOutputStream openOutputStream(final File file) throws IOException { return openOutputStream(file, false); } /** * Opens a {@link FileOutputStream} for the specified file, checking and * creating the parent directory if it does not exist. *

* At the end of the method either the stream will be successfully opened, * or an exception will have been thrown. *

* The parent directory will be created if it does not exist. * The file will be created if it does not exist. * An exception is thrown if the file object exists but is a directory. * An exception is thrown if the file exists but cannot be written to. * An exception is thrown if the parent directory cannot be created. * * @param file the file to open for output, must not be {@code null} * @param append if {@code true}, then bytes will be added to the * end of the file rather than overwriting * @return a new {@link FileOutputStream} for the specified file * @throws IOException if the file object is a directory * @throws IOException if the file cannot be written to * @throws IOException if a parent directory needs creating but that fails */ public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException { if (file.exists()) { if (file.isDirectory()) { throw new IOException("File '" + file + "' exists but is a directory"); } if (file.canWrite() == false) { throw new IOException("File '" + file + "' cannot be written to"); } } else { final File parent = file.getParentFile(); if (parent != null) { if (!parent.mkdirs() && !parent.isDirectory()) { throw new IOException("Directory '" + parent + "' could not be created"); } } } return new FileOutputStream(file, append); } /** * Makes any necessary but nonexistent parent directories for a given File. If the parent directory cannot be * created then an IOException is thrown. * * @param file file with parent to create, must not be {@code null} * @throws NullPointerException if the file is {@code null} * @throws IOException if the parent directory cannot be created */ public static void forceMkdirParent(final File file) throws IOException { final File parent = file.getParentFile(); if (parent == null) { return; } forceMkdir(parent); } /** * Makes a directory, including any necessary but nonexistent parent * directories. If a file already exists with specified name but it is * not a directory then an IOException is thrown. * If the directory cannot be created (or does not already exist) * then an IOException is thrown. * * @param directory directory to create, must not be {@code null} * @throws NullPointerException if the directory is {@code null} * @throws IOException if the directory cannot be created or the file already exists but is not a directory */ public static void forceMkdir(final File directory) throws IOException { if (directory.exists()) { if (!directory.isDirectory()) { final String message = "File " + directory + " exists and is " + "not a directory. Unable to create directory."; throw new IOException(message); } } else { if (!directory.mkdirs()) { // Double-check that some other thread or process hasn't made // the directory in the background if (!directory.isDirectory()) { final String message = "Unable to create directory " + directory; throw new IOException(message); } } } } /** * Deletes a file. If file is a directory, delete it and all sub-directories. *

* The difference between File.delete() and this method are: *

    *
  • A directory to be deleted does not have to be empty.
  • *
  • You get exceptions when a file or directory cannot be deleted. * (java.io.File methods returns a boolean)
  • *
* * @param file file or directory to delete, must not be {@code null} * @throws NullPointerException if the directory is {@code null} * @throws FileNotFoundException if the file was not found * @throws IOException in case deletion is unsuccessful */ public static void forceDelete(final File file) throws IOException { if (!file.exists()) { return; } if (file.isDirectory()) { deleteDirectory(file); } else { final boolean filePresent = file.exists(); if (!file.delete()) { if (!filePresent) { throw new FileNotFoundException("File does not exist: " + file); } final String message = "Unable to delete file: " + file; throw new IOException(message); } } } /** * Deletes a directory recursively. * * @param directory directory to delete * @throws IOException in case deletion is unsuccessful * @throws IllegalArgumentException if {@code directory} does not exist or is not a directory */ public static void deleteDirectory(final File directory) throws IOException { if (!directory.exists()) { return; } // 大部分不需要处理Symlink的情况 // if (!isSymlink(directory)) { cleanDirectory(directory); // } if (!directory.delete()) { final String message = "Unable to delete directory " + directory + "."; throw new IOException(message); } } /** * Cleans a directory without deleting it. * * @param directory directory to clean * @throws IOException in case cleaning is unsuccessful * @throws IllegalArgumentException if {@code directory} does not exist or is not a directory */ public static void cleanDirectory(final File directory) throws IOException { final File[] files = verifiedListFiles(directory); IOException exception = null; for (final File file : files) { try { forceDelete(file); } catch (final IOException ioe) { exception = ioe; } } if (null != exception) { throw exception; } } /** * Lists files in a directory, asserting that the supplied directory satisfies exists and is a directory * @param directory The directory to list * @return The files in the directory, never null. * @throws IOException if an I/O error occurs */ private static File[] verifiedListFiles(File directory) throws IOException { if (!directory.exists()) { final String message = directory + " does not exist"; throw new IllegalArgumentException(message); } if (!directory.isDirectory()) { final String message = directory + " is not a directory"; throw new IllegalArgumentException(message); } final File[] files = directory.listFiles(); if (files == null) { // null if security restricted throw new IOException("Failed to list contents of " + directory); } return files; } //----------------------------------------------------------------------- /** * Reads the contents of a file into a String. * The file is always closed. * * @param file the file to read, must not be {@code null} * @param encoding the encoding to use, {@code null} means platform default * @return the file contents, never {@code null} * @throws IOException in case of an I/O error */ public static String readFileToString(final File file, final Charset encoding) throws IOException { InputStream in = null; try { in = openInputStream(file); return IOUtils.toString(in, Charsets.toCharset(encoding)); } finally { CloseableUtils.closeQuietly(in); } } /** * Writes a String to a file creating the file if it does not exist. *

* NOTE: As from v1.3, the parent directories of the file will be created * if they do not exist. * * @param file the file to write * @param data the content to write to the file * @param encoding the encoding to use, {@code null} means platform default * @throws IOException in case of an I/O error * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM */ public static void writeStringToFile(final File file, final String data, final Charset encoding) throws IOException { writeStringToFile(file, data, encoding, false); } /** * Writes a String to a file creating the file if it does not exist. * * @param file the file to write * @param data the content to write to the file * @param encoding the encoding to use, {@code null} means platform default * @param append if {@code true}, then the String will be added to the * end of the file rather than overwriting * @throws IOException in case of an I/O error */ public static void writeStringToFile(final File file, final String data, final Charset encoding, final boolean append) throws IOException { OutputStream out = null; try { out = openOutputStream(file, append); IOUtils.write(data, out, encoding); out.close(); // don't swallow close Exception if copy completes normally } finally { CloseableUtils.closeQuietly(out); } } /** * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories. *

* The difference between File.delete() and this method are: *

    *
  • A directory to be deleted does not have to be empty.
  • *
  • No exceptions are thrown when a file or directory cannot be deleted.
  • *
* * @param file file or directory to delete, can be {@code null} * @return {@code true} if the file or directory was deleted, otherwise * {@code false} */ public static boolean deleteQuietly(final File file) { if (file == null) { return false; } try { if (file.isDirectory()) { cleanDirectory(file); } } catch (final Exception ignored) { } try { return file.delete(); } catch (final Exception ignored) { return false; } } public static void copyDir(final File srcFile, final File destFile) throws IOException { copyDir(srcFile, destFile, true); } public static void copyDir(final File srcFile, final File destFile, final boolean preserveFileDate) throws IOException { checkFileRequirements(srcFile, destFile); if (!srcFile.isDirectory()) { throw new IOException("Source '" + srcFile + "' exists but is not a directory"); } if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) { throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); } if (destFile.exists() && destFile.canWrite() == false) { throw new IOException("Destination '" + destFile + "' exists but is read-only"); } File[] files = srcFile.listFiles(); for (File file : files) { copyFile(file, new File(destFile, file.getName()), preserveFileDate); } } public static void copyFile(final File srcFile, final File destFile) throws IOException { copyFile(srcFile, destFile, true); } public static void copyFile(final File srcFile, final File destFile, final boolean preserveFileDate) throws IOException { checkFileRequirements(srcFile, destFile); if (srcFile.isDirectory()) { throw new IOException("Source '" + srcFile + "' exists but is a directory"); } if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) { throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); } final File parentFile = destFile.getParentFile(); if (parentFile != null) { if (!parentFile.mkdirs() && !parentFile.isDirectory()) { throw new IOException("Destination '" + parentFile + "' directory cannot be created"); } } if (destFile.exists() && destFile.canWrite() == false) { throw new IOException("Destination '" + destFile + "' exists but is read-only"); } doCopyFile(srcFile, destFile, preserveFileDate); } /** * Internal copy file method. * This caches the original file length, and throws an IOException * if the output file length is different from the current input file length. * So it may fail if the file changes size. * It may also fail with "IllegalArgumentException: Negative size" if the input file is truncated part way * through copying the data and the new file size is less than the current position. * * @param srcFile the validated source file, must not be {@code null} * @param destFile the validated destination file, must not be {@code null} * @param preserveFileDate whether to preserve the file date * @throws IOException if an error occurs * @throws IOException if the output file length is not the same as the input file length after the * copy completes * @throws IllegalArgumentException "Negative size" if the file is truncated so that the size is less than the * position */ private static void doCopyFile(final File srcFile, final File destFile, final boolean preserveFileDate) throws IOException { if (destFile.exists() && destFile.isDirectory()) { throw new IOException("Destination '" + destFile + "' exists but is a directory"); } FileInputStream fis = null; FileOutputStream fos = null; FileChannel input = null; FileChannel output = null; try { fis = new FileInputStream(srcFile); fos = new FileOutputStream(destFile); input = fis.getChannel(); output = fos.getChannel(); final long size = input.size(); // TODO See IO-386 long pos = 0; long count = 0; while (pos < size) { final long remain = size - pos; count = remain > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : remain; final long bytesCopied = output.transferFrom(input, pos, count); if (bytesCopied == 0) { // IO-385 - can happen if file is truncated after caching the size break; // ensure we don't loop forever } pos += bytesCopied; } } finally { CloseableUtils.closeQuietly(output, fos, input, fis); } final long srcLen = srcFile.length(); // TODO See IO-386 final long dstLen = destFile.length(); // TODO See IO-386 if (srcLen != dstLen) { throw new IOException("Failed to copy full contents from '" + srcFile + "' to '" + destFile + "' Expected length: " + srcLen + " Actual: " + dstLen); } if (preserveFileDate) { destFile.setLastModified(srcFile.lastModified()); } } /** * checks requirements for file copy * @param src the source file * @param dest the destination * @throws FileNotFoundException if the destination does not exist */ private static void checkFileRequirements(File src, File dest) throws FileNotFoundException { if (src == null) { throw new NullPointerException("Source must not be null"); } if (dest == null) { throw new NullPointerException("Destination must not be null"); } if (!src.exists()) { throw new FileNotFoundException("Source '" + src + "' does not exist"); } } /** * Copies bytes from an {@link InputStream} source to a file * destination. The directories up to destination * will be created if they don't already exist. destination * will be overwritten if it already exists. * The {@code source} stream is closed. * See {@link #copyToFile(InputStream, File)} for a method that does not close the input stream. * * @param source the InputStream to copy bytes from, must not be {@code null}, will be closed * @param destination the non-directory File to write bytes to * (possibly overwriting), must not be {@code null} * @throws IOException if destination is a directory * @throws IOException if destination cannot be written * @throws IOException if destination needs creating but can't be * @throws IOException if an IO error occurs during copying */ public static void copyInputStreamToFile(final InputStream source, final File destination) throws IOException { try { copyToFile(source, destination); } finally { CloseableUtils.closeQuietly(source); } } /** * Copies bytes from an {@link InputStream} source to a file * destination. The directories up to destination * will be created if they don't already exist. destination * will be overwritten if it already exists. * The {@code source} stream is left open, e.g. for use with {@link java.util.zip.ZipInputStream ZipInputStream}. * See {@link #copyInputStreamToFile(InputStream, File)} for a method that closes the input stream. * * @param source the InputStream to copy bytes from, must not be {@code null} * @param destination the non-directory File to write bytes to * (possibly overwriting), must not be {@code null} * @throws IOException if destination is a directory * @throws IOException if destination cannot be written * @throws IOException if destination needs creating but can't be * @throws IOException if an IO error occurs during copying */ public static void copyToFile(final InputStream source, final File destination) throws IOException { final FileOutputStream output = openOutputStream(destination); try { IOUtils.copy(source, output); output.close(); // don't swallow close Exception if copy completes normally } finally { CloseableUtils.closeQuietly(output); } } /** * Moves a file. *

* When the destination file is on another file system, do a "copy and delete". * * @param srcFile the file to be moved * @param destFile the destination file * @throws NullPointerException if source or destination is {@code null} * @throws IOException if the destination file exists * @throws IOException if source or destination is invalid * @throws IOException if an IO error occurs moving the file */ public static void moveFile(final File srcFile, final File destFile) throws IOException { if (srcFile == null) { throw new NullPointerException("Source must not be null"); } if (destFile == null) { throw new NullPointerException("Destination must not be null"); } if (!srcFile.exists()) { throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); } if (srcFile.isDirectory()) { throw new IOException("Source '" + srcFile + "' is a directory"); } if (destFile.exists()) { throw new IOException("Destination '" + destFile + "' already exists"); } if (destFile.isDirectory()) { throw new IOException("Destination '" + destFile + "' is a directory"); } final boolean rename = srcFile.renameTo(destFile); if (!rename) { copyFile(srcFile, destFile); if (!srcFile.delete()) { FileUtils.deleteQuietly(destFile); throw new IOException("Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'"); } } } public static long sizeOf(final File file) { if (!file.exists()) { final String message = file + " does not exist"; throw new IllegalArgumentException(message); } if (file.isDirectory()) { return sizeOfDirectory0(file); // private method; expects directory } else { return file.length(); } } // Private method, must be invoked will a directory parameter /** * the size of a director * @param directory the directory to check * @return the size */ private static long sizeOfDirectory0(final File directory) { final File[] files = directory.listFiles(); if (files == null) { // null if security restricted return 0L; } long size = 0; for (final File file : files) { // try { // if (!isSymlink(file)) { size += sizeOf0(file); // internal method if (size < 0) { break; } // } // } catch (final IOException ioe) { // // Ignore exceptions caught when asking if a File is a symlink. // } } return size; } // Internal method - does not check existence /** * the size of a file * @param file the file to check * @return the size of the fil */ private static long sizeOf0(File file) { if (file.isDirectory()) { return sizeOfDirectory0(file); } else { return file.length(); // will be 0 if file does not exist } } /** * 得到文件名字(不包含扩展名) *

* 例:/sdcard/test/abc.zxy/ -> abc * * @param filePath * @return */ public static String getFileNameWithoutExt(String filePath) { if (TextUtils.isEmpty(filePath)) { return filePath; } int extensionPosition = filePath.lastIndexOf("."); int filePosition = filePath.lastIndexOf(File.separator); if (filePosition == -1) { return (extensionPosition == -1 ? filePath : filePath.substring(0, extensionPosition)); } if (extensionPosition == -1) { return filePath.substring(filePosition + 1); } return (filePosition < extensionPosition ? filePath.substring(filePosition + 1, extensionPosition) : filePath.substring(filePosition + 1)); } /** * 根据输入的文件路径,得到扩展名 * * @param filePath * @return */ public static String getFileExt(String filePath) { if (TextUtils.isEmpty(filePath)) { return filePath; } int extPosi = filePath.lastIndexOf("."); int filePosi = filePath.lastIndexOf(File.separator); if (extPosi == -1) { return ""; } return (filePosi >= extPosi) ? "" : filePath.substring(extPosi + 1); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/FixOTranslucentOrientation.java ================================================ package com.qihoo360.replugin.utils; import android.app.Activity; import android.content.pm.ActivityInfo; import android.content.res.TypedArray; import com.qihoo360.replugin.helper.LogDebug; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * @Description: Only fullscreen opaque activities can request orientation */ public class FixOTranslucentOrientation { private static final String TAG = "FixOTranslucentOri"; public static void fix(Activity activity) { if (android.os.Build.VERSION.SDK_INT != 26){ return; } try { int[] styleableRes = (int[]) ReflectUtils.getClass("com.android.internal.R$styleable").getField("Window").get(null); final TypedArray ta = activity.obtainStyledAttributes(styleableRes); Method m = ActivityInfo.class.getMethod("isTranslucentOrFloating", TypedArray.class); m.setAccessible(true); boolean isTranslucentOrFloating = (boolean) m.invoke(null, ta); m.setAccessible(false); LogDebug.d(TAG, "activity create before: " + activity.getClass().getName() + " isTranslucentOrFloating =" + isTranslucentOrFloating); if (!isTranslucentOrFloating) { return; } Field field = Activity.class.getDeclaredField("mActivityInfo"); field.setAccessible(true); ActivityInfo o = (ActivityInfo) field.get(activity); o.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; field.setAccessible(false); } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/IOUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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. */ package com.qihoo360.replugin.utils; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.nio.charset.Charset; /** * 一些和IO操作(流)相关的类。大部分摘自 Apache IO Library * * @author RePlugin Team, Apache IO Library */ public class IOUtils { /** * Represents the end-of-file (or stream). */ public static final int EOF = -1; /** * The default buffer size ({@value}) to use for * {@link #copyLarge(InputStream, OutputStream)} * and * {@link #copyLarge(Reader, Writer)} */ private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; /** * Gets the contents of an InputStream as a String * using the specified character encoding. *

* This method buffers the input internally, so there is no need to use a * BufferedInputStream. *

* * @param input the InputStream to read from * @param encoding the encoding to use, null means platform default * @return the requested String * @throws NullPointerException if the input is null * @throws IOException if an I/O error occurs */ public static String toString(final InputStream input, final Charset encoding) throws IOException { final StringBuilderWriter sw = new StringBuilderWriter(); copy(input, sw, encoding); return sw.toString(); } // copy from InputStream //----------------------------------------------------------------------- /** * Copies bytes from an InputStream to an * OutputStream. *

* This method buffers the input internally, so there is no need to use a * BufferedInputStream. *

* Large streams (over 2GB) will return a bytes copied value of * -1 after the copy has completed since the correct * number of bytes cannot be returned as an int. For large streams * use the copyLarge(InputStream, OutputStream) method. * * @param input the InputStream to read from * @param output the OutputStream to write to * @return the number of bytes copied, or -1 if > Integer.MAX_VALUE * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs */ public static int copy(final InputStream input, final OutputStream output) throws IOException { final long count = copyLarge(input, output); if (count > Integer.MAX_VALUE) { return -1; } return (int) count; } /** * Copies bytes from an InputStream to an OutputStream using an internal buffer of the * given size. *

* This method buffers the input internally, so there is no need to use a BufferedInputStream. *

* * @param input the InputStream to read from * @param output the OutputStream to write to * @param bufferSize the bufferSize used to copy from the input to the output * @return the number of bytes copied * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs */ public static long copy(final InputStream input, final OutputStream output, final int bufferSize) throws IOException { return copyLarge(input, output, new byte[bufferSize]); } /** * Copies bytes from a large (over 2GB) InputStream to an * OutputStream. *

* This method buffers the input internally, so there is no need to use a * BufferedInputStream. *

* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. * * @param input the InputStream to read from * @param output the OutputStream to write to * @return the number of bytes copied * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs */ public static long copyLarge(final InputStream input, final OutputStream output) throws IOException { return copy(input, output, DEFAULT_BUFFER_SIZE); } /** * Copies bytes from a large (over 2GB) InputStream to an * OutputStream. *

* This method uses the provided buffer, so there is no need to use a * BufferedInputStream. *

* * @param input the InputStream to read from * @param output the OutputStream to write to * @param buffer the buffer to use for the copy * @return the number of bytes copied * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs */ public static long copyLarge(final InputStream input, final OutputStream output, final byte[] buffer) throws IOException { long count = 0; int n; while (EOF != (n = input.read(buffer))) { output.write(buffer, 0, n); count += n; } return count; } /** * Copies bytes from an InputStream to chars on a * Writer using the specified character encoding. *

* This method buffers the input internally, so there is no need to use a * BufferedInputStream. *

* This method uses {@link InputStreamReader}. * * @param input the InputStream to read from * @param output the Writer to write to * @param inputEncoding the encoding to use for the input stream, null means platform default * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs */ public static void copy(final InputStream input, final Writer output, final Charset inputEncoding) throws IOException { final InputStreamReader in = new InputStreamReader(input, Charsets.toCharset(inputEncoding)); copy(in, output); } // copy from Reader //----------------------------------------------------------------------- /** * Copies chars from a Reader to a Writer. *

* This method buffers the input internally, so there is no need to use a * BufferedReader. *

* Large streams (over 2GB) will return a chars copied value of * -1 after the copy has completed since the correct * number of chars cannot be returned as an int. For large streams * use the copyLarge(Reader, Writer) method. * * @param input the Reader to read from * @param output the Writer to write to * @return the number of characters copied, or -1 if > Integer.MAX_VALUE * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs */ public static int copy(final Reader input, final Writer output) throws IOException { final long count = copyLarge(input, output); if (count > Integer.MAX_VALUE) { return -1; } return (int) count; } /** * Copies chars from a large (over 2GB) Reader to a Writer. *

* This method buffers the input internally, so there is no need to use a * BufferedReader. *

* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. * * @param input the Reader to read from * @param output the Writer to write to * @return the number of characters copied * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs */ public static long copyLarge(final Reader input, final Writer output) throws IOException { return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); } /** * Copies chars from a large (over 2GB) Reader to a Writer. *

* This method uses the provided buffer, so there is no need to use a * BufferedReader. *

* * @param input the Reader to read from * @param output the Writer to write to * @param buffer the buffer to be used for the copy * @return the number of characters copied * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs */ public static long copyLarge(final Reader input, final Writer output, final char[] buffer) throws IOException { long count = 0; int n; while (EOF != (n = input.read(buffer))) { output.write(buffer, 0, n); count += n; } return count; } // write byte[] //----------------------------------------------------------------------- /** * Writes chars from a String to bytes on an * OutputStream using the specified character encoding. *

* This method uses {@link String#getBytes(String)}. * * @param data the String to write, null ignored * @param output the OutputStream to write to * @param encoding the encoding to use, null means platform default * @throws NullPointerException if output is null * @throws IOException if an I/O error occurs */ public static void write(final String data, final OutputStream output, final Charset encoding) throws IOException { if (data != null) { output.write(data.getBytes(Charsets.toCharset(encoding))); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/InterpretDex2OatHelper.java ================================================ package com.qihoo360.replugin.utils; import android.os.Build; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * InterpretDex2OatHelper *

* snippet from: Tinker's TinkerDexOptimizer.java (https://github.com/Tencent/tinker) *

* Tinker is a hot-fix solution library for Android, it supports dex, library and resources update without reinstall apk. */ public class InterpretDex2OatHelper { private static String getCurrentInstructionSet() throws Exception { Class clazz = Class.forName("dalvik.system.VMRuntime"); Method currentGet = clazz.getDeclaredMethod("getCurrentInstructionSet"); return (String) currentGet.invoke(null); } public static long getOdexSize(String oatFilePath) { File file = new File(oatFilePath); return file.exists() ? file.length() : -1; } public static void interpretDex2Oat(String dexFilePath, String oatFilePath) throws IOException { String targetISA = null; try { targetISA = getCurrentInstructionSet(); } catch (Exception e) { e.printStackTrace(); } final File oatFile = new File(oatFilePath); if (!oatFile.exists()) { oatFile.getParentFile().mkdirs(); } final List commandAndParams = new ArrayList<>(); commandAndParams.add("dex2oat"); // for 7.1.1, duplicate class fix if (Build.VERSION.SDK_INT >= 24) { commandAndParams.add("--runtime-arg"); commandAndParams.add("-classpath"); commandAndParams.add("--runtime-arg"); commandAndParams.add("&"); } commandAndParams.add("--dex-file=" + dexFilePath); commandAndParams.add("--oat-file=" + oatFilePath); commandAndParams.add("--instruction-set=" + targetISA); if (Build.VERSION.SDK_INT > 25) { commandAndParams.add("--compiler-filter=quicken"); } else { commandAndParams.add("--compiler-filter=interpret-only"); } final ProcessBuilder pb = new ProcessBuilder(commandAndParams); pb.redirectErrorStream(true); final Process dex2oatProcess = pb.start(); StreamConsumer.consumeInputStream(dex2oatProcess.getInputStream()); StreamConsumer.consumeInputStream(dex2oatProcess.getErrorStream()); try { final int ret = dex2oatProcess.waitFor(); if (ret != 0) { throw new IOException("dex2oat works unsuccessfully, exit code: " + ret); } } catch (InterruptedException e) { throw new IOException("dex2oat is interrupted, msg: " + e.getMessage(), e); } } private static class StreamConsumer { static final Executor STREAM_CONSUMER = Executors.newSingleThreadExecutor(); static void consumeInputStream(final InputStream is) { STREAM_CONSUMER.execute(new Runnable() { @Override public void run() { if (is == null) { return; } final byte[] buffer = new byte[256]; try { while ((is.read(buffer)) > 0) { // To satisfy checkstyle rules. } } catch (IOException ignored) { // Ignored. } finally { try { is.close(); } catch (Exception ignored) { // Ignored. } } } }); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/ReflectUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils; import android.content.Context; import com.qihoo360.replugin.helper.LogRelease; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import static com.qihoo360.replugin.helper.LogDebug.MISC_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * 和反射操作有关的Utils * * @author RePlugin Team */ public final class ReflectUtils { // ---------------- // Class & Constructor // ---------------- public static Class getClass(final String className) throws ClassNotFoundException { return Class.forName(className); } public static T invokeConstructor(Class cls, Class[] parameterTypes, Object... args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor c = cls.getConstructor(parameterTypes); if (c != null) { c.setAccessible(true); return c.newInstance(args); } return null; } // ---------------- // Field // ---------------- public static Field getField(Class cls, String fieldName) { // From Apache: FieldUtils.getField() // check up the superclass hierarchy for (Class acls = cls; acls != null; acls = acls.getSuperclass()) { try { final Field field = acls.getDeclaredField(fieldName); // getDeclaredField checks for non-public scopes as well // and it returns accurate results setAccessible(field, true); return field; } catch (final NoSuchFieldException ex) { // NOPMD // ignore } } // check the public interface case. This must be manually searched for // incase there is a public supersuperclass field hidden by a private/package // superclass field. Field match = null; for (final Class class1 : cls.getInterfaces()) { try { final Field test = class1.getField(fieldName); Validate.isTrue(match == null, "Reference to field %s is ambiguous relative to %s" + "; a matching field exists on two or more implemented interfaces.", fieldName, cls); match = test; } catch (final NoSuchFieldException ex) { // NOPMD // ignore } } return match; } public static Object readStaticField(Class c, String fieldName) throws NoSuchFieldException, IllegalAccessException { return readField(c, null, fieldName); } public static Object readField(Object target, String fieldName) throws IllegalAccessException, NoSuchFieldException { return readField(target.getClass(), target, fieldName); } public static Object readField(Class c, Object target, String fieldName) throws IllegalAccessException, NoSuchFieldException { Field f = getField(c, fieldName); return readField(f, target); } public static Object readField(final Field field, final Object target) throws IllegalAccessException { return field.get(target); } public static void writeField(Object target, String fName, Object value) throws NoSuchFieldException, IllegalAccessException { writeField(target.getClass(), target, fName, value); } public static void writeField(Class c, Object object, String fName, Object value) throws NoSuchFieldException, IllegalAccessException { Field f = getField(c, fName); writeField(f, object, value); } public static void writeField(final Field field, final Object target, final Object value) throws IllegalAccessException { field.set(target, value); } public static List getAllFieldsList(final Class cls) { // From Apache: FieldUtils.getAllFieldsList() Validate.isTrue(cls != null, "The class must not be null"); final List allFields = new ArrayList<>(); Class currentClass = cls; while (currentClass != null) { final Field[] declaredFields = currentClass.getDeclaredFields(); for (final Field field : declaredFields) { allFields.add(field); } currentClass = currentClass.getSuperclass(); } return allFields; } public static void removeFieldFinalModifier(final Field field) { // From Apache: FieldUtils.removeFinalModifier() Validate.isTrue(field != null, "The field must not be null"); try { if (Modifier.isFinal(field.getModifiers())) { // Do all JREs implement Field with a private ivar called "modifiers"? final Field modifiersField = Field.class.getDeclaredField("modifiers"); final boolean doForceAccess = !modifiersField.isAccessible(); if (doForceAccess) { modifiersField.setAccessible(true); } try { modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); } finally { if (doForceAccess) { modifiersField.setAccessible(false); } } } } catch (final NoSuchFieldException ignored) { // The field class contains always a modifiers field } catch (final IllegalAccessException ignored) { // The modifiers field is made accessible } } // ---------------- // Method // ---------------- public static Method getMethod(Class cls, String methodName, Class... parameterTypes) { // check up the superclass hierarchy for (Class acls = cls; acls != null; acls = acls.getSuperclass()) { try { final Method method = acls.getDeclaredMethod(methodName, parameterTypes); // getDeclaredField checks for non-public scopes as well // and it returns accurate results setAccessible(method, true); return method; } catch (final NoSuchMethodException ex) { // NOPMD // ignore } } // check the public interface case. This must be manually searched for // incase there is a public supersuperclass field hidden by a private/package // superclass field. Method match = null; for (final Class class1 : cls.getInterfaces()) { try { final Method test = class1.getMethod(methodName, parameterTypes); Validate.isTrue(match == null, "Reference to field %s is ambiguous relative to %s" + "; a matching field exists on two or more implemented interfaces.", methodName, cls); match = test; } catch (final NoSuchMethodException ex) { // NOPMD // ignore } } return match; } public static Object invokeMethod(final Object object, final String methodName, Class[] methodParamTypes, Object... args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class clz = object.getClass(); Method m = getMethod(clz, methodName, methodParamTypes); return m.invoke(args); } public static Object invokeMethod(ClassLoader loader, String clzName, String methodName, Object methodReceiver, Class[] methodParamTypes, Object... methodParamValues) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { if (methodReceiver == null) { return null; } Class clz = Class.forName(clzName, false, loader); if (clz != null) { Method med = clz.getMethod(methodName, methodParamTypes); if (med != null) { med.setAccessible(true); return med.invoke(methodReceiver, methodParamValues); } } return null; } public static void setAccessible(AccessibleObject ao, boolean value) { if (ao.isAccessible() != value) { ao.setAccessible(value); } } // ---------------- // Other // ---------------- public static final void dumpObject(Object object, FileDescriptor fd, PrintWriter writer, String[] args) { try { Class c = object.getClass(); do { writer.println("c=" + c.getName()); Field fields[] = c.getDeclaredFields(); for (Field f : fields) { boolean acc = f.isAccessible(); if (!acc) { f.setAccessible(true); } Object o = f.get(object); writer.print(f.getName()); writer.print("="); if (o != null) { writer.println(o.toString()); } else { writer.println("null"); } if (!acc) { f.setAccessible(acc); } } c = c.getSuperclass(); } while (c != null && !c.equals(Object.class) && !c.equals(Context.class)); } catch (Throwable e) { if (LOGR) { LogRelease.e(MISC_TAG, e.getMessage(), e); } } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/StringBuilderWriter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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. */ package com.qihoo360.replugin.utils; import java.io.Serializable; import java.io.Writer; /** * {@link Writer} implementation that outputs to a {@link StringBuilder}. *

* NOTE: This implementation, as an alternative to * java.io.StringWriter, provides an un-synchronized * (i.e. for use in a single thread) implementation for better performance. * For safe usage with multiple {@link Thread}s then * java.io.StringWriter should be used. * * @version $Id: StringBuilderWriter.java 1722253 2015-12-30 00:36:12Z ggregory $ * @since 2.0 */ public class StringBuilderWriter extends Writer implements Serializable { private static final long serialVersionUID = -146927496096066153L; private final StringBuilder builder; /** * Constructs a new {@link StringBuilder} instance with default capacity. */ public StringBuilderWriter() { this.builder = new StringBuilder(); } /** * Constructs a new {@link StringBuilder} instance with the specified capacity. * * @param capacity The initial capacity of the underlying {@link StringBuilder} */ public StringBuilderWriter(final int capacity) { this.builder = new StringBuilder(capacity); } /** * Constructs a new instance with the specified {@link StringBuilder}. * *

If {@code builder} is null a new instance with default capacity will be created.

* * @param builder The String builder. May be null. */ public StringBuilderWriter(final StringBuilder builder) { this.builder = builder != null ? builder : new StringBuilder(); } /** * Appends a single character to this Writer. * * @param value The character to append * @return This writer instance */ @Override public Writer append(final char value) { builder.append(value); return this; } /** * Appends a character sequence to this Writer. * * @param value The character to append * @return This writer instance */ @Override public Writer append(final CharSequence value) { builder.append(value); return this; } /** * Appends a portion of a character sequence to the {@link StringBuilder}. * * @param value The character to append * @param start The index of the first character * @param end The index of the last character + 1 * @return This writer instance */ @Override public Writer append(final CharSequence value, final int start, final int end) { builder.append(value, start, end); return this; } /** * Closing this writer has no effect. */ @Override public void close() { // no-op } /** * Flushing this writer has no effect. */ @Override public void flush() { // no-op } /** * Writes a String to the {@link StringBuilder}. * * @param value The value to write */ @Override public void write(final String value) { if (value != null) { builder.append(value); } } /** * Writes a portion of a character array to the {@link StringBuilder}. * * @param value The value to write * @param offset The index of the first character * @param length The number of characters to write */ @Override public void write(final char[] value, final int offset, final int length) { if (value != null) { builder.append(value, offset, length); } } /** * Returns the underlying builder. * * @return The underlying builder */ public StringBuilder getBuilder() { return builder; } /** * Returns {@link StringBuilder#toString()}. * * @return The contents of the String builder. */ @Override public String toString() { return builder.toString(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/Validate.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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. */ package com.qihoo360.replugin.utils; import java.util.Collection; import java.util.Map; import java.util.regex.Pattern; /** *

This class assists in validating arguments. The validation methods are * based along the following principles: *

    *
  • An invalid {@code null} argument causes a {@link NullPointerException}.
  • *
  • A non-{@code null} argument causes an {@link IllegalArgumentException}.
  • *
  • An invalid index into an array/collection/map/string causes an {@link IndexOutOfBoundsException}.
  • *
* *

All exceptions messages are * format strings * as defined by the Java platform. For example:

* *
 * Validate.isTrue(i > 0, "The value must be greater than zero: %d", i);
 * Validate.notNull(surname, "The surname must not be %s", null);
 * 
* *

#ThreadSafe#

* @see String#format(String, Object...) * @since 2.0 */ public class Validate { private static final String DEFAULT_NOT_NAN_EX_MESSAGE = "The validated value is not a number"; private static final String DEFAULT_FINITE_EX_MESSAGE = "The value is invalid: %f"; private static final String DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE = "The value %s is not in the specified exclusive range of %s to %s"; private static final String DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE = "The value %s is not in the specified inclusive range of %s to %s"; private static final String DEFAULT_MATCHES_PATTERN_EX = "The string %s does not match the pattern %s"; private static final String DEFAULT_IS_NULL_EX_MESSAGE = "The validated object is null"; private static final String DEFAULT_IS_TRUE_EX_MESSAGE = "The validated expression is false"; private static final String DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE = "The validated array contains null element at index: %d"; private static final String DEFAULT_NO_NULL_ELEMENTS_COLLECTION_EX_MESSAGE = "The validated collection contains null element at index: %d"; private static final String DEFAULT_NOT_BLANK_EX_MESSAGE = "The validated character sequence is blank"; private static final String DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE = "The validated array is empty"; private static final String DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE = "The validated character sequence is empty"; private static final String DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE = "The validated collection is empty"; private static final String DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE = "The validated map is empty"; private static final String DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE = "The validated array index is invalid: %d"; private static final String DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE = "The validated character sequence index is invalid: %d"; private static final String DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE = "The validated collection index is invalid: %d"; private static final String DEFAULT_VALID_STATE_EX_MESSAGE = "The validated state is false"; private static final String DEFAULT_IS_ASSIGNABLE_EX_MESSAGE = "Cannot assign a %s to a %s"; private static final String DEFAULT_IS_INSTANCE_OF_EX_MESSAGE = "Expected type: %s, actual: %s"; /** * Constructor. This class should not normally be instantiated. */ public Validate() { super(); } // isTrue //--------------------------------------------------------------------------------- /** *

Validate that the argument condition is {@code true}; otherwise * throwing an exception with the specified message. This method is useful when * validating according to an arbitrary boolean expression, such as validating a * primitive number or using your own custom validation expression.

* *
Validate.isTrue(i > 0.0, "The value must be greater than zero: %d", i);
* *

For performance reasons, the long value is passed as a separate parameter and * appended to the exception message only in the case of an error.

* * @param expression the boolean expression to check * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param value the value to append to the message when invalid * @throws IllegalArgumentException if expression is {@code false} * @see #isTrue(boolean) * @see #isTrue(boolean, String, double) * @see #isTrue(boolean, String, Object...) */ public static void isTrue(final boolean expression, final String message, final long value) { if (expression == false) { throw new IllegalArgumentException(String.format(message, Long.valueOf(value))); } } /** *

Validate that the argument condition is {@code true}; otherwise * throwing an exception with the specified message. This method is useful when * validating according to an arbitrary boolean expression, such as validating a * primitive number or using your own custom validation expression.

* *
Validate.isTrue(d > 0.0, "The value must be greater than zero: %s", d);
* *

For performance reasons, the double value is passed as a separate parameter and * appended to the exception message only in the case of an error.

* * @param expression the boolean expression to check * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param value the value to append to the message when invalid * @throws IllegalArgumentException if expression is {@code false} * @see #isTrue(boolean) * @see #isTrue(boolean, String, long) * @see #isTrue(boolean, String, Object...) */ public static void isTrue(final boolean expression, final String message, final double value) { if (expression == false) { throw new IllegalArgumentException(String.format(message, Double.valueOf(value))); } } /** *

Validate that the argument condition is {@code true}; otherwise * throwing an exception with the specified message. This method is useful when * validating according to an arbitrary boolean expression, such as validating a * primitive number or using your own custom validation expression.

* *
     * Validate.isTrue(i >= min && i <= max, "The value must be between %d and %d", min, max);
     * Validate.isTrue(myObject.isOk(), "The object is not okay");
* * @param expression the boolean expression to check * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @throws IllegalArgumentException if expression is {@code false} * @see #isTrue(boolean) * @see #isTrue(boolean, String, long) * @see #isTrue(boolean, String, double) */ public static void isTrue(final boolean expression, final String message, final Object... values) { if (expression == false) { throw new IllegalArgumentException(String.format(message, values)); } } /** *

Validate that the argument condition is {@code true}; otherwise * throwing an exception. This method is useful when validating according * to an arbitrary boolean expression, such as validating a * primitive number or using your own custom validation expression.

* *
     * Validate.isTrue(i > 0);
     * Validate.isTrue(myObject.isOk());
* *

The message of the exception is "The validated expression is * false".

* * @param expression the boolean expression to check * @throws IllegalArgumentException if expression is {@code false} * @see #isTrue(boolean, String, long) * @see #isTrue(boolean, String, double) * @see #isTrue(boolean, String, Object...) */ public static void isTrue(final boolean expression) { if (expression == false) { throw new IllegalArgumentException(DEFAULT_IS_TRUE_EX_MESSAGE); } } // notNull //--------------------------------------------------------------------------------- /** *

Validate that the specified argument is not {@code null}; * otherwise throwing an exception. * *

Validate.notNull(myObject, "The object must not be null");
* *

The message of the exception is "The validated object is * null".

* * @param the object type * @param object the object to check * @return the validated object (never {@code null} for method chaining) * @throws NullPointerException if the object is {@code null} * @see #notNull(Object, String, Object...) */ public static T notNull(final T object) { return notNull(object, DEFAULT_IS_NULL_EX_MESSAGE); } /** *

Validate that the specified argument is not {@code null}; * otherwise throwing an exception with the specified message. * *

Validate.notNull(myObject, "The object must not be null");
* * @param the object type * @param object the object to check * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message * @return the validated object (never {@code null} for method chaining) * @throws NullPointerException if the object is {@code null} * @see #notNull(Object) */ public static T notNull(final T object, final String message, final Object... values) { if (object == null) { throw new NullPointerException(String.format(message, values)); } return object; } // notEmpty array //--------------------------------------------------------------------------------- /** *

Validate that the specified argument array is neither {@code null} * nor a length of zero (no elements); otherwise throwing an exception * with the specified message. * *

Validate.notEmpty(myArray, "The array must not be empty");
* * @param the array type * @param array the array to check, validated not null by this method * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @return the validated array (never {@code null} method for chaining) * @throws NullPointerException if the array is {@code null} * @throws IllegalArgumentException if the array is empty * @see #notEmpty(Object[]) */ public static T[] notEmpty(final T[] array, final String message, final Object... values) { if (array == null) { throw new NullPointerException(String.format(message, values)); } if (array.length == 0) { throw new IllegalArgumentException(String.format(message, values)); } return array; } /** *

Validate that the specified argument array is neither {@code null} * nor a length of zero (no elements); otherwise throwing an exception. * *

Validate.notEmpty(myArray);
* *

The message in the exception is "The validated array is * empty". * * @param the array type * @param array the array to check, validated not null by this method * @return the validated array (never {@code null} method for chaining) * @throws NullPointerException if the array is {@code null} * @throws IllegalArgumentException if the array is empty * @see #notEmpty(Object[], String, Object...) */ public static T[] notEmpty(final T[] array) { return notEmpty(array, DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE); } // notEmpty collection //--------------------------------------------------------------------------------- /** *

Validate that the specified argument collection is neither {@code null} * nor a size of zero (no elements); otherwise throwing an exception * with the specified message. * *

Validate.notEmpty(myCollection, "The collection must not be empty");
* * @param the collection type * @param collection the collection to check, validated not null by this method * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @return the validated collection (never {@code null} method for chaining) * @throws NullPointerException if the collection is {@code null} * @throws IllegalArgumentException if the collection is empty * @see #notEmpty(Object[]) */ public static > T notEmpty(final T collection, final String message, final Object... values) { if (collection == null) { throw new NullPointerException(String.format(message, values)); } if (collection.isEmpty()) { throw new IllegalArgumentException(String.format(message, values)); } return collection; } /** *

Validate that the specified argument collection is neither {@code null} * nor a size of zero (no elements); otherwise throwing an exception. * *

Validate.notEmpty(myCollection);
* *

The message in the exception is "The validated collection is * empty".

* * @param the collection type * @param collection the collection to check, validated not null by this method * @return the validated collection (never {@code null} method for chaining) * @throws NullPointerException if the collection is {@code null} * @throws IllegalArgumentException if the collection is empty * @see #notEmpty(Collection, String, Object...) */ public static > T notEmpty(final T collection) { return notEmpty(collection, DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE); } // notEmpty map //--------------------------------------------------------------------------------- /** *

Validate that the specified argument map is neither {@code null} * nor a size of zero (no elements); otherwise throwing an exception * with the specified message. * *

Validate.notEmpty(myMap, "The map must not be empty");
* * @param the map type * @param map the map to check, validated not null by this method * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @return the validated map (never {@code null} method for chaining) * @throws NullPointerException if the map is {@code null} * @throws IllegalArgumentException if the map is empty * @see #notEmpty(Object[]) */ public static > T notEmpty(final T map, final String message, final Object... values) { if (map == null) { throw new NullPointerException(String.format(message, values)); } if (map.isEmpty()) { throw new IllegalArgumentException(String.format(message, values)); } return map; } /** *

Validate that the specified argument map is neither {@code null} * nor a size of zero (no elements); otherwise throwing an exception. * *

Validate.notEmpty(myMap);
* *

The message in the exception is "The validated map is * empty".

* * @param the map type * @param map the map to check, validated not null by this method * @return the validated map (never {@code null} method for chaining) * @throws NullPointerException if the map is {@code null} * @throws IllegalArgumentException if the map is empty * @see #notEmpty(Map, String, Object...) */ public static > T notEmpty(final T map) { return notEmpty(map, DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE); } // notEmpty string //--------------------------------------------------------------------------------- /** *

Validate that the specified argument character sequence is * neither {@code null} nor a length of zero (no characters); * otherwise throwing an exception with the specified message. * *

Validate.notEmpty(myString, "The string must not be empty");
* * @param the character sequence type * @param chars the character sequence to check, validated not null by this method * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @return the validated character sequence (never {@code null} method for chaining) * @throws NullPointerException if the character sequence is {@code null} * @throws IllegalArgumentException if the character sequence is empty * @see #notEmpty(CharSequence) */ public static T notEmpty(final T chars, final String message, final Object... values) { if (chars == null) { throw new NullPointerException(String.format(message, values)); } if (chars.length() == 0) { throw new IllegalArgumentException(String.format(message, values)); } return chars; } /** *

Validate that the specified argument character sequence is * neither {@code null} nor a length of zero (no characters); * otherwise throwing an exception with the specified message. * *

Validate.notEmpty(myString);
* *

The message in the exception is "The validated * character sequence is empty".

* * @param the character sequence type * @param chars the character sequence to check, validated not null by this method * @return the validated character sequence (never {@code null} method for chaining) * @throws NullPointerException if the character sequence is {@code null} * @throws IllegalArgumentException if the character sequence is empty * @see #notEmpty(CharSequence, String, Object...) */ public static T notEmpty(final T chars) { return notEmpty(chars, DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE); } // validIndex array //--------------------------------------------------------------------------------- /** *

Validates that the index is within the bounds of the argument * array; otherwise throwing an exception with the specified message.

* *
Validate.validIndex(myArray, 2, "The array index is invalid: ");
* *

If the array is {@code null}, then the message of the exception * is "The validated object is null".

* * @param the array type * @param array the array to check, validated not null by this method * @param index the index to check * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @return the validated array (never {@code null} for method chaining) * @throws NullPointerException if the array is {@code null} * @throws IndexOutOfBoundsException if the index is invalid * @see #validIndex(Object[], int) * * @since 3.0 */ public static T[] validIndex(final T[] array, final int index, final String message, final Object... values) { Validate.notNull(array); if (index < 0 || index >= array.length) { throw new IndexOutOfBoundsException(String.format(message, values)); } return array; } /** *

Validates that the index is within the bounds of the argument * array; otherwise throwing an exception.

* *
Validate.validIndex(myArray, 2);
* *

If the array is {@code null}, then the message of the exception * is "The validated object is null".

* *

If the index is invalid, then the message of the exception is * "The validated array index is invalid: " followed by the * index.

* * @param the array type * @param array the array to check, validated not null by this method * @param index the index to check * @return the validated array (never {@code null} for method chaining) * @throws NullPointerException if the array is {@code null} * @throws IndexOutOfBoundsException if the index is invalid * @see #validIndex(Object[], int, String, Object...) * * @since 3.0 */ public static T[] validIndex(final T[] array, final int index) { return validIndex(array, index, DEFAULT_VALID_INDEX_ARRAY_EX_MESSAGE, Integer.valueOf(index)); } // validIndex collection //--------------------------------------------------------------------------------- /** *

Validates that the index is within the bounds of the argument * collection; otherwise throwing an exception with the specified message.

* *
Validate.validIndex(myCollection, 2, "The collection index is invalid: ");
* *

If the collection is {@code null}, then the message of the * exception is "The validated object is null".

* * @param the collection type * @param collection the collection to check, validated not null by this method * @param index the index to check * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @return the validated collection (never {@code null} for chaining) * @throws NullPointerException if the collection is {@code null} * @throws IndexOutOfBoundsException if the index is invalid * @see #validIndex(Collection, int) * * @since 3.0 */ public static > T validIndex(final T collection, final int index, final String message, final Object... values) { Validate.notNull(collection); if (index < 0 || index >= collection.size()) { throw new IndexOutOfBoundsException(String.format(message, values)); } return collection; } /** *

Validates that the index is within the bounds of the argument * collection; otherwise throwing an exception.

* *
Validate.validIndex(myCollection, 2);
* *

If the index is invalid, then the message of the exception * is "The validated collection index is invalid: " * followed by the index.

* * @param the collection type * @param collection the collection to check, validated not null by this method * @param index the index to check * @return the validated collection (never {@code null} for method chaining) * @throws NullPointerException if the collection is {@code null} * @throws IndexOutOfBoundsException if the index is invalid * @see #validIndex(Collection, int, String, Object...) * * @since 3.0 */ public static > T validIndex(final T collection, final int index) { return validIndex(collection, index, DEFAULT_VALID_INDEX_COLLECTION_EX_MESSAGE, Integer.valueOf(index)); } // validIndex string //--------------------------------------------------------------------------------- /** *

Validates that the index is within the bounds of the argument * character sequence; otherwise throwing an exception with the * specified message.

* *
Validate.validIndex(myStr, 2, "The string index is invalid: ");
* *

If the character sequence is {@code null}, then the message * of the exception is "The validated object is null".

* * @param the character sequence type * @param chars the character sequence to check, validated not null by this method * @param index the index to check * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @return the validated character sequence (never {@code null} for method chaining) * @throws NullPointerException if the character sequence is {@code null} * @throws IndexOutOfBoundsException if the index is invalid * @see #validIndex(CharSequence, int) * * @since 3.0 */ public static T validIndex(final T chars, final int index, final String message, final Object... values) { Validate.notNull(chars); if (index < 0 || index >= chars.length()) { throw new IndexOutOfBoundsException(String.format(message, values)); } return chars; } /** *

Validates that the index is within the bounds of the argument * character sequence; otherwise throwing an exception.

* *
Validate.validIndex(myStr, 2);
* *

If the character sequence is {@code null}, then the message * of the exception is "The validated object is * null".

* *

If the index is invalid, then the message of the exception * is "The validated character sequence index is invalid: " * followed by the index.

* * @param the character sequence type * @param chars the character sequence to check, validated not null by this method * @param index the index to check * @return the validated character sequence (never {@code null} for method chaining) * @throws NullPointerException if the character sequence is {@code null} * @throws IndexOutOfBoundsException if the index is invalid * @see #validIndex(CharSequence, int, String, Object...) * * @since 3.0 */ public static T validIndex(final T chars, final int index) { return validIndex(chars, index, DEFAULT_VALID_INDEX_CHAR_SEQUENCE_EX_MESSAGE, Integer.valueOf(index)); } // validState //--------------------------------------------------------------------------------- /** *

Validate that the stateful condition is {@code true}; otherwise * throwing an exception. This method is useful when validating according * to an arbitrary boolean expression, such as validating a * primitive number or using your own custom validation expression.

* *
     * Validate.validState(field > 0);
     * Validate.validState(this.isOk());
* *

The message of the exception is "The validated state is * false".

* * @param expression the boolean expression to check * @throws IllegalStateException if expression is {@code false} * @see #validState(boolean, String, Object...) * * @since 3.0 */ public static void validState(final boolean expression) { if (expression == false) { throw new IllegalStateException(DEFAULT_VALID_STATE_EX_MESSAGE); } } /** *

Validate that the stateful condition is {@code true}; otherwise * throwing an exception with the specified message. This method is useful when * validating according to an arbitrary boolean expression, such as validating a * primitive number or using your own custom validation expression.

* *
Validate.validState(this.isOk(), "The state is not OK: %s", myObject);
* * @param expression the boolean expression to check * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @throws IllegalStateException if expression is {@code false} * @see #validState(boolean) * * @since 3.0 */ public static void validState(final boolean expression, final String message, final Object... values) { if (expression == false) { throw new IllegalStateException(String.format(message, values)); } } // matchesPattern //--------------------------------------------------------------------------------- /** *

Validate that the specified argument character sequence matches the specified regular * expression pattern; otherwise throwing an exception.

* *
Validate.matchesPattern("hi", "[a-z]*");
* *

The syntax of the pattern is the one used in the {@link Pattern} class.

* * @param input the character sequence to validate, not null * @param pattern the regular expression pattern, not null * @throws IllegalArgumentException if the character sequence does not match the pattern * @see #matchesPattern(CharSequence, String, String, Object...) * * @since 3.0 */ public static void matchesPattern(final CharSequence input, final String pattern) { // TODO when breaking BC, consider returning input if (Pattern.matches(pattern, input) == false) { throw new IllegalArgumentException(String.format(DEFAULT_MATCHES_PATTERN_EX, input, pattern)); } } /** *

Validate that the specified argument character sequence matches the specified regular * expression pattern; otherwise throwing an exception with the specified message.

* *
Validate.matchesPattern("hi", "[a-z]*", "%s does not match %s", "hi" "[a-z]*");
* *

The syntax of the pattern is the one used in the {@link Pattern} class.

* * @param input the character sequence to validate, not null * @param pattern the regular expression pattern, not null * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @throws IllegalArgumentException if the character sequence does not match the pattern * @see #matchesPattern(CharSequence, String) * * @since 3.0 */ public static void matchesPattern(final CharSequence input, final String pattern, final String message, final Object... values) { // TODO when breaking BC, consider returning input if (Pattern.matches(pattern, input) == false) { throw new IllegalArgumentException(String.format(message, values)); } } // notNaN //--------------------------------------------------------------------------------- /** *

Validates that the specified argument is not {@code NaN}; otherwise * throwing an exception.

* *
Validate.notNaN(myDouble);
* *

The message of the exception is "The validated value is not a * number".

* * @param value the value to validate * @throws IllegalArgumentException if the value is not a number * @see #notNaN(double, String, Object...) * * @since 3.5 */ public static void notNaN(final double value) { notNaN(value, DEFAULT_NOT_NAN_EX_MESSAGE); } /** *

Validates that the specified argument is not {@code NaN}; otherwise * throwing an exception with the specified message.

* *
Validate.notNaN(myDouble, "The value must be a number");
* * @param value the value to validate * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message * @throws IllegalArgumentException if the value is not a number * @see #notNaN(double) * * @since 3.5 */ public static void notNaN(final double value, final String message, final Object... values) { if (Double.isNaN(value)) { throw new IllegalArgumentException(String.format(message, values)); } } // finite //--------------------------------------------------------------------------------- /** *

Validates that the specified argument is not infinite or {@code NaN}; * otherwise throwing an exception.

* *
Validate.finite(myDouble);
* *

The message of the exception is "The value is invalid: %f".

* * @param value the value to validate * @throws IllegalArgumentException if the value is infinite or {@code NaN} * @see #finite(double, String, Object...) * * @since 3.5 */ public static void finite(final double value) { finite(value, DEFAULT_FINITE_EX_MESSAGE, value); } /** *

Validates that the specified argument is not infinite or {@code NaN}; * otherwise throwing an exception with the specified message.

* *
Validate.finite(myDouble, "The argument must contain a numeric value");
* * @param value the value to validate * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message * @throws IllegalArgumentException if the value is infinite or {@code NaN} * @see #finite(double) * * @since 3.5 */ public static void finite(final double value, final String message, final Object... values) { if (Double.isNaN(value) || Double.isInfinite(value)) { throw new IllegalArgumentException(String.format(message, values)); } } // inclusiveBetween //--------------------------------------------------------------------------------- /** *

Validate that the specified argument object fall between the two * inclusive values specified; otherwise, throws an exception.

* *
Validate.inclusiveBetween(0, 2, 1);
* * @param the type of the argument object * @param start the inclusive start value, not null * @param end the inclusive end value, not null * @param value the object to validate, not null * @throws IllegalArgumentException if the value falls outside the boundaries * @see #inclusiveBetween(Object, Object, Comparable, String, Object...) * * @since 3.0 */ public static void inclusiveBetween(final T start, final T end, final Comparable value) { // TODO when breaking BC, consider returning value if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); } } /** *

Validate that the specified argument object fall between the two * inclusive values specified; otherwise, throws an exception with the * specified message.

* *
Validate.inclusiveBetween(0, 2, 1, "Not in boundaries");
* * @param the type of the argument object * @param start the inclusive start value, not null * @param end the inclusive end value, not null * @param value the object to validate, not null * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @throws IllegalArgumentException if the value falls outside the boundaries * @see #inclusiveBetween(Object, Object, Comparable) * * @since 3.0 */ public static void inclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { // TODO when breaking BC, consider returning value if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { throw new IllegalArgumentException(String.format(message, values)); } } /** * Validate that the specified primitive value falls between the two * inclusive values specified; otherwise, throws an exception. * *
Validate.inclusiveBetween(0, 2, 1);
* * @param start the inclusive start value * @param end the inclusive end value * @param value the value to validate * @throws IllegalArgumentException if the value falls outside the boundaries (inclusive) * * @since 3.3 */ @SuppressWarnings("boxing") public static void inclusiveBetween(final long start, final long end, final long value) { // TODO when breaking BC, consider returning value if (value < start || value > end) { throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); } } /** * Validate that the specified primitive value falls between the two * inclusive values specified; otherwise, throws an exception with the * specified message. * *
Validate.inclusiveBetween(0, 2, 1, "Not in range");
* * @param start the inclusive start value * @param end the inclusive end value * @param value the value to validate * @param message the exception message if invalid, not null * * @throws IllegalArgumentException if the value falls outside the boundaries * * @since 3.3 */ public static void inclusiveBetween(final long start, final long end, final long value, final String message) { // TODO when breaking BC, consider returning value if (value < start || value > end) { throw new IllegalArgumentException(message); } } /** * Validate that the specified primitive value falls between the two * inclusive values specified; otherwise, throws an exception. * *
Validate.inclusiveBetween(0.1, 2.1, 1.1);
* * @param start the inclusive start value * @param end the inclusive end value * @param value the value to validate * @throws IllegalArgumentException if the value falls outside the boundaries (inclusive) * * @since 3.3 */ @SuppressWarnings("boxing") public static void inclusiveBetween(final double start, final double end, final double value) { // TODO when breaking BC, consider returning value if (value < start || value > end) { throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); } } /** * Validate that the specified primitive value falls between the two * inclusive values specified; otherwise, throws an exception with the * specified message. * *
Validate.inclusiveBetween(0.1, 2.1, 1.1, "Not in range");
* * @param start the inclusive start value * @param end the inclusive end value * @param value the value to validate * @param message the exception message if invalid, not null * * @throws IllegalArgumentException if the value falls outside the boundaries * * @since 3.3 */ public static void inclusiveBetween(final double start, final double end, final double value, final String message) { // TODO when breaking BC, consider returning value if (value < start || value > end) { throw new IllegalArgumentException(message); } } // exclusiveBetween //--------------------------------------------------------------------------------- /** *

Validate that the specified argument object fall between the two * exclusive values specified; otherwise, throws an exception.

* *
Validate.exclusiveBetween(0, 2, 1);
* * @param the type of the argument object * @param start the exclusive start value, not null * @param end the exclusive end value, not null * @param value the object to validate, not null * @throws IllegalArgumentException if the value falls outside the boundaries * @see #exclusiveBetween(Object, Object, Comparable, String, Object...) * * @since 3.0 */ public static void exclusiveBetween(final T start, final T end, final Comparable value) { // TODO when breaking BC, consider returning value if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); } } /** *

Validate that the specified argument object fall between the two * exclusive values specified; otherwise, throws an exception with the * specified message.

* *
Validate.exclusiveBetween(0, 2, 1, "Not in boundaries");
* * @param the type of the argument object * @param start the exclusive start value, not null * @param end the exclusive end value, not null * @param value the object to validate, not null * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @throws IllegalArgumentException if the value falls outside the boundaries * @see #exclusiveBetween(Object, Object, Comparable) * * @since 3.0 */ public static void exclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { // TODO when breaking BC, consider returning value if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { throw new IllegalArgumentException(String.format(message, values)); } } /** * Validate that the specified primitive value falls between the two * exclusive values specified; otherwise, throws an exception. * *
Validate.exclusiveBetween(0, 2, 1);
* * @param start the exclusive start value * @param end the exclusive end value * @param value the value to validate * @throws IllegalArgumentException if the value falls out of the boundaries * * @since 3.3 */ @SuppressWarnings("boxing") public static void exclusiveBetween(final long start, final long end, final long value) { // TODO when breaking BC, consider returning value if (value <= start || value >= end) { throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); } } /** * Validate that the specified primitive value falls between the two * exclusive values specified; otherwise, throws an exception with the * specified message. * *
Validate.exclusiveBetween(0, 2, 1, "Not in range");
* * @param start the exclusive start value * @param end the exclusive end value * @param value the value to validate * @param message the exception message if invalid, not null * * @throws IllegalArgumentException if the value falls outside the boundaries * * @since 3.3 */ public static void exclusiveBetween(final long start, final long end, final long value, final String message) { // TODO when breaking BC, consider returning value if (value <= start || value >= end) { throw new IllegalArgumentException(message); } } /** * Validate that the specified primitive value falls between the two * exclusive values specified; otherwise, throws an exception. * *
Validate.exclusiveBetween(0.1, 2.1, 1.1);
* * @param start the exclusive start value * @param end the exclusive end value * @param value the value to validate * @throws IllegalArgumentException if the value falls out of the boundaries * * @since 3.3 */ @SuppressWarnings("boxing") public static void exclusiveBetween(final double start, final double end, final double value) { // TODO when breaking BC, consider returning value if (value <= start || value >= end) { throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); } } /** * Validate that the specified primitive value falls between the two * exclusive values specified; otherwise, throws an exception with the * specified message. * *
Validate.exclusiveBetween(0.1, 2.1, 1.1, "Not in range");
* * @param start the exclusive start value * @param end the exclusive end value * @param value the value to validate * @param message the exception message if invalid, not null * * @throws IllegalArgumentException if the value falls outside the boundaries * * @since 3.3 */ public static void exclusiveBetween(final double start, final double end, final double value, final String message) { // TODO when breaking BC, consider returning value if (value <= start || value >= end) { throw new IllegalArgumentException(message); } } // isInstanceOf //--------------------------------------------------------------------------------- /** * Validates that the argument is an instance of the specified class, if not throws an exception. * *

This method is useful when validating according to an arbitrary class

* *
Validate.isInstanceOf(OkClass.class, object);
* *

The message of the exception is "Expected type: {type}, actual: {obj_type}"

* * @param type the class the object must be validated against, not null * @param obj the object to check, null throws an exception * @throws IllegalArgumentException if argument is not of specified class * @see #isInstanceOf(Class, Object, String, Object...) * * @since 3.0 */ public static void isInstanceOf(final Class type, final Object obj) { // TODO when breaking BC, consider returning obj if (type.isInstance(obj) == false) { throw new IllegalArgumentException(String.format(DEFAULT_IS_INSTANCE_OF_EX_MESSAGE, type.getName(), obj == null ? "null" : obj.getClass().getName())); } } /** *

Validate that the argument is an instance of the specified class; otherwise * throwing an exception with the specified message. This method is useful when * validating according to an arbitrary class

* *
Validate.isInstanceOf(OkClass.class, object, "Wrong class, object is of class %s",
     *   object.getClass().getName());
* * @param type the class the object must be validated against, not null * @param obj the object to check, null throws an exception * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @throws IllegalArgumentException if argument is not of specified class * @see #isInstanceOf(Class, Object) * * @since 3.0 */ public static void isInstanceOf(final Class type, final Object obj, final String message, final Object... values) { // TODO when breaking BC, consider returning obj if (type.isInstance(obj) == false) { throw new IllegalArgumentException(String.format(message, values)); } } // isAssignableFrom //--------------------------------------------------------------------------------- /** * Validates that the argument can be converted to the specified class, if not, throws an exception. * *

This method is useful when validating that there will be no casting errors.

* *
Validate.isAssignableFrom(SuperClass.class, object.getClass());
* *

The message format of the exception is "Cannot assign {type} to {superType}"

* * @param superType the class the class must be validated against, not null * @param type the class to check, not null * @throws IllegalArgumentException if type argument is not assignable to the specified superType * @see #isAssignableFrom(Class, Class, String, Object...) * * @since 3.0 */ public static void isAssignableFrom(final Class superType, final Class type) { // TODO when breaking BC, consider returning type if (superType.isAssignableFrom(type) == false) { throw new IllegalArgumentException(String.format(DEFAULT_IS_ASSIGNABLE_EX_MESSAGE, type == null ? "null" : type.getName(), superType.getName())); } } /** * Validates that the argument can be converted to the specified class, if not throws an exception. * *

This method is useful when validating if there will be no casting errors.

* *
Validate.isAssignableFrom(SuperClass.class, object.getClass());
* *

The message of the exception is "The validated object can not be converted to the" * followed by the name of the class and "class"

* * @param superType the class the class must be validated against, not null * @param type the class to check, not null * @param message the {@link String#format(String, Object...)} exception message if invalid, not null * @param values the optional values for the formatted exception message, null array not recommended * @throws IllegalArgumentException if argument can not be converted to the specified class * @see #isAssignableFrom(Class, Class) */ public static void isAssignableFrom(final Class superType, final Class type, final String message, final Object... values) { // TODO when breaking BC, consider returning type if (superType.isAssignableFrom(type) == false) { throw new IllegalArgumentException(String.format(message, values)); } } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/basic/ArrayMap.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils.basic; import java.util.Collection; import java.util.Map; import java.util.Set; /** * ArrayMap is a generic key->value mapping data structure that is * designed to be more memory efficient than a traditional {@link java.util.HashMap}, * this implementation is a version of the platform's * {@link ArrayMap} that can be used on older versions of the platform. * It keeps its mappings in an array data structure -- an integer array of hash * codes for each item, and an Object array of the key/value pairs. This allows it to * avoid having to create an extra object for every entry put in to the map, and it * also tries to control the growth of the size of these arrays more aggressively * (since growing them only requires copying the entries in the array, not rebuilding * a hash map). *

*

If you don't need the standard Java container APIs provided here (iterators etc), * consider using {@link SimpleArrayMap} instead.

*

*

Note that this implementation is not intended to be appropriate for data structures * that may contain large numbers of items. It is generally slower than a traditional * HashMap, since lookups require a binary search and adds and removes require inserting * and deleting entries in the array. For containers holding up to hundreds of items, * the performance difference is not significant, less than 50%.

*

*

Because this container is intended to better balance memory use, unlike most other * standard Java containers it will shrink its array as items are removed from it. Currently * you have no control over this shrinking -- if you set a capacity and then remove an * item, it may reduce the capacity to better match the current size. In the future an * explicit call to set the capacity should turn off this aggressive shrinking behavior.

*/ public class ArrayMap extends SimpleArrayMap implements Map { MapCollections mCollections; public ArrayMap() { super(); } /** * Create a new ArrayMap with a given initial capacity. */ public ArrayMap(int capacity) { super(capacity); } /** * Create a new ArrayMap with the mappings from the given ArrayMap. */ public ArrayMap(SimpleArrayMap map) { super(map); } private MapCollections getCollection() { if (mCollections == null) { mCollections = new MapCollections() { @Override protected int colGetSize() { return mSize; } @Override protected Object colGetEntry(int index, int offset) { return mArray[(index << 1) + offset]; } @Override protected int colIndexOfKey(Object key) { return indexOfKey(key); } @Override protected int colIndexOfValue(Object value) { return indexOfValue(value); } @Override protected Map colGetMap() { return ArrayMap.this; } @Override protected void colPut(K key, V value) { put(key, value); } @Override protected V colSetValue(int index, V value) { return setValueAt(index, value); } @Override protected void colRemoveAt(int index) { removeAt(index); } @Override protected void colClear() { clear(); } }; } return mCollections; } /** * Determine if the array map contains all of the keys in the given collection. * * @param collection The collection whose contents are to be checked against. * @return Returns true if this array map contains a key for every entry * in collection, else returns false. */ public boolean containsAll(Collection collection) { return MapCollections.containsAllHelper(this, collection); } /** * Perform a {@link #put(Object, Object)} of all key/value pairs in map * * @param map The map whose contents are to be retrieved. */ @Override public void putAll(Map map) { ensureCapacity(mSize + map.size()); for (Entry entry : map.entrySet()) { put(entry.getKey(), entry.getValue()); } } /** * Remove all keys in the array map that exist in the given collection. * * @param collection The collection whose contents are to be used to remove keys. * @return Returns true if any keys were removed from the array map, else false. */ public boolean removeAll(Collection collection) { return MapCollections.removeAllHelper(this, collection); } /** * Remove all keys in the array map that do not exist in the given collection. * * @param collection The collection whose contents are to be used to determine which * keys to keep. * @return Returns true if any keys were removed from the array map, else false. */ public boolean retainAll(Collection collection) { return MapCollections.retainAllHelper(this, collection); } /** * Return a {@link Set} for iterating over and interacting with all mappings * in the array map. *

*

Note: this is a very inefficient way to access the array contents, it * requires generating a number of temporary objects.

*

*

Note:

the semantics of this * Set are subtly different than that of a {@link java.util.HashMap}: most important, * the {@link Entry Map.Entry} object returned by its iterator is a single * object that exists for the entire iterator, so you can not hold on to it * after calling {@link java.util.Iterator#next() Iterator.next}.

*/ @Override public Set> entrySet() { return getCollection().getEntrySet(); } /** * Return a {@link Set} for iterating over and interacting with all keys * in the array map. *

*

Note: this is a fairly inefficient way to access the array contents, it * requires generating a number of temporary objects.

*/ @Override public Set keySet() { return getCollection().getKeySet(); } /** * Return a {@link Collection} for iterating over and interacting with all values * in the array map. *

*

Note: this is a fairly inefficient way to access the array contents, it * requires generating a number of temporary objects.

*/ @Override public Collection values() { return getCollection().getValues(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/basic/ArraySet.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils.basic; import android.util.Log; import java.lang.reflect.Array; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * ArraySet is a generic set data structure that is designed to be more memory efficient than a * traditional {@link java.util.HashSet}. The design is very similar to * {@link ArrayMap}, with all of the caveats described there. This implementation is * separate from ArrayMap, however, so the Object array contains only one item for each * entry in the set (instead of a pair for a mapping). *

*

Note that this implementation is not intended to be appropriate for data structures * that may contain large numbers of items. It is generally slower than a traditional * HashSet, since lookups require a binary search and adds and removes require inserting * and deleting entries in the array. For containers holding up to hundreds of items, * the performance difference is not significant, less than 50%.

*

*

Because this container is intended to better balance memory use, unlike most other * standard Java containers it will shrink its array as items are removed from it. Currently * you have no control over this shrinking -- if you set a capacity and then remove an * item, it may reduce the capacity to better match the current size. In the future an * explicit call to set the capacity should turn off this aggressive shrinking behavior.

*/ public final class ArraySet implements Collection, Set { private static final boolean DEBUG = false; private static final String TAG = "ArraySet"; /** * The minimum amount by which the capacity of a ArraySet will increase. * This is tuned to be relatively space-efficient. */ private static final int BASE_SIZE = 4; /** * Maximum number of entries to have in array caches. */ private static final int CACHE_SIZE = 10; /** * Caches of small array objects to avoid spamming garbage. The cache * Object[] variable is a pointer to a linked list of array objects. * The first entry in the array is a pointer to the next array in the * list; the second entry is a pointer to the int[] hash code array for it. */ static Object[] mBaseCache; static int mBaseCacheSize; static Object[] mTwiceBaseCache; static int mTwiceBaseCacheSize; int[] mHashes; Object[] mArray; int mSize; MapCollections mCollections; private int indexOf(Object key, int hash) { final int N = mSize; // Important fast case: if nothing is in here, nothing to look for. if (N == 0) { return ~0; } int index = ContainerHelpers.binarySearch(mHashes, N, hash); // If the hash code wasn't found, then we have no entry for this key. if (index < 0) { return index; } // If the key at the returned index matches, that's what we want. if (key.equals(mArray[index])) { return index; } // Search for a matching key after the index. int end; for (end = index + 1; end < N && mHashes[end] == hash; end++) { if (key.equals(mArray[end])) return end; } // Search for a matching key before the index. for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) { if (key.equals(mArray[i])) return i; } // Key not found -- return negative value indicating where a // new entry for this key should go. We use the end of the // hash chain to reduce the number of array entries that will // need to be copied when inserting. return ~end; } private int indexOfNull() { final int N = mSize; // Important fast case: if nothing is in here, nothing to look for. if (N == 0) { return ~0; } int index = ContainerHelpers.binarySearch(mHashes, N, 0); // If the hash code wasn't found, then we have no entry for this key. if (index < 0) { return index; } // If the key at the returned index matches, that's what we want. if (null == mArray[index]) { return index; } // Search for a matching key after the index. int end; for (end = index + 1; end < N && mHashes[end] == 0; end++) { if (null == mArray[end]) return end; } // Search for a matching key before the index. for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) { if (null == mArray[i]) return i; } // Key not found -- return negative value indicating where a // new entry for this key should go. We use the end of the // hash chain to reduce the number of array entries that will // need to be copied when inserting. return ~end; } private void allocArrays(final int size) { if (size == (BASE_SIZE * 2)) { synchronized (ArraySet.class) { if (mTwiceBaseCache != null) { final Object[] array = mTwiceBaseCache; mArray = array; mTwiceBaseCache = (Object[]) array[0]; mHashes = (int[]) array[1]; array[0] = array[1] = null; mTwiceBaseCacheSize--; if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have " + mTwiceBaseCacheSize + " entries"); return; } } } else if (size == BASE_SIZE) { synchronized (ArraySet.class) { if (mBaseCache != null) { final Object[] array = mBaseCache; mArray = array; mBaseCache = (Object[]) array[0]; mHashes = (int[]) array[1]; array[0] = array[1] = null; mBaseCacheSize--; if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have " + mBaseCacheSize + " entries"); return; } } } mHashes = new int[size]; mArray = new Object[size]; } private static void freeArrays(final int[] hashes, final Object[] array, final int size) { if (hashes.length == (BASE_SIZE * 2)) { synchronized (ArraySet.class) { if (mTwiceBaseCacheSize < CACHE_SIZE) { array[0] = mTwiceBaseCache; array[1] = hashes; for (int i = size - 1; i >= 2; i--) { array[i] = null; } mTwiceBaseCache = array; mTwiceBaseCacheSize++; if (DEBUG) Log.d(TAG, "Storing 2x cache " + array + " now have " + mTwiceBaseCacheSize + " entries"); } } } else if (hashes.length == BASE_SIZE) { synchronized (ArraySet.class) { if (mBaseCacheSize < CACHE_SIZE) { array[0] = mBaseCache; array[1] = hashes; for (int i = size - 1; i >= 2; i--) { array[i] = null; } mBaseCache = array; mBaseCacheSize++; if (DEBUG) Log.d(TAG, "Storing 1x cache " + array + " now have " + mBaseCacheSize + " entries"); } } } } /** * Create a new empty ArraySet. The default capacity of an array map is 0, and * will grow once items are added to it. */ public ArraySet() { mHashes = ContainerHelpers.EMPTY_INTS; mArray = ContainerHelpers.EMPTY_OBJECTS; mSize = 0; } /** * Create a new ArraySet with a given initial capacity. */ public ArraySet(int capacity) { if (capacity == 0) { mHashes = ContainerHelpers.EMPTY_INTS; mArray = ContainerHelpers.EMPTY_OBJECTS; } else { allocArrays(capacity); } mSize = 0; } /** * Create a new ArraySet with the mappings from the given ArraySet. */ public ArraySet(ArraySet set) { this(); if (set != null) { addAll(set); } } /** * {@hide} */ public ArraySet(Collection set) { this(); if (set != null) { addAll(set); } } /** * Make the array map empty. All storage is released. */ @Override public void clear() { if (mSize != 0) { freeArrays(mHashes, mArray, mSize); mHashes = ContainerHelpers.EMPTY_INTS; mArray = ContainerHelpers.EMPTY_OBJECTS; mSize = 0; } } /** * Ensure the array map can hold at least minimumCapacity * items. */ public void ensureCapacity(int minimumCapacity) { if (mHashes.length < minimumCapacity) { final int[] ohashes = mHashes; final Object[] oarray = mArray; allocArrays(minimumCapacity); if (mSize > 0) { System.arraycopy(ohashes, 0, mHashes, 0, mSize); System.arraycopy(oarray, 0, mArray, 0, mSize); } freeArrays(ohashes, oarray, mSize); } } /** * Check whether a value exists in the set. * * @param key The value to search for. * @return Returns true if the value exists, else false. */ @Override public boolean contains(Object key) { return indexOf(key) >= 0; } /** * Returns the index of a value in the set. * * @param key The value to search for. * @return Returns the index of the value if it exists, else a negative integer. */ public int indexOf(Object key) { return key == null ? indexOfNull() : indexOf(key, key.hashCode()); } /** * Return the value at the given index in the array. * * @param index The desired index, must be between 0 and {@link #size()}-1. * @return Returns the value stored at the given index. */ public E valueAt(int index) { return (E) mArray[index]; } /** * Return true if the array map contains no items. */ @Override public boolean isEmpty() { return mSize <= 0; } /** * Adds the specified object to this set. The set is not modified if it * already contains the object. * * @param value the object to add. * @return {@code true} if this set is modified, {@code false} otherwise. * @throws ClassCastException when the class of the object is inappropriate for this set. */ @Override public boolean add(E value) { final int hash; int index; if (value == null) { hash = 0; index = indexOfNull(); } else { hash = value.hashCode(); index = indexOf(value, hash); } if (index >= 0) { return false; } index = ~index; if (mSize >= mHashes.length) { final int n = mSize >= (BASE_SIZE * 2) ? (mSize + (mSize >> 1)) : (mSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE); if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n); final int[] ohashes = mHashes; final Object[] oarray = mArray; allocArrays(n); if (mHashes.length > 0) { if (DEBUG) Log.d(TAG, "add: copy 0-" + mSize + " to 0"); System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length); System.arraycopy(oarray, 0, mArray, 0, oarray.length); } freeArrays(ohashes, oarray, mSize); } if (index < mSize) { if (DEBUG) Log.d(TAG, "add: move " + index + "-" + (mSize - index) + " to " + (index + 1)); System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index); System.arraycopy(mArray, index, mArray, index + 1, mSize - index); } mHashes[index] = hash; mArray[index] = value; mSize++; return true; } /** * Perform a {@link #add(Object)} of all values in array * * @param array The array whose contents are to be retrieved. */ public void addAll(ArraySet array) { final int N = array.mSize; ensureCapacity(mSize + N); if (mSize == 0) { if (N > 0) { System.arraycopy(array.mHashes, 0, mHashes, 0, N); System.arraycopy(array.mArray, 0, mArray, 0, N); mSize = N; } } else { for (int i = 0; i < N; i++) { add(array.valueAt(i)); } } } /** * Removes the specified object from this set. * * @param object the object to remove. * @return {@code true} if this set was modified, {@code false} otherwise. */ @Override public boolean remove(Object object) { final int index = indexOf(object); if (index >= 0) { removeAt(index); return true; } return false; } /** * Remove the key/value mapping at the given index. * * @param index The desired index, must be between 0 and {@link #size()}-1. * @return Returns the value that was stored at this index. */ public E removeAt(int index) { final Object old = mArray[index]; if (mSize <= 1) { // Now empty. if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0"); freeArrays(mHashes, mArray, mSize); mHashes = ContainerHelpers.EMPTY_INTS; mArray = ContainerHelpers.EMPTY_OBJECTS; mSize = 0; } else { if (mHashes.length > (BASE_SIZE * 2) && mSize < mHashes.length / 3) { // Shrunk enough to reduce size of arrays. We don't allow it to // shrink smaller than (BASE_SIZE*2) to avoid flapping between // that and BASE_SIZE. final int n = mSize > (BASE_SIZE * 2) ? (mSize + (mSize >> 1)) : (BASE_SIZE * 2); if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n); final int[] ohashes = mHashes; final Object[] oarray = mArray; allocArrays(n); mSize--; if (index > 0) { if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0"); System.arraycopy(ohashes, 0, mHashes, 0, index); System.arraycopy(oarray, 0, mArray, 0, index); } if (index < mSize) { if (DEBUG) Log.d(TAG, "remove: copy from " + (index + 1) + "-" + mSize + " to " + index); System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index); System.arraycopy(oarray, index + 1, mArray, index, mSize - index); } } else { mSize--; if (index < mSize) { if (DEBUG) Log.d(TAG, "remove: move " + (index + 1) + "-" + mSize + " to " + index); System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index); System.arraycopy(mArray, index + 1, mArray, index, mSize - index); } mArray[mSize] = null; } } return (E) old; } /** * Perform a {@link #remove(Object)} of all values in array * * @param array The array whose contents are to be removed. */ public boolean removeAll(ArraySet array) { // TODO: If array is sufficiently large, a marking approach might be beneficial. In a first // pass, use the property that the sets are sorted by hash to make this linear passes // (except for hash collisions, which means worst case still n*m), then do one // collection pass into a new array. This avoids binary searches and excessive memcpy. final int N = array.mSize; // Note: ArraySet does not make thread-safety guarantees. So instead of OR-ing together all // the single results, compare size before and after. final int originalSize = mSize; for (int i = 0; i < N; i++) { remove(array.valueAt(i)); } return originalSize != mSize; } /** * Return the number of items in this array map. */ @Override public int size() { return mSize; } @Override public Object[] toArray() { Object[] result = new Object[mSize]; System.arraycopy(mArray, 0, result, 0, mSize); return result; } @Override public T[] toArray(T[] array) { if (array.length < mSize) { @SuppressWarnings("unchecked") T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(), mSize); array = newArray; } System.arraycopy(mArray, 0, array, 0, mSize); if (array.length > mSize) { array[mSize] = null; } return array; } /** * {@inheritDoc} *

*

This implementation returns false if the object is not a set, or * if the sets have different sizes. Otherwise, for each value in this * set, it checks to make sure the value also exists in the other set. * If any value doesn't exist, the method returns false; otherwise, it * returns true. */ @Override public boolean equals(Object object) { if (this == object) { return true; } if (object instanceof Set) { Set set = (Set) object; if (size() != set.size()) { return false; } try { for (int i = 0; i < mSize; i++) { E mine = valueAt(i); if (!set.contains(mine)) { return false; } } } catch (NullPointerException ignored) { return false; } catch (ClassCastException ignored) { return false; } return true; } return false; } /** * {@inheritDoc} */ @Override public int hashCode() { final int[] hashes = mHashes; int result = 0; for (int i = 0, s = mSize; i < s; i++) { result += hashes[i]; } return result; } /** * {@inheritDoc} *

*

This implementation composes a string by iterating over its values. If * this set contains itself as a value, the string "(this Set)" * will appear in its place. */ @Override public String toString() { if (isEmpty()) { return "{}"; } StringBuilder buffer = new StringBuilder(mSize * 14); buffer.append('{'); for (int i = 0; i < mSize; i++) { if (i > 0) { buffer.append(", "); } Object value = valueAt(i); if (value != this) { buffer.append(value); } else { buffer.append("(this Set)"); } } buffer.append('}'); return buffer.toString(); } // ------------------------------------------------------------------------ // Interop with traditional Java containers. Not as efficient as using // specialized collection APIs. // ------------------------------------------------------------------------ private MapCollections getCollection() { if (mCollections == null) { mCollections = new MapCollections() { @Override protected int colGetSize() { return mSize; } @Override protected Object colGetEntry(int index, int offset) { return mArray[index]; } @Override protected int colIndexOfKey(Object key) { return indexOf(key); } @Override protected int colIndexOfValue(Object value) { return indexOf(value); } @Override protected Map colGetMap() { throw new UnsupportedOperationException("not a map"); } @Override protected void colPut(E key, E value) { add(key); } @Override protected E colSetValue(int index, E value) { throw new UnsupportedOperationException("not a map"); } @Override protected void colRemoveAt(int index) { removeAt(index); } @Override protected void colClear() { clear(); } }; } return mCollections; } /** * Return an {@link Iterator} over all values in the set. *

*

Note: this is a fairly inefficient way to access the array contents, it * requires generating a number of temporary objects and allocates additional state * information associated with the container that will remain for the life of the container.

*/ @Override public Iterator iterator() { return getCollection().getKeySet().iterator(); } /** * Determine if the array set contains all of the values in the given collection. * * @param collection The collection whose contents are to be checked against. * @return Returns true if this array set contains a value for every entry * in collection, else returns false. */ @Override public boolean containsAll(Collection collection) { Iterator it = collection.iterator(); while (it.hasNext()) { if (!contains(it.next())) { return false; } } return true; } /** * Perform an {@link #add(Object)} of all values in collection * * @param collection The collection whose contents are to be retrieved. */ @Override public boolean addAll(Collection collection) { ensureCapacity(mSize + collection.size()); boolean added = false; for (E value : collection) { added |= add(value); } return added; } /** * Remove all values in the array set that exist in the given collection. * * @param collection The collection whose contents are to be used to remove values. * @return Returns true if any values were removed from the array set, else false. */ @Override public boolean removeAll(Collection collection) { boolean removed = false; for (Object value : collection) { removed |= remove(value); } return removed; } /** * Remove all values in the array set that do not exist in the given collection. * * @param collection The collection whose contents are to be used to determine which * values to keep. * @return Returns true if any values were removed from the array set, else false. */ @Override public boolean retainAll(Collection collection) { boolean removed = false; for (int i = mSize - 1; i >= 0; i--) { if (!collection.contains(mArray[i])) { removeAt(i); removed = true; } } return removed; } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/basic/ByteConvertor.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils.basic; /** * 在byte[] 和 int long 之间进行类型转换, 打印byte[]到string 里面 * * @author RePlugin Team */ public class ByteConvertor { public static String toHex(byte[] buf) { if (buf == null) { return ""; } StringBuilder result = new StringBuilder(2 * buf.length); for (byte element : buf) { appendHex(result, element); } return result.toString(); } private static final String HEX = "0123456789ABCDEF"; private static void appendHex(StringBuilder sb, byte b) { // bigEnd sb.append(HEX.charAt(b & 0x0f)).append(HEX.charAt((b >> 4) & // 0x0f)); sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f)); } public static int toInt(byte[] byteArray4) { int intValue = 0; intValue |= byteArray4[3] & 0xff; intValue <<= 8; intValue |= byteArray4[2] & 0xff; intValue <<= 8; intValue |= byteArray4[1] & 0xff; intValue <<= 8; intValue |= byteArray4[0] & 0xff; return intValue; } public static long toLong(byte[] byteArray8) { long longValue = 0; longValue |= byteArray8[7] & 0xff; longValue <<= 8; longValue |= byteArray8[6] & 0xff; longValue <<= 8; longValue |= byteArray8[5] & 0xff; longValue <<= 8; longValue |= byteArray8[4] & 0xff; longValue <<= 8; longValue |= byteArray8[3] & 0xff; longValue <<= 8; longValue |= byteArray8[2] & 0xff; longValue <<= 8; longValue |= byteArray8[1] & 0xff; longValue <<= 8; longValue |= byteArray8[0] & 0xff; return longValue; } public static byte[] toBytes(int intValue) { byte[] byteValue = new byte[4]; byteValue[0] = (byte) (intValue & 0xff); byteValue[1] = (byte) ((intValue & 0xff00) >> 8); byteValue[2] = (byte) ((intValue & 0xff0000) >> 16); byteValue[3] = (byte) ((intValue & 0xff000000) >> 24); return byteValue; } public static byte[] toBytes(long longValue) { byte[] byteValue = new byte[8]; byteValue[0] = (byte) (longValue & 0xffl); byteValue[1] = (byte) ((longValue & 0xff00l) >> 8); byteValue[2] = (byte) ((longValue & 0xff0000l) >> 16); byteValue[3] = (byte) ((longValue & 0xff000000l) >> 24); byteValue[4] = (byte) ((longValue & 0xff00000000l) >> 32); byteValue[5] = (byte) ((longValue & 0xff0000000000l) >> 40); byteValue[6] = (byte) ((longValue & 0xff000000000000l) >> 48); byteValue[7] = (byte) ((longValue & 0xff00000000000000l) >> 56); return byteValue; } public static byte[] subBytes(byte[] buf, int from, int len) { byte[] subBuf = new byte[len]; for (int i = 0; i < len; i++) { subBuf[i] = buf[from + i]; } return subBuf; } /** * Converts a byte array into a String hexidecimal characters in lower case * * null returns null */ public static String bytesToHexString(byte[] bytes) { if (bytes == null) { return null; } String table = "0123456789abcdef"; StringBuilder ret = new StringBuilder(2 * bytes.length); for (byte c : bytes) { int b; b = 0x0f & (c >> 4); ret.append(table.charAt(b)); b = 0x0f & c; ret.append(table.charAt(b)); } return ret.toString(); } /** * Converts a hex String to a byte array. * * @param s * A string of hexadecimal characters, must be an even number of * chars long * * @return byte array representation * * @throws RuntimeException * on invalid format */ public static byte[] hexStringToBytes(String s) { byte[] ret; if (s == null) { return null; } int sz = s.length(); ret = new byte[sz / 2]; for (int i = 0; i < sz; i += 2) { ret[i / 2] = (byte) ((hexCharToInt(s.charAt(i)) << 4) | hexCharToInt(s.charAt(i + 1))); } return ret; } public static int hexCharToInt(char c) { if (c >= '0' && c <= '9') { return (c - '0'); } if (c >= 'A' && c <= 'F') { return (c - 'A' + 10); } if (c >= 'a' && c <= 'f') { return (c - 'a' + 10); } throw new RuntimeException("invalid hex char '" + c + "'"); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/basic/ContainerHelpers.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils.basic; /** * @author RePlugin Team */ class ContainerHelpers { static final int[] EMPTY_INTS = new int[0]; static final long[] EMPTY_LONGS = new long[0]; static final Object[] EMPTY_OBJECTS = new Object[0]; public static int idealIntArraySize(int need) { return idealByteArraySize(need * 4) / 4; } public static int idealLongArraySize(int need) { return idealByteArraySize(need * 8) / 8; } public static int idealByteArraySize(int need) { for (int i = 4; i < 32; i++) { if (need <= (1 << i) - 12) { return (1 << i) - 12; } } return need; } public static boolean equal(Object a, Object b) { return a == b || (a != null && a.equals(b)); } // This is Arrays.binarySearch(), but doesn't do any argument validation. static int binarySearch(int[] array, int size, int value) { int lo = 0; int hi = size - 1; while (lo <= hi) { int mid = (lo + hi) >>> 1; int midVal = array[mid]; if (midVal < value) { lo = mid + 1; } else if (midVal > value) { hi = mid - 1; } else { return mid; // value found } } return ~lo; // value not present } static int binarySearch(long[] array, int size, long value) { int lo = 0; int hi = size - 1; while (lo <= hi) { final int mid = (lo + hi) >>> 1; final long midVal = array[mid]; if (midVal < value) { lo = mid + 1; } else if (midVal > value) { hi = mid - 1; } else { return mid; // value found } } return ~lo; // value not present } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/basic/MapCollections.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils.basic; import java.lang.reflect.Array; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * Helper for writing standard Java collection interfaces to a data * structure like {@link ArrayMap}. * * @author RePlugin Team */ abstract class MapCollections { EntrySet mEntrySet; KeySet mKeySet; ValuesCollection mValues; final class ArrayIterator implements Iterator { final int mOffset; int mSize; int mIndex; boolean mCanRemove = false; ArrayIterator(int offset) { mOffset = offset; mSize = colGetSize(); } @Override public boolean hasNext() { return mIndex < mSize; } @Override public T next() { Object res = colGetEntry(mIndex, mOffset); mIndex++; mCanRemove = true; return (T) res; } @Override public void remove() { if (!mCanRemove) { throw new IllegalStateException(); } mIndex--; mSize--; mCanRemove = false; colRemoveAt(mIndex); } } final class MapIterator implements Iterator>, Map.Entry { int mEnd; int mIndex; boolean mEntryValid = false; MapIterator() { mEnd = colGetSize() - 1; mIndex = -1; } @Override public boolean hasNext() { return mIndex < mEnd; } @Override public Map.Entry next() { mIndex++; mEntryValid = true; return this; } @Override public void remove() { if (!mEntryValid) { throw new IllegalStateException(); } colRemoveAt(mIndex); mIndex--; mEnd--; mEntryValid = false; } @Override public K getKey() { if (!mEntryValid) { throw new IllegalStateException( "This container does not support retaining Map.Entry objects"); } return (K) colGetEntry(mIndex, 0); } @Override public V getValue() { if (!mEntryValid) { throw new IllegalStateException( "This container does not support retaining Map.Entry objects"); } return (V) colGetEntry(mIndex, 1); } @Override public V setValue(V object) { if (!mEntryValid) { throw new IllegalStateException( "This container does not support retaining Map.Entry objects"); } return colSetValue(mIndex, object); } @Override public final boolean equals(Object o) { if (!mEntryValid) { throw new IllegalStateException( "This container does not support retaining Map.Entry objects"); } if (!(o instanceof Map.Entry)) { return false; } Map.Entry e = (Map.Entry) o; return ContainerHelpers.equal(e.getKey(), colGetEntry(mIndex, 0)) && ContainerHelpers.equal(e.getValue(), colGetEntry(mIndex, 1)); } @Override public final int hashCode() { if (!mEntryValid) { throw new IllegalStateException( "This container does not support retaining Map.Entry objects"); } final Object key = colGetEntry(mIndex, 0); final Object value = colGetEntry(mIndex, 1); return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } @Override public final String toString() { return getKey() + "=" + getValue(); } } final class EntrySet implements Set> { @Override public boolean add(Map.Entry object) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection> collection) { int oldSize = colGetSize(); for (Map.Entry entry : collection) { colPut(entry.getKey(), entry.getValue()); } return oldSize != colGetSize(); } @Override public void clear() { colClear(); } @Override public boolean contains(Object o) { if (!(o instanceof Map.Entry)) { return false; } Map.Entry e = (Map.Entry) o; int index = colIndexOfKey(e.getKey()); if (index < 0) { return false; } Object foundVal = colGetEntry(index, 1); return ContainerHelpers.equal(foundVal, e.getValue()); } @Override public boolean containsAll(Collection collection) { Iterator it = collection.iterator(); while (it.hasNext()) { if (!contains(it.next())) { return false; } } return true; } @Override public boolean isEmpty() { return colGetSize() == 0; } @Override public Iterator> iterator() { return new MapIterator(); } @Override public boolean remove(Object object) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection collection) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection collection) { throw new UnsupportedOperationException(); } @Override public int size() { return colGetSize(); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public T[] toArray(T[] array) { throw new UnsupportedOperationException(); } @Override public boolean equals(Object object) { return equalsSetHelper(this, object); } @Override public int hashCode() { int result = 0; for (int i = colGetSize() - 1; i >= 0; i--) { final Object key = colGetEntry(i, 0); final Object value = colGetEntry(i, 1); result += ((key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode())); } return result; } } final class KeySet implements Set { @Override public boolean add(K object) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection collection) { throw new UnsupportedOperationException(); } @Override public void clear() { colClear(); } @Override public boolean contains(Object object) { return colIndexOfKey(object) >= 0; } @Override public boolean containsAll(Collection collection) { return containsAllHelper(colGetMap(), collection); } @Override public boolean isEmpty() { return colGetSize() == 0; } @Override public Iterator iterator() { return new ArrayIterator(0); } @Override public boolean remove(Object object) { int index = colIndexOfKey(object); if (index >= 0) { colRemoveAt(index); return true; } return false; } @Override public boolean removeAll(Collection collection) { return removeAllHelper(colGetMap(), collection); } @Override public boolean retainAll(Collection collection) { return retainAllHelper(colGetMap(), collection); } @Override public int size() { return colGetSize(); } @Override public Object[] toArray() { return toArrayHelper(0); } @Override public T[] toArray(T[] array) { return toArrayHelper(array, 0); } @Override public boolean equals(Object object) { return equalsSetHelper(this, object); } @Override public int hashCode() { int result = 0; for (int i = colGetSize() - 1; i >= 0; i--) { Object obj = colGetEntry(i, 0); result += obj == null ? 0 : obj.hashCode(); } return result; } } final class ValuesCollection implements Collection { @Override public boolean add(V object) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection collection) { throw new UnsupportedOperationException(); } @Override public void clear() { colClear(); } @Override public boolean contains(Object object) { return colIndexOfValue(object) >= 0; } @Override public boolean containsAll(Collection collection) { Iterator it = collection.iterator(); while (it.hasNext()) { if (!contains(it.next())) { return false; } } return true; } @Override public boolean isEmpty() { return colGetSize() == 0; } @Override public Iterator iterator() { return new ArrayIterator(1); } @Override public boolean remove(Object object) { int index = colIndexOfValue(object); if (index >= 0) { colRemoveAt(index); return true; } return false; } @Override public boolean removeAll(Collection collection) { int n = colGetSize(); boolean changed = false; for (int i = 0; i < n; i++) { Object cur = colGetEntry(i, 1); if (collection.contains(cur)) { colRemoveAt(i); i--; n--; changed = true; } } return changed; } @Override public boolean retainAll(Collection collection) { int n = colGetSize(); boolean changed = false; for (int i = 0; i < n; i++) { Object cur = colGetEntry(i, 1); if (!collection.contains(cur)) { colRemoveAt(i); i--; n--; changed = true; } } return changed; } @Override public int size() { return colGetSize(); } @Override public Object[] toArray() { return toArrayHelper(1); } @Override public T[] toArray(T[] array) { return toArrayHelper(array, 1); } } public static boolean containsAllHelper(Map map, Collection collection) { Iterator it = collection.iterator(); while (it.hasNext()) { if (!map.containsKey(it.next())) { return false; } } return true; } public static boolean removeAllHelper(Map map, Collection collection) { int oldSize = map.size(); Iterator it = collection.iterator(); while (it.hasNext()) { map.remove(it.next()); } return oldSize != map.size(); } public static boolean retainAllHelper(Map map, Collection collection) { int oldSize = map.size(); Iterator it = map.keySet().iterator(); while (it.hasNext()) { if (!collection.contains(it.next())) { it.remove(); } } return oldSize != map.size(); } public Object[] toArrayHelper(int offset) { final int n = colGetSize(); Object[] result = new Object[n]; for (int i = 0; i < n; i++) { result[i] = colGetEntry(i, offset); } return result; } public T[] toArrayHelper(T[] array, int offset) { final int n = colGetSize(); if (array.length < n) { @SuppressWarnings("unchecked") T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(), n); array = newArray; } for (int i = 0; i < n; i++) { array[i] = (T) colGetEntry(i, offset); } if (array.length > n) { array[n] = null; } return array; } public static boolean equalsSetHelper(Set set, Object object) { if (set == object) { return true; } if (object instanceof Set) { Set s = (Set) object; try { return set.size() == s.size() && set.containsAll(s); } catch (NullPointerException ignored) { return false; } catch (ClassCastException ignored) { return false; } } return false; } public Set> getEntrySet() { if (mEntrySet == null) { mEntrySet = new EntrySet(); } return mEntrySet; } public Set getKeySet() { if (mKeySet == null) { mKeySet = new KeySet(); } return mKeySet; } public Collection getValues() { if (mValues == null) { mValues = new ValuesCollection(); } return mValues; } protected abstract int colGetSize(); protected abstract Object colGetEntry(int index, int offset); protected abstract int colIndexOfKey(Object key); protected abstract int colIndexOfValue(Object key); protected abstract Map colGetMap(); protected abstract void colPut(K key, V value); protected abstract V colSetValue(int index, V value); protected abstract void colRemoveAt(int index); protected abstract void colClear(); } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/basic/SecurityUtil.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils.basic; import com.qihoo360.replugin.utils.CloseableUtils; import com.qihoo360.replugin.utils.FileUtils; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class SecurityUtil { /** 计算给定 byte [] 串的 MD5 */ public static byte[] MD5(byte[] input) { MessageDigest md = null; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } if (md != null) { md.update(input); return md.digest(); } else { return null; } } public static String getMD5(byte[] input) { return ByteConvertor.bytesToHexString(MD5(input)); } public static String getMD5(String input) { if (input == null) { return ""; } return getMD5(input.getBytes()); } /** 计算文件 MD5,返回十六进制串 */ public static String getFileMD5(String filename) { byte[] digest = MD5(filename); if (digest == null) { return null; } else { return ByteConvertor.bytesToHexString(digest); } } public static String getMd5ByFile(File file) { FileInputStream in = null; byte[] digest = null; try { in = new FileInputStream(file); MappedByteBuffer byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()); MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(byteBuffer); digest = md5.digest(); } catch (Exception e) { e.printStackTrace(); } finally { if (null != in) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } if (digest == null) { return null; } else { return ByteConvertor.bytesToHexString(digest); } } public static String getMD5(InputStream inputStream) { byte[] digest = null; BufferedInputStream in = null; try { MessageDigest md = MessageDigest.getInstance("MD5"); in = new BufferedInputStream(inputStream); int theByte = 0; byte[] buffer = new byte[1024]; while ((theByte = in.read(buffer)) != -1) { md.update(buffer, 0, theByte); } digest = md.digest(); } catch (Exception e) { //ignore } finally { if (in != null) { try { in.close(); } catch (Exception e) { //ignore } } } if (digest == null) { return null; } else { return ByteConvertor.bytesToHexString(digest); } } /** 计算文件 MD5,返回 byte []. 如果文件不存在,返回 null. */ public static byte[] MD5(String filename) { return MD5(new File(filename)); } public static byte[] MD5(File file) { InputStream in = null; try { in = FileUtils.openInputStream(file); return MD5(in); } catch (Exception e) { //ignore } finally { CloseableUtils.closeQuietly(in); } return null; } public static final byte[] MD5(InputStream in) throws NoSuchAlgorithmException, IOException { MessageDigest digest = MessageDigest.getInstance("MD5"); byte buffer[] = new byte[4096]; int rc = 0; while ((rc = in.read(buffer)) >= 0) { if (rc > 0) { digest.update(buffer, 0, rc); } } return digest.digest(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/basic/SimpleArrayMap.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils.basic; import android.util.Log; import java.util.Map; /** * Base implementation of {@link ArrayMap} that doesn't include any standard Java * container API interoperability. These features are generally heavier-weight ways * to interact with the container, so discouraged, but they can be useful to make it * easier to use as a drop-in replacement for HashMap. If you don't need them, this * class can be preferrable since it doesn't bring in any of the implementation of those * APIs, allowing that code to be stripped by ProGuard. */ public class SimpleArrayMap { private static final boolean DEBUG = false; private static final String TAG = "ArrayMap"; /** * The minimum amount by which the capacity of a ArrayMap will increase. * This is tuned to be relatively space-efficient. */ private static final int BASE_SIZE = 4; /** * Maximum number of entries to have in array caches. */ private static final int CACHE_SIZE = 10; /** * Caches of small array objects to avoid spamming garbage. The cache * Object[] variable is a pointer to a linked list of array objects. * The first entry in the array is a pointer to the next array in the * list; the second entry is a pointer to the int[] hash code array for it. */ static Object[] mBaseCache; static int mBaseCacheSize; static Object[] mTwiceBaseCache; static int mTwiceBaseCacheSize; int[] mHashes; Object[] mArray; int mSize; int indexOf(Object key, int hash) { final int n = mSize; // Important fast case: if nothing is in here, nothing to look for. if (n == 0) { return ~0; } int index = ContainerHelpers.binarySearch(mHashes, n, hash); // If the hash code wasn't found, then we have no entry for this key. if (index < 0) { return index; } // If the key at the returned index matches, that's what we want. if (key.equals(mArray[index << 1])) { return index; } // Search for a matching key after the index. int end; for (end = index + 1; end < n && mHashes[end] == hash; end++) { if (key.equals(mArray[end << 1])) { return end; } } // Search for a matching key before the index. for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) { if (key.equals(mArray[i << 1])) { return i; } } // Key not found -- return negative value indicating where a // new entry for this key should go. We use the end of the // hash chain to reduce the number of array entries that will // need to be copied when inserting. return ~end; } int indexOfNull() { final int N = mSize; // Important fast case: if nothing is in here, nothing to look for. if (N == 0) { return ~0; } int index = ContainerHelpers.binarySearch(mHashes, N, 0); // If the hash code wasn't found, then we have no entry for this key. if (index < 0) { return index; } // If the key at the returned index matches, that's what we want. if (null == mArray[index<<1]) { return index; } // Search for a matching key after the index. int end; for (end = index + 1; end < N && mHashes[end] == 0; end++) { if (null == mArray[end << 1]) return end; } // Search for a matching key before the index. for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) { if (null == mArray[i << 1]) return i; } // Key not found -- return negative value indicating where a // new entry for this key should go. We use the end of the // hash chain to reduce the number of array entries that will // need to be copied when inserting. return ~end; } private void allocArrays(final int size) { if (size == (BASE_SIZE*2)) { synchronized (ArrayMap.class) { if (mTwiceBaseCache != null) { final Object[] array = mTwiceBaseCache; mArray = array; mTwiceBaseCache = (Object[])array[0]; mHashes = (int[])array[1]; array[0] = array[1] = null; mTwiceBaseCacheSize--; if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have " + mTwiceBaseCacheSize + " entries"); return; } } } else if (size == BASE_SIZE) { synchronized (ArrayMap.class) { if (mBaseCache != null) { final Object[] array = mBaseCache; mArray = array; mBaseCache = (Object[])array[0]; mHashes = (int[])array[1]; array[0] = array[1] = null; mBaseCacheSize--; if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have " + mBaseCacheSize + " entries"); return; } } } mHashes = new int[size]; mArray = new Object[size<<1]; } private static void freeArrays(final int[] hashes, final Object[] array, final int size) { if (hashes.length == (BASE_SIZE*2)) { synchronized (ArrayMap.class) { if (mTwiceBaseCacheSize < CACHE_SIZE) { array[0] = mTwiceBaseCache; array[1] = hashes; for (int i=(size<<1)-1; i>=2; i--) { array[i] = null; } mTwiceBaseCache = array; mTwiceBaseCacheSize++; if (DEBUG) Log.d(TAG, "Storing 2x cache " + array + " now have " + mTwiceBaseCacheSize + " entries"); } } } else if (hashes.length == BASE_SIZE) { synchronized (ArrayMap.class) { if (mBaseCacheSize < CACHE_SIZE) { array[0] = mBaseCache; array[1] = hashes; for (int i=(size<<1)-1; i>=2; i--) { array[i] = null; } mBaseCache = array; mBaseCacheSize++; if (DEBUG) Log.d(TAG, "Storing 1x cache " + array + " now have " + mBaseCacheSize + " entries"); } } } } /** * Create a new empty ArrayMap. The default capacity of an array map is 0, and * will grow once items are added to it. */ public SimpleArrayMap() { mHashes = ContainerHelpers.EMPTY_INTS; mArray = ContainerHelpers.EMPTY_OBJECTS; mSize = 0; } /** * Create a new ArrayMap with a given initial capacity. */ public SimpleArrayMap(int capacity) { if (capacity == 0) { mHashes = ContainerHelpers.EMPTY_INTS; mArray = ContainerHelpers.EMPTY_OBJECTS; } else { allocArrays(capacity); } mSize = 0; } /** * Create a new ArrayMap with the mappings from the given ArrayMap. */ public SimpleArrayMap(SimpleArrayMap map) { this(); if (map != null) { putAll(map); } } /** * Make the array map empty. All storage is released. */ public void clear() { if (mSize != 0) { freeArrays(mHashes, mArray, mSize); mHashes = ContainerHelpers.EMPTY_INTS; mArray = ContainerHelpers.EMPTY_OBJECTS; mSize = 0; } } /** * Ensure the array map can hold at least minimumCapacity * items. */ public void ensureCapacity(int minimumCapacity) { if (mHashes.length < minimumCapacity) { final int[] ohashes = mHashes; final Object[] oarray = mArray; allocArrays(minimumCapacity); if (mSize > 0) { System.arraycopy(ohashes, 0, mHashes, 0, mSize); System.arraycopy(oarray, 0, mArray, 0, mSize<<1); } freeArrays(ohashes, oarray, mSize); } } /** * Check whether a key exists in the array. * * @param key The key to search for. * @return Returns true if the key exists, else false. */ public boolean containsKey(Object key) { return indexOfKey(key) >= 0; } /** * Returns the index of a key in the set. * * @param key The key to search for. * @return Returns the index of the key if it exists, else a negative integer. */ public int indexOfKey(Object key) { return key == null ? indexOfNull() : indexOf(key, key.hashCode()); } int indexOfValue(Object value) { final int N = mSize*2; final Object[] array = mArray; if (value == null) { for (int i=1; i>1; } } } else { for (int i=1; i>1; } } } return -1; } /** * Check whether a value exists in the array. This requires a linear search * through the entire array. * * @param value The value to search for. * @return Returns true if the value exists, else false. */ public boolean containsValue(Object value) { return indexOfValue(value) >= 0; } /** * Retrieve a value from the array. * @param key The key of the value to retrieve. * @return Returns the value associated with the given key, * or null if there is no such key. */ public V get(Object key) { final int index = indexOfKey(key); return index >= 0 ? (V)mArray[(index<<1)+1] : null; } /** * Return the key at the given index in the array. * @param index The desired index, must be between 0 and {@link #size()}-1. * @return Returns the key stored at the given index. */ public K keyAt(int index) { return (K)mArray[index << 1]; } /** * Return the value at the given index in the array. * @param index The desired index, must be between 0 and {@link #size()}-1. * @return Returns the value stored at the given index. */ public V valueAt(int index) { return (V)mArray[(index << 1) + 1]; } /** * Set the value at a given index in the array. * @param index The desired index, must be between 0 and {@link #size()}-1. * @param value The new value to store at this index. * @return Returns the previous value at the given index. */ public V setValueAt(int index, V value) { index = (index << 1) + 1; V old = (V)mArray[index]; mArray[index] = value; return old; } /** * Return true if the array map contains no items. */ public boolean isEmpty() { return mSize <= 0; } /** * Add a new value to the array map. * @param key The key under which to store the value. Must not be null. If * this key already exists in the array, its value will be replaced. * @param value The value to store for the given key. * @return Returns the old value that was stored for the given key, or null if there * was no such key. */ public V put(K key, V value) { final int hash; int index; if (key == null) { hash = 0; index = indexOfNull(); } else { hash = key.hashCode(); index = indexOf(key, hash); } if (index >= 0) { index = (index<<1) + 1; final V old = (V)mArray[index]; mArray[index] = value; return old; } index = ~index; if (mSize >= mHashes.length) { final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1)) : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE); if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n); final int[] ohashes = mHashes; final Object[] oarray = mArray; allocArrays(n); if (mHashes.length > 0) { if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0"); System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length); System.arraycopy(oarray, 0, mArray, 0, oarray.length); } freeArrays(ohashes, oarray, mSize); } if (index < mSize) { if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index) + " to " + (index+1)); System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index); System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1); } mHashes[index] = hash; mArray[index<<1] = key; mArray[(index<<1)+1] = value; mSize++; return null; } /** * Perform a {@link #put(Object, Object)} of all key/value pairs in array * @param array The array whose contents are to be retrieved. */ public void putAll(SimpleArrayMap array) { final int N = array.mSize; ensureCapacity(mSize + N); if (mSize == 0) { if (N > 0) { System.arraycopy(array.mHashes, 0, mHashes, 0, N); System.arraycopy(array.mArray, 0, mArray, 0, N<<1); mSize = N; } } else { for (int i=0; i= 0) { return removeAt(index); } return null; } /** * Remove the key/value mapping at the given index. * @param index The desired index, must be between 0 and {@link #size()}-1. * @return Returns the value that was stored at this index. */ public V removeAt(int index) { final Object old = mArray[(index << 1) + 1]; if (mSize <= 1) { // Now empty. if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0"); freeArrays(mHashes, mArray, mSize); mHashes = ContainerHelpers.EMPTY_INTS; mArray = ContainerHelpers.EMPTY_OBJECTS; mSize = 0; } else { if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) { // Shrunk enough to reduce size of arrays. We don't allow it to // shrink smaller than (BASE_SIZE*2) to avoid flapping between // that and BASE_SIZE. final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2); if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n); final int[] ohashes = mHashes; final Object[] oarray = mArray; allocArrays(n); mSize--; if (index > 0) { if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0"); System.arraycopy(ohashes, 0, mHashes, 0, index); System.arraycopy(oarray, 0, mArray, 0, index << 1); } if (index < mSize) { if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize + " to " + index); System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index); System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1, (mSize - index) << 1); } } else { mSize--; if (index < mSize) { if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize + " to " + index); System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index); System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1, (mSize - index) << 1); } mArray[mSize << 1] = null; mArray[(mSize << 1) + 1] = null; } } return (V)old; } /** * Return the number of items in this array map. */ public int size() { return mSize; } /** * {@inheritDoc} * *

This implementation returns false if the object is not a map, or * if the maps have different sizes. Otherwise, for each key in this map, * values of both maps are compared. If the values for any key are not * equal, the method returns false, otherwise it returns true. */ @Override public boolean equals(Object object) { if (this == object) { return true; } if (object instanceof Map) { Map map = (Map) object; if (size() != map.size()) { return false; } try { for (int i=0; iThis implementation composes a string by iterating over its mappings. If * this map contains itself as a key or a value, the string "(this Map)" * will appear in its place. */ @Override public String toString() { if (isEmpty()) { return "{}"; } StringBuilder buffer = new StringBuilder(mSize * 28); buffer.append('{'); for (int i=0; i 0) { buffer.append(", "); } Object key = keyAt(i); if (key != this) { buffer.append(key); } else { buffer.append("(this Map)"); } buffer.append('='); Object value = valueAt(i); if (value != this) { buffer.append(value); } else { buffer.append("(this Map)"); } } buffer.append('}'); return buffer.toString(); } } ================================================ FILE: replugin-host-library/replugin-host-lib/src/main/java/com/qihoo360/replugin/utils/pkg/PackageFilesUtil.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils.pkg; import android.content.Context; import android.os.Build; import android.text.TextUtils; import android.util.Log; import com.qihoo360.loader2.Constant; import com.qihoo360.mobilesafe.core.BuildConfig; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.utils.FileUtils; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; /** * @author RePlugin Team */ public class PackageFilesUtil { private static final String TAG = "PackageFilesUtil"; private static final String TIMESTAMP_EXT = ".timestamp"; /** * 我们的很多文件,都是在 assets 目录里有一份,如果有更新,则在 files 目录里也有一份。原来的做法是把 assets 目录里的 copy * 到 files 目录,其实没有必要,这里用时间戳判断一下,哪个最新就直接读哪个 */ public static InputStream openLatestInputFile(Context c, String filename) { InputStream is = null; long timestampOfFile = getFileTimestamp(c, filename); long timestampOfAsset = getBundleTimestamp(c, filename); if (timestampOfFile >= timestampOfAsset) { // files 目录的时间戳更新,那么优先读取 files 目录的文件 try { is = c.openFileInput(filename); if (BuildConfig.DEBUG) { Log.i(TAG, "Opening in files directory: " + filename); } } catch (Exception e) { if (BuildConfig.DEBUG) { Log.i(TAG, filename + " in files directory not found, skip."); } } } if (is == null) { // is == null 表明没能从 files 目录读到文件,那么到 assets 目录去读读看 try { is = c.getAssets().open(filename); if (BuildConfig.DEBUG) { Log.i(TAG, "Opening in assets: " + filename); } } catch (FileNotFoundException e) { // 找不到文件?很正常,不做任何处理 // Ignore } catch (IOException e) { if (BuildConfig.DEBUG) { Log.w(TAG, filename, e); } } } return is; } /** @see Utils#openLatestInputFile(Context, String) */ public static long getLatestFileTimestamp(Context c, String filename) { long timestampOfFile = getFileTimestamp(c, filename); long timestampOfAsset = getBundleTimestamp(c, filename); return Math.max(timestampOfFile, timestampOfAsset); } /** * 检查 files 目录下个某个文件是否已经为最新(比 assets 目录下的新) * * @param c * @param filename * @return 如果文件比 assets 下的同名文件的时间戳旧,或者文件不存在,则返回 false. */ public static boolean isFileUpdated(Context c, String filename) { File file = c.getFileStreamPath(filename); if (file == null) { return false; } if (!file.exists()) { return false; } long timestampOfFile = getFileTimestamp(c, filename); long timestampOfAsset = getBundleTimestamp(c, filename); return (timestampOfAsset <= timestampOfFile); } /** 读取文件的时间戳 */ public static long getFileTimestamp(Context c, String filename) { FileInputStream fis = null; try { fis = c.openFileInput(filename + TIMESTAMP_EXT); } catch (Exception e) { //ignore } if (fis != null) { return getTimestampFromStream(fis); } else { return 0; } } // 对于打包的文件,都是放在 assets 目录的,时间戳自然也在 assets 目录 public static long getBundleTimestamp(Context c, String filename) { InputStream fis = null; try { fis = c.getAssets().open(filename + TIMESTAMP_EXT); } catch (Exception e) { //ignore } if (fis != null) { return getTimestampFromStream(fis); } else { return 0; } } private static long getTimestampFromStream(InputStream fis) { DataInputStream dis = null; try { dis = new DataInputStream(fis); String s = dis.readLine(); if (!TextUtils.isEmpty(s)) { long timeStamp = Long.parseLong(s); return timeStamp; } } catch (Exception e) { if (BuildConfig.DEBUG) { Log.e(TAG, "", e); } } finally { try { if (dis != null) { dis.close(); } if (fis != null) { fis.close(); } } catch (Exception e) { if (BuildConfig.DEBUG) { Log.e(TAG, "", e); } } } return 0; } public static boolean isExtractedFromAssetsToFiles(Context c, String filename) { File file = c.getFileStreamPath(filename); if (file == null || !file.exists()) { if (BuildConfig.DEBUG) { Log.i(TAG, "Extract no exist file from assets filename = " + filename); } return true; } // compare file version for extract return compareDataFileVersion(c, filename); } private static boolean compareDataFileVersion(Context c, String fileName) { int assetsVer = -1; int fileVer = -1; DataInputStream dis = null; byte[] magic = new byte[4]; try { dis = new DataInputStream(c.getAssets().open(fileName)); dis.read(magic); if (magic[0] == 'V' && magic[1] == 'D' && magic[2] == 'A' && magic[3] == 'T') { dis.readInt(); dis.readInt(); assetsVer = dis.readInt(); if (BuildConfig.DEBUG) { Log.i(TAG, "Get assets version file=" + fileName + " version=" + assetsVer); } } else { return true; } } catch (Exception e) { if (BuildConfig.DEBUG) { Log.e(TAG, "Get assets version error, file:" + fileName, e); } } finally { if (dis != null) { try { dis.close(); } catch (Exception e) { if (BuildConfig.DEBUG) { Log.e(TAG, "close error", e); } } } } try { dis = new DataInputStream(new FileInputStream(c.getFileStreamPath(fileName))); dis.read(magic); if (magic[0] == 'V' && magic[1] == 'D' && magic[2] == 'A' && magic[3] == 'T') { dis.readInt(); dis.readInt(); fileVer = dis.readInt(); if (BuildConfig.DEBUG) { Log.i(TAG, "Get local version file=" + fileName + " version=" + fileVer); } } else { return true; } } catch (Exception e) { if (BuildConfig.DEBUG) { Log.e(TAG, "Get file version error, file:" + fileName, e); } } finally { if (dis != null) { try { dis.close(); } catch (Exception e) { if (BuildConfig.DEBUG) { Log.e(TAG, "close error", e); } } } } if (assetsVer != -1 && fileVer != -1 && assetsVer <= fileVer) { if (BuildConfig.DEBUG) { Log.i(TAG, "compare file version not extract"); } return false; } else { if (BuildConfig.DEBUG) { Log.i(TAG, "compare file version need extract"); } } return true; } /** * 移除插件及其已释放的Dex、Native库等文件 *

* * @param info 插件信息 */ public static void forceDelete(PluginInfo info) { if (info == null) { return; } try { // 删除插件APK final File apkFile = info.getApkFile(); if (apkFile.exists()) { FileUtils.forceDelete(apkFile); if (BuildConfig.DEBUG) { Log.i(TAG, "delete " + info.getApkFile()); } } // 删除释放后的odex final File dexFile = info.getDexFile(); if (dexFile.exists()) { FileUtils.forceDelete(dexFile); if (BuildConfig.DEBUG) { Log.i(TAG, "delete " + info.getDexFile()); } } if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { // 删除释放后的vdex File dexParent = info.getDexParentDir(); String fileNameWithoutExt = FileUtils.getFileNameWithoutExt(info.getDexFile().getAbsolutePath()); File vdexFile = new File(dexParent, fileNameWithoutExt + ".vdex"); FileUtils.forceDelete(vdexFile); if (BuildConfig.DEBUG) { Log.i(TAG, "delete " + vdexFile); } // 删除 XXX.jar.prof 文件 File profFile = new File(info.getApkFile().getAbsolutePath() + ".prof"); FileUtils.forceDelete(profFile); if (BuildConfig.DEBUG) { Log.i(TAG, "delete " + profFile); } } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { FileUtils.forceDelete(info.getExtraOdexDir()); if (BuildConfig.DEBUG) { Log.i(TAG, "delete " + info.getExtraOdexDir()); } } // 删除Native文件 final File libsFile = info.getNativeLibsDir(); if (libsFile.exists()) { FileUtils.forceDelete(info.getNativeLibsDir()); if (BuildConfig.DEBUG) { Log.i(TAG, "delete " + info.getNativeLibsDir()); } } // 删除进程锁文件 String lockFileName = String.format(Constant.LOAD_PLUGIN_LOCK, info.getApkFile().getName()); File lockFile = new File(RePluginInternal.getAppContext().getFilesDir(), lockFileName); FileUtils.forceDelete(lockFile); if (BuildConfig.DEBUG) { Log.i(TAG, "delete " + lockFile); } } catch (IOException e) { if (LogRelease.LOGR) { e.printStackTrace(); } } catch (IllegalArgumentException e2) { if (LogRelease.LOGR) { e2.printStackTrace(); } } } } ================================================ FILE: replugin-host-library/settings.gradle ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() mavenLocal() maven { allowInsecureProtocol = true url "http://maven.geelib.360.cn/nexus/repository/replugin/"} } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() mavenLocal() maven { allowInsecureProtocol = true url "http://maven.geelib.360.cn/nexus/repository/replugin/"} } } rootProject.name = "replugin-host-library" include ':replugin-host-lib' ================================================ FILE: replugin-plugin-gradle/README.md ================================================ # RePlugin Plugin Gradle RePlugin Plugin Gradle是一个Gradle插件,由 **插件** 负责引入。 该Gradle插件主要负责在插件的编译期中做一些事情,是“动态编译方案”的主要实现者。此外,开发者可通过修改其属性而做一些自定义的操作。 大致包括: * 动态修改主要调用代码,改为调用RePlugin Plugin Gradle(如Activity的继承、Provider的重定向等) 开发者需要依赖此Gradle插件,以实现对RePlugin的接入。请参见WiKi以了解接入方法。 有关RePlugin Host Gradle的详细描述,请访问我们的WiKi,以了解更多的内容。 (文档正在完善,请耐心等待) ================================================ FILE: replugin-plugin-gradle/bintray.gradle ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ // 应用插件 apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish' def baseUrl = 'https://github.com/Qihoo360/RePlugin' def siteUrl = baseUrl def gitUrl = "${baseUrl}/RePlugin" def issueUrl = "${gitUrl}/issues" install { repositories { mavenInstaller { // This generates POM.xml with proper paramters pom.project { //添加项目描述 name 'Gradle Plugin for Android' url siteUrl //设置开源证书信息 licenses { license { name 'The Apache Software License, Version 2.0' url 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } //添加开发者信息 developers { developer { name 'replugin' email 'replugin@gmail.com' } } scm { connection gitUrl developerConnection gitUrl url siteUrl } } } } } //配置上传Bintray相关信息 bintray { user = "" key = "" configurations = ['archives'] pkg { repo = '' // 上传到中央仓库的名称 name = '' // 上传到jcenter 的项目名称 userOrg = '' desc = 'RePlugin - A flexible, stable, easy-to-use Android Plug-in Framework' // 项目描述 websiteUrl = siteUrl issueTrackerUrl = issueUrl vcsUrl = gitUrl labels = ['gradle', 'plugin'] licenses = ['Apache-2.0'] publish = true } } ================================================ FILE: replugin-plugin-gradle/build.gradle ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ plugins { id 'java' id 'groovy' id 'maven-publish' } repositories { google() mavenCentral() } group = 'com.qihoo360.replugin' // 组名 String classPath = ".src.main.groovy.com.qihoo360.replugin.gradle.plugin.AppConstant".replace(".", java.io.File.separator) String verPath = "${project.projectDir}" + classPath + ".groovy" String verLine = new File(verPath).filterLine { it =~ /def static final VER =/ } version = "${verLine.split("\"")[1]}" // 版本 //红色醒目打印显示版本号 java.lang.System.err.println "version=${version}" sourceSets { main { groovy { srcDirs '../replugin-host-gradle/src/main/groovy/com/qihoo360/replugin/gradle/compat' } } } dependencies { implementation 'com.android.tools.build:gradle:7.4.2' implementation 'org.json:json:20160212' implementation 'org.codehaus.groovy:groovy:2.4.7' implementation 'com.squareup:javapoet:1.5.1' implementation 'com.android.tools.build:transform-api:1.5.0' implementation 'org.javassist:javassist:3.18.2-GA' implementation 'commons-io:commons-io:2.5' // gradle:7.4.2 也会依赖guava,codec implementation 'com.google.guava:guava:30.1-jre' implementation 'commons-codec:commons-codec:1.11' implementation gradleApi() implementation localGroovy() } project.ext.RP_ARTIFACT_ID = 'replugin-plugin-gradle' apply from: '../rp-publish.gradle' ================================================ FILE: replugin-plugin-gradle/config.gradle ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ ext { config = this.&readConfig() } def readConfig() { Properties configProperties = new Properties() file('config.properties').withInputStream { configProperties.load(it) } configProperties } ================================================ FILE: replugin-plugin-gradle/config.properties ================================================ # # Copyright (C) 2005-2017 Qihoo 360 Inc. # # 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 # # http://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. # # repoUrl = groupId = com.qihoo360.replugin artifactId = replugin-plugin-gradle # --- android config --- compileSdkVersion = 21 buildToolsVersion = 23.0.3 minSdkVersion = 10 targetSdkVersion = 21 # --- user info --- # 注意:1、仅研发一组同事能修改此URL; # 2、切勿去除,以免误上传到Maven中心库,引发代码泄露危机。 username = password = ================================================ FILE: replugin-plugin-gradle/gradle/wrapper/gradle-wrapper.properties ================================================ # # Copyright (C) 2005-2017 Qihoo 360 Inc. # # 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 # # http://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. # # #Mon Dec 28 10:00:20 PST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip ================================================ FILE: replugin-plugin-gradle/gradle.properties ================================================ # # Copyright (C) 2005-2017 Qihoo 360 Inc. # # 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 # # http://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. # # # 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=-Xmx1536m # 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 ================================================ FILE: replugin-plugin-gradle/gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # 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 case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # 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 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" ] ; 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 # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: replugin-plugin-gradle/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 @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= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @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 Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_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=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software 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: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/AppConstant.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin /** * @author RePlugin Team */ class AppConstant { /** 版本号 */ def static final VER = "${RP_VERSION}" /** 打印信息时候的前缀 */ def static final TAG = "< replugin-plugin-v${VER} >" /** 外部用户配置信息 */ def static final USER_CONFIG = "repluginPluginConfig" /** 用户Task组 */ def static final TASKS_GROUP = "replugin-plugin" /** Task前缀 */ def static final TASKS_PREFIX = "rp" /** 用户Task:强制停止宿主app */ def static final TASK_FORCE_STOP_HOST_APP = TASKS_PREFIX + "ForceStopHostApp" /** 用户Task:启动宿主app */ def static final TASK_START_HOST_APP = TASKS_PREFIX + "StartHostApp" /** 用户Task:重启宿主app */ def static final TASK_RESTART_HOST_APP = TASKS_PREFIX + "RestartHostApp" /** 用户Task:安装插件 */ def static final TASK_INSTALL_PLUGIN = TASKS_PREFIX + "InstallPlugin" /** 用户Task:安装插件 */ def static final TASK_UNINSTALL_PLUGIN = TASKS_PREFIX + "UninstallPlugin" /** 用户Task:运行插件 */ def static final TASK_RUN_PLUGIN = TASKS_PREFIX + "RunPlugin" /** 用户Task:安装并运行插件 */ def static final TASK_INSTALL_AND_RUN_PLUGIN = TASKS_PREFIX + "InstallAndRunPlugin" /** 配置例子 */ static final String CONFIG_EXAMPLE = ''' // 这个plugin需要放在android配置之后,因为需要读取android中的配置项 apply plugin: 'replugin-plugin-gradle\' repluginPluginConfig { pluginName = "demo3" hostApplicationId = "com.qihoo360.replugin.sample.host" hostAppLauncherActivity = "com.qihoo360.replugin.sample.host.MainActivity" } ''' private AppConstant() {} } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/ReClassPlugin.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin import com.android.build.gradle.AppExtension import com.android.build.gradle.AppPlugin import com.qihoo360.replugin.gradle.compat.VariantCompat import com.qihoo360.replugin.gradle.plugin.debugger.PluginDebugger import com.qihoo360.replugin.gradle.plugin.inner.CommonData import com.qihoo360.replugin.gradle.plugin.inner.ReClassTransform import org.gradle.api.Plugin import org.gradle.api.Project /** * @author RePlugin Team */ public class ReClassPlugin implements Plugin { @Override public void apply(Project project) { println "${AppConstant.TAG} Welcome to replugin world ! " /* Extensions */ project.extensions.create(AppConstant.USER_CONFIG, ReClassConfig) def isApp = project.plugins.hasPlugin(AppPlugin) if (isApp) { def config = project.extensions.getByName(AppConstant.USER_CONFIG) def android = project.extensions.getByType(AppExtension) def forceStopHostAppTask = null def startHostAppTask = null def restartHostAppTask = null android.applicationVariants.all { variant -> PluginDebugger pluginDebugger = new PluginDebugger(project, config, variant) def assembleTask = VariantCompat.getAssembleTask(variant) def variantName = captureName(variant.name) def tasks = project.tasks def installPluginTask = tasks.asMap.get(AppConstant.TASK_INSTALL_PLUGIN + variantName) if (installPluginTask == null) { installPluginTask = tasks.create(AppConstant.TASK_INSTALL_PLUGIN + variantName) } installPluginTask.doLast { pluginDebugger.startHostApp() pluginDebugger.uninstall() pluginDebugger.forceStopHostApp() pluginDebugger.startHostApp() pluginDebugger.install() } installPluginTask.group = AppConstant.TASKS_GROUP def uninstallPluginTask = tasks.asMap.get(AppConstant.TASK_UNINSTALL_PLUGIN) if (uninstallPluginTask == null){ uninstallPluginTask = tasks.create(AppConstant.TASK_UNINSTALL_PLUGIN) } uninstallPluginTask.doLast { //generate json pluginDebugger.uninstall() } uninstallPluginTask.group = AppConstant.TASKS_GROUP forceStopHostAppTask = tasks.asMap.get(AppConstant.TASK_FORCE_STOP_HOST_APP) if (forceStopHostAppTask == null) { forceStopHostAppTask = tasks.create(AppConstant.TASK_FORCE_STOP_HOST_APP) forceStopHostAppTask.doLast { //generate json pluginDebugger.forceStopHostApp() } forceStopHostAppTask.group = AppConstant.TASKS_GROUP } startHostAppTask = tasks.asMap.get(AppConstant.TASK_START_HOST_APP) if (startHostAppTask == null) { startHostAppTask = tasks.create(AppConstant.TASK_START_HOST_APP) startHostAppTask.doLast { //generate json pluginDebugger.startHostApp() } startHostAppTask.group = AppConstant.TASKS_GROUP } restartHostAppTask = tasks.asMap.get(AppConstant.TASK_RESTART_HOST_APP) if (restartHostAppTask == null) { restartHostAppTask = tasks.create(AppConstant.TASK_RESTART_HOST_APP) restartHostAppTask.doLast { //generate json pluginDebugger.startHostApp() } restartHostAppTask.group = AppConstant.TASKS_GROUP restartHostAppTask.dependsOn(forceStopHostAppTask) } if (assembleTask) { installPluginTask.dependsOn assembleTask } def runPluginTask = tasks.asMap.get(AppConstant.TASK_RUN_PLUGIN + variantName) if (runPluginTask == null) { runPluginTask = tasks.create(AppConstant.TASK_RUN_PLUGIN + variantName) } runPluginTask.doLast { pluginDebugger.run() } runPluginTask.group = AppConstant.TASKS_GROUP def installAndRunPluginTask = tasks.asMap.get(AppConstant.TASK_INSTALL_AND_RUN_PLUGIN + variantName) if (installAndRunPluginTask == null) { installAndRunPluginTask = tasks.create(AppConstant.TASK_INSTALL_AND_RUN_PLUGIN + variantName) } installAndRunPluginTask.doLast { pluginDebugger.run() } installAndRunPluginTask.group = AppConstant.TASKS_GROUP installAndRunPluginTask.dependsOn installPluginTask } CommonData.appPackage = android.defaultConfig.applicationId println ">>> APP_PACKAGE " + CommonData.appPackage def transform = new ReClassTransform(project) // 将 transform 注册到 android android.registerTransform(transform) } } private String captureName(String name) { char[] cs = name.toCharArray(); cs[0] -= 32; return String.valueOf(cs); } } class ReClassConfig { /** 编译的 App Module 的名称 */ def appModule = ':app' /** 用户声明要忽略的注入器 */ def ignoredInjectors = [] /** 执行 LoaderActivity 替换时,用户声明不需要替换的 Activity */ def ignoredActivities = [] /** 自定义的注入器 */ def customInjectors = [] /** 插件名字,默认null */ def pluginName = null /** 手机存储目录,默认"/sdcard/" */ def phoneStorageDir = "/sdcard/" /** 宿主包名,默认null */ def hostApplicationId = null /** 宿主launcherActivity,默认null */ def hostAppLauncherActivity = null } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/debugger/PluginDebugger.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.debugger import com.qihoo360.replugin.gradle.compat.ScopeCompat import com.qihoo360.replugin.gradle.plugin.AppConstant import com.qihoo360.replugin.gradle.plugin.util.CmdUtil import org.gradle.api.Project /** * @author RePlugin Team */ class PluginDebugger { def project def config def variant File apkFile File adbFile public PluginDebugger(Project project, def config, def variant) { this.project = project this.config = config this.variant = variant def confBaseName = this.variant.outputs[0].variantOutput.baseName String archivesBaseName = project.archivesBaseName String apkBaseName = archivesBaseName + "-" + confBaseName File apkDir = new File(project.getBuildDir(), "outputs" + File.separator + "apk") String unsigned = (variant.signingConfig == null ? "-unsigned.apk" : ".apk"); String apkName = apkBaseName + unsigned apkFile = new File(apkDir, apkName) if (!apkFile.exists() || apkFile.length() == 0) { apkFile = new File(apkDir, confBaseName + File.separator + apkName) } adbFile = project.plugins.getPlugin('android').extension.adbExecutable } /** * 安装插件 * @return 是否命令执行成功 */ public boolean install() { if (isConfigNull()) { return false } //推送apk文件到手机 String pushCmd = "${adbFile.absolutePath} push ${apkFile.absolutePath} ${config.phoneStorageDir}" if (0 != CmdUtil.syncExecute(pushCmd)) { return false } //此处是在安卓机上的目录,直接"/"路径 String apkPath = "${config.phoneStorageDir}" if (!apkPath.endsWith("/")) { //容错处理 apkPath += "/" } apkPath += "${apkFile.name}" //获取写存储权限 String grantWriteStorageCmd = "${adbFile.absolutePath} shell pm grant ${config.hostApplicationId} android.permission.WRITE_EXTERNAL_STORAGE " if (0 != CmdUtil.syncExecute(grantWriteStorageCmd)) { return false } //获取读存储权限 String grantReadStorageCmd = "${adbFile.absolutePath} shell pm grant ${config.hostApplicationId} android.permission.READ_EXTERNAL_STORAGE " if (0 != CmdUtil.syncExecute(grantReadStorageCmd)) { return false } //延迟两秒 String sleepCmd = "${adbFile.absolutePath} shell sleep 2 " if (0 != CmdUtil.syncExecute(sleepCmd)) { return false } //发送安装广播 String installBrCmd = "${adbFile.absolutePath} shell am broadcast -a ${config.hostApplicationId}.replugin.install -e path ${apkPath} -e immediately true " if (0 != CmdUtil.syncExecute(installBrCmd)) { return false } return true } /** * 卸载插件 * @return 是否命令执行成功 */ public boolean uninstall() { if (isConfigNull()) { return false } String cmd = "${adbFile.absolutePath} shell am broadcast -a ${config.hostApplicationId}.replugin.uninstall -e plugin ${config.pluginName}" if (0 != CmdUtil.syncExecute(cmd)) { return false } return true } /** * 强制停止宿主app * @return 是否命令执行成功 */ public boolean forceStopHostApp() { if (isConfigNull()) { return false } String cmd = "${adbFile.absolutePath} shell am force-stop ${config.hostApplicationId}" if (0 != CmdUtil.syncExecute(cmd)) { return false } return true } /** * 启动宿主app * @return 是否命令执行成功 */ public boolean startHostApp() { if (isConfigNull()) { return false } String cmd = "${adbFile.absolutePath} shell am start -n \"${config.hostApplicationId}/${config.hostAppLauncherActivity}\" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER" if (0 != CmdUtil.syncExecute(cmd)) { return false } return true } /** * 运行插件 * @return 是否命令执行成功 */ public boolean run() { if (isConfigNull()) { return false } String installBrCmd = "${adbFile.absolutePath} shell am broadcast -a ${config.hostApplicationId}.replugin.start_activity -e plugin ${config.pluginName}" if (0 != CmdUtil.syncExecute(installBrCmd)) { return false } return true } /** * 检查用户配置项是否为空 * @param config * @return */ private boolean isConfigNull() { //检查adb环境 if (null == adbFile || !adbFile.exists()) { System.err.println "${AppConstant.TAG} Could not find the adb file !!!" return true } if (null == config) { System.err.println "${AppConstant.TAG} the config object can not be null!!!" System.err.println "${AppConstant.CONFIG_EXAMPLE}" return true } if (null == config.hostApplicationId) { System.err.println "${AppConstant.TAG} the config hostApplicationId can not be null!!!" System.err.println "${AppConstant.CONFIG_EXAMPLE}" return true } if (null == config.hostAppLauncherActivity) { System.err.println "${AppConstant.TAG} the config hostAppLauncherActivity can not be null!!!" System.err.println "${AppConstant.CONFIG_EXAMPLE}" return true } return false } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/BaseInjector.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector import org.gradle.api.Project /** * @author RePlugin Team */ public abstract class BaseInjector implements IClassInjector { protected Project project protected String variantDir @Override public Object name() { return getClass().getSimpleName() } public void setProject(Project project) { this.project = project; } public void setVariantDir(String variantDir) { this.variantDir = variantDir; } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/IClassInjector.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector import javassist.ClassPool import org.gradle.api.Project /** * @author RePlugin Team */ interface IClassInjector { /** * 设置project对象 * @param project */ void setProject(Project project) /** * 设置variant目录关键串 * @param variantDir */ void setVariantDir(String variantDir) /** * 注入器名称 */ def name() /** * 对 dir 目录中的 Class 进行注入 */ def injectClass(ClassPool pool, String dir, Map config) } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/Injectors.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector import com.qihoo360.replugin.gradle.plugin.injector.identifier.GetIdentifierInjector import com.qihoo360.replugin.gradle.plugin.injector.localbroadcast.LocalBroadcastInjector import com.qihoo360.replugin.gradle.plugin.injector.provider.ProviderInjector import com.qihoo360.replugin.gradle.plugin.injector.provider.ProviderInjector2 import com.qihoo360.replugin.gradle.plugin.injector.loaderactivity.LoaderActivityInjector /** * @author RePlugin Team */ public enum Injectors { LOADER_ACTIVITY_CHECK_INJECTOR('LoaderActivityInjector', new LoaderActivityInjector(), '替换 Activity 为 LoaderActivity'), LOCAL_BROADCAST_INJECTOR('LocalBroadcastInjector', new LocalBroadcastInjector(), '替换 LocalBroadcast 调用'), PROVIDER_INJECTOR('ProviderInjector', new ProviderInjector(), '替换 Provider 调用'), PROVIDER_INJECTOR2('ProviderInjector2', new ProviderInjector2(), '替换 ContentProviderClient 调用'), GET_IDENTIFIER_INJECTOR('GetIdentifierInjector', new GetIdentifierInjector(), '替换 Resource.getIdentifier 调用') IClassInjector injector String nickName String desc Injectors(String nickName, IClassInjector injector, String desc) { this.injector = injector this.nickName = nickName this.desc = desc; } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/identifier/GetIdentifierExprEditor.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector.identifier import com.qihoo360.replugin.gradle.plugin.inner.CommonData import javassist.CannotCompileException import javassist.expr.ExprEditor import javassist.expr.MethodCall /** * @author RePlugin Team */ public class GetIdentifierExprEditor extends ExprEditor { public def filePath @Override void edit(MethodCall m) throws CannotCompileException { String clsName = m.getClassName() String methodName = m.getMethodName() if (clsName.equalsIgnoreCase('android.content.res.Resources')) { if (methodName == 'getIdentifier') { m.replace('{ $3 = \"' + CommonData.appPackage + '\"; ' + '$_ = $proceed($$);' + ' }') println " GetIdentifierCall => ${filePath} ${methodName}():${m.lineNumber}" } } } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/identifier/GetIdentifierInjector.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector.identifier import com.qihoo360.replugin.gradle.plugin.injector.BaseInjector import com.qihoo360.replugin.gradle.plugin.inner.Util import javassist.ClassPool import java.nio.file.* import java.nio.file.attribute.BasicFileAttributes /** * @author RePlugin Team */ public class GetIdentifierInjector extends BaseInjector { // 表达式编辑器 def editor @Override def injectClass(ClassPool pool, String dir, Map config) { if (editor == null) { editor = new GetIdentifierExprEditor() } Util.newSection() println dir Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor() { @Override FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { //todo only .class String filePath = file.toString() editor.filePath = filePath def stream, ctCls try { stream = new FileInputStream(filePath) ctCls = pool.makeClass(stream); // println ctCls.name if (ctCls.isFrozen()) { ctCls.defrost() } ctCls.getDeclaredMethods().each { it.instrument(editor) } ctCls.getMethods().each { it.instrument(editor) } ctCls.writeFile(dir) } catch (Throwable t) { println " [Warning] --> ${t.toString()}" } finally { if (ctCls != null) { ctCls.detach() } if (stream != null) { stream.close() } } return super.visitFile(file, attrs) } }) } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/loaderactivity/LoaderActivityInjector.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector.loaderactivity import com.qihoo360.replugin.gradle.plugin.injector.BaseInjector import com.qihoo360.replugin.gradle.plugin.inner.CommonData import com.qihoo360.replugin.gradle.plugin.manifest.ManifestAPI import javassist.CannotCompileException import javassist.ClassPool import javassist.CtClass import javassist.expr.ExprEditor import javassist.expr.MethodCall /** * LOADER_ACTIVITY_CHECK_INJECTOR * * 修改普通的 Activity 为 PluginActivity * * @author RePlugin Team */ public class LoaderActivityInjector extends BaseInjector { def private static LOADER_PROP_FILE = 'loader_activities.properties' /* LoaderActivity 替换规则 */ def private static loaderActivityRules = [ 'android.app.Activity' : 'com.qihoo360.replugin.loader.a.PluginActivity', 'android.app.TabActivity' : 'com.qihoo360.replugin.loader.a.PluginTabActivity', 'android.app.ListActivity' : 'com.qihoo360.replugin.loader.a.PluginListActivity', 'android.app.ActivityGroup' : 'com.qihoo360.replugin.loader.a.PluginActivityGroup', 'android.support.v4.app.FragmentActivity' : 'com.qihoo360.replugin.loader.a.PluginFragmentActivity', 'android.support.v7.app.AppCompatActivity': 'com.qihoo360.replugin.loader.a.PluginAppCompatActivity', 'androidx.fragment.app.FragmentActivity' : 'com.qihoo360.replugin.loader.a.PluginFragmentXActivity', 'androidx.appcompat.app.AppCompatActivity': 'com.qihoo360.replugin.loader.a.PluginAppCompatXActivity', 'android.preference.PreferenceActivity' : 'com.qihoo360.replugin.loader.a.PluginPreferenceActivity', 'android.app.ExpandableListActivity' : 'com.qihoo360.replugin.loader.a.PluginExpandableListActivity' ] @Override def injectClass(ClassPool pool, String dir, Map config) { init() /* 遍历程序中声明的所有 Activity */ //每次都new一下,否则多个variant一起构建时只会获取到首个manifest new ManifestAPI().getActivities(project, variantDir).each { // 处理没有被忽略的 Activity if (!(it in CommonData.ignoredActivities)) { handleActivity(pool, it, dir) } } } /** * 处理 Activity * * @param pool * @param activity Activity 名称 * @param classesDir class 文件目录 */ private def handleActivity(ClassPool pool, String activity, String classesDir) { def clsFilePath = classesDir + File.separatorChar + activity.replaceAll('\\.', '/') + '.class' if (!new File(clsFilePath).exists()) { return } println ">>> Handle $activity" def stream, ctCls try { stream = new FileInputStream(clsFilePath) ctCls = pool.makeClass(stream); /* // 打印当前 Activity 的所有父类 CtClass tmpSuper = ctCls.superclass while (tmpSuper != null) { println(tmpSuper.name) tmpSuper = tmpSuper.superclass } */ // ctCls 之前的父类 def originSuperCls = ctCls.superclass /* 从当前 Activity 往上回溯,直到找到需要替换的 Activity */ def superCls = originSuperCls while (superCls != null && !(superCls.name in loaderActivityRules.keySet())) { // println ">>> 向上查找 $superCls.name" ctCls = superCls superCls = ctCls.superclass } // 如果 ctCls 已经是 LoaderActivity,则不修改 if (ctCls.name in loaderActivityRules.values()) { // println " 跳过 ${ctCls.getName()}" return } /* 找到需要替换的 Activity, 修改 Activity 的父类为 LoaderActivity */ if (superCls != null) { def targetSuperClsName = loaderActivityRules.get(superCls.name) // println " ${ctCls.getName()} 的父类 $superCls.name 需要替换为 ${targetSuperClsName}" CtClass targetSuperCls = pool.get(targetSuperClsName) if (ctCls.isFrozen()) { ctCls.defrost() } ctCls.setSuperclass(targetSuperCls) // 修改声明的父类后,还需要方法中所有的 super 调用。 ctCls.getDeclaredMethods().each { outerMethod -> outerMethod.instrument(new ExprEditor() { @Override void edit(MethodCall call) throws CannotCompileException { if (call.isSuper()) { if (call.getMethod().getReturnType().getName() == 'void') { call.replace('{super.' + call.getMethodName() + '($$);}') } else { call.replace('{$_ = super.' + call.getMethodName() + '($$);}') } } } }) } ctCls.writeFile(CommonData.getClassPath(ctCls.name)) println " Replace ${ctCls.name}'s SuperClass ${superCls.name} to ${targetSuperCls.name}" } } catch (Throwable t) { println " [Warning] --> ${t.toString()}" } finally { if (ctCls != null) { ctCls.detach() } if (stream != null) { stream.close() } } } def private init() { /* 延迟初始化 loaderActivityRules */ // todo 从配置中读取,而不是写死在代码中 if (loaderActivityRules == null) { def buildSrcPath = project.project(':buildsrc').projectDir.absolutePath def loaderConfigPath = String.join(File.separator, buildSrcPath, 'res', LOADER_PROP_FILE) loaderActivityRules = new Properties() new File(loaderConfigPath).withInputStream { loaderActivityRules.load(it) } println '\n>>> Activity Rules:' loaderActivityRules.each { println it } println() } } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/localbroadcast/LocalBroadcastExprEditor.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector.localbroadcast import javassist.CannotCompileException import javassist.expr.ExprEditor import javassist.expr.MethodCall /** * @author RePlugin Team */ public class LocalBroadcastExprEditor extends ExprEditor { static def TARGET_CLASS = "" static def includeClass = ['android.support.v4.content.LocalBroadcastManager', 'androidx.localbroadcastmanager.content.LocalBroadcastManager'] static def PROXY_CLASS = 'com.qihoo360.replugin.loader.b.PluginLocalBroadcastManager' /** 处理以下方法 */ static def includeMethodCall = ['getInstance', 'registerReceiver', 'unregisterReceiver', 'sendBroadcast', 'sendBroadcastSync'] /** 待处理文件的物理路径 */ public def filePath @Override void edit(MethodCall call) throws CannotCompileException { if (call.getClassName() in includeClass ) { TARGET_CLASS = call.getClassName() if (!(call.getMethodName() in includeMethodCall)) { // println "Skip $methodName" return } replaceStatement(call) } } def private replaceStatement(MethodCall call) { String method = call.getMethodName() if (method == 'getInstance') { call.replace('{$_ = ' + PROXY_CLASS + '.' + method + '($$);}') } else { def returnType = call.method.returnType.getName() // getInstance 之外的调用,要增加一个参数,请参看 i-library 的 LocalBroadcastClient.java if (returnType == 'void') { call.replace('{' + PROXY_CLASS + '.' + method + '($0, $$);}') } else { call.replace('{$_ = ' + PROXY_CLASS + '.' + method + '($0, $$);}') } } println ">>> Replace: ${filePath} ${TARGET_CLASS}.${method}() ${PROXY_CLASS}.${method}()\n" } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/localbroadcast/LocalBroadcastInjector.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector.localbroadcast import com.qihoo360.replugin.gradle.plugin.injector.BaseInjector import com.qihoo360.replugin.gradle.plugin.inner.Util import javassist.ClassPool import java.nio.file.* import java.nio.file.attribute.BasicFileAttributes /** * LocalBroadcastInjector * * 将插件中的 LocalBroadcast 调用转发到宿主 * * @author RePlugin Team */ public class LocalBroadcastInjector extends BaseInjector { // 表达式编辑器 def editor @Override def injectClass(ClassPool pool, String dir, Map config) { // 不处理非 build 目录下的类 /* if (!dir.contains('build' + File.separator + 'intermediates')) { println "跳过$dir" return } */ if (editor == null) { editor = new LocalBroadcastExprEditor() } Util.newSection() println dir Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor() { @Override FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String filePath = file.toString() editor.filePath = filePath def stream, ctCls try { // 不处理 LocalBroadcastManager.class if (filePath.contains('android/support/v4/content/LocalBroadcastManager') || filePath.contains('androidx/localbroadcastmanager/content/LocalBroadcastManager')) { println "Ignore ${filePath}" return super.visitFile(file, attrs) } stream = new FileInputStream(filePath) ctCls = pool.makeClass(stream); // println ctCls.name if (ctCls.isFrozen()) { ctCls.defrost() } /* 检查方法列表 */ ctCls.getDeclaredMethods().each { it.instrument(editor) } ctCls.getMethods().each { it.instrument(editor) } ctCls.writeFile(dir) } catch (Throwable t) { println " [Warning] --> ${t.toString()}" // t.printStackTrace() } finally { if (ctCls != null) { ctCls.detach() } if (stream != null) { stream.close() } } return super.visitFile(file, attrs) } }) } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/provider/ProviderExprEditor.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector.provider import javassist.CannotCompileException import javassist.expr.ExprEditor import javassist.expr.MethodCall /** * @author RePlugin Team */ public class ProviderExprEditor extends ExprEditor { static def PROVIDER_CLASS = 'com.qihoo360.replugin.loader.p.PluginProviderClient' public def filePath @Override void edit(MethodCall m) throws CannotCompileException { final String clsName = m.getClassName() final String methodName = m.getMethodName() if (!clsName.equalsIgnoreCase('android.content.ContentResolver')) { return } if (!(methodName in ProviderInjector.includeMethodCall)) { // println "跳过$methodName" return } try { replaceStatement(m, methodName, m.lineNumber) } catch (Exception e) { //确保不影响其他 MethodCall println " [Warning] --> ProviderExprEditor : ${e.toString()}" } } def private replaceStatement(MethodCall methodCall, String method, def line) { if (methodCall.getMethodName() == 'registerContentObserver' || methodCall.getMethodName() == 'notifyChange') { methodCall.replace('{' + PROVIDER_CLASS + '.' + method + '(com.qihoo360.replugin.RePlugin.getPluginContext(), $$);}') } else { methodCall.replace('{$_ = ' + PROVIDER_CLASS + '.' + method + '(com.qihoo360.replugin.RePlugin.getPluginContext(), $$);}') } println ">>> Replace: ${filePath} Provider.${method}():${line}" } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/provider/ProviderExprEditor2.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector.provider import javassist.CannotCompileException import javassist.expr.ExprEditor import javassist.expr.MethodCall /** * @author RePlugin Team */ public class ProviderExprEditor2 extends ExprEditor { static def PROVIDER_CLASS = 'com.qihoo360.loader2.mgr.PluginProviderClient2' public def filePath @Override void edit(MethodCall m) throws CannotCompileException { String clsName = m.getClassName() String methodName = m.getMethodName() if (clsName.equalsIgnoreCase('android.content.ContentProviderClient')) { println " ${filePath} ContentProviderClient.${methodName}():${m.lineNumber}" if (!(methodName in ProviderInjector2.includeMethodCall)) { // println "跳过$methodName" return } replaceStatement(m, methodName, m.lineNumber) } } def private replaceStatement(MethodCall methodCall, String method, def line) { methodCall.replace('{$_ = ' + PROVIDER_CLASS + '.' + method + '(com.qihoo360.replugin.RePlugin.getPluginContext(), $$);}') println ">>> Replace: ${filePath} Provider.${method}():${line}" } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/provider/ProviderInjector.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector.provider import com.qihoo360.replugin.gradle.plugin.inner.Util import com.qihoo360.replugin.gradle.plugin.injector.BaseInjector import javassist.ClassPool import java.nio.file.* import java.nio.file.attribute.BasicFileAttributes /** * @author RePlugin Team */ public class ProviderInjector extends BaseInjector { // 处理以下方法 public static def includeMethodCall = ['query', 'getType', 'insert', 'bulkInsert', 'delete', 'update', /// 以下方法 replugin plugin lib 暂未支持,导致字节码修改失败。 'openInputStream', 'openOutputStream', 'openFileDescriptor', 'registerContentObserver', 'acquireContentProviderClient', 'notifyChange', 'toCalledUri', ] // 表达式编辑器 def editor @Override def injectClass(ClassPool pool, String dir, Map config) { // 不处理非 build 目录下的类 /* if (!dir.contains('build' + File.separator + 'intermediates')) { println "跳过$dir" return } */ if (editor == null) { editor = new ProviderExprEditor() } Util.newSection() println dir Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor() { @Override FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String filePath = file.toString() def stream, ctCls try { if (filePath.contains('PluginProviderClient.class')) { throw new Exception('can not replace self ') } stream = new FileInputStream(filePath) ctCls = pool.makeClass(stream); // println ctCls.name if (ctCls.isFrozen()) { ctCls.defrost() } editor.filePath = filePath (ctCls.getDeclaredMethods() + ctCls.getMethods()).each { it.instrument(editor) } ctCls.writeFile(dir) } catch (Throwable t) { println " [Warning] --> ${t.toString()}" // t.printStackTrace() } finally { if (ctCls != null) { ctCls.detach() } if (stream != null) { stream.close() } } return super.visitFile(file, attrs) } }) } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/injector/provider/ProviderInjector2.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.injector.provider import com.qihoo360.replugin.gradle.plugin.inner.Util import com.qihoo360.replugin.gradle.plugin.injector.BaseInjector import javassist.ClassPool import java.nio.file.* import java.nio.file.attribute.BasicFileAttributes /** * @author RePlugin Team */ public class ProviderInjector2 extends BaseInjector { // 处理以下方法 public static def includeMethodCall = ['query', 'update'] // 表达式编辑器 def editor @Override def injectClass(ClassPool pool, String dir, Map config) { // 不处理非 build 目录下的类 /* if (!dir.contains('build' + File.separator + 'intermediates')) { println "跳过$dir" return } */ if (editor == null) { editor = new ProviderExprEditor2() } Util.newSection() println dir Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor() { @Override FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String filePath = file.toString() def stream, ctCls try { if (filePath.contains('PluginProviderClient2.class')) { throw new Exception('can not replace self ') } stream = new FileInputStream(filePath) ctCls = pool.makeClass(stream); // println ctCls.name if (ctCls.isFrozen()) { ctCls.defrost() } editor.filePath = filePath ctCls.getDeclaredMethods().each { it.instrument(editor) } ctCls.getMethods().each { it.instrument(editor) } ctCls.writeFile(dir) } catch (Throwable t) { println " [Warning] --> ${t.toString()}" // t.printStackTrace() } finally { if (ctCls != null) { ctCls.detach() } if (stream != null) { stream.close() } } return super.visitFile(file, attrs) } }) } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/inner/ClassFileVisitor.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.inner import java.nio.file.FileVisitResult import java.nio.file.Path import java.nio.file.SimpleFileVisitor import java.nio.file.attribute.BasicFileAttributes; /** * @author RePlugin Team */ public class ClassFileVisitor extends SimpleFileVisitor { def baseDir @Override FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String path = file.toString() if (path.endsWith('.class') && !path.contains(File.separator + 'R$') && !path.endsWith(File.separator + 'R.class')) { def index = baseDir.length() + 1 def className = path.substring(index).replace('\\', '.').replace('/', '.').replace('.class', '') CommonData.putClassAndPath(className, baseDir) // println className + ' -> ' + baseDir } return super.visitFile(file, attrs) } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/inner/CommonData.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.inner /** * @author RePlugin Team */ public class CommonData { /** 保存类文件名和 class 文件路径的关系 */ def static classAndPath = [:] /** App Module 的名称, 如 ':app', 传 '' 时,使用项目根目录为 App Module */ def static String appModule def static String appPackage /** 执行 LoaderActivity 替换时,不需要替换的 Activity */ def static ignoredActivities = [] def static putClassAndPath(def className, def classFilePath) { classAndPath.put(className, classFilePath) } def static getClassPath(def className) { return classAndPath.get(className) } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/inner/ReClassTransform.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.inner import com.android.build.api.transform.* import com.android.build.gradle.AppPlugin import com.android.build.gradle.BasePlugin import com.android.build.gradle.internal.pipeline.TransformManager import com.qihoo360.replugin.gradle.plugin.injector.IClassInjector import com.qihoo360.replugin.gradle.plugin.injector.Injectors import javassist.ClassPool import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.io.FileUtils import org.gradle.api.GradleException import org.gradle.api.Project import java.util.regex.Pattern /** * @author RePlugin Team */ public class ReClassTransform extends Transform { private Project project /* 需要处理的 jar 包 */ def includeJars = [] as Set def map = [:] public ReClassTransform(Project p) { this.project = p } @Override String getName() { return '___ReClass___' } @Override void transform(Context context, Collection inputs, Collection referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { welcome() /* 读取用户配置 */ def config = project.extensions.getByName('repluginPluginConfig') File rootLocation = null try { rootLocation = outputProvider.rootLocation } catch (Throwable e) { //android gradle plugin 3.0.0+ 修改了私有变量,将其移动到了IntermediateFolderUtils中去 rootLocation = outputProvider.folderUtils.getRootFolder() } if (rootLocation == null) { throw new GradleException("can't get transform root location") } println ">>> rootLocation: ${rootLocation}" // Compatible with path separators for window and Linux, and fit split param based on 'Pattern.quote' def variantDir = rootLocation.absolutePath.split(getName() + Pattern.quote(File.separator))[1] println ">>> variantDir: ${variantDir}" CommonData.appModule = config.appModule CommonData.ignoredActivities = config.ignoredActivities def injectors = includedInjectors(config, variantDir) if (injectors.isEmpty()) { copyResult(inputs, outputProvider) // 跳过 reclass } else { doTransform(inputs, outputProvider, config, injectors) // 执行 reclass } } /** * 返回用户未忽略的注入器的集合 */ def includedInjectors(def cfg, String variantDir) { def injectors = [] Injectors.values().each { //设置project it.injector.setProject(project) //设置variant关键dir it.injector.setVariantDir(variantDir) if (!(it.nickName in cfg.ignoredInjectors)) { injectors << it.nickName } } injectors } /** * 执行 Transform */ def doTransform(Collection inputs, TransformOutputProvider outputProvider, Object config, def injectors) { /* 初始化 ClassPool */ Object pool = initClassPool(inputs) /* 进行注入操作 */ Util.newSection() Injectors.values().each { if (it.nickName in injectors) { println ">>> Do: ${it.nickName}" // 将 NickName 的第 0 个字符转换成小写,用作对应配置的名称 def configPre = Util.lowerCaseAtIndex(it.nickName, 0) doInject(inputs, pool, it.injector, config.properties["${configPre}Config"]) } else { println ">>> Skip: ${it.nickName}" } } if (config.customInjectors != null) { config.customInjectors.each { doInject(inputs, pool, it) } } /* 重打包 */ repackage() /* 拷贝 class 和 jar 包 */ copyResult(inputs, outputProvider) Util.newSection() } /** * 拷贝处理结果 */ def copyResult(def inputs, def outputs) { // Util.newSection() inputs.each { TransformInput input -> input.directoryInputs.each { DirectoryInput dirInput -> copyDir(outputs, dirInput) } input.jarInputs.each { JarInput jarInput -> copyJar(outputs, jarInput) } } } /** * 将解压的 class 文件重新打包,然后删除 class 文件 */ def repackage() { Util.newSection() println '>>> Repackage...' includeJars.each { File jar = new File(it) String JarAfterzip = map.get(jar.getParent() + File.separatorChar + jar.getName()) String dirAfterUnzip = JarAfterzip.replace('.jar', '') // println ">>> 压缩目录 $dirAfterUnzip" Util.zipDir(dirAfterUnzip, JarAfterzip) // println ">>> 删除目录 $dirAfterUnzip" FileUtils.deleteDirectory(new File(dirAfterUnzip)) } } /** * 执行注入操作 */ def doInject(Collection inputs, ClassPool pool, IClassInjector injector, Object config) { try { inputs.each { TransformInput input -> input.directoryInputs.each { handleDir(pool, it, injector, config) } input.jarInputs.each { handleJar(pool, it, injector, config) } } } catch (Throwable t) { println t.toString() } } /** * 初始化 ClassPool */ def initClassPool(Collection inputs) { Util.newSection() def pool = new ClassPool(true) // 添加编译时需要引用的到类到 ClassPool, 同时记录要修改的 jar 到 includeJars Util.getClassPaths(project, inputs, includeJars, map).each { println " $it" pool.insertClassPath(it) } pool } /** * 处理 jar */ def handleJar(ClassPool pool, JarInput input, IClassInjector injector, Object config) { File jar = input.file if (jar.absolutePath in includeJars) { println ">>> Handle Jar: ${jar.absolutePath}" String dirAfterUnzip = map.get(jar.getParent() + File.separatorChar + jar.getName()).replace('.jar', '') injector.injectClass(pool, dirAfterUnzip, config) } } /** * 拷贝 Jar */ def copyJar(TransformOutputProvider output, JarInput input) { File jar = input.file String jarPath = map.get(jar.absolutePath); if (jarPath != null) { jar = new File(jarPath) } if(!jar.exists()){ return } String destName = input.name def hexName = DigestUtils.md5Hex(jar.absolutePath) if (destName.endsWith('.jar')) { destName = destName.substring(0, destName.length() - 4) } File dest = output.getContentLocation(destName + '_' + hexName, input.contentTypes, input.scopes, Format.JAR) FileUtils.copyFile(jar, dest) /* def path = jar.absolutePath if (path in CommonData.includeJars) { println ">>> 拷贝Jar ${path} 到 ${dest.absolutePath}" } */ } /** * 处理目录中的 class 文件 */ def handleDir(ClassPool pool, DirectoryInput input, IClassInjector injector, Object config) { println ">>> Handle Dir: ${input.file.absolutePath}" injector.injectClass(pool, input.file.absolutePath, config) } /** * 拷贝目录 */ def copyDir(TransformOutputProvider output, DirectoryInput input) { File dest = output.getContentLocation(input.name, input.contentTypes, input.scopes, Format.DIRECTORY) FileUtils.copyDirectory(input.file, dest) // println ">>> 拷贝目录 ${input.file.absolutePath} 到 ${dest.absolutePath}" } /** * 欢迎 */ def welcome() { println '\n' 60.times { print '=' } println '\n replugin-plugin-gradle' 60.times { print '=' } println(""" Add repluginPluginConfig to your build.gradle to enable this plugin: repluginPluginConfig { // Name of 'App Module',use '' if root dir is 'App Module'. ':app' as default. appModule = ':app' // Injectors ignored // LoaderActivityInjector: Replace Activity to LoaderActivity // ProviderInjector: Inject provider method call. ignoredInjectors = ['LoaderActivityInjector'] }""") println('\n') } @Override Set getInputTypes() { return TransformManager.CONTENT_CLASS } @Override Set getScopes() { return TransformManager.SCOPE_FULL_PROJECT } @Override boolean isIncremental() { return false } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/inner/Util.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.inner import com.android.build.api.transform.DirectoryInput import com.android.build.api.transform.JarInput import com.android.build.api.transform.TransformInput import com.android.build.gradle.internal.dependency.VariantDependencies import com.google.common.base.Charsets import com.google.common.hash.Hashing import com.qihoo360.replugin.gradle.compat.ScopeCompat import org.apache.commons.io.FileUtils import org.gradle.api.Project import java.nio.file.Files import java.nio.file.Paths import java.util.zip.ZipFile /** * @author RePlugin Team */ public class Util { private static final String INTERMEDIATES_DIR = "intermediates"; /** 生成 ClassPool 使用的 ClassPath 集合,同时将要处理的 jar 写入 includeJars */ def static getClassPaths(Project project, Collection inputs, Set includeJars, Map map) { def classpathList = [] // android.jar def androidJarConfig = project.configurations .maybeCreate(VariantDependencies.CONFIG_NAME_ANDROID_APIS) androidJarConfig.description = "Configuration providing various types of Android JAR file" def androidJarPath = androidJarConfig.asPath classpathList.add(androidJarPath) // 原始项目中引用的 classpathList getProjectClassPath(project, inputs, includeJars, map).each { classpathList.add(it) } newSection() println ">>> ClassPath:" classpathList } /** 获取原始项目中的 ClassPath */ def private static getProjectClassPath(Project project, Collection inputs, Set includeJars, Map map) { def classPath = [] def visitor = new ClassFileVisitor() def projectDir = project.getRootDir().absolutePath println ">>> Unzip Jar ..." inputs.each { TransformInput input -> input.directoryInputs.each { DirectoryInput dirInput -> def dir = dirInput.file.absolutePath classPath << dir visitor.setBaseDir(dir) Files.walkFileTree(Paths.get(dir), visitor) } input.jarInputs.each { JarInput jarInput -> File jar = jarInput.file def jarPath = jar.absolutePath if (!jarPath.contains(projectDir)) { String jarZipDir = project.getBuildDir().path + File.separator + INTERMEDIATES_DIR + File.separator + "exploded-aar" + File.separator + Hashing.sha1().hashString(jarPath, Charsets.UTF_16LE).toString() + File.separator + "class" if (unzip(jarPath, jarZipDir)) { def jarZip = jarZipDir + ".jar" includeJars << jarPath classPath << jarZipDir visitor.setBaseDir(jarZipDir) Files.walkFileTree(Paths.get(jarZipDir), visitor) map.put(jarPath, jarZip) } } else { includeJars << jarPath map.put(jarPath, jarPath) /* 将 jar 包解压,并将解压后的目录加入 classpath */ // println ">>> 解压Jar${jarPath}" String jarZipDir = jar.getParent() + File.separatorChar + jar.getName().replace('.jar', '') if (unzip(jarPath, jarZipDir)) { classPath << jarZipDir visitor.setBaseDir(jarZipDir) Files.walkFileTree(Paths.get(jarZipDir), visitor) } try{ // 删除 jar FileUtils.forceDelete(jar) } catch (Exception e){ e.printStackTrace() } } } } return classPath } /** * 编译环境中 android.jar 的路径 */ def static getAndroidJarPath(def globalScope) { return ScopeCompat.getAndroidJar(globalScope) } /** * 压缩 dirPath 到 zipFilePath */ def static zipDir(String dirPath, String zipFilePath) { File dir = new File(dirPath) if(dir.exists()){ new AntBuilder().zip(destfile: zipFilePath, basedir: dirPath) }else{ println ">>> Zip file is empty! Ignore" } } /** * 解压 zipFilePath 到 目录 dirPath */ def private static boolean unzip(String zipFilePath, String dirPath) { // 若这个Zip包是空内容的(如引入了Bugly就会出现),则直接忽略 if (isZipEmpty(zipFilePath)) { println ">>> Zip file is empty! Ignore"; return false; } new AntBuilder().unzip(src: zipFilePath, dest: dirPath, overwrite: 'true') return true; } /** * 获取 App Project 目录 */ def static appModuleDir(Project project) { appProject(project).projectDir.absolutePath } /** * 获取 App Project */ def static appProject(Project project) { def modelName = CommonData.appModule.trim() if ('' == modelName || ':' == modelName) { project } project.project(modelName) } /** * 将字符串的某个字符转换成 小写 * * @param str 字符串 * @param index 索引 * * @return 转换后的字符串 */ def public static lowerCaseAtIndex(String str, int index) { def len = str.length() if (index > -1 && index < len) { def arr = str.toCharArray() char c = arr[index] if (c >= 'A' && c <= 'Z') { c += 32 } arr[index] = c arr.toString() } else { str } } def static newSection() { 50.times { print '--' } println() } def static boolean isZipEmpty(String zipFilePath) { ZipFile z; try { z = new ZipFile(zipFilePath) return z.size() == 0 } finally { z.close(); } } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/manifest/IManifest.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.manifest; /** * @author RePlugin Team */ public interface IManifest { /** * 获取 AndroidManifest 中声明的所有 Activity */ List getActivities() /** * 应用程序包名 */ String getPackageName() } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/manifest/ManifestAPI.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.manifest import org.gradle.api.GradleException import org.gradle.api.Project import java.util.regex.Pattern /** * @author RePlugin Team */ public class ManifestAPI { def IManifest sManifestAPIImpl def getActivities(Project project, String variantDir) { if (sManifestAPIImpl == null) { sManifestAPIImpl = new ManifestReader(manifestPath(project, variantDir)) } sManifestAPIImpl.activities } /** * 获取 AndroidManifest.xml 路径 */ def static manifestPath(Project project, String variantDir) { // Compatible with path separators for window and Linux, and fit split param based on 'Pattern.quote' def variantDirArray = variantDir.split(Pattern.quote(File.separator)) String variantName = "" variantDirArray.each { //首字母大写进行拼接 variantName += it.capitalize() } println ">>> variantName:${variantName}" //获取processManifestTask def processManifestTask = project.tasks.getByName("process${variantName}Manifest") //如果processManifestTask存在的话 //transform的task目前能保证在processManifestTask之后执行 if (processManifestTask) { File result = null //正常的manifest File manifestOutputFile = null //instant run的manifest File instantRunManifestOutputFile = null try { def dir = processManifestTask.multiApkManifestOutputDirectory.get().asFile manifestOutputFile = new File(dir, "AndroidManifest.xml") } catch (Exception e) { } if (manifestOutputFile == null && instantRunManifestOutputFile == null) { throw new GradleException("can't get manifest file") } //打印 println " manifestOutputFile:${manifestOutputFile} ${manifestOutputFile.exists()}" //先设置为正常的manifest result = manifestOutputFile //最后检测文件是否存在,打印 if (!result.exists()) { println ' AndroidManifest.xml not exist' } //输出路径 println " AndroidManifest.xml 路径:$result" return result.absolutePath } return "" } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/manifest/ManifestReader.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.manifest /** * @author RePlugin Team */ public class ManifestReader implements IManifest { /* AndroidManifest 文件路径 */ def final filePath def manifest public ManifestReader(String path) { filePath = path } @Override List getActivities() { init() def activities = [] String pkg = manifest.@package manifest.application.activity.each { String name = it.'@android:name' if (name.substring(0, 1) == '.') { name = pkg + name } activities << name } activities } @Override String getPackageName() { init() manifest.@package } def private init() { if (manifest == null) { manifest = new XmlSlurper().parse(filePath) } } } ================================================ FILE: replugin-plugin-gradle/src/main/groovy/com/qihoo360/replugin/gradle/plugin/util/CmdUtil.groovy ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. * */ package com.qihoo360.replugin.gradle.plugin.util import com.qihoo360.replugin.gradle.plugin.AppConstant /** * @author RePlugin Team */ class CmdUtil { /** * 同步阻塞执行命令 * @param cmd 命令 * @return 命令执行完毕返回码 */ public static int syncExecute(String cmd){ int cmdReturnCode try { println "${AppConstant.TAG} \$ ${cmd}" Process process = cmd.execute() process.inputStream.eachLine { println "${AppConstant.TAG} - ${it}" } process.waitFor() cmdReturnCode = process.exitValue() }catch (Exception e){ System.err.println "${AppConstant.TAG} the cmd run error !!!" System.err.println "${AppConstant.TAG} ${e}" return -1 } return cmdReturnCode } private CmdUtil() {} } ================================================ FILE: replugin-plugin-gradle/src/main/resources/META-INF/gradle-plugins/replugin-plugin-gradle.properties ================================================ # # Copyright (C) 2005-2017 Qihoo 360 Inc. # # 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 # # http://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. # # implementation-class=com.qihoo360.replugin.gradle.plugin.ReClassPlugin ================================================ FILE: replugin-plugin-library/README.md ================================================ # RePlugin Plugin Library RePlugin Plugin Library是一个Java工程,由 **插件** 负责引入。 该类主要提供通过“Java反射”来调用主程序中 RePlugin Host Library 的相关接口,并提供“双向通信”的能力。 开发者需要依赖此Library,以让您的单品工程变成“插件”。请参见WiKi以了解接入方法。 有关RePlugin Plugin Library的详细描述,请访问我们的WiKi,以了解更多的内容。 ================================================ FILE: replugin-plugin-library/build.gradle ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { id 'com.android.application' version '7.4.2' apply false id 'maven-publish' id "com.jfrog.bintray" version "1.+" } configure(allprojects - project(':replugin-plugin-lib')) { println "applying java plugin to $project" apply plugin: 'java-library' } java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } ================================================ FILE: replugin-plugin-library/gradle/wrapper/gradle-wrapper.properties ================================================ # # Copyright (C) 2005-2017 Qihoo 360 Inc. # # 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 # # http://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. # #Wed Aug 31 14:09:54 CST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip ================================================ FILE: replugin-plugin-library/gradle.properties ================================================ # # Copyright (C) 2005-2017 Qihoo 360 Inc. # # 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 # # http://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. # # 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. # Default value: -Xmx10248m -XX:MaxPermSize=256m # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # 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 org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 android.useAndroidX=true ================================================ FILE: replugin-plugin-library/gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # 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 case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # 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 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" ] ; 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 # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: replugin-plugin-library/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 @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= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @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 Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_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=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software 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: replugin-plugin-library/replugin-plugin-lib/bintray.gradle ================================================ apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'com.jfrog.bintray' def siteUrl = '' // 项目的主页 def gitUrl = '' // Git仓库的url install { repositories.mavenInstaller { // This generates POM.xml with proper parameters pom { artifactId = "" project { packaging 'aar' // Add your description here name 'RePlugin - A flexible, stable, easy-to-use Android Plug-in Framework' //项目描述 url siteUrl // Set your license licenses { license { name 'Apache License 2.0' url 'http://www.apache.org/licenses/LICENSE-2.0' } } developers { developer { id 'qihoo360' //填写的一些基本信息 name 'qihoo360' email 'replugin@gmail.com' } } scm { connection gitUrl developerConnection gitUrl url siteUrl } } } } } task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } task javadoc(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } artifacts { //archives javadocJar archives sourcesJar } Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) bintray { user = "" key = "" configurations = ['archives'] pkg { repo = '' // 上传到中央仓库的名称 name = '' // 上传到jcenter 的项目名称 userOrg = '' desc = 'RePlugin - A flexible, stable, easy-to-use Android Plug-in Framework' // 项目描述 websiteUrl = siteUrl vcsUrl = gitUrl licenses = ["Apache-2.0"] publish = true } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/build.gradle ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ plugins { id 'com.android.library' id 'maven-publish' } android { namespace 'com.qihoo360.replugin.library' compileSdk 28 defaultConfig { minSdkVersion 9 targetSdkVersion 28 versionCode 1 versionName "1.0" consumerProguardFiles 'replugin-library-rules.pro' buildConfigField "int", 'VERSION_CODE', String.valueOf(1) buildConfigField 'String', 'VERSION_NAME', "\"" + "1.0" + "\"" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { abortOnError false } } dependencies { compileOnly 'com.android.support:appcompat-v7:28.+' compileOnly 'androidx.appcompat:appcompat:1.1.0' } project.ext.RP_ARTIFACT_ID = 'replugin-plugin-lib' apply from: '../../rp-publish.gradle' ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in ./sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # 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 -repackageclasses 'library' -keep class com.qihoo360.replugin.loader.a.** { public *; } -keep class com.qihoo360.replugin.loader.b.** { public *; } -keep class com.qihoo360.replugin.loader.p.** { public *; } -keep class com.qihoo360.replugin.loader.s.** { public *; } -keep class com.qihoo360.replugin.base.IPC { public *; } -keep class com.qihoo360.replugin.Entry { *; } -keep class com.qihoo360.replugin.RePlugin { public *; } -keep class com.qihoo360.replugin.model.PluginInfo { public *; } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/replugin-library-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in ./sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # 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.qihoo360.replugin.Entry { *; } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/AndroidManifest.xml ================================================ ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/aidl/com/qihoo360/loader2/IPlugin.aidl ================================================ package com.qihoo360.loader2; /** * @author RePlugin Team */ interface IPlugin { IBinder query(String name); } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/aidl/com/qihoo360/replugin/IBinderGetter.aidl ================================================ package com.qihoo360.replugin; /** * Binder的获取器,可用于延迟加载IBinder的情况。 *

* 目前用于: *

* * RePlugin.registerGlobalBinderDelayed * * @author RePlugin Team */ interface IBinderGetter { /** * 获取IBinder对象 */ IBinder get(); } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/Entry.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.content.Context; import android.os.IBinder; import android.os.RemoteException; import com.qihoo360.loader2.IPlugin; /** * @author RePlugin Team */ public class Entry { /** * @param context 插件上下文 * @param cl HOST程序的类加载器 * @param manager 插件管理器 * @return */ public static final IBinder create(Context context, ClassLoader cl, IBinder manager) { // 初始化插件框架 RePluginFramework.init(cl); // 初始化Env RePluginEnv.init(context, cl, manager); return new IPlugin.Stub() { @Override public IBinder query(String name) throws RemoteException { return RePluginServiceManager.getInstance().getService(name); } }; } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/MethodInvoker.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.utils.ReflectUtils; import java.lang.reflect.Method; /** * method-invoker的封装 * * @author RePlugin Team */ public class MethodInvoker { private static final String TAG = "MethodInvoker"; private ClassLoader mLoader; private String mClassName; private String mMethodName; private Class[] mParamTypes; private Method mMethod; private boolean mInitialized; private boolean mAvailable; public MethodInvoker(ClassLoader loader, String className, String methodName, Class[] paramTypes) { mLoader = loader; mClassName = className; mMethodName = methodName; mParamTypes = paramTypes; mMethod = null; mInitialized = false; mAvailable = false; } public Object call(Object methodReceiver, Object... methodParamValues) { if (!mInitialized) { try { mInitialized = true; mMethod = ReflectUtils.getMethod(mLoader, mClassName, mMethodName, mParamTypes); mAvailable = true; } catch (Exception e) { if (LogDebug.LOG) { LogDebug.d(TAG, "get method error !!! (Maybe the version of replugin-host-lib is too low)", e); } } } if (mMethod != null) { try { return ReflectUtils.invokeMethod(mMethod, methodReceiver, methodParamValues); } catch (Exception e) { if (LogDebug.LOG) { LogDebug.d(TAG, "invoker method error !!! (Maybe the version of replugin-host-lib is too low)", e); } } } return null; } public ClassLoader getClassLoader() { return mLoader; } public boolean isAvailable() { return mAvailable; } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/RePlugin.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.res.Resources; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.view.View; import android.view.ViewGroup; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.i.IPluginManager; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.packages.PluginRunningList; import com.qihoo360.replugin.utils.ParcelUtils; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** * RePlugin的对外入口类

* 宿主App可直接调用此类中的方法,来使用插件化的几乎全部的逻辑。 * * @author RePlugin Team */ public class RePlugin { static final String TAG = "RePlugin"; /** * 表示目标进程根据实际情况自动调配 */ public static final String PROCESS_AUTO = "" + IPluginManager.PROCESS_AUTO; /** * 表示目标为UI进程 */ public static final String PROCESS_UI = "" + IPluginManager.PROCESS_UI; /** * 表示目标为常驻进程(名字可变,见BuildConfig内字段) */ public static final String PROCESS_PERSIST = "" + IPluginManager.PROCESS_PERSIST; /** * 安装此插件

* 注意:

* 1、这里只将APK移动(或复制)到“插件路径”下,不释放优化后的Dex和Native库,不会加载插件

* 2、支持“纯APK”和“p-n”(旧版,即将废弃)插件

* 3、此方法是【同步】的,耗时较少 * * @param path 插件安装的地址。必须是“绝对路径”。通常可以用context.getFilesDir()来做 * @return 安装成功的插件信息,外界可直接读取 * @since 2.0.0 (1.x版本为installDelayed) */ public static PluginInfo install(String path) { if (!RePluginFramework.mHostInitialized) { return null; } try { Object obj = ProxyRePluginVar.install.call(null, path); if (obj != null) { // 跨ClassLoader进行parcel对象的构造 Parcel p = ParcelUtils.createFromParcelable((Parcelable) obj); return PluginInfo.CREATOR.createFromParcel(p); } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 预加载此插件。此方法会立即释放优化后的Dex和Native库,但不会运行插件代码。

* 具体用法可参见preload(PluginInfo)的说明 * * @param pluginName 要加载的插件名 * @return 预加载是否成功 * @see #preload(PluginInfo) * @since 2.0.0 */ public static boolean preload(String pluginName) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.preload.call(null, pluginName); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 预加载此插件。此方法会立即释放优化后的Dex和Native库,但不会运行插件代码。

* 使用场景:在“安装”完成后“提前释放Dex”(时间算在“安装过程”中)。这样下次启动插件时则速度飞快

* 注意:

* 1、该方法非必须调用(见“使用场景”)。换言之,只要涉及到插件加载,就会自动完成preload操作,无需开发者关心

* 2、Dex和Native库会占用大量的“内部存储空间”。故除非插件是“确定要用的”,否则不必在安装完成后立即调用此方法

* 3、该方法为【同步】调用,且耗时较久(尤其是dex2oat的过程),建议在线程中使用 * * @param pi 要加载的插件信息 * @return 预加载是否成功 * @hide * @see #install(String) * @since 2.0.0 */ public static boolean preload(PluginInfo pi) { if (!RePluginFramework.mHostInitialized) { return false; } try { // 跨classloader创建PluginInfo对象 // TODO 如果有更优雅的方式,可优化 Object p = ParcelUtils.createFromParcelable(pi, RePluginEnv.getHostCLassLoader(), "com.qihoo360.replugin.model.PluginInfo"); Object obj = ProxyRePluginVar.preload2.call(null, p); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 开启一个插件的Activity

* 其中Intent的ComponentName的Key应为插件名(而不是包名),可使用createIntent方法来创建Intent对象 * * @param context Context对象 * @param intent 要打开Activity的Intent,其中ComponentName的Key必须为插件名 * @return 插件Activity是否被成功打开? * FIXME 是否需要Exception来做? * @see #createIntent(String, String) * @since 1.0.0 */ public static boolean startActivity(Context context, Intent intent) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.startActivity.call(null, context, intent); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 开启一个插件的Activity,无需调用createIntent或设置ComponentName来修改Intent * * @param context Context对象 * @param intent 要打开Activity的Intent,其中ComponentName的Key必须为插件名 * @param pluginName 插件名。稍后会填充到Intent中 * @param activity 插件的Activity。稍后会填充到Intent中 * @see #startActivity(Context, Intent) * @since 1.0.0 */ public static boolean startActivity(Context context, Intent intent, String pluginName, String activity) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.startActivity2.call(null, context, intent, pluginName, activity); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 通过 forResult 方式启动一个插件的 Activity * * @param activity 源 Activity * @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名 * @param requestCode 请求码 * @see #startActivityForResult(Activity, Intent, int, Bundle) * @since 2.1.3 */ public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.startActivityForResult.call(null, activity, intent, requestCode); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 通过 forResult 方式启动一个插件的 Activity * * @param activity 源 Activity * @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名 * @param requestCode 请求码 * @param options 附加的数据 * @see #startActivityForResult(Activity, Intent, int, Bundle) * @since 2.1.3 */ public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.startActivityForResult2.call(null, activity, intent, requestCode, options); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 创建一个用来定向到插件组件的Intent

*

* 推荐用法:

* * Intent in = RePlugin.createIntent("clean", "com.qihoo360.mobilesafe.clean.CleanActivity"); *

* 当然,也可以用标准的Android创建方法:

* * Intent in = new Intent();

* in.setComponent(new ComponentName("clean", "com.qihoo360.mobilesafe.clean.CleanActivity")); * * * @param pluginName 插件名 * @param cls 目标全名 * @return 可以被RePlugin识别的Intent * @since 1.0.0 */ public static Intent createIntent(String pluginName, String cls) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (Intent) ProxyRePluginVar.createIntent.call(null, pluginName, cls); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 创建一个用来定向到插件组件的ComponentName,其Key为插件名,Value为目标组件的类全名 * * @param pluginName 插件名 * @param cls 目标组件全名 * @return 一个修改过的ComponentName对象 * @since 1.0.0 */ public static ComponentName createComponentName(String pluginName, String cls) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (ComponentName) ProxyRePluginVar.createComponentName.call(null, pluginName, cls); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 是否使用Dev版AAR?可支持一些"调试特性",但该AAR【千万不要用于发布环境】

* Dev版的AAR可支持如下特性:

* 1、插件签名不正确时仍允许被安装进来,这样利于调试(发布环境上则容易导致严重安全隐患)

* 2、可以打出一些完整的日志(发布环境上则容易被逆向,进而对框架稳定性、私密性造成严重影响) * * @return 是否使用Dev版的AAR? * @since 1.0.0 */ public static boolean isForDev() { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.isForDev.call(null); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 获取当前版本 * * @return 版本号,如2.2.2等 * @since 2.2.2 */ public static String getVersion() { if (!RePluginFramework.mHostInitialized) { return null; } try { return (String) ProxyRePluginVar.getVersion.call(null); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 获取SDK的版本信息 * * @return SDK的版本,如2.0.0等 * @since 2.0.0 * @deprecated 已废弃,请使用 getVersion() 方法 */ public static String getSDKVersion() { return getVersion(); } /** * 加载插件,并获取插件的包信息

* 注意:这里会尝试加载插件,并释放其Jar包。但不会读取资源,也不会释放oat/odex

* 性能消耗(小 → 大):ComponentList/PackageInfo(This) < Resources < ClassLoader < Context < Binder * * @param pluginName 插件名 * @return PackageInfo对象 * @see PackageInfo * @since 1.0.0 */ public static PackageInfo fetchPackageInfo(String pluginName) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (PackageInfo) ProxyRePluginVar.fetchPackageInfo.call(null, pluginName); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 加载插件,并获取插件的资源信息

* 注意:这里会尝试安装插件,并释放其Jar包,读取资源,但不会释放oat/odex。

* 性能消耗(小 → 大):ComponentList/PackageInfo < Resources(This) < ClassLoader < Context < Binder * * @param pluginName 插件名 * @return Resources对象 * @see Resources * @since 1.0.0 */ public static Resources fetchResources(String pluginName) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (Resources) ProxyRePluginVar.fetchResources.call(null, pluginName); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 加载插件,并获取插件自身的ClassLoader对象,以调用插件内部的类

* 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久

* 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < ClassLoader(This) < Context < Binder * * @param pluginName 插件名 * @return 插件的ClassLoader对象 * @since 1.0.0 */ public static ClassLoader fetchClassLoader(String pluginName) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (ClassLoader) ProxyRePluginVar.fetchClassLoader.call(null, pluginName); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 加载插件,并获取插件自身的Context对象,以获取资源等信息

* 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久

* 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < ClassLoader < Context(This) < Binder * * @param pluginName 插件名 * @return 插件的Context对象 * @since 1.0.0 */ public static Context fetchContext(String pluginName) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (Context) ProxyRePluginVar.fetchContext.call(null, pluginName); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 加载插件,并通过插件里的Plugin类,获取插件定义的IBinder

* 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久

* 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < Context/ClassLoader < Binder(This)

*

* PluginBinder(如使用使用本方法)和GlobalBinder类方法(如getGlobalBinder)的不同:

* 1、PluginBinder需要指定插件;GlobalBinder无需指定

* 2、PluginBinder获取的是插件内部已定义好的Binder;GlobalBinder在获取时必须先在代码中注册 * * @param pluginName 插件名 * @param module 要加载的插件模块 * @param process 进程名 TODO 现阶段只能使用IPluginManager中的值,请务必使用它们,否则会出现问题 * @return 返回插件定义的IBinder对象,供外界使用 * @see #getGlobalBinder(String) * @since 1.0.0 */ public static IBinder fetchBinder(String pluginName, String module, String process) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (IBinder) ProxyRePluginVar.fetchBinder2.call(null, pluginName, module, process); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 在当前进程加载插件,并通过插件里的Plugin类,获取插件定义的IBinder

* 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久

* 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < Context/ClassLoader < Binder(This)

*

* PluginBinder(如使用使用本方法)和GlobalBinder类方法(如getGlobalBinder)的不同:

* 1、PluginBinder需要指定插件;GlobalBinder无需指定

* 2、PluginBinder获取的是插件内部已定义好的Binder;GlobalBinder在获取时必须先在代码中注册 * * @param pluginName 插件名 * @param module 要加载的插件模块 * @return 返回插件定义的IBinder对象,供外界使用 * @see #getGlobalBinder(String) * @since 1.0.0 */ public static IBinder fetchBinder(String pluginName, String module) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (IBinder) ProxyRePluginVar.fetchBinder.call(null, pluginName, module); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 通过ClassLoader对象来获取该ClassLoader应属于哪个插件 *

* 该方法消耗非常小,可直接使用 * * @param cl ClassLoader对象 * @return 插件名 * @since 1.0.0 */ public static String fetchPluginNameByClassLoader(ClassLoader cl) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (String) ProxyRePluginVar.fetchPluginNameByClassLoader.call(null, cl); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 通过资源名(包括前缀和具体名字),来获取指定插件里的资源的ID *

* 性能消耗:等同于 fetchResources * * @param pluginName 插件名 * @param resTypeAndName 要获取的“资源类型+资源名”,格式为:“[type]/[name]”。例如:

* → layout/common_title → 从“布局”里获取common_title的ID

* → drawable/common_bg → 从“可绘制图片”里获取common_bg的ID

* 详细见Android官方的说明 * @return 资源的ID。若为0,则表示资源没有找到,无法使用 * @since 2.2.0 (老的host-lib版本也能使用) */ public static int fetchResourceIdByName(String pluginName, String resTypeAndName) { if (!RePluginFramework.mHostInitialized) { return 0; } return RePluginCompat.fetchResourceIdByName(pluginName, resTypeAndName); } /** * 通过Layout名,来获取插件内的View,并自动做“强制类型转换”(也可直接使用View类型)

* 注意:若使用的是公共库,则务必按照Provided的形式引入,否则会出现“不同ClassLoader”导致的ClassCastException

* 当然,非公共库不受影响,但请务必使用Android Framework内的View(例如WebView、ViewGroup等),或索性直接使用View * * @param pluginName 插件名 * @param layoutName Layout名字 * @param root Optional view to be the parent of the generated hierarchy. * @return 插件的View。若为Null则表示获取失败 * @throws ClassCastException 若不是想要的那个View类型,或者ClassLoader不同,则可能会出现此异常。应确保View类型正确 * @since 2.2.0 (老的host-lib版本也能使用) */ public static T fetchViewByLayoutName(String pluginName, String layoutName, ViewGroup root) { if (!RePluginFramework.mHostInitialized) { return null; } return RePluginCompat.fetchViewByLayoutName(pluginName, layoutName, root); } /** * 获取所有插件的列表(指已安装的) * * @return PluginInfo的表 * @since 2.0.0(1.x版本为getExistPlugins) */ public static List getPluginInfoList() { if (!RePluginFramework.mHostInitialized) { return null; } try { List list = (List) ProxyRePluginVar.getPluginInfoList.call(null); if (list != null && list.size() > 0) { List ret = new ArrayList<>(); for (Object o : list) { // 跨ClassLoader进行parcel对象的构造 Parcel p = ParcelUtils.createFromParcelable((Parcelable) o); PluginInfo nPi = PluginInfo.CREATOR.createFromParcel(p); ret.add(nPi); } return ret; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 获取指定插件的信息 * * @param name 插件名 * @return PluginInfo对象 * @since 1.2.0 */ public static PluginInfo getPluginInfo(String name) { if (!RePluginFramework.mHostInitialized) { return null; } try { Object obj = ProxyRePluginVar.getPluginInfo.call(null, name); if (obj != null) { // 跨ClassLoader进行parcel对象的构造 Parcel p = ParcelUtils.createFromParcelable((Parcelable) obj); return PluginInfo.CREATOR.createFromParcel(p); } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 获取当前插件的版本号,可以是VersionCode,也可以是meta-data中的ver。 * * @param name 插件名 * @return 插件版本号。若为-1则表示插件不存在 * @since 2.0.0 */ public static int getPluginVersion(String name) { if (!RePluginFramework.mHostInitialized) { return -1; } try { Object obj = ProxyRePluginVar.getPluginVersion.call(null, name); if (obj != null) { return (Integer) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return -1; } /** * 判断插件是否已被安装(但不一定被使用过,如可能不会释放Dex、Native库等)

* 注意:RePlugin 1.x版本中,isPluginInstalled方法等于现在的isPluginUsed,故含义有变 * * @param pluginName 插件名 * @return 是否被安装 * @since 2.0.0 (1.x版本为isPluginExists) */ public static boolean isPluginInstalled(String pluginName) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.isPluginInstalled.call(null, pluginName); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 判断插件是否曾被使用过。只要释放过Dex、Native的,就认为是“使用过”的

* 和isPluginDexExtracted的区别:插件会在升级完成后,会删除旧Dex。其isPluginDexExtracted为false,而isPluginUsed仍为true * * @param pluginName 插件名 * @return 插件是否已被使用过 * @since 2.0.0 */ public static boolean isPluginUsed(String pluginName) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.isPluginUsed.call(null, pluginName); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 判断当前插件是否已释放了Dex、Native库等 * * @param pluginName 插件名 * @return 是否已被使用过 * @since 2.0.0 (原为isPluginInstalled) */ public static boolean isPluginDexExtracted(String pluginName) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.isPluginDexExtracted.call(null, pluginName); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 当前插件是否在运行。只要任意进程在,就都属于此情况 * * @param pluginName 插件名 * @return 插件是否正在被运行 * @since 2.0.0 */ public static boolean isPluginRunning(String pluginName) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.isPluginRunning.call(null, pluginName); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 当前插件是否在指定进程中运行 * * @param pluginName 插件名 * @param process 指定的进程名,必须为全名 * @return 插件是否在指定进程中运行 * @since 2.0.0 */ public static boolean isPluginRunningInProcess(String pluginName, String process) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.isPluginRunningInProcess.call(null, pluginName, process); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 获取所有正在运行的插件列表 * * @return 所有正在运行的插件的List * @see PluginRunningList * @since 2.0.0 */ public static PluginRunningList getRunningPlugins() { if (!RePluginFramework.mHostInitialized) { return null; } try { Object obj = ProxyRePluginVar.getRunningPlugins.call(null); if (obj != null) { // 跨ClassLoader创建parcelable对象 Parcel p = ParcelUtils.createFromParcelable((Parcelable) obj); PluginRunningList.CREATOR.createFromParcel(p); } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } // FIXME return null; } /** * 获取正在运行此插件的进程名列表

* 若要获取PID,可在拿到列表后,通过IPC.getPidByProcessName来反查 * * @param pluginName 要查询的插件名 * @return 正在运行此插件的进程名列表。一定不会为Null * @since 2.0.0 */ public static String[] getRunningProcessesByPlugin(String pluginName) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (String[]) ProxyRePluginVar.getRunningProcessesByPlugin.call(null, pluginName); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 当前是否处于"常驻进程"? * * @return 是否处于常驻进程 * @since 1.1.0 */ public static boolean isCurrentPersistentProcess() { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.isCurrentPersistentProcess.call(null); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 注册“安装完成后的通知”广播

* 此为“本地”广播,插件内也可以接收到。开发者也可以自行注册,做法:

* * IntentFilter itf = new IntentFilter(MP.ACTION_NEW_PLUGIN);

* LocalBroadcastManager.getInstance(context).registerReceiver(r, itf); * * * @param context Context对象 * @param r 要绑定的BroadcastReceiver对象 * @since 1.0.0 */ public static void registerInstalledReceiver(Context context, BroadcastReceiver r) { if (!RePluginFramework.mHostInitialized) { return; } ProxyRePluginVar.registerInstalledReceiver.call(null, context, r); } /** * 注册一个无需插件名,可被全局使用的Binder对象。Binder对象必须事先创建好

* 有关GlobalBinder的详细介绍,请参见getGlobalBinder的说明

* 有关此方法和registerGlobalBinderDelayed的区别,请参见其方法说明。 * * @param name Binder的描述名 * @param binder Binder对象 * @return 是否注册成功 * @see #getGlobalBinder(String) * @see #registerGlobalBinderDelayed(String, IBinderGetter) * @since 1.2.0 */ public static boolean registerGlobalBinder(String name, IBinder binder) { if (!RePluginFramework.mHostInitialized) { return false; } Object obj = ProxyRePluginVar.registerGlobalBinder.call(null, name, binder); if (obj != null) { return (Boolean) obj; } return false; } /** * 注册一个无需插件名,可被全局使用的Binder对象,但Binder对象只有在“用到时”才会被创建

* 有关GlobalBinder的详细介绍,请参见getGlobalBinder的说明

*

* 和registerGlobalBinder不同的是:

* 1、前者的binder对象必须事先创建好并传递到参数中

*   适用于Binder在注册时就立即创建(性能消耗小),或未来使用频率非常多的情况。如“用户账号服务”、“基础服务”等

* 2、后者会在getGlobalBinder指定的name被首次调用后,才会尝试获取Binder对象

*   适用于Binder只在使用时才被创建(确保启动性能快),或未来调用频率较少的情况。如“Root服务”、“特色功能服务”等

* * @param name Binder的描述名 * @param getter 当getGlobalBinder调用时匹配到name后,会调用getter.get()方法来获取IBinder对象 * @return 是否延迟注册成功 * @since 2.1.0 前面的sdk版本没有keepIBinderGetter */ public static boolean registerGlobalBinderDelayed(String name, IBinderGetter getter) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.registerGlobalBinderDelayed.call(null, name, getter); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 取消全局Binder对象的注册。这样当调用getGlobalBinder时将不再返回结果

* 有关globalBinder的详细介绍,请参见registerGlobalBinder的说明 * * @param name Binder的描述名 * @return 是否取消成功 * @see #getGlobalBinder(String) * @since 1.2.0 */ public static boolean unregisterGlobalBinder(String name) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.unregisterGlobalBinder.call(null, name); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } public static IBinder getGlobalBinder(String name) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (IBinder) ProxyRePluginVar.getGlobalBinder.call(null, name); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 注册一个“跳转”类。一旦系统或自身想调用指定类时,将自动跳转到插件里的另一个类。

* 例如,系统想访问CallShowService类,但此类在宿主中不存在,只在CallShow中有,则:

* 未注册“跳转类”时:直接到宿主中寻找CallShowService类,找到后就加载,找不到就崩溃(若不Catch)

* 注册“挑转类”后,直接将CallShowService的调用“跳转到”插件的CallShowService类中(名字可以不同)。这种情况下,需要调用:

* * RePlugin.registerHookingClass("com.qihoo360.mobilesafe.CallShowService",

*             RePlugin.createComponentName("callshow", "com.qihoo360.callshow.CallShowService2"),

*             DummyService.class); * *

* 该方法可以玩出很多【新花样】。如,可用于以下场景:

* * 已在宿主Manifest中声明了插件的四大组件,只是想借助插件化框架来找到该类并加载进来(如前述例子)。

* * 某些不方便(不好用,或需要云控)的类,想借机替换成插件里的。

*   如我们有一个LaunchUtils类,现在想使用Utils插件中的同样的类来替代。 * * @param source 要替换的类的全名 * @param target 要替换的类的目标,需要使用 createComponentName 方法来创建 * @param defClass 若要替换的类不存在,或插件不可用,则应该使用一个默认的Class。 *

* 可替换成如下的形式,也可以传Null。但若访问的是四大组件,传Null可能会导致出现App崩溃(且无法被Catch) *

* DummyService.class *

* DummyActivity.class *

* DummyProvider.class *

* DummyReceiver.class * @since 1.0.0 */ public static void registerHookingClass(String source, ComponentName target, Class defClass) { if (!RePluginFramework.mHostInitialized) { return; } ProxyRePluginVar.registerHookingClass.call(null, source, target, defClass); } /** * 查询某个 Component 是否是“跳转”类 * * @param component 要查询的组件信息,其中 packageName 为插件名称,className 为要查询的类名称 * @since 2.0.0 */ public static boolean isHookingClass(ComponentName component) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginVar.isHookingClass.call(null, component); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 取消对某个“跳转”类的注册,恢复原状。

* 请参见 registerHookingClass 的详细说明 * * @param source 要替换的类的全名 * @see #registerHookingClass(String, ComponentName, Class) * @since 2.1.6 */ public static void unregisterHookingClass(String source) { if (!RePluginFramework.mHostInitialized) { return; } ProxyRePluginVar.unregisterHookingClass.call(null, source); } /** * 注册一个可供其他模块调用的IBinder,供IPlugin.query使用 * * @param name 注册的IBinder名 * @param binder 注册的IBinder对象 */ public static void registerPluginBinder(String name, IBinder binder) { RePluginServiceManager.getInstance().addService(name, binder); } /** * 获取宿主的Context * @return 宿主的Context */ public static Context getHostContext() { return RePluginEnv.getHostContext(); } /** * 获取宿主的ClassLoader * @return 宿主的ClassLoader */ public static ClassLoader getHostClassLoader() { return RePluginEnv.getHostCLassLoader(); } /** * 获取该插件的PluginContext * @return */ public static Context getPluginContext() { return RePluginEnv.getPluginContext(); } /** * 判断是否运行在宿主环境中 * @return 是否运行在宿主环境 */ public static boolean isHostInitialized() { return RePluginFramework.isHostInitialized(); } /** * dump RePlugin框架运行时的详细信息,包括:Activity 坑位映射表,正在运行的 Service,以及详细的插件信息 * * @param fd * @param writer * @param args * @since 2.2.2 */ public static void dump(FileDescriptor fd, PrintWriter writer, String[] args) { if (!RePluginFramework.mHostInitialized) { return; } try { ProxyRePluginVar.dump.call(null, fd, writer, args); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } } static class ProxyRePluginVar { private static MethodInvoker install; private static MethodInvoker preload; private static MethodInvoker preload2; private static MethodInvoker startActivity; private static MethodInvoker startActivity2; private static MethodInvoker startActivityForResult; private static MethodInvoker startActivityForResult2; private static MethodInvoker createIntent; private static MethodInvoker createComponentName; private static MethodInvoker isForDev; private static MethodInvoker getVersion; private static MethodInvoker fetchPackageInfo; private static MethodInvoker fetchResources; private static MethodInvoker fetchClassLoader; private static MethodInvoker fetchContext; private static MethodInvoker fetchBinder; private static MethodInvoker fetchBinder2; private static MethodInvoker fetchPluginNameByClassLoader; private static MethodInvoker getPluginInfoList; private static MethodInvoker getPluginInfo; private static MethodInvoker getPluginVersion; private static MethodInvoker isPluginInstalled; private static MethodInvoker isPluginUsed; private static MethodInvoker isPluginDexExtracted; private static MethodInvoker isPluginRunning; private static MethodInvoker isPluginRunningInProcess; private static MethodInvoker getRunningPlugins; private static MethodInvoker getRunningProcessesByPlugin; private static MethodInvoker isCurrentPersistentProcess; private static MethodInvoker registerInstalledReceiver; private static MethodInvoker registerGlobalBinder; private static MethodInvoker registerGlobalBinderDelayed; private static MethodInvoker unregisterGlobalBinder; private static MethodInvoker getGlobalBinder; private static MethodInvoker registerHookingClass; private static MethodInvoker isHookingClass; private static MethodInvoker unregisterHookingClass; private static MethodInvoker dump; static void initLocked(final ClassLoader classLoader) { // 初始化Replugin的相关方法 final String rePlugin = "com.qihoo360.replugin.RePlugin"; install = new MethodInvoker(classLoader, rePlugin, "install", new Class[]{String.class}); preload = new MethodInvoker(classLoader, rePlugin, "preload", new Class[]{String.class}); // 这里的参数类型PluginInfo是主程序ClassLoader中的PluginInfo try { Class hostPluginInfo = classLoader.loadClass("com.qihoo360.replugin.model.PluginInfo"); preload2 = new MethodInvoker(classLoader, rePlugin, "preload", new Class[]{PluginInfo.class}); } catch (ClassNotFoundException e) { // } startActivity = new MethodInvoker(classLoader, rePlugin, "startActivity", new Class[]{Context.class, Intent.class}); startActivity2 = new MethodInvoker(classLoader, rePlugin, "startActivity", new Class[]{Context.class, Intent.class, String.class, String.class}); startActivityForResult = new MethodInvoker(classLoader, rePlugin, "startActivityForResult", new Class[]{Activity.class, Intent.class, int.class}); startActivityForResult2 = new MethodInvoker(classLoader, rePlugin, "startActivityForResult", new Class[]{Activity.class, Intent.class, int.class, Bundle.class}); createIntent = new MethodInvoker(classLoader, rePlugin, "createIntent", new Class[]{String.class, String.class}); createComponentName = new MethodInvoker(classLoader, rePlugin, "createComponentName", new Class[]{String.class, String.class}); isForDev = new MethodInvoker(classLoader, rePlugin, "isForDev", new Class[]{}); getVersion = new MethodInvoker(classLoader, rePlugin, "getVersion", new Class[]{}); fetchPackageInfo = new MethodInvoker(classLoader, rePlugin, "fetchPackageInfo", new Class[]{String.class}); fetchResources = new MethodInvoker(classLoader, rePlugin, "fetchResources", new Class[]{String.class}); fetchClassLoader = new MethodInvoker(classLoader, rePlugin, "fetchClassLoader", new Class[]{String.class}); fetchContext = new MethodInvoker(classLoader, rePlugin, "fetchContext", new Class[]{String.class}); fetchBinder = new MethodInvoker(classLoader, rePlugin, "fetchBinder", new Class[]{String.class, String.class}); fetchBinder2 = new MethodInvoker(classLoader, rePlugin, "fetchBinder", new Class[]{String.class, String.class, String.class}); fetchPluginNameByClassLoader = new MethodInvoker(classLoader, rePlugin, "fetchPluginNameByClassLoader", new Class[]{ClassLoader.class}); getPluginInfoList = new MethodInvoker(classLoader, rePlugin, "getPluginInfoList", new Class[]{}); getPluginInfo = new MethodInvoker(classLoader, rePlugin, "getPluginInfo", new Class[]{String.class}); getPluginVersion = new MethodInvoker(classLoader, rePlugin, "getPluginVersion", new Class[]{String.class}); isPluginInstalled = new MethodInvoker(classLoader, rePlugin, "isPluginInstalled", new Class[]{String.class}); isPluginUsed = new MethodInvoker(classLoader, rePlugin, "isPluginUsed", new Class[]{String.class}); isPluginDexExtracted = new MethodInvoker(classLoader, rePlugin, "isPluginDexExtracted", new Class[]{String.class}); isPluginRunning = new MethodInvoker(classLoader, rePlugin, "isPluginRunning", new Class[]{String.class}); isPluginRunningInProcess = new MethodInvoker(classLoader, rePlugin, "isPluginRunningInProcess", new Class[]{String.class, String.class}); getRunningPlugins = new MethodInvoker(classLoader, rePlugin, "getRunningPlugins", new Class[]{}); getRunningProcessesByPlugin = new MethodInvoker(classLoader, rePlugin, "getRunningProcessesByPlugin", new Class[]{String.class}); isCurrentPersistentProcess = new MethodInvoker(classLoader, rePlugin, "isCurrentPersistentProcess", new Class[]{}); registerInstalledReceiver = new MethodInvoker(classLoader, rePlugin, "registerInstalledReceiver", new Class[]{Context.class, BroadcastReceiver.class}); registerGlobalBinder = new MethodInvoker(classLoader, rePlugin, "registerGlobalBinder", new Class[]{String.class, IBinder.class}); Class cGetter = null; try { cGetter = classLoader.loadClass("com.qihoo360.replugin.IBinderGetter"); } catch (Exception e) { // ignore } registerGlobalBinderDelayed = new MethodInvoker(classLoader, rePlugin, "registerGlobalBinderDelayed", new Class[]{String.class, cGetter}); unregisterGlobalBinder = new MethodInvoker(classLoader, rePlugin, "unregisterGlobalBinder", new Class[]{String.class}); getGlobalBinder = new MethodInvoker(classLoader, rePlugin, "getGlobalBinder", new Class[]{String.class}); registerHookingClass = new MethodInvoker(classLoader, rePlugin, "registerHookingClass", new Class[]{String.class, ComponentName.class, Class.class}); isHookingClass = new MethodInvoker(classLoader, rePlugin, "isHookingClass", new Class[]{ComponentName.class}); unregisterHookingClass = new MethodInvoker(classLoader, rePlugin, "unregisterHookingClass", new Class[]{String.class}); dump = new MethodInvoker(classLoader, rePlugin, "dump", new Class[]{FileDescriptor.class, PrintWriter.class, (new String[0]).getClass()}); } } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/RePluginCompat.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.content.Context; import android.content.pm.PackageInfo; import android.content.res.Resources; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.qihoo360.replugin.helper.LogDebug; import static com.qihoo360.replugin.RePlugin.TAG; import static com.qihoo360.replugin.RePlugin.fetchContext; import static com.qihoo360.replugin.RePlugin.fetchPackageInfo; import static com.qihoo360.replugin.RePlugin.fetchResources; /** * 为了兼容旧版本RePlugin而做的类

* 必须和host-lib中的方法“近乎完全一样”才可以,避免出现行为不一致的问题

*

* 注意:依赖host-lib内部调用的(例如调用了Loader的逻辑等)、或者行为高度和宿主一致的,则还是应该走正常的反射流程 * * @author RePlugin Team * @since 2.2.0 */ class RePluginCompat { /** * @see RePlugin#fetchResourceIdByName(String, String) */ static int fetchResourceIdByName(String pluginName, String resTypeAndName) { PackageInfo pi = fetchPackageInfo(pluginName); if (pi == null) { // 插件没有找到 if (LogDebug.LOG) { LogDebug.e(TAG, "fetchResourceIdByName: Plugin not found. pn=" + pluginName + "; resName=" + resTypeAndName); } return 0; } Resources res = fetchResources(pluginName); if (res == null) { // 不太可能出现此问题,同样为插件没有找到 if (LogDebug.LOG) { LogDebug.e(TAG, "fetchResourceIdByName: Plugin not found (fetchResources). pn=" + pluginName + "; resName=" + resTypeAndName); } return 0; } // Identifier的第一个参数想要的是: // [包名]:[类型名]/[资源名]。其中[类型名]/[资源名]就是 resTypeAndName 参数 // 例如:com.qihoo360.replugin.sample.demo2:layout/from_demo1 String idKey = pi.packageName + ":" + resTypeAndName; return res.getIdentifier(idKey, null, null); } /** * @see RePlugin#fetchViewByLayoutName(String, String, ViewGroup) */ public static T fetchViewByLayoutName(String pluginName, String layoutName, ViewGroup root) { Context context = fetchContext(pluginName); if (context == null) { // 插件没有找到 if (LogDebug.LOG) { LogDebug.e(TAG, "fetchViewByLayoutName: Plugin not found. pn=" + pluginName + "; layoutName=" + layoutName); } } String resTypeAndName = "layout/" + layoutName; int id = fetchResourceIdByName(pluginName, resTypeAndName); if (id <= 0) { // 无法拿到资源,可能是资源没有找到 if (LogDebug.LOG) { LogDebug.e(TAG, "fetchViewByLayoutName: fetch failed! pn=" + pluginName + "; layoutName=" + layoutName); } return null; } // TODO 可能要考虑WebView在API 19以上的特殊性 // 强制转换到T类型,一旦转换出错就抛出ClassCastException异常并告诉外界 // noinspection unchecked return (T) LayoutInflater.from(context).inflate(id, root); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/RePluginEnv.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.content.Context; import android.content.ContextWrapper; import android.os.IBinder; /** * 插件环境相关变量的封装 * * @author RePlugin Team */ public class RePluginEnv { private static Context sPluginContext; private static Context sHostContext; private static ClassLoader sHostClassLoader; private static IBinder sPluginManager; /** * NOTE:如需使用MobileSafeHelper类,请务必先在Entry中调用此方法 */ static void init(Context context, ClassLoader cl, IBinder manager) { sPluginContext = context; // 确保获取的一定是主程序的Context sHostContext = ((ContextWrapper) context).getBaseContext(); sHostClassLoader = cl; // 从宿主传过来的,目前恒为null sPluginManager = manager; } /** * 获取宿主部分的Context对象 * 可用来:获取宿主部分的资源、反射类、Info等信息 *

* 注意:此Context对象不能用于插件(如资源、类等)的获取。 * 若需使用插件的Context对象,可直接调用Activity中的getApplicationContext(7.1.0及以后才支持),或直接使用Activity对象即可 */ public static Context getHostContext() { return sHostContext; } /** * 获取宿主部分的ClassLoader对象 * 可用来:获取宿主部分的反射类 *

* 注意:同HostContext一样,这里的ClassLoader不能加载插件中的class */ public static ClassLoader getHostCLassLoader() { return sHostClassLoader; } /** * 获取该插件的PluginContext * @return */ public static Context getPluginContext() { return sPluginContext; } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/RePluginFramework.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.util.Log; import com.qihoo360.replugin.loader.b.PluginLocalBroadcastManager; import com.qihoo360.replugin.loader.p.PluginProviderClient; import com.qihoo360.replugin.loader.s.PluginServiceClient; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.helper.LogRelease; /** * @author RePlugin Team */ public class RePluginFramework { private static final String TAG = "RePluginFramework"; private static final byte[] LOCK = new byte[0]; private static volatile boolean mInitialized; /** * 注:内部框架使用 */ public static volatile boolean mHostInitialized; /** * 插件需要用此初始化代码 * * @param cl * @return 返回true表示运行在HOST中,返回false表示运行的是独立APK */ public static boolean init(ClassLoader cl) { synchronized (LOCK) { return initLocked(cl); } } /** * @return 返回true表示运行在HOST中,返回false表示可能是非HOST环境 */ public static boolean isHostInitialized() { return mHostInitialized; } private static boolean initLocked(ClassLoader cl) { if (mInitialized) { return mHostInitialized; } mInitialized = true; try { // RePluginInternal.ProxyRePluginInternalVar.initLocked(cl); RePlugin.ProxyRePluginVar.initLocked(cl); PluginLocalBroadcastManager.ProxyLocalBroadcastManagerVar.initLocked(cl); PluginProviderClient.ProxyRePluginProviderClientVar.initLocked(cl); PluginServiceClient.ProxyRePluginServiceClientVar.initLocked(cl); IPC.ProxyIPCVar.initLocked(cl); mHostInitialized = true; } catch (final Throwable e) { if (LogRelease.LOGR) { Log.e(TAG, e.getMessage(), e); } } return mHostInitialized; } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/RePluginInternal.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.text.TextUtils; import com.qihoo360.replugin.helper.LogDebug; import com.qihoo360.replugin.i.IPluginManager; import com.qihoo360.replugin.library.BuildConfig; import static com.qihoo360.replugin.helper.LogDebug.TAG; /** * 对框架暴露的一些通用的接口。 *

* 注意:插件框架内部使用,外界请不要调用。 * * @author RePlugin Team */ public class RePluginInternal { public static final boolean FOR_DEV = BuildConfig.DEBUG; /** * @param activity * @param newBase * @return 为Activity构造一个base Context * @hide 内部方法,插件框架使用 * 插件的Activity创建成功后通过此方法获取其base context */ public static Context createActivityContext(Activity activity, Context newBase) { if (!RePluginFramework.mHostInitialized) { return newBase; } try { return (Context) ProxyRePluginInternalVar.createActivityContext.call(null, activity, newBase); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * @param activity * @param savedInstanceState * @hide 内部方法,插件框架使用 * 插件的Activity的onCreate调用前调用此方法 */ public static void handleActivityCreateBefore(Activity activity, Bundle savedInstanceState) { if (!RePluginFramework.mHostInitialized) { return; } ProxyRePluginInternalVar.handleActivityCreateBefore.call(null, activity, savedInstanceState); } /** * @param activity * @param savedInstanceState * @hide 内部方法,插件框架使用 * 插件的Activity的onCreate调用后调用此方法 */ public static void handleActivityCreate(Activity activity, Bundle savedInstanceState) { if (!RePluginFramework.mHostInitialized) { return; } ProxyRePluginInternalVar.handleActivityCreate.call(null, activity, savedInstanceState); } /** * @param activity * @hide 内部方法,插件框架使用 * 插件的Activity的onDestroy调用后调用此方法 */ public static void handleActivityDestroy(Activity activity) { if (!RePluginFramework.mHostInitialized) { return; } ProxyRePluginInternalVar.handleActivityDestroy.call(null, activity); } /** * @param activity * @param savedInstanceState * @hide 内部方法,插件框架使用 * 插件的Activity的onRestoreInstanceState调用后调用此方法 */ public static void handleRestoreInstanceState(Activity activity, Bundle savedInstanceState) { if (!RePluginFramework.mHostInitialized) { return; } ProxyRePluginInternalVar.handleRestoreInstanceState.call(null, activity, savedInstanceState); } /** * @param activity Activity上下文 * @param intent * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑 * @hide 内部方法,插件框架使用 * 启动一个插件中的activity * 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制 */ public static boolean startActivity(Activity activity, Intent intent) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginInternalVar.startActivity.call(null, activity, intent); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * @param activity Activity上下文 * @param intent * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑 * Added by liupo * @hide 内部方法,插件框架使用 * 启动一个插件中的activity * 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制 */ public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode) { return startActivityForResult(activity, intent, requestCode, null); } /** * @param activity Activity上下文 * @param intent * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑 * Added by Jiongxuan Zhang * @hide 内部方法,插件框架使用 * 启动一个插件中的activity * 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制 */ public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) { if (!RePluginFramework.mHostInitialized) { return false; } try { Object obj = ProxyRePluginInternalVar.startActivityForResult.call(null, activity, intent, requestCode, options); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } // replugin-host-lib 版本小于 2.1.3 时 return startActivityForResultCompat(activity, intent, requestCode, options); } /** * 如果 replugin-host-lib 版本小于 2.1.3,使用此 compat 方法。 */ private static boolean startActivityForResultCompat(Activity activity, Intent intent, int requestCode, Bundle options) { String plugin = getPluginName(activity, intent); if (LogDebug.LOG) { LogDebug.d(TAG, "start activity with startActivityForResult: intent=" + intent); } if (TextUtils.isEmpty(plugin)) { return false; } ComponentName cn = intent.getComponent(); if (cn == null) { return false; } String name = cn.getClassName(); ComponentName cnNew = loadPluginActivity(intent, plugin, name, IPluginManager.PROCESS_AUTO); if (cnNew == null) { return false; } intent.setComponent(cnNew); if (Build.VERSION.SDK_INT >= 16) { activity.startActivityForResult(intent, requestCode, options); } else { activity.startActivityForResult(intent, requestCode); } return true; } /** * 加载插件Activity,在startActivity之前调用 * * @param intent * @param plugin 插件名 * @param target 目标Service名,如果传null,则取获取到的第一个 * @param process 是否在指定进程中启动 * @return */ public static ComponentName loadPluginActivity(Intent intent, String plugin, String target, int process) { if (!RePluginFramework.mHostInitialized) { return null; } try { return (ComponentName) ProxyRePluginInternalVar.loadPluginActivity.call(null, intent, plugin, target, process); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 获取插件名称 */ private static String getPluginName(Activity activity, Intent intent) { String plugin = ""; if (intent.getComponent() != null) { plugin = intent.getComponent().getPackageName(); } // 如果 plugin 是包名,则说明启动的是本插件。 if (TextUtils.isEmpty(plugin) || plugin.contains(".")) { plugin = RePlugin.fetchPluginNameByClassLoader(activity.getClassLoader()); } // 否则是其它插件 return plugin; } static class ProxyRePluginInternalVar { private static MethodInvoker createActivityContext; private static MethodInvoker handleActivityCreateBefore; private static MethodInvoker handleActivityCreate; private static MethodInvoker handleActivityDestroy; private static MethodInvoker handleRestoreInstanceState; private static MethodInvoker startActivity; private static MethodInvoker startActivityForResult; private static MethodInvoker loadPluginActivity; static void initLocked(final ClassLoader classLoader) { final String factory2 = "com.qihoo360.i.Factory2"; final String factory = "com.qihoo360.i.Factory"; // 初始化Factory2相关方法 createActivityContext = new MethodInvoker(classLoader, factory2, "createActivityContext", new Class[]{Activity.class, Context.class}); handleActivityCreateBefore = new MethodInvoker(classLoader, factory2, "handleActivityCreateBefore", new Class[]{Activity.class, Bundle.class}); handleActivityCreate = new MethodInvoker(classLoader, factory2, "handleActivityCreate", new Class[]{Activity.class, Bundle.class}); handleActivityDestroy = new MethodInvoker(classLoader, factory2, "handleActivityDestroy", new Class[]{Activity.class}); handleRestoreInstanceState = new MethodInvoker(classLoader, factory2, "handleRestoreInstanceState", new Class[]{Activity.class, Bundle.class}); startActivity = new MethodInvoker(classLoader, factory2, "startActivity", new Class[]{Activity.class, Intent.class}); startActivityForResult = new MethodInvoker(classLoader, factory2, "startActivityForResult", new Class[]{Activity.class, Intent.class, int.class, Bundle.class}); // 初始化Factory相关方法 loadPluginActivity = new MethodInvoker(classLoader, factory, "loadPluginActivity", new Class[]{Intent.class, String.class, String.class, int.class}); } } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/RePluginServiceManager.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin; import android.os.IBinder; import android.text.TextUtils; import com.qihoo360.replugin.helper.LogDebug; import java.util.concurrent.ConcurrentHashMap; /** * 插件内部向外提供服务的管理实现类 *

* 这里注册过来的binder服务,是为了外界(其它插件或者Host宿主)通过IPlugin.query() 接口获取到所需要的服务 * 服务注册时机:建议在Application.onCreate中注册,方便插件加载后便可通过query接口对外提供服务 * * @author RePlugin Team */ public class RePluginServiceManager { private static final String TAG = "Entry.SM"; private static RePluginServiceManager sInstance; private ConcurrentHashMap mServices = new ConcurrentHashMap(); /** * 单例 * * @return */ public static RePluginServiceManager getInstance() { if (sInstance != null) { return sInstance; } synchronized (RePluginServiceManager.class) { if (sInstance == null) { sInstance = new RePluginServiceManager(); } } return sInstance; } /** * 注册服务,供IPlugin.query使用 * * @param name * @param service */ public void addService(final String name, final IBinder service) { if (LogDebug.LOG) { LogDebug.d(TAG, "add service for IPlugin.query, name = " + name); } mServices.put(name, service); } /** * 获取已注册的IBinder * * @param name * @return */ public IBinder getService(final String name) { if (LogDebug.LOG) { LogDebug.d(TAG, "get service for IPlugin.query, name = " + name); } if (TextUtils.isEmpty(name)) { throw new IllegalArgumentException("service name can not value null"); } IBinder ret = mServices.get(name); if (ret == null) { return null; } if (!ret.isBinderAlive() || !ret.pingBinder()) { mServices.remove(name); return null; } return ret; } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/base/IPC.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.base; import android.content.Context; import android.content.Intent; import android.text.TextUtils; import com.qihoo360.replugin.MethodInvoker; import com.qihoo360.replugin.RePluginFramework; import com.qihoo360.replugin.helper.LogDebug; /** * 用于“进程间通信”的类。插件和宿主可使用此类来做一些跨进程发送广播、判断进程等工作。 * * @author RePlugin Team */ public class IPC { private static final String TAG = "IPC"; /** * 获取当前进程名称(从缓存中) * * @return 当前进程名 */ public static String getCurrentProcessName() { if (!RePluginFramework.isHostInitialized()) { return null; } try { return (String) ProxyIPCVar.getCurrentProcessName.call(null); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 获取当前进程的ID(PID) * * @return 当前进程的ID */ public static int getCurrentProcessId() { if (!RePluginFramework.isHostInitialized()) { return -1; } try { Object obj = ProxyIPCVar.getCurrentProcessId.call(null); if (obj != null) { return (Integer) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return -1; } /** * 获取常驻进程名 * * @return 常驻进程名 */ public static String getPersistentProcessName() { if (!RePluginFramework.isHostInitialized()) { return null; } try { return (String) ProxyIPCVar.getPersistentProcessName.call(null); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 获取“插件处理逻辑所在进程名” * 若为“单进程模型”则返回UI进程,否则返回“常驻进程”名 * * @return 插件处理逻辑所在进程名 */ public static String getPluginHostProcessName() { if (!RePluginFramework.isHostInitialized()) { return null; } try { return (String) ProxyIPCVar.getPluginHostProcessName.call(null); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 是否为“插件处理逻辑所在进程”? * 若为“单进程模型”则判断当前是否在UI进程,否则判断是否在“常驻进程” * * @return 若为True,则表示当前正处于“插件处理逻辑所在进程” */ public static boolean isPluginHostProcess() { if (!RePluginFramework.isHostInitialized()) { return false; } try { Object obj = ProxyIPCVar.isPluginHostProcess.call(null); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 当前是否为“UI进程”(主进程)? * * @return 是否为UI进程 */ public static boolean isUIProcess() { if (!RePluginFramework.isHostInitialized()) { return false; } try { Object obj = ProxyIPCVar.isUIProcess.call(null); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 当前是否为“常驻进程”? * * @return 是否为常驻进程 */ public static boolean isPersistentProcess() { if (!RePluginFramework.isHostInitialized()) { return false; } try { Object obj = ProxyIPCVar.isPersistentProcess.call(null); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 是否支持在“常驻进程”中处理插件逻辑?若应用有常驻进程,则应将此设为True * 若为False,则处理插件的逻辑全部放在UI进程中(单进程) * * @return 是否支持? */ public static boolean isPersistentEnable() { if (!RePluginFramework.isHostInitialized()) { return false; } try { Object obj = ProxyIPCVar.isPersistentEnable.call(null); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 通过进程名来获取PID。仅允许获取应用自己的进程 * * @param processName 进程名 * @return PID,若为-1则表示没有此进程,或获取时出现问题 */ public static int getPidByProcessName(String processName) { if (!RePluginFramework.isHostInitialized()) { return -1; } try { Object obj = ProxyIPCVar.getPidByProcessName.call(null, processName); if (obj != null) { return (Integer) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return -1; } /** * 通过PID来获取进程名。仅允许获取应用自己的进程 * * @param pid 进程PID * @return 进程名,若为null则表示没有此进程,或获取时出现问题 */ public static String getProcessNameByPid(int pid) { if (!RePluginFramework.isHostInitialized()) { return null; } try { return (String) ProxyIPCVar.getProcessNameByPid.call(null, pid); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 获取当前宿主的包名(从缓存中) * * @return 宿主的包名 */ public static String getPackageName() { if (!RePluginFramework.isHostInitialized()) { return null; } try { return (String) ProxyIPCVar.getPackageName.call(null); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 多进程使用, 将intent送到目标插件所在进程,对方将受到Local Broadcast * 只有当目标进程存活时才能将数据送达 * 常驻进程通过Local Broadcast注册处理代码 * * @param target 插件名 * @param intent Intent对象 */ public static boolean sendLocalBroadcast2Plugin(Context c, String target, Intent intent) { if (LogDebug.LOG) { LogDebug.d(TAG, "sendLocalBroadcast2Plugin: target=" + target + " intent=" + intent); } if (TextUtils.isEmpty(target)) { return false; } if (!RePluginFramework.isHostInitialized()) { return false; } try { Object obj = ProxyIPCVar.sendLocalBroadcast2Plugin.call(null, c, target, intent); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 多进程使用, 将intent送到目标进程,对方将收到Local Broadcast广播 *

* 只有当目标进程存活时才能将数据送达 *

* 常驻进程通过Local Broadcast注册处理代码 * * @param target 目标进程名 * @param intent Intent对象 */ public static boolean sendLocalBroadcast2Process(Context c, String target, Intent intent) { if (LogDebug.LOG) { LogDebug.d(TAG, "sendLocalBroadcast2Process: target=" + target + " intent=" + intent); } if (TextUtils.isEmpty(target)) { return false; } if (!RePluginFramework.isHostInitialized()) { return false; } try { Object obj = ProxyIPCVar.sendLocalBroadcast2Process.call(null, c, target, intent); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { e.printStackTrace(); } return false; } /** * 发送广播到所有卫士进程(底层已实现权限控制,不会被第三方APP污染) *

* 只有当目标进程存活时才能将数据送达 *

* 卫士任意进程(非single插件进程)只需要通过{@code LocalBroadcastManager}注册即可收到 * * @param intent Intent对象 */ public static boolean sendLocalBroadcast2All(Context c, Intent intent) { if (LogDebug.LOG) { LogDebug.d(TAG, "sendLocalBroadcast2All: intent=" + intent); } if (!RePluginFramework.isHostInitialized()) { return false; } try { Object obj = ProxyIPCVar.sendLocalBroadcast2All.call(null, c, intent); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 多进程使用, 将intent送到目标插件所在进程,对方将收到Local Broadcast广播 *

* 只有当目标进程存活时才能将数据送达 *

* 常驻进程通过Local Broadcast注册处理代码 *

* 会【阻塞】直到所有消息处理完成后才能继续 * * @param target 插件名 * @param intent Intent对象 */ public static boolean sendLocalBroadcast2PluginSync(Context c, String target, Intent intent) { if (LogDebug.LOG) { LogDebug.d(TAG, "sendLocalBroadcast2PluginSync: target=" + target + " intent=" + intent); } if (TextUtils.isEmpty(target)) { return false; } if (!RePluginFramework.isHostInitialized()) { return false; } try { Object obj = ProxyIPCVar.sendLocalBroadcast2PluginSync.call(null, c, target, intent); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 多进程使用, 将intent送到目标进程,对方将收到Local Broadcast广播 *

* 只有当目标进程存活时才能将数据送达 *

* 常驻进程通过Local Broadcast注册处理代码 *

* 会【阻塞】直到所有消息处理完成后才能继续 * * @param target 目标进程名 * @param intent Intent对象 */ public static boolean sendLocalBroadcast2ProcessSync(Context c, String target, Intent intent) { if (LogDebug.LOG) { LogDebug.d(TAG, "sendLocalBroadcast2ProcessSync: target=" + target + " intent=" + intent); } if (TextUtils.isEmpty(target)) { return false; } if (!RePluginFramework.isHostInitialized()) { return false; } try { Object obj = ProxyIPCVar.sendLocalBroadcast2ProcessSync.call(null, c, target, intent); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * 发送广播到所有卫士进程(底层已实现权限控制,不会被第三方APP污染) *

* 只有当目标进程存活时才能将数据送达 *

* 卫士任意进程(非single插件进程)只需要通过{@code LocalBroadcastManager}注册即可收到 *

* 会【阻塞】直到所有消息处理完成后才能继续 * * @param intent Intent对象 */ public static boolean sendLocalBroadcast2AllSync(Context c, Intent intent) { if (LogDebug.LOG) { LogDebug.d(TAG, "sendLocalBroadcast2AllSync: intent=" + intent); } if (!RePluginFramework.isHostInitialized()) { return false; } try { Object obj = ProxyIPCVar.sendLocalBroadcast2AllSync.call(null, c, intent); if (obj != null) { return (Boolean) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } public static class ProxyIPCVar { private static MethodInvoker getCurrentProcessName; private static MethodInvoker getCurrentProcessId; private static MethodInvoker getPersistentProcessName; private static MethodInvoker getPluginHostProcessName; private static MethodInvoker isPluginHostProcess; private static MethodInvoker isUIProcess; private static MethodInvoker isPersistentProcess; private static MethodInvoker isPersistentEnable; private static MethodInvoker getPidByProcessName; private static MethodInvoker getProcessNameByPid; private static MethodInvoker getPackageName; private static MethodInvoker sendLocalBroadcast2Plugin; private static MethodInvoker sendLocalBroadcast2Process; private static MethodInvoker sendLocalBroadcast2All; private static MethodInvoker sendLocalBroadcast2PluginSync; private static MethodInvoker sendLocalBroadcast2ProcessSync; private static MethodInvoker sendLocalBroadcast2AllSync; public static void initLocked(final ClassLoader classLoader) { // final String IPC = "com.qihoo360.replugin.base.IPC"; getCurrentProcessName = new MethodInvoker(classLoader, IPC, "getCurrentProcessName", new Class[]{}); getCurrentProcessId = new MethodInvoker(classLoader, IPC, "getCurrentProcessId", new Class[]{}); getPersistentProcessName = new MethodInvoker(classLoader, IPC, "getPersistentProcessName", new Class[]{}); getPluginHostProcessName = new MethodInvoker(classLoader, IPC, "getPluginHostProcessName", new Class[]{}); isPluginHostProcess = new MethodInvoker(classLoader, IPC, "isPluginHostProcess", new Class[]{}); isUIProcess = new MethodInvoker(classLoader, IPC, "isUIProcess", new Class[]{}); isPersistentProcess = new MethodInvoker(classLoader, IPC, "isPersistentProcess", new Class[]{}); isPersistentEnable = new MethodInvoker(classLoader, IPC, "isPersistentEnable", new Class[]{}); getPidByProcessName = new MethodInvoker(classLoader, IPC, "getPidByProcessName", new Class[]{String.class}); getProcessNameByPid = new MethodInvoker(classLoader, IPC, "getProcessNameByPid", new Class[]{int.class}); getPackageName = new MethodInvoker(classLoader, IPC, "getPackageName", new Class[]{}); sendLocalBroadcast2Plugin = new MethodInvoker(classLoader, IPC, "sendLocalBroadcast2Plugin", new Class[]{Context.class, String.class, Intent.class}); sendLocalBroadcast2Process = new MethodInvoker(classLoader, IPC, "sendLocalBroadcast2Process", new Class[]{Context.class, String.class, Intent.class}); sendLocalBroadcast2All = new MethodInvoker(classLoader, IPC, "sendLocalBroadcast2All", new Class[]{Context.class, Intent.class}); sendLocalBroadcast2PluginSync = new MethodInvoker(classLoader, IPC, "sendLocalBroadcast2PluginSync", new Class[]{Context.class, String.class, Intent.class}); sendLocalBroadcast2ProcessSync = new MethodInvoker(classLoader, IPC, "sendLocalBroadcast2ProcessSync", new Class[]{Context.class, String.class, Intent.class}); sendLocalBroadcast2AllSync = new MethodInvoker(classLoader, IPC, "sendLocalBroadcast2AllSync", new Class[]{Context.class, Intent.class}); } } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/helper/JSONHelper.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.helper; import com.qihoo360.replugin.RePluginInternal; import org.json.JSONException; import org.json.JSONObject; /** * 和JSON操作有关的帮助类 * * @author RePlugin Team */ public class JSONHelper { private static final boolean LOG = RePluginInternal.FOR_DEV; /** * 不抛出异常,直接Put * * @param jo JSONObject对象 * @param key 键 * @param value 值 */ public static void putNoThrows(JSONObject jo, String key, T value) { try { jo.put(key, value); } catch (JSONException e) { if (LOG) { e.printStackTrace(); } } } /** * 克隆一份JSON对象 * * @param from JSON对象 * @return 克隆后的JSON对象 */ public static JSONObject cloneNoThrows(JSONObject from) { try { // 不能用new JsonObject(JSONObject, String[])版本,因为不是深拷贝 return new JSONObject(from.toString()); } catch (JSONException e) { // 不太可能走到这里 if (LOG) { e.printStackTrace(); } return null; } } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/helper/LogDebug.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.helper; import android.os.Build; import android.os.Debug; import android.util.Log; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.base.IPC; import com.qihoo360.replugin.model.PluginInfo; /** * 只在Debug环境下才输出的各种日志,只有当setDebug为true时才会出来 *

* 注意:Release版不会输出,而且会被Proguard删除掉 * * @author RePlugin Team */ public class LogDebug { public static final String TAG = "RePlugin"; private static final String TAG_PREFIX = TAG + "."; /** * 是否输出日志?若用的是nolog编译出的AAR,则这里为False * 注意:所有使用LogDebug前,必须先用此字段来做判断。这样Release阶段才会被彻底删除掉 * 如: * * if (LogDebug.LOG) { * LogDebug.v("xxx", "yyy"); * } * */ public static final boolean LOG = RePluginInternal.FOR_DEV; /** * 允许Dump出一些内容 */ public static final boolean DUMP_ENABLED = LOG; /** * Send a verbose log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int v(String tag, String msg) { if (RePluginInternal.FOR_DEV) { return Log.v(TAG_PREFIX + tag, msg); } return -1; } /** * Send a verbose log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int v(String tag, String msg, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.v(TAG_PREFIX + tag, msg, tr); } return -1; } /** * Send a debug log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int d(String tag, String msg) { if (RePluginInternal.FOR_DEV) { return Log.d(TAG_PREFIX + tag, msg); } return -1; } /** * Send a debug log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int d(String tag, String msg, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.d(TAG_PREFIX + tag, msg, tr); } return -1; } /** * Send an info log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int i(String tag, String msg) { if (RePluginInternal.FOR_DEV) { return Log.i(TAG_PREFIX + tag, msg); } return -1; } /** * Send a inifo log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int i(String tag, String msg, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.i(TAG_PREFIX + tag, msg, tr); } return -1; } /** * Send a warning log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int w(String tag, String msg) { if (RePluginInternal.FOR_DEV) { return Log.w(TAG_PREFIX + tag, msg); } return -1; } /** * Send a warning log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int w(String tag, String msg, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.w(TAG_PREFIX + tag, msg, tr); } return -1; } /** * Send a warning log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param tr An exception to log */ public static int w(String tag, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.w(TAG_PREFIX + tag, tr); } return -1; } /** * Send an error log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int e(String tag, String msg) { if (RePluginInternal.FOR_DEV) { return Log.e(TAG_PREFIX + tag, msg); } return -1; } /** * Send a error log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int e(String tag, String msg, Throwable tr) { if (RePluginInternal.FOR_DEV) { return Log.e(TAG_PREFIX + tag, msg, tr); } return -1; } /** * 打印当前内存占用日志,方便外界诊断。注意,这会显著消耗性能(约50ms左右) * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int printMemoryStatus(String tag, String msg) { if (RePluginInternal.FOR_DEV) { Debug.MemoryInfo mi = new Debug.MemoryInfo(); Debug.getMemoryInfo(mi); String mit = "desc=, memory_v_0_0_1, process=, " + IPC.getCurrentProcessName() + ", totalPss=, " + mi.getTotalPss() + ", dalvikPss=, " + mi.dalvikPss + ", nativeSize=, " + mi.nativePss + ", otherPss=, " + mi.otherPss + ", "; return Log.i(tag + "-MEMORY", mit + msg); } return -1; } /** * 打印当前内存占用日志,方便外界诊断。注意,这会显著消耗性能(约50ms左右) * * @param pi * @param load * @return */ public static int printPluginInfo(PluginInfo pi, int load) { long apk = pi.getApkFile().length(); long dex = pi.getDexFile().length(); return printMemoryStatus(TAG, "act=, loadLocked, flag=, Start, pn=, " + pi.getName() + ", type=, " + load + ", apk=, " + apk + ", odex=, " + dex + ", sys_api=, " + Build.VERSION.SDK_INT); } /** * @deprecated 为兼容卫士,以后干掉 */ public static final String PLUGIN_TAG = "ws001"; /** * @deprecated 为兼容卫士,以后干掉 */ public static final String MAIN_TAG = "ws000"; /** * @deprecated 为兼容卫士,以后干掉 */ public static final String MISC_TAG = "ws002"; } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/helper/LogRelease.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.helper; import android.util.Log; /** * 可同时在Debug和Release上输出的日志 * * @author RePlugin Team */ public class LogRelease { /** * 是否输出日志?因为这里的日志都是“必须要输出的”,故默认均为true(除非有极特殊需要) * 注意:所有使用LogRelease前,必须先用此字段来做判断 * 如: * * if (LogRelease.LOGR) { * LogRelease.v("xxx", "yyy"); * } * */ public static final boolean LOGR = true; /** * Send a verbose log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int v(String tag, String msg) { return Log.v(tag, msg); } /** * Send a verbose log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int v(String tag, String msg, Throwable tr) { return Log.v(tag, msg, tr); } /** * Send a debug log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int d(String tag, String msg) { return Log.d(tag, msg); } /** * Send a debug log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int d(String tag, String msg, Throwable tr) { return Log.d(tag, msg, tr); } /** * Send an info log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int i(String tag, String msg) { return Log.i(tag, msg); } /** * Send a inifo log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int i(String tag, String msg, Throwable tr) { return Log.i(tag, msg, tr); } /** * Send a warning log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int w(String tag, String msg) { return Log.w(tag, msg); } /** * Send a warning log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int w(String tag, String msg, Throwable tr) { return Log.w(tag, msg, tr); } /** * Send a warning log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param tr An exception to log */ public static int w(String tag, Throwable tr) { return Log.w(tag, tr); } /** * Send an error log message. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int e(String tag, String msg) { return Log.e(tag, msg); } /** * Send a error log message and log the exception. * * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. * @param tr An exception to log */ public static int e(String tag, String msg, Throwable tr) { return Log.e(tag, msg, tr); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/i/IPluginManager.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.i; /** * 负责插件和插件之间的interface互通,可通过插件Entry得到,也可通过wrapper类Factory直接调用 * * @author RePlugin Team */ public interface IPluginManager { /** * 自动分配插件进程 */ int PROCESS_AUTO = Integer.MIN_VALUE; /** * UI进程 */ int PROCESS_UI = -1; /** * 常驻进程 */ int PROCESS_PERSIST = -2; } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/PluginResource.java ================================================ package com.qihoo360.replugin.loader; import android.annotation.TargetApi; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.Movie; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.TextUtils; import android.util.TypedValue; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.utils.ReflectUtils; import java.io.InputStream; public class PluginResource extends Resources { private Context mContext; private Resources mPluginResource; private Resources mHostResources; public PluginResource(Context context) { super(context.getResources().getAssets(), context.getResources().getDisplayMetrics(), context.getResources().getConfiguration()); this.mContext = context; this.mPluginResource = context.getResources(); if (RePlugin.isHostInitialized()) { mHostResources = RePlugin.getHostContext().getResources(); } else { mHostResources = context.getResources(); } } @Override public CharSequence getText(int id) throws NotFoundException { try { return mPluginResource.getText(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getText(id); } } @Override public CharSequence getQuantityText(int id, int quantity) throws NotFoundException { try { return mPluginResource.getQuantityText(id, quantity); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getQuantityText(id, quantity); } } @Override public String getString(int id) throws NotFoundException { try { return mPluginResource.getString(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getString(id); } } @Override public String getString(int id, Object... formatArgs) throws NotFoundException { try { return mPluginResource.getString(id, formatArgs); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getString(id, formatArgs); } } @Override public String getQuantityString(int id, int quantity, Object... formatArgs) throws NotFoundException { try { return mPluginResource.getQuantityString(id, quantity, formatArgs); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getQuantityString(id, quantity, formatArgs); } } @Override public String getQuantityString(int id, int quantity) throws NotFoundException { try { return mPluginResource.getQuantityString(id, quantity); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getQuantityString(id, quantity); } } @Override public CharSequence getText(int id, CharSequence def) { try { return mPluginResource.getText(id, def); } catch (Exception e) { e.printStackTrace(); return mHostResources.getText(id, def); } } @Override public CharSequence[] getTextArray(int id) throws NotFoundException { try { return mPluginResource.getTextArray(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getTextArray(id); } } @Override public String[] getStringArray(int id) throws NotFoundException { try { return mPluginResource.getStringArray(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getStringArray(id); } } @Override public int[] getIntArray(int id) throws NotFoundException { try { return mPluginResource.getIntArray(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getIntArray(id); } } @Override public TypedArray obtainTypedArray(int id) throws NotFoundException { try { return mPluginResource.obtainTypedArray(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.obtainTypedArray(id); } } @Override public float getDimension(int id) throws NotFoundException { try { return mPluginResource.getDimension(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getDimension(id); } } @Override public int getDimensionPixelOffset(int id) throws NotFoundException { try { return mPluginResource.getDimensionPixelOffset(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getDimensionPixelOffset(id); } } @Override public int getDimensionPixelSize(int id) throws NotFoundException { try { return mPluginResource.getDimensionPixelSize(id); } catch (NotFoundException e) { e.printStackTrace(); try { return mHostResources.getDimensionPixelSize(id); } catch (NotFoundException e1) { return 0; } } } @TargetApi(Build.VERSION_CODES.CUPCAKE) @Override public float getFraction(int id, int base, int pbase) { try { return mPluginResource.getFraction(id, base, pbase); } catch (Exception e) { e.printStackTrace(); return mHostResources.getFraction(id, base, pbase); } } @Override public Drawable getDrawable(int id) throws NotFoundException { try { return mPluginResource.getDrawable(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getDrawable(id); } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public Drawable getDrawable(int id, Theme theme) throws NotFoundException { try { return mPluginResource.getDrawable(id, theme); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getDrawable(id, theme); } } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) @Override public Drawable getDrawableForDensity(int id, int density) throws NotFoundException { try { return mPluginResource.getDrawableForDensity(id, density); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getDrawableForDensity(id, density); } } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) @Override public Drawable getDrawableForDensity(int id, int density, Theme theme) { try { return mPluginResource.getDrawableForDensity(id, density); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getDrawableForDensity(id, density); } } @Override public Movie getMovie(int id) throws NotFoundException { try { return mPluginResource.getMovie(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getMovie(id); } } @Override public int getColor(int id) throws NotFoundException { try { return mPluginResource.getColor(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getColor(id); } } @Override public ColorStateList getColorStateList(int id) throws NotFoundException { try { return mPluginResource.getColorStateList(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getColorStateList(id); } } @TargetApi(Build.VERSION_CODES.CUPCAKE) @Override public boolean getBoolean(int id) throws NotFoundException { try { return mPluginResource.getBoolean(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getBoolean(id); } } @Override public int getInteger(int id) throws NotFoundException { try { return mPluginResource.getInteger(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getInteger(id); } } @Override public XmlResourceParser getLayout(int id) throws NotFoundException { try { return mPluginResource.getLayout(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getLayout(id); } } @Override public XmlResourceParser getAnimation(int id) throws NotFoundException { try { return mPluginResource.getAnimation(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getAnimation(id); } } @Override public XmlResourceParser getXml(int id) throws NotFoundException { try { return mPluginResource.getXml(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getXml(id); } } @Override public InputStream openRawResource(int id) throws NotFoundException { try { return mPluginResource.openRawResource(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.openRawResource(id); } } @TargetApi(Build.VERSION_CODES.CUPCAKE) @Override public InputStream openRawResource(int id, TypedValue value) throws NotFoundException { try { return mPluginResource.openRawResource(id, value); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.openRawResource(id, value); } } @Override public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException { try { return mPluginResource.openRawResourceFd(id); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.openRawResourceFd(id); } } @Override public void getValue(int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException { try { mPluginResource.getValue(id, outValue, resolveRefs); } catch (NotFoundException e) { e.printStackTrace(); mHostResources.getValue(id, outValue, resolveRefs); } } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) @Override public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs) throws NotFoundException { try { mPluginResource.getValueForDensity(id, density, outValue, resolveRefs); } catch (NotFoundException e) { e.printStackTrace(); mHostResources.getValueForDensity(id, density, outValue, resolveRefs); } } @Override public String getResourceName(int resid) throws NotFoundException { try { return mPluginResource.getResourceName(resid); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getResourceName(resid); } } @Override public String getResourcePackageName(int resid) throws NotFoundException { try { return mPluginResource.getResourcePackageName(resid); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getResourcePackageName(resid); } } @Override public String getResourceTypeName(int resid) throws NotFoundException { try { return mPluginResource.getResourceTypeName(resid); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getResourceTypeName(resid); } } @Override public String getResourceEntryName(int resid) throws NotFoundException { try { return mPluginResource.getResourceEntryName(resid); } catch (NotFoundException e) { e.printStackTrace(); return mHostResources.getResourceEntryName(resid); } } @Override public int getIdentifier(String name, String defType, String defPackage) { try { if (RePlugin.isHostInitialized()) { //当defPackage不是插件包名,也不是宿主包名时,比如android,vivo等, // 此时需要用宿主的resources进行加载 //plugin gradle会替换getIdentifier的defPackage,所以此处用反射方式进行处理 if (!TextUtils.equals(RePlugin.getPluginContext().getPackageName(), defPackage) && !TextUtils.equals(RePlugin.getHostContext().getPackageName(), defPackage)) { return Integer.parseInt(String.valueOf(ReflectUtils.invokeMethod(mHostResources.getClass().getClassLoader(), "android.content.res.Resources", "getIdentifier", mHostResources, new Class[]{String.class, String.class, String.class}, name, defType, defPackage))); } } else { if (!TextUtils.equals(mContext.getPackageName(), defPackage)) { return Integer.parseInt(String.valueOf(ReflectUtils.invokeMethod(mHostResources.getClass().getClassLoader(), "android.content.res.Resources", "getIdentifier", mHostResources, new Class[]{String.class, String.class, String.class}, name, defType, defPackage))); } } return mPluginResource.getIdentifier(name, defType, defPackage); } catch (Exception e) { e.printStackTrace(); } return 0; } @Override public Configuration getConfiguration() { try { return mPluginResource.getConfiguration(); } catch (Exception e) { e.printStackTrace(); return mHostResources.getConfiguration(); } } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.a; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.loader.PluginResource; /** * 插件内的BaseActivity,建议插件内所有的Activity都要继承此类 * 此类通过override方式来完成插件框架的相关工作 * * @author RePlugin Team */ public abstract class PluginActivity extends Activity { private PluginResource pluginResource; @Override protected void attachBaseContext(Context newBase) { newBase = RePluginInternal.createActivityContext(this, newBase); pluginResource = new PluginResource(newBase); super.attachBaseContext(newBase); } @Override public Resources getResources() { if (pluginResource != null){ return pluginResource; } return super.getResources(); } @Override protected void onCreate(Bundle savedInstanceState) { // RePluginInternal.handleActivityCreateBefore(this, savedInstanceState); super.onCreate(savedInstanceState); // RePluginInternal.handleActivityCreate(this, savedInstanceState); } @Override protected void onDestroy() { // RePluginInternal.handleActivityDestroy(this); super.onDestroy(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // RePluginInternal.handleRestoreInstanceState(this, savedInstanceState); try { super.onRestoreInstanceState(savedInstanceState); } catch (Throwable e) { // Added by Jiongxuan Zhang // Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0 // java.lang.IllegalArgumentException: Wrong state class异常 // 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致 // 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity // 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题 // 解决:将其Catch住,这样系统在找ViewState时不会出错。 // 后遗症: // 1、可能无法恢复系统级View的保存的状态; // 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据 if (LogRelease.LOGR) { LogRelease.e("PluginActivity", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e); } } } @Override public void startActivity(Intent intent) { // if (RePluginInternal.startActivity(this, intent)) { // 这个地方不需要回调startActivityAfter,因为Factory2最终还是会回调回来,最终还是要走super.startActivity() return; } super.startActivity(intent); } @Override public void startActivityForResult(Intent intent, int requestCode) { // if (RePluginInternal.startActivityForResult(this, intent, requestCode)) { // 这个地方不需要回调startActivityAfter,因为Factory2最终还是会回调回来,最终还是要走super.startActivityForResult() return; } super.startActivityForResult(intent, requestCode); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginActivityGroup.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.a; import android.app.ActivityGroup; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.loader.PluginResource; /** * @author RePlugin Team */ public abstract class PluginActivityGroup extends ActivityGroup { private PluginResource pluginResource; @Override protected void attachBaseContext(Context newBase) { newBase = RePluginInternal.createActivityContext(this, newBase); pluginResource = new PluginResource(newBase); super.attachBaseContext(newBase); } @Override public Resources getResources() { if (pluginResource != null){ return pluginResource; } return super.getResources(); } @Override protected void onCreate(Bundle savedInstanceState) { // RePluginInternal.handleActivityCreateBefore(this, savedInstanceState); super.onCreate(savedInstanceState); // RePluginInternal.handleActivityCreate(this, savedInstanceState); } @Override protected void onDestroy() { // RePluginInternal.handleActivityDestroy(this); super.onDestroy(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // RePluginInternal.handleRestoreInstanceState(this, savedInstanceState); try { super.onRestoreInstanceState(savedInstanceState); } catch (Throwable e) { // Added by Jiongxuan Zhang // Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0 // java.lang.IllegalArgumentException: Wrong state class异常 // 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致 // 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity // 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题 // 解决:将其Catch住,这样系统在找ViewState时不会出错。 // 后遗症: // 1、可能无法恢复系统级View的保存的状态; // 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据 if (LogRelease.LOGR) { LogRelease.e("PluginActivityGroup", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e); } } } @Override public void startActivity(Intent intent) { // if (RePluginInternal.startActivity(this, intent)) { return; } super.startActivity(intent); } @Override public void startActivityForResult(Intent intent, int requestCode) { // if (RePluginInternal.startActivityForResult(this, intent, requestCode)) { return; } super.startActivityForResult(intent, requestCode); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginAppCompatActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.a; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.app.AppCompatActivity; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.loader.PluginResource; import java.lang.reflect.Field; /** * @author RePlugin Team */ public abstract class PluginAppCompatActivity extends AppCompatActivity { private PluginResource pluginResource; @Override protected void attachBaseContext(Context newBase) { newBase = RePluginInternal.createActivityContext(this, newBase); pluginResource = new PluginResource(newBase); super.attachBaseContext(newBase); } @Override public Resources getResources() { if (pluginResource != null){ return pluginResource; } return super.getResources(); } @Override public Context getBaseContext() { return super.getBaseContext(); } @Override protected void onCreate(Bundle savedInstanceState) { // RePluginInternal.handleActivityCreateBefore(this, savedInstanceState); super.onCreate(savedInstanceState); // RePluginInternal.handleActivityCreate(this, savedInstanceState); } @Override protected void onDestroy() { // RePluginInternal.handleActivityDestroy(this); super.onDestroy(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // RePluginInternal.handleRestoreInstanceState(this, savedInstanceState); try { super.onRestoreInstanceState(savedInstanceState); } catch (Throwable e) { // Added by Jiongxuan Zhang // Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0 // java.lang.IllegalArgumentException: Wrong state class异常 // 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致 // 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity // 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题 // 解决:将其Catch住,这样系统在找ViewState时不会出错。 // 后遗症: // 1、可能无法恢复系统级View的保存的状态; // 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据 if (LogRelease.LOGR) { LogRelease.e("PluginFragmentActivity", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e); } } } @Override public void startActivity(Intent intent) { // if (RePluginInternal.startActivity(this, intent)) { // 这个地方不需要回调startActivityAfter,因为RePluginInternal最终还是会回调回来,最终还是要走super.startActivity() return; } super.startActivity(intent); } @Override public void startActivityForResult(Intent intent, int requestCode) { startActivityForResult(intent, requestCode, null); } @Override public void startActivityForResult(Intent intent, int requestCode, Bundle options) { // if (RePluginInternal.startActivityForResult(this, intent, requestCode, options)) { return; } if (Build.VERSION.SDK_INT >= 16) { super.startActivityForResult(intent, requestCode, options); } else { super.startActivityForResult(intent, requestCode); } } /** * 这里的做法是支持Fragment中调用startActivityForResult特性 *

* 由于卫士的插件需要hook住XXX-Activity的startActivity和startActivityForResult接口 * 但早期版本的support-v4在startActivityFromFragment中直接调用了super.startActivityForResult, 因此这里还需要hook住这个点 * 但新版的support-v4中Fragment最终的调用链还是会走到本XXX-Activity的startActivityForResult接口,因此不需要适配startActivityFromFragment(Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options)接口 * * @param fragment * @param intent * @param requestCode */ @Override public void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { if (requestCode == -1) { startActivityForResult(intent, -1); } else if ((requestCode & -65536) != 0) { throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); } else { int newRequestCode = -1; try { Field f = Fragment.class.getDeclaredField("mIndex"); boolean acc = f.isAccessible(); if (!acc) { f.setAccessible(true); } Object o = f.get(fragment); if (!acc) { f.setAccessible(acc); } int index = (Integer) o; newRequestCode = ((index + 1) << 16) + (requestCode & '\uffff'); } catch (Throwable e) { // Do Noting } startActivityForResult(intent, newRequestCode); } } @Override public String getPackageCodePath() { return super.getPackageCodePath(); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginAppCompatXActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.a; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.loader.PluginResource; import java.lang.reflect.Field; /** * @author RePlugin Team */ public abstract class PluginAppCompatXActivity extends AppCompatActivity { private PluginResource pluginResource; @Override protected void attachBaseContext(Context newBase) { newBase = RePluginInternal.createActivityContext(this, newBase); pluginResource = new PluginResource(newBase); super.attachBaseContext(newBase); } @Override public Resources getResources() { if (pluginResource != null){ return pluginResource; } return super.getResources(); } @Override public Context getBaseContext() { return super.getBaseContext(); } @Override protected void onCreate(Bundle savedInstanceState) { // RePluginInternal.handleActivityCreateBefore(this, savedInstanceState); super.onCreate(savedInstanceState); // RePluginInternal.handleActivityCreate(this, savedInstanceState); } @Override protected void onDestroy() { // RePluginInternal.handleActivityDestroy(this); super.onDestroy(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // RePluginInternal.handleRestoreInstanceState(this, savedInstanceState); try { super.onRestoreInstanceState(savedInstanceState); } catch (Throwable e) { // Added by Jiongxuan Zhang // Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0 // java.lang.IllegalArgumentException: Wrong state class异常 // 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致 // 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity // 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题 // 解决:将其Catch住,这样系统在找ViewState时不会出错。 // 后遗症: // 1、可能无法恢复系统级View的保存的状态; // 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据 if (LogRelease.LOGR) { LogRelease.e("PluginFragmentActivity", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e); } } } @Override public void startActivity(Intent intent) { // if (RePluginInternal.startActivity(this, intent)) { // 这个地方不需要回调startActivityAfter,因为RePluginInternal最终还是会回调回来,最终还是要走super.startActivity() return; } super.startActivity(intent); } @Override public void startActivityForResult(Intent intent, int requestCode) { startActivityForResult(intent, requestCode, null); } @Override public void startActivityForResult(Intent intent, int requestCode, Bundle options) { // if (RePluginInternal.startActivityForResult(this, intent, requestCode, options)) { return; } if (Build.VERSION.SDK_INT >= 16) { super.startActivityForResult(intent, requestCode, options); } else { super.startActivityForResult(intent, requestCode); } } /** * 这里的做法是支持Fragment中调用startActivityForResult特性 *

* 由于卫士的插件需要hook住XXX-Activity的startActivity和startActivityForResult接口 * 但早期版本的support-v4在startActivityFromFragment中直接调用了super.startActivityForResult, 因此这里还需要hook住这个点 * 但新版的support-v4中Fragment最终的调用链还是会走到本XXX-Activity的startActivityForResult接口,因此不需要适配startActivityFromFragment(Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options)接口 * * @param fragment * @param intent * @param requestCode */ @Override public void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { if (requestCode == -1) { startActivityForResult(intent, -1); } else if ((requestCode & -65536) != 0) { throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); } else { int newRequestCode = -1; try { Field f = Fragment.class.getDeclaredField("mIndex"); boolean acc = f.isAccessible(); if (!acc) { f.setAccessible(true); } Object o = f.get(fragment); if (!acc) { f.setAccessible(acc); } int index = (Integer) o; newRequestCode = ((index + 1) << 16) + (requestCode & '\uffff'); } catch (Throwable e) { // Do Noting } startActivityForResult(intent, newRequestCode); } } @Override public String getPackageCodePath() { return super.getPackageCodePath(); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginExpandableListActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.a; import android.app.ExpandableListActivity; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.loader.PluginResource; /** * @author RePlugin Team */ public abstract class PluginExpandableListActivity extends ExpandableListActivity { private PluginResource pluginResource; @Override protected void attachBaseContext(Context newBase) { newBase = RePluginInternal.createActivityContext(this, newBase); pluginResource = new PluginResource(newBase); super.attachBaseContext(newBase); } @Override public Resources getResources() { if (pluginResource != null){ return pluginResource; } return super.getResources(); } @Override protected void onCreate(Bundle savedInstanceState) { // RePluginInternal.handleActivityCreateBefore(this, savedInstanceState); super.onCreate(savedInstanceState); // RePluginInternal.handleActivityCreate(this, savedInstanceState); } @Override protected void onDestroy() { // RePluginInternal.handleActivityDestroy(this); super.onDestroy(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // RePluginInternal.handleRestoreInstanceState(this, savedInstanceState); try { super.onRestoreInstanceState(savedInstanceState); } catch (Throwable e) { // Added by Jiongxuan Zhang // Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0 // java.lang.IllegalArgumentException: Wrong state class异常 // 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致 // 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity // 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题 // 解决:将其Catch住,这样系统在找ViewState时不会出错。 // 后遗症: // 1、可能无法恢复系统级View的保存的状态; // 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据 if (LogRelease.LOGR) { LogRelease.e("PluginExpandableListActivity", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e); } } } @Override public void startActivity(Intent intent) { // if (RePluginInternal.startActivity(this, intent)) { return; } super.startActivity(intent); } @Override public void startActivityForResult(Intent intent, int requestCode) { // if (RePluginInternal.startActivityForResult(this, intent, requestCode)) { return; } super.startActivityForResult(intent, requestCode); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginFragmentActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.a; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.loader.PluginResource; import java.lang.reflect.Field; /** * @author RePlugin Team */ public abstract class PluginFragmentActivity extends FragmentActivity { private PluginResource pluginResource; @Override protected void attachBaseContext(Context newBase) { newBase = RePluginInternal.createActivityContext(this, newBase); pluginResource = new PluginResource(newBase); super.attachBaseContext(newBase); } @Override public Resources getResources() { if (pluginResource != null){ return pluginResource; } return super.getResources(); } @Override public Context getBaseContext() { return super.getBaseContext(); } @Override protected void onCreate(Bundle savedInstanceState) { // RePluginInternal.handleActivityCreateBefore(this, savedInstanceState); super.onCreate(savedInstanceState); // RePluginInternal.handleActivityCreate(this, savedInstanceState); } @Override protected void onDestroy() { // RePluginInternal.handleActivityDestroy(this); super.onDestroy(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // RePluginInternal.handleRestoreInstanceState(this, savedInstanceState); try { super.onRestoreInstanceState(savedInstanceState); } catch (Throwable e) { // Added by Jiongxuan Zhang // Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0 // java.lang.IllegalArgumentException: Wrong state class异常 // 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致 // 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity // 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题 // 解决:将其Catch住,这样系统在找ViewState时不会出错。 // 后遗症: // 1、可能无法恢复系统级View的保存的状态; // 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据 if (LogRelease.LOGR) { LogRelease.e("PluginFragmentActivity", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e); } } } @Override public void startActivity(Intent intent) { // if (RePluginInternal.startActivity(this, intent)) { // 这个地方不需要回调startActivityAfter,因为RePluginInternal最终还是会回调回来,最终还是要走super.startActivity() return; } super.startActivity(intent); } @Override public void startActivityForResult(Intent intent, int requestCode) { startActivityForResult(intent, requestCode, null); } @Override public void startActivityForResult(Intent intent, int requestCode, Bundle options) { // if (RePluginInternal.startActivityForResult(this, intent, requestCode, options)) { return; } if (Build.VERSION.SDK_INT >= 16) { super.startActivityForResult(intent, requestCode, options); } else { super.startActivityForResult(intent, requestCode); } } /** * 这里的做法是支持Fragment中调用startActivityForResult特性 *

* 由于卫士的插件需要hook住XXX-Activity的startActivity和startActivityForResult接口 * 但早期版本的support-v4在startActivityFromFragment中直接调用了super.startActivityForResult, 因此这里还需要hook住这个点 * 但新版的support-v4中Fragment最终的调用链还是会走到本XXX-Activity的startActivityForResult接口,因此不需要适配startActivityFromFragment(Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options)接口 * * @param fragment * @param intent * @param requestCode */ @Override public void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { if (requestCode == -1) { startActivityForResult(intent, -1); } else if ((requestCode & -65536) != 0) { throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); } else { int newRequestCode = -1; try { Field f = Fragment.class.getDeclaredField("mIndex"); boolean acc = f.isAccessible(); if (!acc) { f.setAccessible(true); } Object o = f.get(fragment); if (!acc) { f.setAccessible(acc); } int index = (Integer) o; newRequestCode = ((index + 1) << 16) + (requestCode & '\uffff'); } catch (Throwable e) { // Do Noting } startActivityForResult(intent, newRequestCode); } } @Override public String getPackageCodePath() { return super.getPackageCodePath(); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginFragmentXActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.a; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.loader.PluginResource; import java.lang.reflect.Field; /** * @author RePlugin Team */ public abstract class PluginFragmentXActivity extends FragmentActivity { private PluginResource pluginResource; @Override protected void attachBaseContext(Context newBase) { newBase = RePluginInternal.createActivityContext(this, newBase); pluginResource = new PluginResource(newBase); super.attachBaseContext(newBase); } @Override public Resources getResources() { if (pluginResource != null){ return pluginResource; } return super.getResources(); } @Override public Context getBaseContext() { return super.getBaseContext(); } @Override protected void onCreate(Bundle savedInstanceState) { // RePluginInternal.handleActivityCreateBefore(this, savedInstanceState); super.onCreate(savedInstanceState); // RePluginInternal.handleActivityCreate(this, savedInstanceState); } @Override protected void onDestroy() { // RePluginInternal.handleActivityDestroy(this); super.onDestroy(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // RePluginInternal.handleRestoreInstanceState(this, savedInstanceState); try { super.onRestoreInstanceState(savedInstanceState); } catch (Throwable e) { // Added by Jiongxuan Zhang // Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0 // java.lang.IllegalArgumentException: Wrong state class异常 // 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致 // 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity // 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题 // 解决:将其Catch住,这样系统在找ViewState时不会出错。 // 后遗症: // 1、可能无法恢复系统级View的保存的状态; // 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据 if (LogRelease.LOGR) { LogRelease.e("PluginFragmentActivity", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e); } } } @Override public void startActivity(Intent intent) { // if (RePluginInternal.startActivity(this, intent)) { // 这个地方不需要回调startActivityAfter,因为RePluginInternal最终还是会回调回来,最终还是要走super.startActivity() return; } super.startActivity(intent); } @Override public void startActivityForResult(Intent intent, int requestCode) { startActivityForResult(intent, requestCode, null); } @Override public void startActivityForResult(Intent intent, int requestCode, Bundle options) { // if (RePluginInternal.startActivityForResult(this, intent, requestCode, options)) { return; } if (Build.VERSION.SDK_INT >= 16) { super.startActivityForResult(intent, requestCode, options); } else { super.startActivityForResult(intent, requestCode); } } /** * 这里的做法是支持Fragment中调用startActivityForResult特性 *

* 由于卫士的插件需要hook住XXX-Activity的startActivity和startActivityForResult接口 * 但早期版本的support-v4在startActivityFromFragment中直接调用了super.startActivityForResult, 因此这里还需要hook住这个点 * 但新版的support-v4中Fragment最终的调用链还是会走到本XXX-Activity的startActivityForResult接口,因此不需要适配startActivityFromFragment(Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options)接口 * * @param fragment * @param intent * @param requestCode */ @Override public void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { if (requestCode == -1) { startActivityForResult(intent, -1); } else if ((requestCode & -65536) != 0) { throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); } else { int newRequestCode = -1; try { Field f = Fragment.class.getDeclaredField("mIndex"); boolean acc = f.isAccessible(); if (!acc) { f.setAccessible(true); } Object o = f.get(fragment); if (!acc) { f.setAccessible(acc); } int index = (Integer) o; newRequestCode = ((index + 1) << 16) + (requestCode & '\uffff'); } catch (Throwable e) { // Do Noting } startActivityForResult(intent, newRequestCode); } } @Override public String getPackageCodePath() { return super.getPackageCodePath(); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginListActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.a; import android.app.ListActivity; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.loader.PluginResource; /** * @author RePlugin Team */ public abstract class PluginListActivity extends ListActivity { private PluginResource pluginResource; @Override protected void attachBaseContext(Context newBase) { newBase = RePluginInternal.createActivityContext(this, newBase); pluginResource = new PluginResource(newBase); super.attachBaseContext(newBase); } @Override public Resources getResources() { if (pluginResource != null){ return pluginResource; } return super.getResources(); } @Override protected void onCreate(Bundle savedInstanceState) { // RePluginInternal.handleActivityCreateBefore(this, savedInstanceState); super.onCreate(savedInstanceState); // RePluginInternal.handleActivityCreate(this, savedInstanceState); } @Override protected void onDestroy() { // RePluginInternal.handleActivityDestroy(this); super.onDestroy(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // RePluginInternal.handleRestoreInstanceState(this, savedInstanceState); try { super.onRestoreInstanceState(savedInstanceState); } catch (Throwable e) { // Added by Jiongxuan Zhang // Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0 // java.lang.IllegalArgumentException: Wrong state class异常 // 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致 // 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity // 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题 // 解决:将其Catch住,这样系统在找ViewState时不会出错。 // 后遗症: // 1、可能无法恢复系统级View的保存的状态; // 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据 if (LogRelease.LOGR) { LogRelease.e("PluginListActivity", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e); } } } @Override public void startActivity(Intent intent) { // if (RePluginInternal.startActivity(this, intent)) { // 这个地方不需要回调startActivityAfter,因为RePluginInternal最终还是会回调回来,最终还是要走super.startActivity() return; } super.startActivity(intent); } @Override public void startActivityForResult(Intent intent, int requestCode) { // if (RePluginInternal.startActivityForResult(this, intent, requestCode)) { // 这个地方不需要回调startActivityAfter,因为RePluginInternal最终还是会回调回来,最终还是要走super.startActivityForResult() return; } super.startActivityForResult(intent, requestCode); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginPreferenceActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.a; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; import android.preference.PreferenceActivity; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.loader.PluginResource; /** * @author RePlugin Team */ public class PluginPreferenceActivity extends PreferenceActivity { private PluginResource pluginResource; @Override protected void attachBaseContext(Context newBase) { newBase = RePluginInternal.createActivityContext(this, newBase); pluginResource = new PluginResource(newBase); super.attachBaseContext(newBase); } @Override public Resources getResources() { if (pluginResource != null){ return pluginResource; } return super.getResources(); } @Override protected void onCreate(Bundle savedInstanceState) { // RePluginInternal.handleActivityCreateBefore(this, savedInstanceState); super.onCreate(savedInstanceState); // RePluginInternal.handleActivityCreate(this, savedInstanceState); } @Override protected void onDestroy() { // RePluginInternal.handleActivityDestroy(this); super.onDestroy(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // RePluginInternal.handleRestoreInstanceState(this, savedInstanceState); try { super.onRestoreInstanceState(savedInstanceState); } catch (Throwable e) { // Added by Jiongxuan Zhang // Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0 // java.lang.IllegalArgumentException: Wrong state class异常 // 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致 // 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity // 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题 // 解决:将其Catch住,这样系统在找ViewState时不会出错。 // 后遗症: // 1、可能无法恢复系统级View的保存的状态; // 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据 if (LogRelease.LOGR) { LogRelease.e("PluginActivity", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e); } } } @Override public void startActivity(Intent intent) { // if (RePluginInternal.startActivity(this, intent)) { // 这个地方不需要回调startActivityAfter,因为Factory2最终还是会回调回来,最终还是要走super.startActivity() return; } super.startActivity(intent); } @Override public void startActivityForResult(Intent intent, int requestCode) { // if (RePluginInternal.startActivityForResult(this, intent, requestCode)) { // 这个地方不需要回调startActivityAfter,因为Factory2最终还是会回调回来,最终还是要走super.startActivityForResult() return; } super.startActivityForResult(intent, requestCode); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/a/PluginTabActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.a; import android.app.TabActivity; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; import com.qihoo360.replugin.RePluginInternal; import com.qihoo360.replugin.helper.LogRelease; import com.qihoo360.replugin.loader.PluginResource; /** * @author RePlugin Team */ public abstract class PluginTabActivity extends TabActivity { private PluginResource pluginResource; @Override protected void attachBaseContext(Context newBase) { newBase = RePluginInternal.createActivityContext(this, newBase); pluginResource = new PluginResource(newBase); super.attachBaseContext(newBase); } @Override public Resources getResources() { if (pluginResource != null){ return pluginResource; } return super.getResources(); } @Override protected void onCreate(Bundle savedInstanceState) { // RePluginInternal.handleActivityCreateBefore(this, savedInstanceState); super.onCreate(savedInstanceState); // RePluginInternal.handleActivityCreate(this, savedInstanceState); } @Override protected void onDestroy() { // RePluginInternal.handleActivityDestroy(this); super.onDestroy(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // RePluginInternal.handleRestoreInstanceState(this, savedInstanceState); try { super.onRestoreInstanceState(savedInstanceState); } catch (Throwable e) { // Added by Jiongxuan Zhang // Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0 // java.lang.IllegalArgumentException: Wrong state class异常 // 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致 // 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity // 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题 // 解决:将其Catch住,这样系统在找ViewState时不会出错。 // 后遗症: // 1、可能无法恢复系统级View的保存的状态; // 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据 if (LogRelease.LOGR) { LogRelease.e("PluginTabActivity", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e); } } } @Override public void startActivity(Intent intent) { // if (RePluginInternal.startActivity(this, intent)) { // 这个地方不需要回调startActivityAfter,因为RePluginInternal最终还是会回调回来,最终还是要走super.startActivity() return; } super.startActivity(intent); } @Override public void startActivityForResult(Intent intent, int requestCode) { // if (RePluginInternal.startActivityForResult(this, intent, requestCode)) { // 这个地方不需要回调startActivityAfter,因为RePluginInternal最终还是会回调回来,最终还是要走super.startActivityForResult() return; } super.startActivityForResult(intent, requestCode); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/b/PluginLocalBroadcastManager.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.b; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Handler; import android.os.Message; import android.util.Log; import com.qihoo360.replugin.MethodInvoker; import com.qihoo360.replugin.RePluginEnv; import com.qihoo360.replugin.RePluginFramework; import java.util.ArrayList; import java.util.HashMap; import java.util.Set; /** * @author RePlugin Team */ /** * Helper to register for and send broadcasts of Intents to local objects * within your process. This is has a number of advantages over sending * global broadcasts with {@link android.content.Context#sendBroadcast}: *

    *
  • You know that the data you are broadcasting won't leave your app, so * don't need to worry about leaking private data. *
  • It is not possible for other applications to send these broadcasts to * your app, so you don't need to worry about having security holes they can * exploit. *
  • It is more efficient than sending a global broadcast through the * system. *
*/ public class PluginLocalBroadcastManager { private static class ReceiverRecord { final IntentFilter filter; final BroadcastReceiver receiver; boolean broadcasting; ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) { filter = _filter; receiver = _receiver; } @Override public String toString() { StringBuilder builder = new StringBuilder(128); builder.append("Receiver{"); builder.append(receiver); builder.append(" filter="); builder.append(filter); builder.append("}"); return builder.toString(); } } private static class BroadcastRecord { final Intent intent; final ArrayList receivers; BroadcastRecord(Intent _intent, ArrayList _receivers) { intent = _intent; receivers = _receivers; } } private static final String TAG = "PluginLocalBroadcastManager"; private static final boolean DEBUG = false; private final Context mAppContext; private final HashMap> mReceivers = new HashMap>(); private final HashMap> mActions = new HashMap>(); private final ArrayList mPendingBroadcasts = new ArrayList(); static final int MSG_EXEC_PENDING_BROADCASTS = 1; private final Handler mHandler; private static final Object mLock = new Object(); private static PluginLocalBroadcastManager mInstance; private static Object sOrigInstance; public static Object getInstance(Context context) { synchronized (mLock) { if (RePluginFramework.mHostInitialized) { try { sOrigInstance = ProxyLocalBroadcastManagerVar.getInstance.call(null, context); } catch (Exception e) { e.printStackTrace(); } } if (mInstance == null) { Context appContext = RePluginEnv.getHostContext(); if (appContext == null) { appContext = context.getApplicationContext(); } mInstance = new PluginLocalBroadcastManager(appContext); } return mInstance; } } private PluginLocalBroadcastManager(Context context) { mAppContext = context; mHandler = new Handler(context.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_EXEC_PENDING_BROADCASTS: executePendingBroadcasts(); break; default: super.handleMessage(msg); } } }; } /** * Register a receive for any local broadcasts that match the given IntentFilter. * * @param receiver The BroadcastReceiver to handle the broadcast. * @param filter Selects the Intent broadcasts to be received. * * @see #unregisterReceiver */ public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { if (RePluginFramework.mHostInitialized) { try { ProxyLocalBroadcastManagerVar.registerReceiver.call(sOrigInstance, receiver, filter); } catch (Exception e) { e.printStackTrace(); } return; } synchronized (mReceivers) { ReceiverRecord entry = new ReceiverRecord(filter, receiver); ArrayList filters = mReceivers.get(receiver); if (filters == null) { filters = new ArrayList(1); mReceivers.put(receiver, filters); } filters.add(filter); for (int i=0; i entries = mActions.get(action); if (entries == null) { entries = new ArrayList(1); mActions.put(action, entries); } entries.add(entry); } } } /** * Unregister a previously registered BroadcastReceiver. All * filters that have been registered for this BroadcastReceiver will be * removed. * * @param receiver The BroadcastReceiver to unregister. * * @see #registerReceiver */ public void unregisterReceiver(BroadcastReceiver receiver) { if (RePluginFramework.mHostInitialized) { try { ProxyLocalBroadcastManagerVar.unregisterReceiver.call(sOrigInstance, receiver); } catch (Exception e) { e.printStackTrace(); } return; } synchronized (mReceivers) { ArrayList filters = mReceivers.remove(receiver); if (filters == null) { return; } for (int i=0; i receivers = mActions.get(action); if (receivers != null) { for (int k=0; k categories = intent.getCategories(); final boolean debug = DEBUG || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); if (debug) { Log.v( TAG, "Resolving type " + type + " scheme " + scheme + " of intent " + intent); } ArrayList entries = mActions.get(intent.getAction()); if (entries != null) { if (debug) { Log.v(TAG, "Action list: " + entries); } ArrayList receivers = null; for (int i=0; i= 0) { if (debug) { Log.v(TAG, " Filter matched! match=0x" + Integer.toHexString(match)); } if (receivers == null) { receivers = new ArrayList(); } receivers.add(receiver); receiver.broadcasting = true; } else { if (debug) { String reason; switch (match) { case IntentFilter.NO_MATCH_ACTION: reason = "action"; break; case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break; case IntentFilter.NO_MATCH_DATA: reason = "data"; break; case IntentFilter.NO_MATCH_TYPE: reason = "type"; break; default: reason = "unknown reason"; break; } Log.v(TAG, " Filter did not match: " + reason); } } } if (receivers != null) { for (int i=0; i[]{Context.class}); registerReceiver = new MethodInvoker(classLoader, target, "registerReceiver", new Class[]{BroadcastReceiver.class, IntentFilter.class}); unregisterReceiver = new MethodInvoker(classLoader, target, "unregisterReceiver", new Class[]{BroadcastReceiver.class}); sendBroadcast = new MethodInvoker(classLoader, target, "sendBroadcast", new Class[]{Intent.class}); sendBroadcastSync = new MethodInvoker(classLoader, target, "sendBroadcastSync", new Class[]{Intent.class}); } } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/p/PluginProviderClient.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.p; import android.annotation.TargetApi; import android.content.ContentProviderClient; import android.content.ContentValues; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import com.qihoo360.replugin.MethodInvoker; import com.qihoo360.replugin.RePluginFramework; import com.qihoo360.replugin.helper.LogDebug; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.OutputStream; /** * 一种能够对【插件】的Provider做增加、删除、改变、查询的接口。 * 就像使用ContentResolver一样 * * @author RePlugin Team */ public class PluginProviderClient { /** * 调用插件里的Provider * * @see android.content.ContentResolver#query(Uri, String[], String, String[], String) */ public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { return c.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder); } try { return (Cursor) ProxyRePluginProviderClientVar.query.call(null, c, uri, projection, selection, selectionArgs, sortOrder); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 调用插件里的Provider * * @see android.content.ContentResolver#query(Uri, String[], String, String[], String, CancellationSignal) */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { return c.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder, cancellationSignal); } try { return (Cursor) ProxyRePluginProviderClientVar.query2.call(null, c, uri, projection, selection, selectionArgs, sortOrder, cancellationSignal); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 调用插件里的Provider * * @see android.content.ContentResolver#insert(Uri, ContentValues) */ public static Uri insert(Context c, Uri uri, ContentValues values) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { return c.getContentResolver().insert(uri, values); } try { return (Uri) ProxyRePluginProviderClientVar.insert.call(null, c, uri, values); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } /** * 调用插件里的Provider * * @see android.content.ContentResolver#bulkInsert(Uri, ContentValues[]) */ public static int bulkInsert(Context c, Uri uri, ContentValues[] values) { if (c == null) { return 0; } if (!RePluginFramework.mHostInitialized) { return c.getContentResolver().bulkInsert(uri, values); } try { Object obj = ProxyRePluginProviderClientVar.bulkInsert.call(null, c, uri, values); if (obj != null) { return (Integer) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return -1; } /** * 调用插件里的Provider * * @see android.content.ContentResolver#delete(Uri, String, String[]) */ public static int delete(Context c, Uri uri, String selection, String[] selectionArgs) { if (c == null) { return 0; } if (!RePluginFramework.mHostInitialized) { return c.getContentResolver().delete(uri, selection, selectionArgs); } try { Object obj = ProxyRePluginProviderClientVar.delete.call(null, c, uri, selection, selectionArgs); if (obj != null) { return (Integer) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return -1; } /** * 调用插件里的Provider * * @see android.content.ContentResolver#update(Uri, ContentValues, String, String[]) */ public static int update(Context c, Uri uri, ContentValues values, String selection, String[] selectionArgs) { if (c == null) { return 0; } if (!RePluginFramework.mHostInitialized) { return c.getContentResolver().update(uri, values, selection, selectionArgs); } try { Object obj = ProxyRePluginProviderClientVar.update.call(null, c, uri, values, selection, selectionArgs); if (obj != null) { return (Integer) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return -1; } /** * 调用插件里的Provider * * @see android.content.ContentResolver#getType(Uri) */ public static String getType(Context c, Uri uri) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { return c.getContentResolver().getType(uri); } try { Object obj = ProxyRePluginProviderClientVar.getType.call(null, c, uri); if (obj != null) { return (String) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } public static InputStream openInputStream(Context c, Uri uri) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { try { return c.getContentResolver().openInputStream(uri); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } try { Object obj = ProxyRePluginProviderClientVar.openInputStream.call(null, c, uri); if (obj != null) { return (InputStream) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } public static OutputStream openOutputStream(Context c, Uri uri) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { try { return c.getContentResolver().openOutputStream(uri); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } try { Object obj = ProxyRePluginProviderClientVar.openOutputStream.call(null, c, uri); if (obj != null) { return (OutputStream) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } @TargetApi(3) public static OutputStream openOutputStream(Context c, Uri uri, String mode) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { try { return c.getContentResolver().openOutputStream(uri, mode); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } try { Object obj = ProxyRePluginProviderClientVar.openOutputStream2.call(null, c, uri, mode); if (obj != null) { return (OutputStream) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } public static ParcelFileDescriptor openFileDescriptor(Context c, Uri uri, String mode) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { try { return c.getContentResolver().openFileDescriptor(uri, mode); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } try { Object obj = ProxyRePluginProviderClientVar.openFileDescriptor.call(null, c, uri, mode); if (obj != null) { return (ParcelFileDescriptor) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } @TargetApi(19) public static ParcelFileDescriptor openFileDescriptor(Context c, Uri uri, String mode, CancellationSignal cancellationSignal) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { try { return c.getContentResolver().openFileDescriptor(uri, mode, cancellationSignal); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } try { Object obj = ProxyRePluginProviderClientVar.openFileDescriptor2.call(null, c, uri, mode, cancellationSignal); if (obj != null) { return (ParcelFileDescriptor) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } public static void registerContentObserver(Context c, Uri uri, boolean notifyForDescendents, ContentObserver observer) { if (c == null) { return; } if (!RePluginFramework.mHostInitialized) { c.getContentResolver().registerContentObserver(uri, notifyForDescendents, observer); return; } try { ProxyRePluginProviderClientVar.registerContentObserver.call(null, c, uri, notifyForDescendents, observer); return; } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return; } /** * TODO 支持{@link android.content.ContentResolver#acquireContentProviderClient(Uri)} * * @param c * @param name * @return */ @TargetApi(Build.VERSION_CODES.ECLAIR) public static ContentProviderClient acquireContentProviderClient(Context c, String name) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { return c.getContentResolver().acquireContentProviderClient(name); } try { Object obj = ProxyRePluginProviderClientVar.acquireContentProviderClient.call(null, c, name); if (obj != null) { return (ContentProviderClient) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } public static void notifyChange(Context c, Uri uri, ContentObserver observer) { if (c == null) { return; } if (!RePluginFramework.mHostInitialized) { c.getContentResolver().notifyChange(uri, observer); return; } try { ProxyRePluginProviderClientVar.notifyChange.call(null, c, uri, observer); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } } public static void notifyChange(Context c, Uri uri, ContentObserver observer, boolean b) { if (c == null) { return; } if (!RePluginFramework.mHostInitialized) { c.getContentResolver().notifyChange(uri, observer, b); return; } try { ProxyRePluginProviderClientVar.notifyChange2.call(null, c, uri, observer, b); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } } public static Uri toCalledUri(Context c, Uri uri) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { return uri; } try { Object obj = ProxyRePluginProviderClientVar.toCalledUri.call(null, c, uri); if (obj != null) { return (Uri) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } public static Uri toCalledUri(Context c, String plugin, Uri uri, int process) { if (c == null) { return null; } if (!RePluginFramework.mHostInitialized) { return uri; } try { Object obj = ProxyRePluginProviderClientVar.toCalledUri2.call(null, c, plugin, uri, process); if (obj != null) { return (Uri) obj; } } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } return null; } public static class ProxyRePluginProviderClientVar { private static MethodInvoker query; private static MethodInvoker query2; private static MethodInvoker insert; private static MethodInvoker bulkInsert; private static MethodInvoker delete; private static MethodInvoker update; private static MethodInvoker getType; private static MethodInvoker openInputStream; private static MethodInvoker openOutputStream; private static MethodInvoker openOutputStream2; private static MethodInvoker openFileDescriptor; private static MethodInvoker openFileDescriptor2; private static MethodInvoker registerContentObserver; private static MethodInvoker acquireContentProviderClient; private static MethodInvoker notifyChange; private static MethodInvoker notifyChange2; private static MethodInvoker toCalledUri; private static MethodInvoker toCalledUri2; public static void initLocked(final ClassLoader classLoader) { // String rePluginProviderClient = "com.qihoo360.loader2.mgr.PluginProviderClient"; query = new MethodInvoker(classLoader, rePluginProviderClient, "query", new Class[]{Context.class, Uri.class, String[].class, String.class, String[].class, String.class}); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { query2 = new MethodInvoker(classLoader, rePluginProviderClient, "query", new Class[]{Context.class, Uri.class, String[].class, String.class, String[].class, String.class, CancellationSignal.class}); } insert = new MethodInvoker(classLoader, rePluginProviderClient, "insert", new Class[]{Context.class, Uri.class, ContentValues.class}); bulkInsert = new MethodInvoker(classLoader, rePluginProviderClient, "bulkInsert", new Class[]{Context.class, Uri.class, ContentValues[].class}); delete = new MethodInvoker(classLoader, rePluginProviderClient, "delete", new Class[]{Context.class, Uri.class, String.class, String[].class}); update = new MethodInvoker(classLoader, rePluginProviderClient, "update", new Class[]{Context.class, Uri.class, ContentValues.class, String.class, String[].class}); // new supported getType = new MethodInvoker(classLoader, rePluginProviderClient, "getType", new Class[]{Context.class, Uri.class}); openInputStream = new MethodInvoker(classLoader, rePluginProviderClient, "openInputStream", new Class[]{Context.class, Uri.class}); openOutputStream = new MethodInvoker(classLoader, rePluginProviderClient, "openOutputStream", new Class[]{Context.class, Uri.class}); openOutputStream2 = new MethodInvoker(classLoader, rePluginProviderClient, "openOutputStream", new Class[]{Context.class, Uri.class, String.class}); openFileDescriptor = new MethodInvoker(classLoader, rePluginProviderClient, "openFileDescriptor", new Class[]{Context.class, Uri.class, String.class}); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { openFileDescriptor2 = new MethodInvoker(classLoader, rePluginProviderClient, "openFileDescriptor", new Class[]{Context.class, Uri.class, String.class, CancellationSignal.class}); } registerContentObserver = new MethodInvoker(classLoader, rePluginProviderClient, "registerContentObserver", new Class[]{Context.class, Uri.class, Boolean.class, ContentObserver.class}); acquireContentProviderClient = new MethodInvoker(classLoader, rePluginProviderClient, "acquireContentProviderClient", new Class[]{Context.class, String.class}); notifyChange = new MethodInvoker(classLoader, rePluginProviderClient, "notifyChange", new Class[]{Context.class, Uri.class, ContentObserver.class}); notifyChange2 = new MethodInvoker(classLoader, rePluginProviderClient, "notifyChange", new Class[]{Context.class, Uri.class, ContentObserver.class, Boolean.class}); toCalledUri = new MethodInvoker(classLoader, rePluginProviderClient, "toCalledUri", new Class[]{Context.class, Uri.class}); toCalledUri2 = new MethodInvoker(classLoader, rePluginProviderClient, "toCalledUri", new Class[]{Context.class, String.class, Uri.class, Integer.class}); } } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/loader/s/PluginServiceClient.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.loader.s; import android.app.Service; import com.qihoo360.replugin.RePluginFramework; import com.qihoo360.replugin.MethodInvoker; import com.qihoo360.replugin.helper.LogDebug; /** * 一种能够对【插件】的服务进行:启动、停止、绑定、解绑等功能的类 * 所有针对插件命令的操作,均从此类开始。 *

* 外界可直接使用此类 * * @author RePlugin Team */ public class PluginServiceClient { /** * 在插件服务中停止服务。近似于Service.stopSelf * 注意:此方法应在插件服务中被调用 * * @param s 要停止的插件服务 * @see android.app.Service#stopSelf() */ public static void stopSelf(Service s) { if (!RePluginFramework.mHostInitialized) { s.stopSelf(); return; } try { ProxyRePluginServiceClientVar.stopSelf.call(null, s); } catch (Exception e) { if (LogDebug.LOG) { e.printStackTrace(); } } } public static class ProxyRePluginServiceClientVar { private static MethodInvoker stopSelf; public static void initLocked(final ClassLoader classLoader) { // final String rePluginServiceClient = "com.qihoo360.loader2.mgr.PluginServiceClient"; stopSelf = new MethodInvoker(classLoader, rePluginServiceClient, "stopSelf", new Class[]{Service.class}); } } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/model/PluginInfo.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.model; import android.content.Context; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginEnv; import com.qihoo360.replugin.helper.JSONHelper; import com.qihoo360.replugin.helper.LogDebug; import org.json.JSONException; import org.json.JSONObject; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Arrays; /** * 用来描述插件的描述信息。以Json来封装 * * @author RePlugin Team */ public class PluginInfo implements Serializable, Parcelable, Cloneable { private static final long serialVersionUID = -6531475023210445876L; private static final String TAG = "PluginInfo"; /** * 表示一个尚未安装的"纯APK"插件,其path指向下载完成后APK所在位置 */ public static final int TYPE_NOT_INSTALL = 10; /** * 表示一个释放过的"纯APK"插件,其path指向释放后的那个APK包

*

* 注意:此时可能还并未安装,仅仅是将APK拷贝到相应目录而已。例如,若是通过RePlugin.installDelayed时即为如此 */ public static final int TYPE_EXTRACTED = 11; /** * 表示为P-n已安装,其path指向释放后的那个Jar包(存在于app_plugins_v3,如clean-10-10-102.jar,一个APK) * * @deprecated 只用于旧的P-n插件,可能会废弃 */ public static final int TYPE_PN_INSTALLED = 1; /** * 内置插件 */ public static final int TYPE_BUILTIN = 2; /** * 表示为P-n还未安装,其path指向释放前的那个Jar包(在Files目录下,如p-n-clean.jar,有V5文件头) * * @deprecated 只用于旧的P-n插件,可能会废弃 */ public static final int TYPE_PN_JAR = 3; /** * 表示“不确定的框架版本号”,只有旧P-n插件才需要在Load期间得到框架版本号 * * @deprecated 只用于旧的P-n插件,可能会废弃 */ public static final int FRAMEWORK_VERSION_UNKNOWN = 0; /** * HOST向下兼容版本 */ private static final int ADAPTER_COMPATIBLE_VERSION = 10; /** * HOST版本 */ public static final int ADAPTER_CURRENT_VERSION = 12; /** * 插件存放目录 */ private static final String LOCAL_PLUGIN_SUB_DIR = "plugins_v3"; /** * 插件ODEX存放目录 */ private static final String LOCAL_PLUGIN_ODEX_SUB_DIR = "plugins_v3_odex"; /** * 插件data存放目录 */ public static final String LOCAL_PLUGIN_DATA_SUB_DIR = "plugins_v3_data"; /** * 插件Native(SO库)存放目录 */ private static final String LOCAL_PLUGIN_DATA_LIB_DIR = "plugins_v3_libs"; /** * "纯APK"插件存放目录 */ private static final String LOCAL_PLUGIN_APK_SUB_DIR = "p_a"; /** * "纯APK"中释放Odex的目录 */ private static final String LOCAL_PLUGIN_APK_ODEX_SUB_DIR = "p_od"; /** * 纯"APK"插件的Native(SO库)存放目录 */ private static final String LOCAL_PLUGIN_APK_LIB_DIR = "p_n"; /** * "纯APK"插件同版本升级时插件、Odex、Native(SO库)的用于覆盖的存放目录 */ private static final String LOCAL_PLUGIN_APK_COVER_DIR = "p_c"; /** * 插件extra dex(优化前)释放的以插件名独立隔离的子目录 * 适用于 android 5.0 以下,5.0以上不会用到该目录 */ private static final String LOCAL_PLUGIN_INDEPENDENT_EXTRA_DEX_SUB_DIR = "_ed"; /** * 插件extra dex(优化后)释放的以插件名独立隔离的子目录 */ private static final String LOCAL_PLUGIN_INDEPENDENT_EXTRA_ODEX_SUB_DIR = "_eod"; private transient JSONObject mJson; private String mJsonText; // 若插件需要更新,则会有此值 private PluginInfo mPendingUpdate; // 若插件需要卸载,则会有此值 private PluginInfo mPendingDelete; // 若插件需要同版本覆盖安装更新,则会有此值 private PluginInfo mPendingCover; private boolean mIsPendingCover; // 若当前为“新的PluginInfo”且为“同版本覆盖”,则为了能区分路径,则需要将此字段同步到Json文件中 // 若当前为“新的PluginInfo”,则其“父Info”是什么? // 通常当前这个Info会包裹在“mPendingUpdate/mPendingDelete/mPendingCover”内 // 此信息【不会】做持久化工作。下次重启进程后会消失 private PluginInfo mParentInfo; private PluginInfo(JSONObject jo) { initPluginInfo(jo); } private PluginInfo(String name, int low, int high, int ver) { mJson = new JSONObject(); JSONHelper.putNoThrows(mJson, "name", name); JSONHelper.putNoThrows(mJson, "low", low); JSONHelper.putNoThrows(mJson, "high", high); JSONHelper.putNoThrows(mJson, "ver", ver); } private PluginInfo(String pkgName, String alias, int low, int high, int version, String path, int type) { // 如Low、High不正确,则给个默认值(等于应用的“最小支持协议版本”) if (low <= 0) { low = ADAPTER_COMPATIBLE_VERSION; } if (high <= 0) { high = ADAPTER_COMPATIBLE_VERSION; } mJson = new JSONObject(); JSONHelper.putNoThrows(mJson, "pkgname", pkgName); JSONHelper.putNoThrows(mJson, "ali", alias); JSONHelper.putNoThrows(mJson, "name", makeName(pkgName, alias)); JSONHelper.putNoThrows(mJson, "low", low); JSONHelper.putNoThrows(mJson, "high", high); setVersion(version); setPath(path); setType(type); } private void initPluginInfo(JSONObject jo) { mJson = jo; // 缓存“待更新”的插件信息 JSONObject ujo = jo.optJSONObject("upinfo"); if (ujo != null) { mPendingUpdate = new PluginInfo(ujo); } // 缓存“待卸载”的插件信息 JSONObject djo = jo.optJSONObject("delinfo"); if (djo != null) { mPendingDelete = new PluginInfo(djo); } // 缓存"待覆盖安装"的插件信息 JSONObject cjo = jo.optJSONObject("coverinfo"); if (cjo != null) { mPendingCover = new PluginInfo(cjo); } // 缓存"待覆盖安装"的插件覆盖字段 mIsPendingCover = jo.optBoolean("cover"); } // 通过别名和包名来最终确认插件名 // 注意:老插件会用到"name"字段,同时出于性能考虑,故必须写在Json中。见调用此方法的地方 private String makeName(String pkgName, String alias) { if (!TextUtils.isEmpty(alias)) { return alias; } if (!TextUtils.isEmpty(pkgName)) { return pkgName; } return ""; } /** * 获取插件名,如果有别名,则返回别名,否则返回插件包名

* (注意:旧插件"p-n"的"别名"就是插件名) */ public String getName() { return mJson.optString("name"); } /** * 获取插件包名 */ public String getPackageName() { return mJson.optString("pkgname"); } /** * 获取插件别名 */ public String getAlias() { return mJson.optString("ali"); } /** * 获取插件的版本 */ public int getVersion() { return mJson.optInt("ver"); } /** * 获取最新的插件,目前所在的位置 */ public String getPath() { return mJson.optString("path"); } /** * 设置最新的插件,目前所在的位置

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 */ public void setPath(String path) { JSONHelper.putNoThrows(mJson, "path", path); } /** * 插件是否被使用过?只要释放过Dex的都认为是true * * @return 插件是否使用过? */ public boolean isUsed() { // 注意:该方法不单纯获取JSON中的值,而是根据插件类型(p-n、纯APK)、所处环境(新插件、当前插件)而定 if (isPnPlugin()) { // 为兼容以前逻辑,p-n仍是判断dex是否存在 return isDexExtracted(); } else if (getParentInfo() != null) { // 若PluginInfo是其它PluginInfo中的PendingUpdate,则返回那个PluginInfo的Used即可 return getParentInfo().isUsed(); } else { // 若是纯APK,且不是PendingUpdate,则直接从Json中获取 return mJson.optBoolean("used"); } } /** * 设置插件是否被使用过

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 * * @param used 插件是否被使用过 */ public void setIsUsed(boolean used) { JSONHelper.putNoThrows(mJson, "used", used); } /** * 获取Long型的,可用来对比的版本号 */ public long getVersionValue() { return mJson.optLong("verv"); } /** * 插件的Dex是否已被优化(释放)了? * * @return */ public boolean isDexExtracted() { File f = getDexFile(); // 文件存在,且大小不为 0 时,才返回 true。 return f.exists() && f.length() > 0; } /** * 获取APK存放的文件信息

* 若为"纯APK"插件,则会位于app_p_a中;若为"p-n"插件,则会位于"app_plugins_v3"中

* 注意:若支持同版本覆盖安装的话,则会位于app_p_c中;

* * @return Apk所在的File对象 */ public File getApkFile() { return new File(getApkDir(), makeInstalledFileName() + ".jar"); } /** * 获取APK存放目录 * * @return */ public String getApkDir() { // 必须使用宿主的Context对象,防止出现“目录定位到插件内”的问题 Context context = RePluginEnv.getHostContext(); File dir; if (isPnPlugin()) { dir = context.getDir(LOCAL_PLUGIN_SUB_DIR, 0); } else if (getIsPendingCover()) { dir = context.getDir(LOCAL_PLUGIN_APK_COVER_DIR, 0); } else { dir = context.getDir(LOCAL_PLUGIN_APK_SUB_DIR, 0); } return dir.getAbsolutePath(); } /** * 获取或创建(如果需要)某个插件的Dex目录,用于放置dex文件 * 注意:仅供框架内部使用;仅适用于Android 4.4.x及以下 * * @param dirSuffix 目录后缀 * @return 插件的Dex所在目录的File对象 */ private File getDexDir(File dexDir, String dirSuffix) { File dir = new File(dexDir, makeInstalledFileName() + dirSuffix); if (!dir.exists()) { dir.mkdir(); } return dir; } /** * 获取Extra Dex(优化前)生成时所在的目录

* 若为"纯APK"插件,则会位于app_p_od/xx_ed中;若为"p-n"插件,则会位于"app_plugins_v3_odex/xx_ed"中

* 若支持同版本覆盖安装的话,则会位于app_p_c/xx_ed中;

* 注意:仅供框架内部使用;仅适用于Android 4.4.x及以下 * * @return 优化前Extra Dex所在目录的File对象 */ public File getExtraDexDir() { return getDexDir(getDexParentDir(), LOCAL_PLUGIN_INDEPENDENT_EXTRA_DEX_SUB_DIR); } /** * 获取Extra Dex(优化后)生成时所在的目录

* 若为"纯APK"插件,则会位于app_p_od/xx_eod中;若为"p-n"插件,则会位于"app_plugins_v3_odex/xx_eod"中

* 若支持同版本覆盖安装的话,则会位于app_p_c/xx_eod中;

* 注意:仅供框架内部使用;仅适用于Android 4.4.x及以下 * * @return 优化后Extra Dex所在目录的File对象 */ public File getExtraOdexDir() { return getDexDir(getDexParentDir(), LOCAL_PLUGIN_INDEPENDENT_EXTRA_ODEX_SUB_DIR); } /** * 获取Dex(优化后)生成时所在的目录

* * Android O之前: * 若为"纯APK"插件,则会位于app_p_od中;若为"p-n"插件,则会位于"app_plugins_v3_odex"中

* 若支持同版本覆盖安装的话,则会位于app_p_c中;

* * Android O: * APK存放目录/oat/{cpuType} * * 注意:仅供框架内部使用 * @return 优化后Dex所在目录的File对象 */ public File getDexParentDir() { // 必须使用宿主的Context对象,防止出现“目录定位到插件内”的问题 Context context = RePluginEnv.getHostContext(); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { return new File(getApkDir() + File.separator + "oat" + File.separator + VMRuntimeCompat.getArtOatCpuType()); } else { if (isPnPlugin()) { return context.getDir(LOCAL_PLUGIN_ODEX_SUB_DIR, 0); } else if (getIsPendingCover()) { return context.getDir(LOCAL_PLUGIN_APK_COVER_DIR, 0); } else { return context.getDir(LOCAL_PLUGIN_APK_ODEX_SUB_DIR, 0); } } } /** * 获取Dex(优化后)所在的文件信息

* * Android O 之前: * 若为"纯APK"插件,则会位于app_p_od中;若为"p-n"插件,则会位于"app_plugins_v3_odex"中

* * Android O: * APK存放目录/oat/{cpuType}/XXX.odex * * 注意:仅供框架内部使用 * * @return 优化后Dex所在文件的File对象 */ public File getDexFile() { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { File dir = getDexParentDir(); return new File(dir, makeInstalledFileName() + ".odex"); } else { File dir = getDexParentDir(); return new File(dir, makeInstalledFileName() + ".dex"); } } /** * 根据类型来获取SO释放的路径

* 若为"纯APK"插件,则会位于app_p_n中;若为"p-n"插件,则会位于"app_plugins_v3_libs"中

* 若支持同版本覆盖安装的话,则会位于app_p_c中;

* 注意:仅供框架内部使用 * * @return SO释放路径所在的File对象 */ public File getNativeLibsDir() { // 必须使用宿主的Context对象,防止出现“目录定位到插件内”的问题 Context context = RePluginEnv.getHostContext(); File dir; if (isPnPlugin()) { dir = context.getDir(LOCAL_PLUGIN_DATA_LIB_DIR, 0); } else if (getIsPendingCover()) { dir = context.getDir(LOCAL_PLUGIN_APK_COVER_DIR, 0); } else { dir = context.getDir(LOCAL_PLUGIN_APK_LIB_DIR, 0); } return new File(dir, makeInstalledFileName()); } /** * 获取插件当前所处的类型。详细见TYPE_XXX常量 */ public int getType() { return mJson.optInt("type"); } /** * 设置插件当前所处的类型。详细见TYPE_XXX常量

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 */ public void setType(int type) { JSONHelper.putNoThrows(mJson, "type", type); } /** * 是否已准备好了新版本? * * @return 是否已准备好 */ public boolean isNeedUpdate() { return mPendingUpdate != null; } /** * 获取将来要更新的插件的信息,将会在下次启动时才能被使用 * * @return 插件更新信息 */ public PluginInfo getPendingUpdate() { return mPendingUpdate; } /** * 是否需要删除插件? * * @return 是否需要卸载插件 */ public boolean isNeedUninstall() { return mPendingDelete != null; } /** * 获取将来要卸载的插件的信息,将会在下次启动时才能被使用 * * @return 插件卸载信息 */ public PluginInfo getPendingDelete() { return mPendingDelete; } /** * 是否已准备好了新待覆盖的版本? * * @return 是否已准备好 */ public boolean isNeedCover() { return mPendingCover != null; } /** * 获取将来要覆盖更新的插件的信息,将会在下次启动时才能被使用 * * @return 插件覆盖安装信息 */ public PluginInfo getPendingCover() { return mPendingCover; } /** * 此PluginInfo是否包含同版本覆盖的字段?只在调用RePlugin.install方法才能看到

* 注意:仅框架内部使用 * * @return 是否包含同版本覆盖字段 */ public boolean getIsPendingCover() { return mIsPendingCover; } /** * 获取最小支持宿主API的版本 */ public int getLowInterfaceApi() { return mJson.optInt("low", ADAPTER_COMPATIBLE_VERSION); } /** * 获取最高支持宿主API的版本 * * @deprecated 可能会废弃 */ public int getHighInterfaceApi() { return mJson.optInt("high", ADAPTER_COMPATIBLE_VERSION); } /** * 获取框架的版本号

* 此版本号不同于“协议版本”。这直接关系到四大组件和其它模块的加载情况 */ public int getFrameworkVersion() { // 仅p-n插件在用 // 之所以默认为FRAMEWORK_VERSION_UNKNOWN,是因为在这里还只是读取p-n文件头,框架版本需要在loadDex阶段获得 return mJson.optInt("frm_ver", FRAMEWORK_VERSION_UNKNOWN); } /** * 设置框架的版本号

* 注意:若为“纯APK”方案所用,则修改后需调用PluginInfoList.save来保存,否则会无效 * * @param version 框架版本号 */ public void setFrameworkVersion(int version) { JSONHelper.putNoThrows(mJson, "frm_ver", version); } /** * 生成用于放入app_plugin_v3(app_p_n)等目录下的插件的文件名,其中:

* 1、“纯APK”方案:得到混淆后的文件名(规则见代码内容)

* 2、“旧p-n”和“内置插件”(暂定)方案:得到类似 shakeoff_10_10_103 这样的比较规范的文件名

* 3、只获取文件名,其目录和扩展名仍需在外面定义 * * @return 文件名(不含扩展名) */ public String makeInstalledFileName() { if (isPnPlugin() || getType() == TYPE_BUILTIN) { return formatName(); } else { // 混淆插件名字,做法: // 1. 生成最初的名字:[插件包名(小写)][协议最低版本][协议最高版本][插件版本][ak] // 必须用小写和数字、无特殊字符,否则hashCode后会有一定的重复率 // 2. 将其生成出hashCode // 3. 将整体数字 - 88 String n = getPackageName().toLowerCase() + getLowInterfaceApi() + getHighInterfaceApi() + getVersion() + "ak"; int h = n.hashCode() - 88; return Integer.toString(h); } } /** * 若此Info为“新PluginInfo”,则这里返回的是“其父Info”的内容。通常和PendingUpdate有关 * * @return 父PluginInfo */ public PluginInfo getParentInfo() { return mParentInfo; } private void setVersion(int version) { JSONHelper.putNoThrows(mJson, "ver", version); JSONHelper.putNoThrows(mJson, "verv", buildCompareValue()); } // ------------------------- // Parcelable and Cloneable // ------------------------- public static final Creator CREATOR = new Creator() { @Override public PluginInfo createFromParcel(Parcel source) { return new PluginInfo(source); } @Override public PluginInfo[] newArray(int size) { return new PluginInfo[size]; } }; private PluginInfo(Parcel source) { JSONObject jo = null; String txt = null; try { txt = source.readString(); jo = new JSONObject(txt); } catch (JSONException e) { if (LogDebug.LOG) { LogDebug.e(TAG, "PluginInfo: mJson error! s=" + txt, e); } jo = new JSONObject(); } initPluginInfo(jo); } @Override public Object clone() { PluginInfo pluginInfo = null; // 通过 transient 和 Serializable 配合实现深拷贝 this.mJsonText = this.mJson != null ? this.mJson.toString() : null; // Object -> Stream -> clone Object try { ByteArrayOutputStream byteArrOut = new ByteArrayOutputStream(); ObjectOutputStream objOut = new ObjectOutputStream(byteArrOut); objOut.writeObject(this); ByteArrayInputStream byteArrIn = new ByteArrayInputStream(byteArrOut.toByteArray()); ObjectInputStream objIn = new ObjectInputStream(byteArrIn); pluginInfo = (PluginInfo) objIn.readObject(); if (pluginInfo != null && !TextUtils.isEmpty(this.mJsonText)) { pluginInfo.mJson = new JSONObject(this.mJsonText); JSONObject ujo = pluginInfo.mJson.optJSONObject("upinfo"); if (ujo != null) { pluginInfo.mPendingUpdate = new PluginInfo(ujo); } JSONObject djo = pluginInfo.mJson.optJSONObject("delinfo"); if (djo != null) { pluginInfo.mPendingDelete = new PluginInfo(djo); } JSONObject cjo = pluginInfo.mJson.optJSONObject("coverinfo"); if (cjo != null) { pluginInfo.mPendingCover = new PluginInfo(cjo); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } return pluginInfo; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mJson.toString()); } @Override public String toString() { StringBuilder b = new StringBuilder(); b.append("PInfo { "); toContentString(b); b.append(" }"); return b.toString(); } private void toContentString(StringBuilder b) { // 插件名 + 版本 + 框架版本 { b.append('<'); b.append(getName()).append(':').append(getVersion()); b.append('(').append(getFrameworkVersion()).append(')'); b.append("> "); } // 当前是否为PendingUpdate的信息 if (mParentInfo != null) { b.append("[HAS_PARENT] "); } // 插件类型 { if (getType() == TYPE_BUILTIN) { b.append("[BUILTIN] "); } else if (isPnPlugin()) { b.append("[P-N] "); } else { b.append("[APK] "); } } // 插件是否已释放 if (isDexExtracted()) { b.append("[DEX_EXTRACTED] "); } // 插件是否“正在使用” if (RePlugin.isPluginRunning(getName())) { b.append("[RUNNING] "); } // 哪些进程使用 String[] processes = RePlugin.getRunningProcessesByPlugin(getName()); if (processes != null) { b.append("processes=").append(Arrays.toString(processes)).append(' '); } // 插件基本信息 if (mJson != null) { b.append("js=").append(mJson).append(' '); } // 和插件路径有关(除APK路径以外) { b.append("dex=").append(getDexFile()).append(' '); b.append("nlib=").append(getNativeLibsDir()); } } @Override public int hashCode() { return mJson.hashCode(); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (this.getClass() != obj.getClass()) { return false; } PluginInfo pluginInfo = (PluginInfo) obj; try { return pluginInfo.mJson.toString().equals(mJson.toString()); } catch (Exception e) { return false; } } public static final String format(String name, int low, int high, int ver) { return name + "-" + low + "-" + high + "-" + ver; } private String formatName() { return format(getName(), getLowInterfaceApi(), getHighInterfaceApi(), getVersion()); } private final long buildCompareValue() { long x; x = getHighInterfaceApi() & 0x7fff; long v1 = x << (32 + 16); x = getLowInterfaceApi() & 0xffff; long v2 = x << 32; long v3 = getVersion(); return v1 | v2 | v3; } /** * 判断是否为p-n类型的插件? * * @return 是否为p-n类型的插件 * @deprecated 只用于旧的P-n插件,可能会废弃 */ public boolean isPnPlugin() { int type = getType(); return type == TYPE_PN_INSTALLED || type == TYPE_PN_JAR || type == TYPE_BUILTIN; } private static class VMRuntimeCompat { public static final String ARM = "arm"; public static final String ARM64 = "arm64"; private static final byte[] GET_LOCKER = new byte[0]; private static volatile Boolean sIs64Bit; /** * 精确判断是否为64位 */ public static boolean is64Bit() { // 最终使用下列方法: // VMRuntime.getRuntime().is64Bit(); if (sIs64Bit != null) { return sIs64Bit; } synchronized (GET_LOCKER) { if (sIs64Bit != null) { return sIs64Bit; } // 确保只获取一次。但不排除个别手机一上来获取会有问题(没遇到) sIs64Bit = is64BitImpl(); return sIs64Bit; } } private static boolean is64BitImpl() { try { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // Android API 21之前不支持64位CPU return false; } Class clzVMRuntime = Class.forName("dalvik.system.VMRuntime"); if (clzVMRuntime == null) { return false; } Method mthVMRuntimeGet = clzVMRuntime.getDeclaredMethod("getRuntime"); if (mthVMRuntimeGet == null) { return false; } Object objVMRuntime = mthVMRuntimeGet.invoke(null); if (objVMRuntime == null) { return false; } Method sVMRuntimeIs64BitMethod = clzVMRuntime.getDeclaredMethod("is64Bit"); if (sVMRuntimeIs64BitMethod == null) { return false; } Object objIs64Bit = sVMRuntimeIs64BitMethod.invoke(objVMRuntime); if (objIs64Bit instanceof Boolean) { return (boolean) objIs64Bit; } } catch (Throwable e) { if (LogDebug.LOG) { e.printStackTrace(); } } return false; } /** * Art虚拟机,引入AOT编译后,读取oat目录下当前正在使用的目录 * TODO 目前仅支持arm * * @return */ public static String getArtOatCpuType() { return VMRuntimeCompat.is64Bit() ? ARM64 : ARM; } } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/packages/PluginRunningList.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.packages; import android.os.Parcel; import android.os.Parcelable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * 所有正在运行的插件列表 * * @author RePlugin Team */ public class PluginRunningList implements Parcelable, Iterable, Cloneable { private final ArrayList mList; String mProcessName; int mPid = Integer.MIN_VALUE; PluginRunningList() { mList = new ArrayList<>(); } PluginRunningList(PluginRunningList list) { // 复制一份,而不是复用以前的,避免外界影响到这里 mProcessName = list.mProcessName; mPid = list.mPid; mList = new ArrayList<>(list.getList()); } void setProcessInfo(String processName, int pid) { mProcessName = processName; mPid = pid; } void add(String s) { if (!isRunning(s)) { mList.add(s); } } boolean isRunning(String pluginName) { return mList.contains(pluginName); } boolean hasRunning() { return !mList.isEmpty(); } // 获取List内部对象。使用此方法时务必小心,确保外界不会影响这里后才能使用 List getList() { return mList; } @Override public Iterator iterator() { return mList.iterator(); } @Override public String toString() { StringBuilder b = new StringBuilder(); b.append("PRunningL{ "); // 进程名+PID if (mPid == Integer.MIN_VALUE) { b.append(""); } else { b.append('<'); b.append(mProcessName); b.append(':'); b.append(mPid); b.append("> "); } // 进程运行列表 b.append(mList); b.append(" }"); return b.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PluginRunningList strings = (PluginRunningList) o; if (mPid != strings.mPid) return false; if (!mList.equals(strings.mList)) return false; return mProcessName != null ? mProcessName.equals(strings.mProcessName) : strings.mProcessName == null; } @Override public int hashCode() { int result = mList.hashCode(); result = 31 * result + (mProcessName != null ? mProcessName.hashCode() : 0); result = 31 * result + mPid; return result; } @Override protected Object clone() throws CloneNotSupportedException { return new PluginRunningList(this); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mProcessName); dest.writeInt(mPid); dest.writeSerializable(mList); } public static final Creator CREATOR = new Creator() { public PluginRunningList createFromParcel(Parcel in) { return new PluginRunningList(in); } public PluginRunningList[] newArray(int size) { return new PluginRunningList[size]; } }; private PluginRunningList(Parcel in) { mProcessName = in.readString(); mPid = in.readInt(); mList = (ArrayList) in.readSerializable(); } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/utils/ParcelUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils; import android.os.Parcel; import android.os.Parcelable; import java.lang.reflect.Field; /** * 和解析Parcel有关的工具类 * * @author RePlugin Team */ public class ParcelUtils { /** * 根据Parcelable对象,构造出适合在任何地方使用的Parcel对象 * 该方法可杜绝ClassCastException异常 *

* 注意: * 用完必须调用p.recycle方法 *

* Before: *

\
     * // ERROR: Might be "ClassCastException" Error
     * XXX x = intent.getParcelableExtra("XXX");
     * 
*

* After: *

     * Parcelable pa = intent.getParcelableExtra("XXX");
     * Parcel p = ParcelUtils.createFromParcelable(pa);
     * 

* // Create a new XXX object to avoid "ClassCastException" * XXX x = new XXX(p); *

*

* 原因: * 即使包名、类名完全相同,若ClassLoader对象不同,则仍会抛出类型转换异常 * 因此需要将其“重新”生成一份,等于用不同的ClassLoader生成两个对象,自然避免该问题 *

* 常见于BroadcastReceiver中的Bundle,系统在判断源进程和目标进程一致时,会“透传”Bundle过来, * 故就算设置了setClassLoader,也不会做unparcel,自然也就会导致ClassCastException了 * * @param pa 要构造的Parcelable对象 * @return 可被构造函数使用的Parcel对象 */ public static Parcel createFromParcelable(Parcelable pa) { if (pa == null) { return null; } Parcel p = Parcel.obtain(); pa.writeToParcel(p, 0); p.setDataPosition(0); return p; } /** * 调用另一个ClassLoader中的实现了Parcelable接口类的Parcelable$Creator成员 * 进行远程Object的创建 * * @param pa * @param loader * @return */ public static Object createFromParcelable(Parcelable pa, ClassLoader loader, String cln) { try { Field f = loader.loadClass(cln).getField("CREATOR"); // 以防万一 f.setAccessible(true); Parcelable.Creator creator = (Parcelable.Creator) f.get(null); return creator.createFromParcel(createFromParcelable(pa)); } catch (ClassNotFoundException e) { } catch (NoSuchFieldException e) { } catch (IllegalAccessException e) { } return null; } } ================================================ FILE: replugin-plugin-library/replugin-plugin-lib/src/main/java/com/qihoo360/replugin/utils/ReflectUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.utils; import android.content.Context; import com.qihoo360.replugin.helper.LogRelease; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static com.qihoo360.replugin.helper.LogDebug.MISC_TAG; import static com.qihoo360.replugin.helper.LogRelease.LOGR; /** * @author RePlugin Team */ public final class ReflectUtils { public static final void setFieldNonE(Class c, Object object, String fName, Object value) { try { setField(c, object, fName, value); } catch (Throwable e) { e.printStackTrace(); } } public static final void setField(Class c, Object object, String fName, Object value) throws NoSuchFieldException, IllegalAccessException { Field f = c.getDeclaredField(fName); boolean acc = f.isAccessible(); if (!acc) { f.setAccessible(true); } f.set(object, value); if (!acc) { f.setAccessible(acc); } } public static final void dumpObject(Object object, FileDescriptor fd, PrintWriter writer, String[] args) { try { Class c = object.getClass(); do { writer.println("c=" + c.getName()); Field fields[] = c.getDeclaredFields(); for (Field f : fields) { boolean acc = f.isAccessible(); if (!acc) { f.setAccessible(true); } Object o = f.get(object); writer.print(f.getName()); writer.print("="); if (o != null) { writer.println(o.toString()); } else { writer.println("null"); } if (!acc) { f.setAccessible(acc); } } c = c.getSuperclass(); } while (c != null && !c.equals(Object.class) && !c.equals(Context.class)); } catch (Throwable e) { if (LOGR) { LogRelease.e(MISC_TAG, e.getMessage(), e); } } } public static Object invokeMethod(ClassLoader loader, String clzName, String methodName, Object methodReceiver, Class[] methodParamTypes, Object... methodParamValues) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { if (methodReceiver == null) { return null; } return invokeMethod(getMethod(loader, clzName, methodName, methodParamTypes), methodReceiver, methodParamValues); } public static Method getMethod(ClassLoader loader, String clzName, String methodName, Class[] methodParamTypes) throws ClassNotFoundException, NoSuchMethodException { Class clz = Class.forName(clzName, false, loader); if (clz != null) { return clz.getDeclaredMethod(methodName, methodParamTypes); } return null; } public static Object invokeMethod(Method method, Object methodReceiver, Object... methodParamValues) throws InvocationTargetException, IllegalAccessException { if (method != null) { boolean acc = method.isAccessible(); if (!acc) { method.setAccessible(true); } Object ret = method.invoke(methodReceiver, methodParamValues); if (!acc) { method.setAccessible(false); } return ret; } return null; } } ================================================ FILE: replugin-plugin-library/settings.gradle ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() mavenLocal() maven { allowInsecureProtocol = true url "http://maven.geelib.360.cn/nexus/repository/replugin/"} } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() mavenLocal() maven { allowInsecureProtocol = true url "http://maven.geelib.360.cn/nexus/repository/replugin/"} } } rootProject.name = "replugin-plugin-library" include ':replugin-plugin-lib' ================================================ FILE: replugin-sample/README.md ================================================ # RePlugin Sample RePlugin Sample工程主要为您展现RePlugin插件的主要用法。 它并非是“纯粹”的工程,其内部包含了大量可供参考的调用代码,并含有一些测试代码,帮助开发者在接入时能够对RePlugin有所了解。 包括下列内容: * host → 主程序的Sample * plugin-demo1 → 插件1Sample,大部分逻辑都在其中(内置插件实例)   * plugin-demo2 → 插件2的Sample,主要是配合插件1而做的工作(内置插件实例) * plugin-demo3-kotlin → 插件3的Sample,kotlin插件实例(外置插件实例) 阅读Sample代码需要和RePlugin的用法相结合。请访问我们的WiKi,以了解更多的内容。 ================================================ FILE: replugin-sample/host/app/build.gradle ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ plugins { id 'com.android.application' id 'replugin-host-gradle' } android { namespace 'com.qihoo360.replugin.sample.host' compileSdk 34 defaultConfig { applicationId "com.qihoo360.replugin.sample.host" minSdkVersion 21 targetSdkVersion 34 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } compileOptions{ sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } repluginHostConfig { useAppCompat = true useAndroidX = true // 可以在这里自定义常驻进程的名字 // persistentName = ":XXXXService" } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' implementation "com.qihoo360.replugin:replugin-host-lib:${RP_VERSION}" } ================================================ FILE: replugin-sample/host/app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in C:\Users\***\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # 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: replugin-sample/host/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: replugin-sample/host/app/src/main/assets/external/README ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ 外置插件的模拟安装说明: 1.为演示安装外置插件的过程,此处临时将外置插件放在Host的assets/external/目录下,其实开发者完全可以将待安装的外置插件放到/sdcard、/data等其他目录下(注意存储权限); 2.其中拷贝assets/external/目录下的××.apk文件到/data/data/packageName/files/目录下的过程类似于外置插件在首次安装时的下载过程; 3.其中demo3.apk为replugin-sample/plugin/plugin-demo3-kotlin工程所编译生成的安装包文件; ================================================ FILE: replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/FileProvider.java ================================================ package com.qihoo360.replugin.sample.host; /* * Copyright (C) 2013 The Android Open Source Project * * 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 * * http://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. */ import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.res.XmlResourceParser; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.OpenableColumns; import android.text.TextUtils; import android.webkit.MimeTypeMap; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.Map; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; /** * FileProvider is a special subclass of {@link ContentProvider} that facilitates secure sharing * of files associated with an app by creating a content:// {@link Uri} for a file * instead of a file:/// {@link Uri}. *

* A content URI allows you to grant read and write access using * temporary access permissions. When you create an {@link Intent} containing * a content URI, in order to send the content URI * to a client app, you can also call {@link Intent#setFlags(int) Intent.setFlags()} to add * permissions. These permissions are available to the client app for as long as the stack for * a receiving {@link android.app.Activity} is active. For an {@link Intent} going to a * {@link android.app.Service}, the permissions are available as long as the * {@link android.app.Service} is running. *

* In comparison, to control access to a file:/// {@link Uri} you have to modify the * file system permissions of the underlying file. The permissions you provide become available to * any app, and remain in effect until you change them. This level of access is * fundamentally insecure. *

* The increased level of file access security offered by a content URI * makes FileProvider a key part of Android's security infrastructure. *

* This overview of FileProvider includes the following topics: *

*
    *
  1. Defining a FileProvider
  2. *
  3. Specifying Available Files
  4. *
  5. Retrieving the Content URI for a File
  6. *
  7. Granting Temporary Permissions to a URI
  8. *
  9. Serving a Content URI to Another App
  10. *
*

Defining a FileProvider

*

* Since the default functionality of FileProvider includes content URI generation for files, you * don't need to define a subclass in code. Instead, you can include a FileProvider in your app * by specifying it entirely in XML. To specify the FileProvider component itself, add a * <provider> * element to your app manifest. Set the android:name attribute to * android.support.v4.content.FileProvider. Set the android:authorities * attribute to a URI authority based on a domain you control; for example, if you control the * domain mydomain.com you should use the authority * com.mydomain.fileprovider. Set the android:exported attribute to * false; the FileProvider does not need to be public. Set the * android:grantUriPermissions attribute to true, to allow you * to grant temporary access to files. For example: *

 * <manifest>
 * ...
 * <application>
 * ...
 * <provider
 * android:name="android.support.v4.content.FileProvider"
 * android:authorities="com.mydomain.fileprovider"
 * android:exported="false"
 * android:grantUriPermissions="true">
 * ...
 * </provider>
 * ...
 * </application>
 * </manifest>
*

* If you want to override any of the default behavior of FileProvider methods, extend * the FileProvider class and use the fully-qualified class name in the android:name * attribute of the <provider> element. *

Specifying Available Files

* A FileProvider can only generate a content URI for files in directories that you specify * beforehand. To specify a directory, specify the its storage area and path in XML, using child * elements of the <paths> element. * For example, the following paths element tells FileProvider that you intend to * request content URIs for the images/ subdirectory of your private file area. *
 * <paths xmlns:android="http://schemas.android.com/apk/res/android">
 * <files-path name="my_images" path="images/"/>
 * ...
 * </paths>
 * 
*

* The <paths> element must contain one or more of the following child elements: *

*
*
*
 * <files-path name="name" path="path" />
 * 
*
*
* Represents files in the files/ subdirectory of your app's internal storage * area. This subdirectory is the same as the value returned by {@link Context#getFilesDir() * Context.getFilesDir()}. *
*
 * <external-path name="name" path="path" />
 * 
*
*
* Represents files in the root of your app's external storage area. The path * {@link Context#getExternalFilesDir(String) Context.getExternalFilesDir()} returns the * files/ subdirectory of this this root. *
*
*
 * <cache-path name="name" path="path" />
 * 
*
*
* Represents files in the cache subdirectory of your app's internal storage area. The root path * of this subdirectory is the same as the value returned by {@link Context#getCacheDir() * getCacheDir()}. *
*
*

* These child elements all use the same attributes: *

*
*
* name="name" *
*
* A URI path segment. To enforce security, this value hides the name of the subdirectory * you're sharing. The subdirectory name for this value is contained in the * path attribute. *
*
* path="path" *
*
* The subdirectory you're sharing. While the name attribute is a URI path * segment, the path value is an actual subdirectory name. Notice that the * value refers to a subdirectory, not an individual file or files. You can't * share a single file by its file name, nor can you specify a subset of files using * wildcards. *
*
*

* You must specify a child element of <paths> for each directory that contains * files for which you want content URIs. For example, these XML elements specify two directories: *

 * <paths xmlns:android="http://schemas.android.com/apk/res/android">
 * <files-path name="my_images" path="images/"/>
 * <files-path name="my_docs" path="docs/"/>
 * </paths>
 * 
*

* Put the <paths> element and its children in an XML file in your project. * For example, you can add them to a new file called res/xml/file_paths.xml. * To link this file to the FileProvider, add a * <meta-data> element * as a child of the <provider> element that defines the FileProvider. Set the * <meta-data> element's "android:name" attribute to * android.support.FILE_PROVIDER_PATHS. Set the element's "android:resource" attribute * to @xml/file_paths (notice that you don't specify the .xml * extension). For example: *

 * <provider
 * android:name="android.support.v4.content.FileProvider"
 * android:authorities="com.mydomain.fileprovider"
 * android:exported="false"
 * android:grantUriPermissions="true">
 * <meta-data
 * android:name="android.support.FILE_PROVIDER_PATHS"
 * android:resource="@xml/file_paths" />
 * </provider>
 * 
*

Generating the Content URI for a File

*

* To share a file with another app using a content URI, your app has to generate the content URI. * To generate the content URI, create a new {@link File} for the file, then pass the {@link File} * to {@link #getUriForFile(Context, String, File) getUriForFile()}. You can send the content URI * returned by {@link #getUriForFile(Context, String, File) getUriForFile()} to another app in an * {@link Intent}. The client app that receives the content URI can open the file * and access its contents by calling * {@link android.content.ContentResolver#openFileDescriptor(Uri, String) * ContentResolver.openFileDescriptor} to get a {@link ParcelFileDescriptor}. *

* For example, suppose your app is offering files to other apps with a FileProvider that has the * authority com.mydomain.fileprovider. To get a content URI for the file * default_image.jpg in the images/ subdirectory of your internal storage * add the following code: *

 * File imagePath = new File(Context.getFilesDir(), "images");
 * File newFile = new File(imagePath, "default_image.jpg");
 * Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
 * 
* As a result of the previous snippet, * {@link #getUriForFile(Context, String, File) getUriForFile()} returns the content URI * content://com.mydomain.fileprovider/my_images/default_image.jpg. *

Granting Temporary Permissions to a URI

* To grant an access permission to a content URI returned from * {@link #getUriForFile(Context, String, File) getUriForFile()}, do one of the following: *
    *
  • * Call the method * {@link Context#grantUriPermission(String, Uri, int) * Context.grantUriPermission(package, Uri, mode_flags)} for the content:// * {@link Uri}, using the desired mode flags. This grants temporary access permission for the * content URI to the specified package, according to the value of the * the mode_flags parameter, which you can set to * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION} * or both. The permission remains in effect until you revoke it by calling * {@link Context#revokeUriPermission(Uri, int) revokeUriPermission()} or until the device * reboots. *
  • *
  • * Put the content URI in an {@link Intent} by calling {@link Intent#setData(Uri) setData()}. *
  • *
  • * Next, call the method {@link Intent#setFlags(int) Intent.setFlags()} with either * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} or * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION} or both. *
  • *
  • * Finally, send the {@link Intent} to * another app. Most often, you do this by calling * {@link android.app.Activity#setResult(int, Intent) setResult()}. *

    * Permissions granted in an {@link Intent} remain in effect while the stack of the receiving * {@link android.app.Activity} is active. When the stack finishes, the permissions are * automatically removed. Permissions granted to one {@link android.app.Activity} in a client * app are automatically extended to other components of that app. *

    *
  • *
*

Serving a Content URI to Another App

*

* There are a variety of ways to serve the content URI for a file to a client app. One common way * is for the client app to start your app by calling * {@link android.app.Activity#startActivityForResult(Intent, int, Bundle) startActivityResult()}, * which sends an {@link Intent} to your app to start an {@link android.app.Activity} in your app. * In response, your app can immediately return a content URI to the client app or present a user * interface that allows the user to pick a file. In the latter case, once the user picks the file * your app can return its content URI. In both cases, your app returns the content URI in an * {@link Intent} sent via {@link android.app.Activity#setResult(int, Intent) setResult()}. *

*

* You can also put the content URI in a {@link android.content.ClipData} object and then add the * object to an {@link Intent} you send to a client app. To do this, call * {@link Intent#setClipData(ClipData) Intent.setClipData()}. When you use this approach, you can * add multiple {@link android.content.ClipData} objects to the {@link Intent}, each with its own * content URI. When you call {@link Intent#setFlags(int) Intent.setFlags()} on the {@link Intent} * to set temporary access permissions, the same permissions are applied to all of the content * URIs. *

*

* Note: The {@link Intent#setClipData(ClipData) Intent.setClipData()} method is * only available in platform version 16 (Android 4.1) and later. If you want to maintain * compatibility with previous versions, you should send one content URI at a time in the * {@link Intent}. Set the action to {@link Intent#ACTION_SEND} and put the URI in data by calling * {@link Intent#setData setData()}. *

*

More Information

*

* To learn more about FileProvider, see the Android training class * Sharing Files Securely with URIs. *

*/ public class FileProvider extends ContentProvider { private static final String[] COLUMNS = { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}; private static final String META_DATA_FILE_PROVIDER_PATHS = "android.support.FILE_PROVIDER_PATHS"; private static final String TAG_ROOT_PATH = "root-path"; private static final String TAG_FILES_PATH = "files-path"; private static final String TAG_CACHE_PATH = "cache-path"; private static final String TAG_EXTERNAL = "external-path"; private static final String ATTR_NAME = "name"; private static final String ATTR_PATH = "path"; private static final File DEVICE_ROOT = new File("/"); // @GuardedBy("sCache") private static HashMap sCache = new HashMap(); private PathStrategy mStrategy; /** * The default FileProvider implementation does not need to be initialized. If you want to * override this method, you must provide your own subclass of FileProvider. */ @Override public boolean onCreate() { return true; } /** * After the FileProvider is instantiated, this method is called to provide the system with * information about the provider. * * @param context A {@link Context} for the current component. * @param info A {@link ProviderInfo} for the new provider. */ @Override public void attachInfo(Context context, ProviderInfo info) { super.attachInfo(context, info); // Sanity check our security if (info.exported) { throw new SecurityException("Provider must not be exported"); } if (!info.grantUriPermissions) { throw new SecurityException("Provider must grant uri permissions"); } mStrategy = getPathStrategy(context, info.authority); } /** * Return a content URI for a given {@link File}. Specific temporary * permissions for the content URI can be set with * {@link Context#grantUriPermission(String, Uri, int)}, or added * to an {@link Intent} by calling {@link Intent#setData(Uri) setData()} and then * {@link Intent#setFlags(int) setFlags()}; in both cases, the applicable flags are * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. A FileProvider can only return a * content {@link Uri} for file paths defined in their <paths> * meta-data element. See the Class Overview for more information. * * @param context A {@link Context} for the current component. * @param authority The authority of a {@link FileProvider} defined in a * {@code <provider>} element in your app's manifest. * @param file A {@link File} pointing to the filename for which you want a * content {@link Uri}. * @return A content URI for the file. * @throws IllegalArgumentException When the given {@link File} is outside * the paths supported by the provider. */ public static Uri getUriForFile(Context context, String authority, File file) { final PathStrategy strategy = getPathStrategy(context, authority); return strategy.getUriForFile(file); } /** * Use a content URI returned by * {@link #getUriForFile(Context, String, File) getUriForFile()} to get information about a file * managed by the FileProvider. * FileProvider reports the column names defined in {@link OpenableColumns}: *
    *
  • {@link OpenableColumns#DISPLAY_NAME}
  • *
  • {@link OpenableColumns#SIZE}
  • *
* For more information, see * {@link ContentProvider#query(Uri, String[], String, String[], String) * ContentProvider.query()}. * * @param uri A content URI returned by {@link #getUriForFile}. * @param projection The list of columns to put into the {@link Cursor}. If null all columns are * included. * @param selection Selection criteria to apply. If null then all data that matches the content * URI is returned. * @param selectionArgs An array of {@link String}, containing arguments to bind to * the selection parameter. The query method scans selection from left to * right and iterates through selectionArgs, replacing the current "?" character in * selection with the value at the current position in selectionArgs. The * values are bound to selection as {@link String} values. * @param sortOrder A {@link String} containing the column name(s) on which to sort * the resulting {@link Cursor}. * @return A {@link Cursor} containing the results of the query. */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // ContentProvider has already checked granted permissions final File file = mStrategy.getFileForUri(uri); if (projection == null) { projection = COLUMNS; } String[] cols = new String[projection.length]; Object[] values = new Object[projection.length]; int i = 0; for (String col : projection) { if (OpenableColumns.DISPLAY_NAME.equals(col)) { cols[i] = OpenableColumns.DISPLAY_NAME; values[i++] = file.getName(); } else if (OpenableColumns.SIZE.equals(col)) { cols[i] = OpenableColumns.SIZE; values[i++] = file.length(); } } cols = copyOf(cols, i); values = copyOf(values, i); final MatrixCursor cursor = new MatrixCursor(cols, 1); cursor.addRow(values); return cursor; } /** * Returns the MIME type of a content URI returned by * {@link #getUriForFile(Context, String, File) getUriForFile()}. * * @param uri A content URI returned by * {@link #getUriForFile(Context, String, File) getUriForFile()}. * @return If the associated file has an extension, the MIME type associated with that * extension; otherwise application/octet-stream. */ @Override public String getType(Uri uri) { // ContentProvider has already checked granted permissions final File file = mStrategy.getFileForUri(uri); final int lastDot = file.getName().lastIndexOf('.'); if (lastDot >= 0) { final String extension = file.getName().substring(lastDot + 1); final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); if (mime != null) { return mime; } } return "application/octet-stream"; } /** * By default, this method throws an {@link UnsupportedOperationException}. You must * subclass FileProvider if you want to provide different functionality. */ @Override public Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException("No external inserts"); } /** * By default, this method throws an {@link UnsupportedOperationException}. You must * subclass FileProvider if you want to provide different functionality. */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("No external updates"); } /** * Deletes the file associated with the specified content URI, as * returned by {@link #getUriForFile(Context, String, File) getUriForFile()}. Notice that this * method does not throw an {@link IOException}; you must check its return value. * * @param uri A content URI for a file, as returned by * {@link #getUriForFile(Context, String, File) getUriForFile()}. * @param selection Ignored. Set to {@code null}. * @param selectionArgs Ignored. Set to {@code null}. * @return 1 if the delete succeeds; otherwise, 0. */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // ContentProvider has already checked granted permissions final File file = mStrategy.getFileForUri(uri); return file.delete() ? 1 : 0; } /** * By default, FileProvider automatically returns the * {@link ParcelFileDescriptor} for a file associated with a content:// * {@link Uri}. To get the {@link ParcelFileDescriptor}, call * {@link android.content.ContentResolver#openFileDescriptor(Uri, String) * ContentResolver.openFileDescriptor}. *

* To override this method, you must provide your own subclass of FileProvider. * * @param uri A content URI associated with a file, as returned by * {@link #getUriForFile(Context, String, File) getUriForFile()}. * @param mode Access mode for the file. May be "r" for read-only access, "rw" for read and * write access, or "rwt" for read and write access that truncates any existing file. * @return A new {@link ParcelFileDescriptor} with which you can access the file. */ @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { // ContentProvider has already checked granted permissions final File file = mStrategy.getFileForUri(uri); final int fileMode = modeToMode(mode); return ParcelFileDescriptor.open(file, fileMode); } /** * Return {@link FileProvider.PathStrategy} for given authority, either by parsing or * returning from cache. */ private static PathStrategy getPathStrategy(Context context, String authority) { PathStrategy strat; synchronized (sCache) { strat = sCache.get(authority); if (strat == null) { try { strat = parsePathStrategy(context, authority); } catch (IOException e) { throw new IllegalArgumentException( "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e); } catch (XmlPullParserException e) { throw new IllegalArgumentException( "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e); } sCache.put(authority, strat); } } return strat; } /** * Parse and return {@link PathStrategy} for given authority as defined in * {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}. * * @see #getPathStrategy(Context, String) */ private static PathStrategy parsePathStrategy(Context context, String authority) throws IOException, XmlPullParserException { final SimplePathStrategy strat = new SimplePathStrategy(authority); final ProviderInfo info = context.getPackageManager() .resolveContentProvider(authority, PackageManager.GET_META_DATA); final XmlResourceParser in = info.loadXmlMetaData( context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS); if (in == null) { throw new IllegalArgumentException( "Missing " + META_DATA_FILE_PROVIDER_PATHS + " meta-data"); } int type; while ((type = in.next()) != END_DOCUMENT) { if (type == START_TAG) { final String tag = in.getName(); final String name = in.getAttributeValue(null, ATTR_NAME); String path = in.getAttributeValue(null, ATTR_PATH); File target = null; if (TAG_ROOT_PATH.equals(tag)) { target = buildPath(DEVICE_ROOT, path); } else if (TAG_FILES_PATH.equals(tag)) { target = buildPath(context.getFilesDir(), path); } else if (TAG_CACHE_PATH.equals(tag)) { target = buildPath(context.getCacheDir(), path); } else if (TAG_EXTERNAL.equals(tag)) { target = buildPath(Environment.getExternalStorageDirectory(), path); } if (target != null) { strat.addRoot(name, target); } } } return strat; } /** * Strategy for mapping between {@link File} and {@link Uri}. *

* Strategies must be symmetric so that mapping a {@link File} to a * {@link Uri} and then back to a {@link File} points at the original * target. *

* Strategies must remain consistent across app launches, and not rely on * dynamic state. This ensures that any generated {@link Uri} can still be * resolved if your process is killed and later restarted. * * @see FileProvider.SimplePathStrategy */ interface PathStrategy { /** * Return a {@link Uri} that represents the given {@link File}. */ public Uri getUriForFile(File file); /** * Return a {@link File} that represents the given {@link Uri}. */ public File getFileForUri(Uri uri); } /** * Strategy that provides access to files living under a narrow whitelist of * filesystem roots. It will throw {@link SecurityException} if callers try * accessing files outside the configured roots. *

* For example, if configured with * {@code addRoot("myfiles", context.getFilesDir())}, then * {@code context.getFileStreamPath("foo.txt")} would map to * {@code content://myauthority/myfiles/foo.txt}. */ static class SimplePathStrategy implements PathStrategy { private final String mAuthority; private final HashMap mRoots = new HashMap(); public SimplePathStrategy(String authority) { mAuthority = authority; } /** * Add a mapping from a name to a filesystem root. The provider only offers * access to files that live under configured roots. */ public void addRoot(String name, File root) { if (TextUtils.isEmpty(name)) { throw new IllegalArgumentException("Name must not be empty"); } try { // Resolve to canonical path to keep path checking fast root = root.getCanonicalFile(); } catch (IOException e) { throw new IllegalArgumentException( "Failed to resolve canonical path for " + root, e); } mRoots.put(name, root); } @Override public Uri getUriForFile(File file) { String path; try { path = file.getCanonicalPath(); } catch (IOException e) { throw new IllegalArgumentException("Failed to resolve canonical path for " + file); } // Find the most-specific root path Map.Entry mostSpecific = null; for (Map.Entry root : mRoots.entrySet()) { final String rootPath = root.getValue().getPath(); if (path.startsWith(rootPath) && (mostSpecific == null || rootPath.length() > mostSpecific.getValue().getPath().length())) { mostSpecific = root; } } if (mostSpecific == null) { throw new IllegalArgumentException( "Failed to find configured root that contains " + path); } // Start at first char of path under root final String rootPath = mostSpecific.getValue().getPath(); if (rootPath.endsWith("/")) { path = path.substring(rootPath.length()); } else { path = path.substring(rootPath.length() + 1); } // Encode the tag and path separately path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/"); return new Uri.Builder().scheme("content") .authority(mAuthority).encodedPath(path).build(); } @Override public File getFileForUri(Uri uri) { String path = uri.getEncodedPath(); final int splitIndex = path.indexOf('/', 1); final String tag = Uri.decode(path.substring(1, splitIndex)); path = Uri.decode(path.substring(splitIndex + 1)); final File root = mRoots.get(tag); if (root == null) { throw new IllegalArgumentException("Unable to find configured root for " + uri); } File file = new File(root, path); try { file = file.getCanonicalFile(); } catch (IOException e) { throw new IllegalArgumentException("Failed to resolve canonical path for " + file); } if (!file.getPath().startsWith(root.getPath())) { throw new SecurityException("Resolved path jumped beyond configured root"); } return file; } } /** * Copied from ContentResolver.java */ private static int modeToMode(String mode) { int modeBits; if ("r".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_READ_ONLY; } else if ("w".equals(mode) || "wt".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE; } else if ("wa".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_APPEND; } else if ("rw".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE; } else if ("rwt".equals(mode)) { modeBits = ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE; } else { throw new IllegalArgumentException("Invalid mode: " + mode); } return modeBits; } private static File buildPath(File base, String... segments) { File cur = base; for (String segment : segments) { if (segment != null) { cur = new File(cur, segment); } } return cur; } private static String[] copyOf(String[] original, int newLength) { final String[] result = new String[newLength]; System.arraycopy(original, 0, result, 0, newLength); return result; } private static Object[] copyOf(Object[] original, int newLength) { final Object[] result = new Object[newLength]; System.arraycopy(original, 0, result, 0, newLength); return result; } } ================================================ FILE: replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/MainActivity.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.sample.host; import android.app.Activity; import android.app.ProgressDialog; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.view.View; import android.widget.Toast; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.model.PluginInfo; import com.qihoo360.replugin.utils.FileUtils; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; /** * @author RePlugin Team */ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_start_demo1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 刻意以“包名”来打开 RePlugin.startActivity(MainActivity.this, RePlugin.createIntent("com.qihoo360.replugin.sample.demo1", "com.qihoo360.replugin.sample.demo1.MainActivity")); } }); findViewById(R.id.btn_start_plugin_for_result).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 刻意以“Alias(别名)”来打开 Intent intent = new Intent(); intent.setComponent(new ComponentName("demo1", "com.qihoo360.replugin.sample.demo1.activity.for_result.ForResultActivity")); RePlugin.startActivityForResult(MainActivity.this, intent, REQUEST_CODE_DEMO1, null); } }); findViewById(R.id.btn_load_fragment_from_demo1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(MainActivity.this, PluginFragmentActivity.class)); } }); findViewById(R.id.btn_start_demo3).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 若没有安装,则直接提示“错误” // TODO 将来把回调串联上 if (RePlugin.isPluginInstalled("demo3")) { RePlugin.startActivity(MainActivity.this, RePlugin.createIntent("demo3", "com.qihoo360.replugin.sample.demo3.MainActivity")); } else { Toast.makeText(MainActivity.this, "You must install demo3 first!", Toast.LENGTH_SHORT).show(); } } }); findViewById(R.id.btn_start_demo4).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 示例:直接通过宿主打开WebView插件中的Activity // FIXME: 后续可以将webview MainActivity URL 改为动态传入 // 若没有安装,则直接提示“错误” if (RePlugin.isPluginInstalled("webview")) { RePlugin.startActivity(MainActivity.this, RePlugin.createIntent("webview", "com.qihoo360.replugin.sample.webview.MainActivity")); } else { Toast.makeText(MainActivity.this, "You must install webview first!", Toast.LENGTH_SHORT).show(); } } }); findViewById(R.id.btn_install_apk_from_assets).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final ProgressDialog pd = ProgressDialog.show(MainActivity.this, "Installing...", "Please wait...", true, true); // FIXME: 仅用于安装流程演示 2017/7/24 new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { simulateInstallExternalPlugin(); pd.dismiss(); } }, 1000); } }); // 刻意使用Thread的ClassLoader来测试效果 testThreadClassLoader(); } private void testThreadClassLoader() { // 在2.1.7及以前版本,如果直接调用此方法,则拿到的ClassLoader可能是PathClassLoader或者为空。有极个别Java库会用到此方法 // 这里务必确保:cl == getClassLoader(),才符合预期 ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl != getClassLoader()) { throw new RuntimeException("Thread.current.classLoader != getClassLoader(). cl=" + cl + "; getC=" + getClassLoader()); } } private static final int REQUEST_CODE_DEMO1 = 0x011; private static final int RESULT_CODE_DEMO1 = 0x012; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_DEMO1 && resultCode == RESULT_CODE_DEMO1) { Toast.makeText(this, data.getStringExtra("data"), Toast.LENGTH_SHORT).show(); } } /** * 模拟安装或升级(覆盖安装)外置插件 * 注意:为方便演示,外置插件临时放置到Host的assets/external目录下,具体说明见README

*/ private void simulateInstallExternalPlugin() { String demo3Apk= "demo3.apk"; String demo3apkPath = "external" + File.separator + demo3Apk; // 文件是否已经存在?直接删除重来 String pluginFilePath = getFilesDir().getAbsolutePath() + File.separator + demo3Apk; File pluginFile = new File(pluginFilePath); if (pluginFile.exists()) { FileUtils.deleteQuietly(pluginFile); } // 开始复制 copyAssetsFileToAppFiles(demo3apkPath, demo3Apk); PluginInfo info = null; if (pluginFile.exists()) { info = RePlugin.install(pluginFilePath); } if (info != null) { RePlugin.startActivity(MainActivity.this, RePlugin.createIntent(info.getName(), "com.qihoo360.replugin.sample.demo3.MainActivity")); } else { Toast.makeText(MainActivity.this, "install external plugin failed", Toast.LENGTH_SHORT).show(); } } /** * 从assets目录中复制某文件内容 * @param assetFileName assets目录下的Apk源文件路径 * @param newFileName 复制到/data/data/package_name/files/目录下文件名 */ private void copyAssetsFileToAppFiles(String assetFileName, String newFileName) { InputStream is = null; FileOutputStream fos = null; int buffsize = 1024; try { is = this.getAssets().open(assetFileName); fos = this.openFileOutput(newFileName, Context.MODE_PRIVATE); int byteCount = 0; byte[] buffer = new byte[buffsize]; while((byteCount = is.read(buffer)) != -1) { fos.write(buffer, 0, byteCount); } fos.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { is.close(); fos.close(); } catch (Exception e) { e.printStackTrace(); } } } } ================================================ FILE: replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/PluginFragmentActivity.java ================================================ package com.qihoo360.replugin.sample.host; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import com.qihoo360.replugin.RePlugin; /** * 打开插件中的Fragment *

* 作者 coder * 创建时间 2017/7/6 */ public class PluginFragmentActivity extends FragmentActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); /** * 注意: * * 如果一个插件是内置插件,那么这个插件的名字就是文件的前缀,比如:demo1.jar插件的名字就是demo1(host-gradle插件自动生成),可以执行诸如RePlugin.fetchClassLoader("demo1")的操作; * 如果一个插件是外置插件,通过RePlugin.install("/sdcard/demo1.apk")安装的,则必须动态获取这个插件的名字来使用: * PluginInfo pluginInfo = RePlugin.install("/sdcard/demo1.apk"); * RePlugin.preload(pluginInfo);//耗时 * String name = pluginInfo != null ? pluginInfo.getName() : null; * ClassLoader classLoader = RePlugin.fetchClassLoader(name); */ boolean isBuiltIn = true; String pluginName = isBuiltIn ? "demo1" : "com.qihoo360.replugin.sample.demo1"; //注册相关Fragment的类 //注册一个全局Hook用于拦截系统对XX类的寻找定向到Demo1中的XX类主要是用于在xml中可以直接使用插件中的类 RePlugin.registerHookingClass("com.qihoo360.replugin.sample.demo1.fragment.DemoFragment", RePlugin.createComponentName(pluginName, "com.qihoo360.replugin.sample.demo1.fragment.DemoFragment"), null); setContentView(R.layout.activity_plugin_fragment); //代码使用插件Fragment ClassLoader d1ClassLoader = RePlugin.fetchClassLoader(pluginName);//获取插件的ClassLoader try { Fragment fragment = d1ClassLoader.loadClass("com.qihoo360.replugin.sample.demo1.fragment.DemoCodeFragment").asSubclass(Fragment.class).newInstance();//使用插件的Classloader获取指定Fragment实例 getSupportFragmentManager().beginTransaction().add(R.id.container2, fragment).commit();//添加Fragment到UI } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } ================================================ FILE: replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/SampleApplication.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.sample.host; import android.content.Context; import android.content.Intent; import android.util.Log; import com.qihoo360.replugin.RePlugin; import com.qihoo360.replugin.RePluginApplication; import com.qihoo360.replugin.RePluginCallbacks; import com.qihoo360.replugin.RePluginConfig; import com.qihoo360.replugin.RePluginEventCallbacks; /** * @author RePlugin Team */ public class SampleApplication extends RePluginApplication { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // FIXME 允许接收rpRunPlugin等Gradle Task,发布时请务必关掉,以免出现问题 RePlugin.enableDebugger(base, BuildConfig.DEBUG); } // ---------- // 自定义行为 // ---------- /** * RePlugin允许提供各种“自定义”的行为,让您“无需修改源代码”,即可实现相应的功能 */ @Override protected RePluginConfig createConfig() { RePluginConfig c = new RePluginConfig(); // 允许“插件使用宿主类”。默认为“关闭” c.setUseHostClassIfNotFound(true); // FIXME RePlugin默认会对安装的外置插件进行签名校验,这里先关掉,避免调试时出现签名错误 c.setVerifySign(!BuildConfig.DEBUG); // 针对“安装失败”等情况来做进一步的事件处理 c.setEventCallbacks(new HostEventCallbacks(this)); // FIXME 若宿主为Release,则此处应加上您认为"合法"的插件的签名,例如,可以写上"宿主"自己的。 // RePlugin.addCertSignature("AAAAAAAAA"); // 在Art上,优化第一次loadDex的速度 // c.setOptimizeArtLoadDex(true); return c; } @Override protected RePluginCallbacks createCallbacks() { return new HostCallbacks(this); } /** * 宿主针对RePlugin的自定义行为 */ private class HostCallbacks extends RePluginCallbacks { private static final String TAG = "HostCallbacks"; private HostCallbacks(Context context) { super(context); } @Override public boolean onPluginNotExistsForActivity(Context context, String plugin, Intent intent, int process) { // FIXME 当插件"没有安装"时触发此逻辑,可打开您的"下载对话框"并开始下载。 // FIXME 其中"intent"需传递到"对话框"内,这样可在下载完成后,打开这个插件的Activity if (BuildConfig.DEBUG) { Log.d(TAG, "onPluginNotExistsForActivity: Start download... p=" + plugin + "; i=" + intent); } return super.onPluginNotExistsForActivity(context, plugin, intent, process); } /* @Override public PluginDexClassLoader createPluginClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { String odexName = pi.makeInstalledFileName() + ".dex"; if (RePlugin.getConfig().isOptimizeArtLoadDex()) { Dex2OatUtils.injectLoadDex(dexPath, optimizedDirectory, odexName); } long being = System.currentTimeMillis(); PluginDexClassLoader pluginDexClassLoader = super.createPluginClassLoader(pi, dexPath, optimizedDirectory, librarySearchPath, parent); if (BuildConfig.DEBUG) { Log.d(Dex2OatUtils.TAG, "createPluginClassLoader use:" + (System.currentTimeMillis() - being)); String odexAbsolutePath = (optimizedDirectory + File.separator + odexName); Log.d(Dex2OatUtils.TAG, "createPluginClassLoader odexSize:" + InterpretDex2OatHelper.getOdexSize(odexAbsolutePath)); } return pluginDexClassLoader; } */ } private class HostEventCallbacks extends RePluginEventCallbacks { private static final String TAG = "HostEventCallbacks"; public HostEventCallbacks(Context context) { super(context); } @Override public void onInstallPluginFailed(String path, InstallResult code) { // FIXME 当插件安装失败时触发此逻辑。您可以在此处做“打点统计”,也可以针对安装失败情况做“特殊处理” // 大部分可以通过RePlugin.install的返回值来判断是否成功 if (BuildConfig.DEBUG) { Log.d(TAG, "onInstallPluginFailed: Failed! path=" + path + "; r=" + code); } super.onInstallPluginFailed(path, code); } @Override public void onStartActivityCompleted(String plugin, String activity, boolean result) { // FIXME 当打开Activity成功时触发此逻辑,可在这里做一些APM、打点统计等相关工作 super.onStartActivityCompleted(plugin, activity, result); } } } ================================================ FILE: replugin-sample/host/app/src/main/java/com/qihoo360/replugin/sample/host/TimeUtils.java ================================================ /* * Copyright (C) 2005-2017 Qihoo 360 Inc. * * 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 * * http://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. */ package com.qihoo360.replugin.sample.host; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; /** * @author RePlugin Team */ public class TimeUtils { private static final DateFormat DEFAULT_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); /** * 将时间戳转为时间字符串 * 格式为yyyy-MM-dd HH:mm:ss

* * @param millis 毫秒时间戳 * @return 时间字符串 */ public static String millis2String(final long millis) { return millis2String(millis, DEFAULT_FORMAT); } /** * 将时间戳转为时间字符串 * 格式为format

* * @param millis 毫秒时间戳 * @param format 时间格式 * @return 时间字符串 */ public static String millis2String(final long millis, final DateFormat format) { return format.format(new Date(millis)); } /** * 获取当前时间字符串 * 格式为yyyy-MM-dd HH:mm:ss

* * @return 时间字符串 */ public static String getNowString() { return millis2String(System.currentTimeMillis(), DEFAULT_FORMAT); } /** * 获取当前时间字符串 * 格式为format

* * @param format 时间格式 * @return 时间字符串 */ public static String getNowString(final DateFormat format) { return millis2String(System.currentTimeMillis(), format); } } ================================================ FILE: replugin-sample/host/app/src/main/res/layout/activity_main.xml ================================================