likes
comments
collection
share

Flutter项目下载依赖失败的问题解决与反思

作者站长头像
站长
· 阅读数 9

Flutter中的Android 与 Android中的Flutter

前言

事情是这个样子的,本身公司网络最近不稳定,在 Flutter 项目中添加了 flutter_bmflocation 插件之后,导致项目无法运行,下载依赖失败。

我真的服了,怎会如此,怎么解决?接下来我们就带着疑问往下走:

为什么会这样?

开启 VPN 之后会好吗?

设置 AS 代理会好吗?

用 Android 项目会好吗?

他们之间有什么区别?

一、问题

比较常见的问题要么是失败,要么是一直挂起,例如:

Running "flutter packages get" in startup_namer...
The setter 'readEventsEnabled=' was called on null.
Receiver: null
Tried calling: readEventsEnabled=false

或者:

Error on line xx, column xx: Unexpected token

为什么?

众所周知的原因,在中国大陆地区,如果你的 Flutter 项目依赖于从 Flutter 官方的 Pub 仓库中获取的包,那么访问 Pub 仓库可能需要使用 VPN 或者其他网络代理工具,以绕过由于网络限制而无法直接访问的问题。

如果你不使用 VPN 或者镜像源,并且无法直接访问 Flutter 官方的 Pub 仓库,那么在执行 flutter pub get 命令时,会出现类似上述的错误提示。

所以两个解决思路,开启 VPN 或者 替换镜像源。

类似我们 Android 开发中可以在 build.gradle 中常用的阿里云的镜像源一样,在 pub 的镜像源我们可以替换。

官方推荐的解决方案替换为设置环境变量,使用国内镜像下载:

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

当然有条件的最好还是拥有一个 VPN ,不然就算我们的 flutter_bmflocation 下载完成了,但是它内部又集成了 Android 的定位 SDK,需要下载,还有一些 Lib 的环境与 Kotlin 的版本需要下载。

有人可能说了,那我在 Flutter 中的 android 项目中的修改 build.gradle 的源不就好了吗?

类似这样:

A problem occurred configuring project ':flutter_baidu_mapapi_base'. Could not resolve all files for configuration ':flutter_baidu_mapapi_base:classpath'. Could not download guava-27.0.1-jre.jar (com.google.guava:guava:27.0.1-jre) > Could not get resource 'repo.maven.apache.org/maven2/com/…'. > Read timed out Could not download kotlin-reflect-1.3.41.jar (org.jetbrains.kotlin:kotlin-reflect:1.3.41) > Could not get resource 'repo.maven.apache.org/maven2/org/…'. > Could not GET 'repo.maven.apache.org/maven2/org/…'. > Read timed out Could not download kotlin-stdlib-1.3.41.jar (org.jetbrains.kotlin:kotlin-stdlib:1.3.41) > Could not get resource 'repo.maven.apache.org/maven2/org/…'. > Read timed out Failed to notify project evaluation listener. Could not get unknown property 'android' for project ':flutter_baidu_mapapi_base' of type org.gradle.api.Project. Could not get unknown property 'android' for project ':flutter_baidu_mapapi_base' of type org.gradle.api.Project.

这下又怎么解决?

二、解决

如果是在网上查阅资料,大概率是要你清除缓存,清除 Flutter 缓存,清除 Build 缓存,清除 Gradle 缓存。

大概率是没用的,有一种办法是开启 VPN 并设置 AS 代理。

以我使用的 Clash 应用为例:

Flutter项目下载依赖失败的问题解决与反思

我们在AS中就能设置本机代理,打开AS的设置页面

Flutter项目下载依赖失败的问题解决与反思

如果你的VPN节点网络稳定,基本上不会有问题。

关键是我的网络不稳定,节点都挂了,导致几个远程依赖无法下载,应用无法运行,怎么办?

人都要急死了,此时我们可以利用 Android 项目来依赖,它可以支持 build.gradle 的国内镜像下载。

打开Flutter项目中的 android 项目,在一个新的 AS Project 中运行。

Flutter项目下载依赖失败的问题解决与反思

此时大概率还是会下载 android 项目的编译库,kotlin库,jectpack库等。之前我 Flutter 项目之前不是运行到 Android 设备上了?为什么单独开启 android 的项目确要‘重新’下载。

这里挖个坑,因为这两种方式根本就不是一回事。先不管它,让他下载吧,就算没有 VPN 我添加了国内镜像下载一样可以快速下载,很快我们就下载完成,开始编译。

咦?报错!

Flutter项目下载依赖失败的问题解决与反思

还是之前那个坑,之前用 Flutter 项目跑过,这里冲突了,我们需要把 Flutter 项目的缓存清除一下

命令:

flutter clean

然后再次运行,哦,可以了,哦,又出错了。

此时是因为 Flutter 产物打包出错,因为没有 pub 依赖,我们需要

flutter pub get

获取到依赖之后就能正常的打包 Flutter 产物,此时单独的 android 项目就能运行了。

由于有 android 的国内镜像,所以 flutter_bmflocation 中依赖的 guava 与 kotlin 都能正常下载,也能直接运行了。

那么此时我们切换到 Flutter 项目直接运行 Flutter 项目也能直接运行了,因为已经下载过了已经缓存过了,它也不会再次去下载。

所以这种方式也是可以的,Flutter 下载不下来,去单独的 android 项目中下载。

并且单独的运行 android 项目也是很有必要的。

因为在一些特殊的场景我们需要代码提示,比如在写 Channel 的时候,如果直接在 Flutter 项目中的 android 项目中写代码是没有代码提示的,如果单词拼错很容易出现问题,如果在单独的 android 项目中写 Channel 就会有代码提示,可以避免一些不必要的错误。

三、反思

在此之前我们先简单的过一下 Android 运行 Flutter的几种方式。

  1. 一种是 Flutter 原生项目中的 android 项目,最终依赖的是 app_plugin_loader.gradle

  2. 一种是 Flutter 集成到 Android 项目中,最终依赖的是 module_plugin_loader.gradle

先说说 Flutter 集成到 Android 项目,安装官方文档的指导【传送门】

总共是分两种方式,一种是 AAR 的集成,一种是源码集成。

首先是 AAR 的集成,我们通过Flutter打包出 AAR 文件。

Flutter项目下载依赖失败的问题解决与反思

我们可以直接把 AAR 导入到 Android 项目的 Lib 中。

Flutter项目下载依赖失败的问题解决与反思

我们引入这个 aar 包的时候还需要引入 flutter_embedding 相关依赖。

    implementation fileTree(dir: 'libs', include: ['ftrecruiter_debug-1.0.1.aar'])
    implementation 'io.flutter:flutter_embedding_debug:1.0.0-ec975089acb540fc60752606a3d3ba809dd1528b'
    implementation 'io.flutter:armeabi_v7a_debug:1.0.0-ec975089acb540fc60752606a3d3ba809dd1528b'
    implementation 'io.flutter:arm64_v8a_debug:1.0.0-ec975089acb540fc60752606a3d3ba809dd1528b'
    implementation 'io.flutter:x86_64_debug:1.0.0-ec975089acb540fc60752606a3d3ba809dd1528b'

每次 Flutter 更新之后还需要把 AAR 粘贴过来,比较麻烦。对 flutter_embedding 的依赖还需要指定版本,更加麻烦。

所以用本地 Maven 仓库去加载 AAR 就相对更方便一点。

Android中指定路径的Maven仓库:

String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
repositories {
    // 本地仓库路径
    maven {
        url 'E:/FlutterProject/merge_flutter_android/ftrecruiter/build/host/outputs/repo'
    }
    maven {
        url 'E:/FlutterProject/merge_flutter_android/ft_malay/build/host/outputs/repo'
    }
    maven {
        url 'E:/FlutterProject/merge_flutter_android/flutter_merge/build/host/outputs/repo'
    }

    // flutter.so 和 flutter_embedding.jar 所在的远端仓库
    maven {
        url "$storageUrl/download.flutter.io"
    }

我们就可以直接依赖

dependencies {
    implementation "androidx.multidex:multidex:2.0.1"

    debugImplementation 'com.hongyegroup.ftrecruiter:flutter_debug:1.0.1'
    releaseImplementation 'com.hongyegroup.ftrecruiter:flutter_release:1.0.1'

在 Flutter 中我们就可以指定编译 AAR 的版本与环境了:

flutter build aar --target-platform android-arm,android-arm64,android-x64 --release --version={版本号}

编译出来之后,Android这边直接修改指定的版本即可。

而使用源码依赖的方式就无需 Flutter 去每次编译 AAR 去 Android 端依赖。直接就能用,特别适合开发中的场景。

源码的集成方式,我们只需要修改两处:

settings.gradle:

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

然后在build.gradle中使用固定写法:

 implementation project(':flutter')

集成完成之后,如果现有的 AppCompatActivity 想要跳转到 Flutter 中的页面我们是需要自行初始化引擎:

一般我们封装如下:

public class FlutterEngineManager {
    private static volatile FlutterEngineManager sInstance;

    private FlutterEngine mFlutterEngine;

    public static FlutterEngineManager getInstance() {
        if (sInstance == null) {
            synchronized (FlutterEngineManager.class) {
                if (sInstance == null) {
                    sInstance = new FlutterEngineManager();
                }
            }
        }
        return sInstance;
    }

    public void startFlutterEngine(Context context) {
        if (mFlutterEngine == null) {
            mFlutterEngine = new FlutterEngine(context);
//            mFlutterEngine.getNavigationChannel().setInitialRoute("/forgot");  //可选择初始化路由

            mFlutterEngine.getDartExecutor().executeDartEntrypoint(
                    DartExecutor.DartEntrypoint.createDefault()
            );
        }
    }

    public void stopFlutterEngine() {
        if (mFlutterEngine != null) {
            mFlutterEngine.destroy();
            mFlutterEngine = null;
        }
    }

    public FlutterEngine getFlutterEngine() {
        if (mFlutterEngine == null) {
            throw new IllegalStateException("FlutterEngine has not been initialized.");
        }
        return mFlutterEngine;
    }
}

使用:

public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);


        View btnToHome = findViewById(R.id.btn_to_home);
        btnToHome.setOnClickListener(view -> {
            gotoFlutterHomePage();
        });

    }

    private void gotoFlutterHomePage() {

        String cacheId = "flutter_engine";
        FlutterEngineManager.getInstance().startFlutterEngine(getApplicationContext());
        FlutterEngine flutterEngine = FlutterEngineManager.getInstance().getFlutterEngine();
        if (flutterEngine != null) {
            FlutterEngineCache.getInstance().put(cacheId, flutterEngine);
        }

        Intent intent = FlutterActivity
//                .withNewEngine()   //创建新的引擎
//                .dartEntrypointArgs(null)
//                .initialRoute("/")

                .withCachedEngine(cacheId)  //使用缓存的引擎
                .build(this);    //构建intent便于跳转

        startActivity(intent);
    }
}

而我们继承自 FlutterActivity 的页面则不需要处理引擎逻辑,FlutterActivity会自动负责引擎的创建、加载和管理。如同我们的 Flutter 项目里面的 Android 模块一样。

回到我们之前说的坑点,有什么区别呢?

就和它们最终依赖的gradle文件一样,一个是 app_plugin_loader.gradle ,一个是 module_plugin_loader.gradle 。

这两个gradle文件的主要区别在于它们所应用的上下文和配置方式。

  1. app_plugin_loader.gradle: 这个gradle文件适用于在Flutter原生项目中使用插件。当你创建一个Flutter原生项目时,Flutter为你生成了一个基本的Android项目,并在其中集成了Flutter引擎和相关配置。在这种情况下,app_plugin_loader.gradle用于在Flutter模块中加载和管理插件。它通过flutter.gradle脚本引入,并提供了在Flutter原生项目中添加、配置和管理插件的功能。

  2. module_plugin_loader.gradle: 这个gradle文件适用于将Flutter集成到现有的Android项目中作为一个模块。当你希望在现有的Android项目中使用Flutter时,可以将Flutter作为一个子模块集成到该项目中。在这种情况下,你需要自己手动配置Flutter的加载和管理。

这两种方式的运行和编译区别主要涉及到Flutter的工作原理和集成方式。

  1. 运行Flutter的启动方式:在Flutter原生项目中,整个应用程序被构建为一个独立的Flutter引擎,并嵌入到Android项目中。这意味着Flutter代码在编译时会被转换为本地机器码,并包含在最终的APK中。因此,Flutter原生项目在每次更新时需要重新编译和构建整个应用程序,以生成新的APK文件。

  2. 运行Android的启动方式:将Flutter集成到现有的Android项目中作为一个模块时,Flutter代码是以动态库的形式存在,并在运行时加载。这意味着Flutter代码不会在编译时被转换为本地机器码,而是作为一个独立的Flutter引擎被打包并随着APK一起发布。因此,更新Flutter模块时只需要替换或更新Flutter引擎部分,而无需重新编译整个应用程序。

一个很明显的标识是一个右上角有Debug标识,一个没有,前者由于Flutter模块是作为动态库加载的,它可以实现动态更新的能力。当你更新Flutter模块时,可以只替换Flutter引擎部分,而无需重新编译整个应用程序。这使得更新Flutter模块变得更加高效和快速。

而使用Android的启动方式来运行,则需要重新编译整个应用程序,因为Flutter代码已经被转换为本地机器码。这意味着每次更新都需要重新构建并生成新的APK文件。也就是没有动态更新的能力了。

后记

本文主要记录了依赖的一些踩坑记录,如 Flutter 依赖 Pub 插件,以及依赖下载对应的 Android 插件的解决方案。

简单的介绍了 Flutter 项目中 android 模块的单独运行的注意方式,以及国内镜像的使用与下载依赖库。

区别使用 Flutter 项目的运行与单独 Android 模块的运行的异同。

以及简单回顾了一下 Flutter 的 App 模式运行(app_plugin_loader.gradle)与 Module 模式的运行(module_plugin_loader.gradle)的区别,以及 Flutter 以 Module 模式集成到 Android 项目中的几种方法。

后续也会持续分享一些实际开发中 Flutter 的踩坑与其他实现方案思路,有兴趣可以关注一下。

关于本文的分享,由于我经验不足,如果有更好的方式欢迎大家一起交流学习,如讲的错漏的地方,希望同学们可以评论区指出。

如果感觉本文对你有一点点点的启发,还望你能点赞支持一下,你的支持是我最大的动力啦。

Ok,这一期就此完结。

Flutter项目下载依赖失败的问题解决与反思