Android工程集成Flutter Module及混合開發(fā)

Flutter混編方案

  • Flutter 工程作為原生工程共用的子模塊,維持原有的原生工程管理方式不變。這種模式,就是三端分離模式。
  • 除了實(shí)現(xiàn)輕量級接入,還可以快速實(shí)現(xiàn) Flutter 功能的“熱插拔”,降低原生工程的改造成本。而 Flutter 工程獨(dú)自管理,無需打開原生工程,可直接進(jìn)行 Dart 代碼和原生代碼的開發(fā)調(diào)試。
  • 三端工程分離模式的關(guān)鍵是抽離 Flutter 工程,將不同平臺的構(gòu)建產(chǎn)物依照標(biāo)準(zhǔn)組件化的形式進(jìn)行管理,即 Android 使用 aar、iOS 使用 pod。換句話說,將 Flutter 模塊打包成 aar 和 pod,這樣原生工程就可以像引用其他第三方原生組件庫那樣快速接入 Flutter 了。
    Flutter混編工程管理方式

平臺通道架構(gòu)設(shè)計(jì)

消息使用platform channels(平臺通道)在客戶端(UI)和宿主(平臺)之間傳遞;

消息和響應(yīng)以異步的形式進(jìn)行傳遞,以確保用戶界面能夠保持響應(yīng);
平臺通道架構(gòu)圖

調(diào)用過程大致如下:

1.客戶端(Flutter端)發(fā)送與方法調(diào)用相對應(yīng)的消息;
2.平臺端(iOS、Android端)接收方法,并返回結(jié)果;

  • iOS端通過FlutterMethodChannel做出響應(yīng);
  • Android端通過MethodChannel做出響應(yīng);

集成Flutter

現(xiàn)有的Flutter是一個(gè)Application項(xiàng)目,現(xiàn)在想把它嵌入原生應(yīng)用中。首先需要將現(xiàn)有Flutter工程轉(zhuǎn)成Module工程,在flutter 工程中的pubspec.yaml 文件中flutter節(jié)點(diǎn)下添加如下配置,然后pug get跑一下

module:
    androidX: true
    androidPackage: com.ganyuan.flutter_module
    iosBundleIdentifier: com.ganyuan.flutterModule

pub get 一下就會生成:.android.ios 文件夾,這兩個(gè)文件是flutter applicaiton沒有的,在 mac上是兩個(gè) 隱藏文件夾,window 是可見的

Android中集成Flutter module

Flutter 可以作為 Gradle 子項(xiàng)目源碼或者 AAR 嵌入到現(xiàn)有的 Android 應(yīng)用程序中。
請注意
你目前現(xiàn)有的 Android 項(xiàng)目可能支持 mipsx86 之類的架構(gòu),然而,F(xiàn)lutter 當(dāng)前僅支持x86_64,armeabi-v7aarm64-v8a 構(gòu)建預(yù)編(AOT)的庫。
可以考慮使用 abiFilters 這個(gè) Android Gradle 插件 API 來指定 APK 中支持的架構(gòu),從而避免 libflutter.so 無法生成而導(dǎo)致應(yīng)用運(yùn)行時(shí)崩潰,具體操作如下:

android {
  //...
  defaultConfig {
    ndk {
      // Filter for architectures supported by Flutter.
      abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
    }
  }
}

Flutter 引擎支持 x86x86_64 的版本,在模擬器以 debug 即時(shí)編譯 (JIT) 模式運(yùn)行時(shí), Flutter 模塊仍可以正常運(yùn)行。
首先在本地安裝Flutter的SDK及環(huán)境配置,FlutterSDK安裝教程
安裝完成后,將我們的Android工程和Flutter工程(默認(rèn)已創(chuàng)建好)放到同一目錄下,如下圖所示

這里采用依賴模塊源碼的方案
該方式可以使你的 Android 項(xiàng)目和 Flutter 項(xiàng)目能夠同步一鍵式構(gòu)建。當(dāng)你需要同時(shí)在這兩個(gè)項(xiàng)目中進(jìn)行快速迭代時(shí),這種方案非常方便。

  1. 將 Flutter 模塊作為子項(xiàng)目添加到宿主應(yīng)用的 settings.gradle 中:
// Include the host app project.
include ':app'                                    // assumed existing content
setBinding(new Binding([gradle: this]))                                // new
evaluate(new File(                                                     // new
  settingsDir.parentFile,                                              // new
  'flutter_module/.android/include_flutter.groovy'                         // new
))                                                                     // new

binding 和 evaluation 腳本可以使 Flutter 模塊將其自身(如 :flutter)和該模塊使用的所有 Flutter 插件(如 :package_info,:video_player 等)都包含在 settings.gradle 的評估的上下文中。

  1. 在你的應(yīng)用中引入對 Flutter 模塊的依賴:
dependencies {
  implementation project(':flutter')
}
  1. 確保Flutter工程和Android工程編譯的SDK版本保持一致,如下圖在app級build.gradle文件中修改
    flutter工程
    Android工程
  2. (可選)盡量保證兩個(gè)工程的gradle版本和jdk版本和引用路徑保持一致,不然可能編譯會報(bào)錯(cuò)
    gradle和jdk版本及引用路徑
    設(shè)置Android工程默認(rèn)jdk路徑,在/File/News Projects Setup/Default Project Structure(根據(jù)Android Studio版本不同可能路徑不同)里設(shè)置
    設(shè)置默認(rèn)jdk路徑
  3. (可選)添加kotlin依賴支持,盡量保證Android和Flutter工程支持的kotlin版本一致
buildscript {
    ext.kotlin_version = '1.8.10'
    repositories {
        google()
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:7.1.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
apply plugin: 'kotlin-android'
dependencies {
    ...
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
  1. 執(zhí)行gradle sync,這一步建議打開科學(xué)上網(wǎng)
    如果報(bào)Failed to apply plugin class ‘FlutterPlugin’的錯(cuò)誤,則需要把settings.gradle中的(RepositoriesMode.FAIL_ON_PROJECT_REPOS)改為(RepositoriesMode.PREFER_PROJECT),如下圖:
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
    repositories {
        google()
        mavenCentral()
    }
}

如果沒法科學(xué)上網(wǎng)的同學(xué)可以在settings.gradle中添加如下代碼

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
    repositories {
        maven { url 'https://maven.aliyun.com/repository/public' }
        maven { url 'https://maven.aliyun.com/repository/public' }
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
    }
}

同時(shí)在project的build.gradle中添加

allprojects {
        repositories {
            maven { url 'https://maven.aliyun.com/repository/public' }
            maven { url 'https://maven.aliyun.com/repository/public' }
            maven { url 'https://maven.aliyun.com/repository/google' }
            maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
        }
    }

再次執(zhí)行gradle sync,build成功后,你的應(yīng)用程序已將 Flutter 模塊添加為依賴項(xiàng)。

7.在 Android 應(yīng)用中添加 Flutter 頁面
這里我們以添加一個(gè)Flutter Fragment為例
首先我們在需要展示Flutter頁面的地方事先預(yù)熱一個(gè)Flutter Engine,以提高Flutter頁面初始加載速度

        // Somewhere in your app, before your FlutterFragment is needed,
        // like in the Application class ...
        // Instantiate a FlutterEngine.
        FlutterEngine flutterEngine = new FlutterEngine(this);
        // Start executing Dart code in the FlutterEngine.
        flutterEngine.getDartExecutor().executeDartEntrypoint(
                DartExecutor.DartEntrypoint.createDefault()
        );
        // Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
        FlutterEngineCache
                .getInstance()
                .put("my_engine_id", flutterEngine);

然后在FlutterFragment中使用預(yù)熱的 FlutterEngine,可以使用工廠方法 withCachedEngine()實(shí)例化 FlutterFragment。

FlutterFragment.withCachedEngine("my_engine_id").build();

FlutterFragment 內(nèi)部可訪問FlutterEngineCache,并且可以根據(jù)傳遞給 withCachedEngine() 的 ID 獲取預(yù)熱的 FlutterEngine。

MainActivity.java代碼:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Somewhere in your app, before your FlutterFragment is needed,
        // like in the Application class ...
        // Instantiate a FlutterEngine.
        FlutterEngine flutterEngine = new FlutterEngine(this);
        // Start executing Dart code in the FlutterEngine.
        flutterEngine.getDartExecutor().executeDartEntrypoint(
                DartExecutor.DartEntrypoint.createDefault()
        );
        // Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
        FlutterEngineCache
                .getInstance()
                .put("my_engine_id", flutterEngine);


        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        BottomNavigationView navView = findViewById(R.id.nav_view);
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
                .build();
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
        NavigationUI.setupWithNavController(binding.navView, navController);
    }

}

HomeFragment.java代碼:

public class HomeFragment extends Fragment {
    
    private FragmentHomeBinding binding;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Fragment fragment = FlutterFragment.withCachedEngine("my_engine_id").build();
        getParentFragmentManager().beginTransaction().add(this.getId(), fragment).commit();
    }

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        HomeViewModel homeViewModel =
                new ViewModelProvider(this).get(HomeViewModel.class);
        binding = FragmentHomeBinding.inflate(inflater, container, false);
        View root = binding.getRoot();
//        final TextView textView = binding.textHome;
//        homeViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
        return root;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }
}

demo運(yùn)行效果:
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容