preface

As mentioned above, the core of Hybrid mode is in the original, and this paper takes the Android part of this project as an example to introduce the implementation of Android part.

Note: Due to various reasons, the Android container in this project ensures core interaction and some important API implementation, and the underlying container optimization mechanism will be considered and improved later.

The general content is as follows:

  • The JSBridge core interaction part

  • UI, Page, Navigator and other common API implementation

  • Implementation of component (custom) API extensions

  • H5 support parts of the container (such as support for fileInput file selection, geolocation, etc. – default does not work)

  • The API’s permission validation allows only one entry, emulating the simplest implementation

  • Other mechanisms such as offline resource loading updates and low-level optimizations are not available for the time being

Project structure

Androidstudio-based projects have been split slightly into modules for ease of management, and since they’ve shifted their focus to the JS front end, they don’t want to spend much effort refactoring Android code anymore, so they’ve just taken the code out of the business, leaving some code that’s a little leaner (but not particularly leaner).

So if you find that your code style, specification, etc., doesn’t fit, just do it.

The overall directory structure is as follows:

quickhybrid-android
    |- app                  // application is the main application
    |   |- api/PayApi       // Extends a component API
    |   |- MainActivity     // Entry page
    |- core                 // Library, the core tool class module, contains some common tool classes
    |   |- baseapp
    |   |- net
    |   |- ui
    |   |- util
    |- jsbridge             // Library, JSBridge module, the core implementation of hybrid development
    |   |- api
    |   |- bean
    |   |- bridge
    |   |- control
    |   |- view
Copy the code

The code architecture

Simple three-fold architecture: underlying core tool classes ->JSBridge implementation -> APP application implementation

core
    |- application                  // Application flow control, Activity management, crash log, etc
    |- baseapp                      // Some basic Activity, Fragment definitions
    |- net                          // Network request related
    |- ui                           // Some UI effects are defined and implemented
    |- util                         // Generic utility class
    
jsbridge
    |- api                          // Define API to open native functionality to H5
    |- bean                         // Put some entity classes
    |- bridge                       // The definition and core implementation of the bridge
    |- control                      // Control classes, including callback control, page load control, file selection control, etc
    |- view                         // Define the webView and Fragment implementations needed for mixed development
    
app
    |- api                          // Extend the custom component API required by the project
    |- AppApplication.java          // Control the application
    |- MainActivity.java            // Control the entry interface
Copy the code

Access configuration

In native applications, there is no escaping the problem of permissions after packaging. Without permissions, many functions cannot be used. For the sake of simplicity, the permissions used in the application are listed here (based on various considerations, not following the minimum principle).

<! - = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = permission configuration statement = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -- -- >
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_CALL_LOG" />
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.READ_LOGS" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.READ_OWNER_DATA" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.READ_PROFILE" />
Copy the code

Note that dynamic permissions are required on 6.0. Make sure you have assigned the corresponding permissions to your application

Gradle configuration

In order for a project to run correctly in AndroidStudio, you need to have the correct Gradle configuration.

A few key configurations are explained here, and the rest can be referred to source code

gradle-wrapper.properties

distributionUrl=https\:/ / services.gradle.org/distributions/gradle-4.2.1-all.zip
Copy the code

If gradle does not compile, you can change the gradle version of the file to the local version as described above. (Otherwise, you may get stuck without science.)

setting.gradle

include ':app'.':jsbridge'.':core'
Copy the code

It’s a simple line of code that references all three modules used

Build. Gradle (core)

Only parts are selected for illustration

apply plugin: 'com.android.library'

android {
    compileSdkVersion 25

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 1
        versionName "1.0". }... } dependencies { compile fileTree(dir:'libs'.include: ['*.jar'])

    compile 'com. Android. Support: appcompat - v7:25.3.1'
    compile 'com. Android. Support: support - v4:25.3.1'
    compile 'com. Android. Support: design: 25.3.1'
    compile 'com. Android. Support: recyclerview - v7:25.3.1'
    compile 'com. Android. Support. The constraint, the constraint - layout: 1.0.2'
    compile 'com. Jakewharton: butterknife: 8.6.0'
    compile 'com. Google. Code. Gson: gson: 2.8.0'
    compile 'com. Journeyapps: zxing - android - embedded: 3.5.0'
    compile 'com. Liulishuo. Filedownloader: library: 1.5.5'
    compile 'com. Nostra13. Universalimageloader: universal - image - loader: 1.9.5'
    compile 'me. The iwf. Photopicker: photopicker: 0.9.10 @ aar'
    compile 'com. Making. Bumptech. Glide: glide: 4.4.1'. }Copy the code

The key messages are:

  • Apply Plugin: ‘com.android.library’ represents a module, not the main application

  • MinSdkVersion 16 indicates the lowest version compatible with 4.1

  • TargetSdkVersion 25 is the compiled version. TargetSdkVersion 22 provides forward compatibility. At 22, no dynamic permissions are required.

  • VersionName and versionCode perform version control

  • < span style = “color: RGB (51, 51, 51); font-size: 14px! Important; word-break: inherit! Important;”

Why don’t we add dependencies with implementation instead of compile? Because the implementation is not transitive, we don’t use jsbridge that references Core, and we need to make sure that we use it in JsBridge, so we use compile.

Build. Gradle (jsbridge)

Some similar code is not posted

apply plugin: 'com.android.library'. dependencies { implementation project(':core')... }Copy the code

The difference here is that core relies on the core module internally and uses the Implementation Project so that the source code of Core can be used inside JsBridge.

Note that implementation is not transitive (core is only exposed to JsBridge, not passed on)

Build. Gradle (app)

Some similar code is not posted

apply plugin: 'com.android.application'

android {
    defaultConfig {
        applicationId "com.quick.quickhybrid"
        versionCode 1
        versionName "1.0"}... } dependencies { implementation project(':core')
    implementation project(':jsbridge')
    implementation fileTree(dir: 'libs'.include: ['*.jar'])
    // ButterKnife8.0 + supports control annotations that must be added to runnable models
    annotationProcessor 'com. Jakewharton: butterknife - compiler: 8.6.0'. }Copy the code

There are a few key bits of information

  • Apply Plugin: ‘com.android.application’ represents the main application, not the module

  • ApplicationId defines the applicationId

  • Also has its own version control, but note that here is the container version number, as in JsBridge is the quick version number, there is a difference

  • Implementation relies on the first two modules, and later introduces any dependencies that might be needed in your application

  • AnnotationProcessor ‘com. Jakewharton: butterknife – compiler: 8.6.0’, this line of code is to make effective butterknife automatic annotation configuration

TargetSdkVersion instructions

The version used in the configuration is 22, because there will be dynamic permissions above this version, which is quite troublesome, and some logic needs to be changed. Therefore, it has not been modified temporarily.

Such as access to private files and so on

Some key code

Code aspect, also cannot explain all one by one, here only enumerates some more important steps to achieve, the rest can refer to the source code

UA agreed

The UA convention has been mentioned in the previous JS project, that is, when loading the Webview, the following UA logo is uniformly added to the webview

WebSettings settings = getSettings();
String ua = settings.getUserAgentString();
// Set the UA of the browser. The JS side uses the UA to determine whether it belongs to the Quick environment
settings.setUserAgentString(ua + " QuickHybridJs/" + BuildConfig.VERSION_NAME);
Copy the code

Some key WebView Settings

// Set JS support
settings.setJavaScriptEnabled(true);
// Sets whether to support meta tags to control zooming
settings.setUseWideViewPort(true);
// Zoom to the size of the screen
settings.setLoadWithOverviewMode(true);
// Set the built-in zoom control (invalid if SupportZoom is false)
settings.setBuiltInZoomControls(true);
// Set the cache mode
// LOAD_DEFAULT implements the load policy based on the cache-control property set in the HTTP header
LOAD_CACHE_ELSE_NETWORK Is obtained locally regardless of whether it has expired
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
settings.setDomStorageEnabled(true);
// Set AppCache to H5 manifest file
String appCachePath = getContext().getCacheDir().getAbsolutePath();
settings.setAppCachePath(appCachePath);
settings.setAppCacheEnabled(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    // Force the Android WebView debug mode to use Chrome inspect(https://developers.google.com/web/tools/chrome-devtools/remote-debugging/)
    WebView.setWebContentsDebuggingEnabled(true);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    CookieManager.getInstance().setAcceptThirdPartyCookies(this.true);
}
Copy the code

Only after the above configuration can most functions of H5 page be opened normally, such as localstorage, cookies, viewport, javascript and so on

Supports H5 geolocation

In QuickWebChromeClient, which inherits WebChromeClient

    @Override
    public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
        callback.invoke(origin, true.false);
        super.onGeolocationPermissionsShowPrompt(origin, callback);
    }
Copy the code

It is necessary to support geolocation again, otherwise pure H5 location cannot obtain geolocation (or forced to use network location)

Support file selection

Also in QuickWebChromeClient, which inherits WebChromeClient

    ** * @param uploadMsg * @param acceptType * @param capture */
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
        loadPage.getFileChooser().showFileChooser(uploadMsg, acceptType, capture);
    }

    /** * Android 5.0+ for ** @param webView * @param filePathCallback * @param fileChooserParams * @return */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
        loadPage.getFileChooser().showFileChooser(webView, filePathCallback, fileChooserParams);
        return true;
    }
Copy the code

The above operation is to actively listen for file selection, and then automatically invoke the native processing scheme, such as pop up a general selection box, selection, etc. Without it, you can’t normally select a file with FileInput, which is actually quite common.

Listen for JSBridge triggers

Also in QuickWebChromeClient, which inherits WebChromeClient

    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        result.confirm(JSBridge.callJava(loadPage.getFragment(), message,loadPage.hasConfig()));
        return true;
    }
Copy the code

For convenience, use onJsPrompt directly as an interaction channel, as mentioned earlier

other

Before directly providing API, there are still a lot of basic work to do, such as browsing history management, monitoring attachment download, page load error handling and so on, here no longer repeat, can refer to the source code directly

Finally, there are some JSBridge implementations, API implementations, that have been more or less covered in other articles in this series, and you can refer directly to the source code

In addition, if there are further container optimization operations, they will be separately sorted and added to this series.

Front-end page Example

For convenience, it is integrated directly into app/assets/. The entry page loads it by default, and you can also view the source code directly

Back to root

  • How to implement a Hybrid framework

The source code

Github implements the framework

quickhybrid/quickhybrid