Native Android project access to fluent AAR

1, Environment construction

First, developers need to build a good development environment according to the construction process of native Android and iOS. Then, go Flutter official website Download the latest SDK, and then unzip it to the user-defined directory. If there is a download problem, you can use the temporary image officially built by fluent for Chinese developers.

export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

In order to facilitate the use of the command line, additional environment variables need to be configured. First, open the terminal using the vim command.

vim ~/.bash_profile  

Then, add the following code to bash_ In the profile file and use source ~ / bash_ The profile command takes the file changes into effect.

export PATH=/Users/mac/Flutter/flutter/bin:$PATH
//Refresh bash_profile
source ~/.bash_profile

After completing the above operations, next use the fluent doctor command to check whether the environment is correct. If successful, the following information will be output.

2, Create a fluent AAR package

There are two main ways to integrate native Android with fluent. One is to create a fluent module and then rely on it as a native module; Another way is to package the fluent module into aar, and then rely on the aar package in the native project. The official recommends aar access.

There are two ways to create a fluent AAR: one is to use Android Studio for generation, and the other is to use the command line directly. Use the command line to create the shuttle module as follows:

flutter create -t module flutter_module

Then, go to the shutter_ Module, execute the fluent build aar command to generate the aar package. If there is no error, it will be displayed in / fluent_ module/. Generate the corresponding aar package in the Android / fluent / build / outputs directory, as shown in the following figure.

build/host/outputs/repo
└── com
    └── example
        └── my_flutter
            ├── flutter_release
            │   ├── 1.0
            │   │   ├── flutter_release-1.0.aar
            │   │   ├── flutter_release-1.0.aar.md5
            │   │   ├── flutter_release-1.0.aar.sha1
            │   │   ├── flutter_release-1.0.pom
            │   │   ├── flutter_release-1.0.pom.md5
            │   │   └── flutter_release-1.0.pom.sha1
            │   ├── maven-metadata.xml
            │   ├── maven-metadata.xml.md5
            │   └── maven-metadata.xml.sha1
            ├── flutter_profile
            │   ├── ...
            └── flutter_debug
                └── ...

Of course, we can also use Android Studio to generate aar packages. Select File - > New - > New shutter project - > shutter module to generate a shutter module project.

Then we select build - > fluent - > build aar to generate aar package.


Next, you can integrate aar in the native Android project.

3, Add fluent dependency

3.1 adding aar dependencies

Official recommendation method

The way to integrate aar packages is as large as the way to integrate ordinary aar packages. First, create a new libs folder in the app directory and create it in build. Exe Add the following configuration to gradle.

android {
    ...

buildTypes {
        profile {
          initWith debug
        }
      } 

    String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?:
      "https://storage.googleapis.com"
      repositories {
        maven {
            url '/Users/mac/Flutter/module_flutter/build/host/outputs/repo'
        }
        maven {
            url "$storageUrl/download.flutter.io"
        }
      }
    
}

dependencies {
      debugImplementation 'com.xzh.module_flutter:flutter_debug:1.0'
      profileImplementation 'com.xzh.module_flutter:flutter_profile:1.0'
      releaseImplementation 'com.xzh.module_flutter:flutter_release:1.0'
    }

Local Libs mode

Of course, we can also copy the generated aar package to the local libs, and then open app / build Add local dependencies to grade, as shown below.

repositories {
    flatDir {
        dirs 'libs'
    }
}

dependencies {
    ...
    //Add local dependency
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation(name: 'flutter_debug-1.0', ext: 'aar')
    implementation 'io.flutter:flutter_embedding_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'
    implementation 'io.flutter:armeabi_v7a_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'
    implementation 'io.flutter:arm64_v8a_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'
    implementation 'io.flutter:x86_64_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'
}

io. flutter:flutter_ embedding_ Where does debug come from? In fact, it is the flutter when build/host/outputs/repo is generated_ release-1.0. In the POM file,

  <groupId>com.example.flutter_library</groupId>
  <artifactId>flutter_release</artifactId>
  <version>1.0</version>
  <packaging>aar</packaging>
  <dependencies>
  <dependency>
  <groupId>io.flutter.plugins.sharedpreferences</groupId>
  <artifactId>shared_preferences_release</artifactId>
  <version>1.0</version>
  <scope>compile</scope>
  </dependency>
  <dependency>
  <groupId>io.flutter</groupId>
  <artifactId>flutter_embedding_release</artifactId>
  <version>1.0.0-626244a72c5d53cc6d00c840987f9059faed511a</version>
  <scope>compile</scope>
  </dependency>

When copying, pay attention to the environment of our local aar package. They correspond to each other one by one. Next, in order to correctly rely on, you also need to build in the outer layer Add the following dependencies to gradle.

buildscript {
repositories {
    google()
    jcenter()
    maven {
        url "http://download. flutter. IO "/ / fluent dependency
    }
  }
dependencies {
    classpath 'com.android.tools.build:gradle:4.0.0'
  }
}

If the native Android project uses the idea of component-based development, it usually depends on a module/lib, such as module_flutter to add.

 stay module_flutter build.gradle Lower configuration
  repositories {
      flatDir {
        dirs 'libs'   // aar directory
      }
    }

In the Lord App Lower configuration
repositories {
//  Detailed path
flatDir {
    dirs 'libs', '../module_flutter/libs'
  }
}

3.2 source code dependency

In addition to aar, we can also rely on the source code of the fluent module. First, we create a module in the native Android project, as shown in the following figure.

After adding successfully, the system will default to settings The following code is generated in the gradle file.

 
include ':app'                                  
setBinding(new Binding([gradle: this]))                              
evaluate(new File(                                                   
  settingsDir.parentFile,                                           
  'my_flutter/.android/include_flutter.groovy'                    
))                                                                   

Then, in APP / build Add source dependencies to the gradle file.

dependencies {
  implementation project(':flutter')
}

3.3 compiling aar using fat aar

If some third-party libraries are introduced into fluent, many projects need to use fat AAR when using fluent. First, in android/build. Add fat AAR dependency to gradle.

 dependencies {
        ...
        com.github.kezong:fat-aar:1.3.6
    }

Then, in android/Flutter/build. Add the following plugin s and dependencies to gradle.

dependencies {
    testImplementation 'junit:junit:4.12'
  
    // Add fluent_ embedding. jar debug
    embed "io.flutter:flutter_embedding_debug:1.0.0-eed171ff3538aa44f061f3768eec3a5908e8e852"
    // Add fluent_ embedding. jar release
    embed "io.flutter:flutter_embedding_release:1.0.0-e1e6ced81d029258d449bdec2ba3cddca9c2ca0c"
    // Add each cpu version of fluent so
    embed "io.flutter:arm64_v8a_debug:1.0.0-eed171ff3538aa44f061f3768eec3a5908e8e852"
    embed "io.flutter:armeabi_v7a_debug:1.0.0-eed171ff3538aa44f061f3768eec3a5908e8e852"
    embed "io.flutter:x86_64_debug:1.0.0-eed171ff3538aa44f061f3768eec3a5908e8e852"
    embed "io.flutter:x86_debug:1.0.0-eed171ff3538aa44f061f3768eec3a5908e8e852"

At this time, if we run the project, we may report an error of Cannot fit requested classes in a single dex file. This is a very old subcontracting problem, which means that the dex exceeds 65k. One dex can no longer be installed, and multiple dex are required. The solution is to just use app / build Add multidex to gradle.

android {
    defaultConfig {
            ···
        multiDexEnabled true
    }
}

dependencies {
    //Android x supports the multidex library
    implementation 'androidx.multidex:multidex:2.0.1'
}

5, Jump to fluent

5.1 start fluteractivity

After integrating Flutter, let's go to androidmanifest Register fluteractivity in XML to achieve a simple jump.

<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  android:exported="true"  />

Then add a jump code to any page, such as.

myButton.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity.createDefaultIntent(this)
    );
  }
});

However, when I run the project and execute the jump, I still report an error. The error information is as follows.

   java.lang.RuntimeException: Unable to start activity ComponentInfo{com.snbc.honey_app/io.flutter.embedding.android.FlutterActivity}: java.lang.IllegalStateException: ensureInitializationComplete must be called after startInitialization
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2946)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3081)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:201)
        at android.app.ActivityThread.main(ActivityThread.java:6806)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)

The error reading should be the initialization problem, but the official document does not mention any code related to the initialization steps. Check the official issue of fluent, indicating that a line of initialization code should be added:

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        FlutterMain.startInitialization(this);
    }
}

Then, I ran again and found the following error.

java.lang.NoClassDefFoundError: Failed resolution of: Landroid/arch/lifecycle/DefaultLifecycleObserver;
        at io.flutter.embedding.engine.FlutterEngine.<init>(FlutterEngine.java:152)
        at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.setupFlutterEngine(FlutterActivityAndFragmentDelegate.java:221)
        at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onAttach(FlutterActivityAndFragmentDelegate.java:145)
        at io.flutter.embedding.android.FlutterActivity.onCreate(FlutterActivity.java:399)
        at android.app.Activity.performCreate(Activity.java:7224)
        at android.app.Activity.performCreate(Activity.java:7213)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2926)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3081)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:201)
        at android.app.ActivityThread.main(ActivityThread.java:6806)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
     Caused by: java.lang.ClassNotFoundException: Didn't find class "android.arch.lifecycle.DefaultLifecycleObserver" on path: DexPathList[[zip file "/data/app/com.example.myapplication-kZH0dnJ-qI1ow1NqGOB2ug==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.myapplication-kZH0dnJ-qI1ow1NqGOB2ug==/lib/arm64, /data/app/com.example.myapplication-kZH0dnJ-qI1ow1NqGOB2ug==/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64]]

The last log indicates that the lifecycle is missing, so you can add lifecycle dependencies, as shown below.

   implementation 'android.arch.lifecycle:common-java8:1.1.0'

Then run it again and there's no problem.

5.2 starting with FlutterEngine

By default, a FlutterEngine is created when each FlutterActivity is created, and each FlutterEngine has an initialization operation. This means that there is a delay in starting a standard fluteractivity. To reduce this delay, we can create a FlutterEngine in advance before starting the FlutterActivity, and then use the FlutterEngine when jumping to the FlutterActivity. The most common way is to initialize the FlutterEngine in the Application, for example.

class MyApplication : Application() {
    
    lateinit var flutterEngine : FlutterEngine

    override fun onCreate() {
        super.onCreate()
        flutterEngine = FlutterEngine(this)
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )
        FlutterEngineCache
            .getInstance()
            .put("my_engine_id", flutterEngine)
    }
}

Then, we can use the buffered FlutterEngine when jumping to FlutterActivity, because the engine has been added when the FlutterEngine is initialized_ ID, so you need to use this engine when starting_ ID to start.

myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withCachedEngine("my_engine_id")
      .build(this)
  )
}

Of course, at startup, we can also jump to a default route. We only need to call the setInitialRoute method at startup.

class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine
  override fun onCreate() {
    super.onCreate()
    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)
    // Configure an initial route.
    flutterEngine.navigationChannel.setInitialRoute("your/route/here");
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )
    // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}

6, Communication with Flutter

After the above operations, we have been able to complete the jump from native Android to fluent. How can we realize the jump from fluent to native Activity or how can fluent destroy itself and return to the native page? At this time, the communication mechanism of fluent and native Android, namely Channel, is used, which are MethodChannel, EventChannel and BasicMessageChannel respectively.

  • MethodChannel: used to transfer method calls. It is a commonly used PlatformChannel.
  • EventChannel: used to deliver events.
  • BasicMessageChannel: used to transfer data.

For this simple jump operation, you can directly use MethodChannel. First of all, we're in flutter_ Create a new PluginManager class in module, and then add the following code.

import 'package:flutter/services.dart';

class PluginManager {
  static const MethodChannel _channel = MethodChannel('plugin_demo');

  static Future<String> pushFirstActivity(Map params) async {
    String resultStr = await _channel.invokeMethod('jumpToMain', params);
    return resultStr;
  }

}

Then, when we click the return button on the fluent entry page, we add a return method, mainly calling PluginManager to send messages, as shown below.

Future<void> backToNative() async {
    String result;
    try {
      result = await PluginManager.pushFirstActivity({'key': 'value'});
    } on PlatformException {
      result = 'fail';
    }
    print('backToNative: '+result);
  }

Next, recompile the aar package with fluent build aar, and add the following code in the configureFlutterEngine method of the native Android's fluent entry page.

class FlutterContainerActivity : FlutterActivity() {

    private val CHANNEL = "plugin_demo"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }


    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "jumpToMain") {
                val params = call.argument<String>("key")
                Toast.makeText(this,"Return to native PAGE",Toast.LENGTH_SHORT).show()
                finish()
                result.success(params)
            } else {
                result.notImplemented()
            }
        }
    }

}

When rerunning the native project, click the return button in the upper left corner of fluent to return to the native page. Other mixed jumps can also be solved in this way.

For the problems of hybrid routing and multiple instances of FlutterEngine in hybrid development, you can refer to FlutterBoost.

Keywords: Android Flutter

Added by fandelem on Wed, 22 Dec 2021 17:23:14 +0200