1. Introduction

With the increase of App business, version iteration and redundant “ancient times” code, App code becomes bloated incremental overlay, developers need to understand each function, single test function compilation time, no unified rapid development framework, low code reuse, it is necessary to component-based development.

2. Componentized architecture

1). Idea of componentized architecture

Component development framework can be divided into different parts, including Android UI, network request, database persistence, image processing, View, tool class, SDK, internal unified style components, etc. The framework includes but is not limited to common functions. If it is a common function in the internal project of the department, it can also be separated into a common inventory in.

           

Componentized architecture diagram

2). What are the componentization schemes and their advantages

① ARouter: the gene comes with support from the webview call, do not need to register with each other (do not know the need to call the APP process name and other information);

(2) ComponentCaller: simple integration, rich functions, full monitoring, transformation of the old project cost low;

③ Google AutoService: Google recommendation, simple inheritance, powerful function, etc.

3). Component implementation of AutoService

Principle: AutoService automatically generates a Processor configuration file in the META-INF folder. This file contains the implementation classes of the service interface. When an external application assembs the module, it can find the implementation class name in the meta-INF /services/ jar file and load the instantiation to complete the module injection.

① Adding a dependency

Implementation 'com. Google. Code. Gson: gson: 2.8.6'Copy the code

② Add common Javapoet apis

AnnotationProcessor 'com. Google. Code. Gson: gson: 2.8.6' / / Kotlin need kapt support apply plugin: 'Kotlin - android apply plugin: 'kotlin - android - extensions / / kapt plug-in, there will be a lot of problems, blog: https://www.jianshu.com/p/b58d733bc54eapply plugin:' kotlin - kapt 'Copy the code

③ Use the @autoService annotation

IWebViewService {fun startWebActivity(context: context, title: String, url: String) fun startWebFragment(url: String): Fragment fun starLocalTestHtml(context: @autoService (IWebViewService::class) class WebViewServiceImpl: IWebViewService { override fun startWebActivity(context: Context, title: String, url: String) { WebActivity.create(context, title, url) } override fun startWebFragment(url: String): Fragment { return WebFragment.create(url) } override fun starLocalTestHtml(context: Context) {WebActivity. CreateHtml (Context)}} / / the third step search instance, communicate binding. StarWebActivity. SetOnClickListener {/ / AutoService tools for realizing AutoService. Load (IWebViewService: : class. Java)? .apply { starLocalTestHtml(this@AccountActivity) } } object AutoService { fun <S> load(clazz: Class<S>): S? { val service = ServiceLoader.load(clazz).iterator() try { if (service.hasNext()) { return service.next() } } catch (e: Exception) { e.printStackTrace() } return null } }Copy the code

The above has completed the preliminary construction of componentization, and the structure is shown in the following figure

                               

3. WebView component encapsulation

1). Component of WebView

WebView consists of four parts: Common methods of component Functions 1.WebView Creates objects loadUrl(): loads web pages. GoBack (): backs up life cycle management status management ShouldOverrideUrlLoading (): show and request events in WebView when opening a web page OnPageStarted (): called when the page is loaded (for example, to handle the loading progress) onPageFinished(): called when the page is finished. 4.WebChromeClient assists in WebView processing OnProgressChanged (): get the loading progress of the Web page & display JavaScript dialog box onReceivedTitle(): Get the title of the Web page (such as website icon, website title) onJsAlert(): warning box that supports JavaScript OnJsPrompt (): Supports javaScript input boxesCopy the code

2). Create a view

Create WebActivity & WebFragment & BaseWebView & IWebCallBack

IWebCallBack: Listens for WebViewClient and WebChromeClient events when a Web page is opened.

WebActivity: Web page entry, IWebCallBack implementation listening and unified management of the page.

WebFragment: Returns a Fragment page for unified event handling.

BaseWebView: Custom WebView configures the WebSettings attribute. IWebCallBack calls back WebViewClient and WebChromeClient events to WebActivity or WebFragment.

Configure the JavascriptInterface method to receive Web events and process them uniformly.

3). Cross-process communication

Web pages require a large amount of memory. In order to avoid the crash of App caused by WebView OOM, the Web page should be run in an independent process and AIDL is used for cross-process communication.

① In order to facilitate management, MainProcess and WebProcess are subcontracted first; The Web page is run in the Web process, and the events and processing of the Web page are in the main process, and the process switch with AIDL, then create one

The aiDL interface of iWebPro mainPro is as follows:

// Declare any non-default types here with 
import statementsimport com.hlc.common.IMainProToWebPro;
interface IWebProToMainPro {   
 /**   
  * Demonstrates some basic types that you can use as parameters   
  * and return values in AIDL.    
  */   
 void handleWebCommand(String commandName,String jsonParams,IMainProToWebPro callBack);
}
Copy the code

Its location is in the Common layer (it can be in the Web module, but the event scheduling needs to be in the APP, the project app is an empty shell).

4) command mode

To manage web page events in a unified manner, the command mode is used. Only one JavascriptInterfacefun interface is defined to respond to Web pages, and the server distributes events by issuing commands. The BaseWebView definition is as follows:

JavascriptInterfacefun takeNativeAction(jsParams: String) { Timber.tag(TAG).d(jsParams) if (jsParams.isNotBlank()) { val jsonParams = Gson().fromJson(jsParams, JsonParams::class.java) Timber.tag(TAG).d(Gson().toJson(jsonParams.param)) WebViewCommandDispatcher.execute(jsonParams.name, jsonParams.param, object : IMainProToWebPro.Stub() { override fun onResult(callBackName: String?, response: String?) { Timber.tag(TAG).d("callBackName:$callBackName,Response:$response") } }) } }Copy the code

It also creates a Command interface in common, with the implementation class handling events and responding to Web requests.

interface Command {  
  fun name(): String   
 fun execute(params: String,callBack:IMainProToWebPro?)
}
Copy the code

5). Event distribution

First create the command manager in the main process and implement the AIDL interface service Stub iWebProtomainpro.stub class, then pass

The ServiceLoader finds all the Command implementation classes and dispatches events based on the server’s commands:

/** * iwebProtomainpro.aidl IWebViewProcessToMainProcessInterface. The aidl * WebViewProcess to MainProcess interface (aidl) * @ author HLC * / object MainProcessCommandManager : IWebProToMainPro.Stub() { private const val TAG = "MainProCommandManager" private val commands = mutableMapOf<String, Init {val serviceLoader = serviceloader.load (Command::class.java) Timber.tag(TAG).d("serviceLoader hasNext :${serviceLoader.iterator().hasNext()}") for (command in serviceLoader) { if (! Commands. Contains (command.name())) {commands[command.name()] = command}}} /** * parse execution command */ Override fun handleWebCommand(commandName: String?, jsonParams: String?, callBack: IMainProToWebPro?) { Timber.tag(TAG).d(jsonParams) if (! commandName.isNullOrBlank() && ! jsonParams.isNullOrBlank()) { commands[commandName]? .execute(jsonParams, callBack) } } }Copy the code

Then to use AIDL, the Service needs to provide the Service interface as an AIDL file, and the AIDL tool will generate a corresponding Java interface, In addition, the generated Service interface contains a Stub Service class for function invocation. The implementation class of the Service needs to bind this Stub Service class:

/ * * * main process command Service, used to connect WebViewProcess and @ author HLC MainProcess * * / class MainProcessCommandService: Service() { override fun onBind(intent: Intent?) : IBinder? { return MainProcessCommandManager } }Copy the code

Events received from Web pages in JavascriptInterface must be distributed to the main process or other processes for processing. In this case, you need to create an event distributor in the Web process to monitor the Service status:

@author HLC */ object WebViewCommandDispatcher: ServiceConnection {private var iWebProToMainPro: IWebProToMainPro? = null fun initAidlConnection() { BaseApplication.baseApplication? . Also {val intent = intent (it, MainProcessCommandService: : class. Java) / / when you start the Service by setAction to start, because the Service in another program, So you wouldn't find it if it was explicit, BindService (intent, this, context. BIND_AUTO_CREATE)}} Override fun onServiceConnected(name: connected) ComponentName? , service: IBinder?) { iWebProToMainPro = IWebProToMainPro.Stub.asInterface(service) } override fun onServiceDisconnected(name: ComponentName?) {iWebProToMainPro = null // initAidlConnection()} Override Fun onBindingDied(name: ComponentName?) {iWebProToMainPro = null // Reconnect service initAidlConnection()} Fun Execute (commandName: String, params: String, callBack: IMainProToWebPro) { iWebProToMainPro? .handleWebCommand(commandName, params, callBack) } }Copy the code

In addition, the service needs to be enabled when the Web page is created, so that the communication between the Web process and the main process is completed, such as opening the login page Command:

@AutoService(Command::class) class LoginCommand : Command { private var mCallBack: IMainProToWebPro? = null private var mCallBackName: String = "" init {liveEventBus. get(eventKey.login_result, String::class.java).observeforever {Timber. Tag (tag). UserName->$it") mCallBack? .onResult(mCallBackName, it) } } override fun name(): String = COMMAND_NAME override fun execute(params: String, callBack: IMainProToWebPro?) { Timber.tag(TAG).d(params) mCallBack = callBack val param = Gson().fromJson(params, LoginParam::class.java) as LoginParam BaseApplication.baseApplication?.also { val intent = Intent() intent.component = ComponentName(it, param.targetClass) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK it.startActivity(intent) } } companion object { private const val TAG = "LoginCommand" private const val COMMAND_NAME = "openLoginPage" } }Copy the code

6). Return the result

After successful login, the user needs to return to the Web page and update the page information. At this time, the cross-process communication is also conducted, and the returned result is usually using callBack to create aiDL interface of IMainProToWebPro. It is passed to the Command implementation as a parameter in the handleWebCommand method of IWebProToMainPro, and the result is returned and the page is updated.

conclusion

AutoService source code, principle analysis;

② Cross-process principle, AIDL principle, Binder mechanism;

③ Function and interface use of WebView;