Flutter系列二:探究Flutter App在Android宿主App中的整合

前面一篇文章我們探討了一下Flutter App如何被整合到iOS App中的剔桨,本文我們接著來討論下Flutter App如何被整合到Android App中的诵闭。

Gradle

我們簡單看一下Android項目的代碼結構:

Android項目的代碼結構

作為Android項目的自動化構建工具,我們先來看看GradleFlutter APP的構建過程中大概做了哪些工作代芜。

settings.gradle

settings.gradle中主要是用來配置Android Project中所有需要依賴的module退子,即進行工程樹的配置。

settings.gradle
// 1
include ':app'

// 2
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()

assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }

def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
// 3
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
  1. 首先引入app module
  2. 讀取local.properties這個文件中的flutter.sdk屬性的值切端,賦值給flutterSdkPath這個變量
  • local.properties中除了Android SDK路徑彻坛,還定義了Flutter相關的一些值。如Flutter SDK路徑踏枣,Flutter構建模式昌屉,Flutter版本編號等
local.properties
sdk.dir=/Users/*/Library/Android/sdk
flutter.sdk=/Users/*/Documents/flutter
flutter.buildMode=debug
flutter.versionName=1.0.0
flutter.versionCode=1
  1. 引入"$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"中的腳本

我們接下來來看看app_plugin_loader.gradle腳本的代碼:

app_plugin_loader.gradle
import groovy.json.JsonSlurper

def flutterProjectRoot = rootProject.projectDir.parentFile

// 1 找到配置文件
def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins-dependencies')
if (!pluginsFile.exists()) {
  return
}

// 2 讀取配置文件
def object = new JsonSlurper().parseText(pluginsFile.text)
assert object instanceof Map
assert object.plugins instanceof Map
assert object.plugins.android instanceof List
object.plugins.android.each { androidPlugin ->
  assert androidPlugin.name instanceof String
  assert androidPlugin.path instanceof String
  def pluginDirectory = new File(androidPlugin.path, 'android')
  assert pluginDirectory.exists()
  include ":${androidPlugin.name}"
  project(":${androidPlugin.name}").projectDir = pluginDirectory
}

  • 讀取Android文件同級目錄下的.flutter-plugins-dependencies文件
  • 讀取該文件下的plugins字段下的android數組茵瀑,對數組的每個元素配置依賴间驮。

提示: 是不是很熟悉? 沒錯iOSPod腳本讀的就是這個文件plugins字段下的的ios字段的值。

{
    "plugins":{
        "android":[
            ...
            {
                "name":"sqflite",
                "path":"/Users/*/Documents/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.3.2+3/",
                "dependencies":[

                ]
            }
            ...
        ],
        "ios":[...],
    }
}

總結一下马昨,最后settings.gradle大致生成的內容如下所示:

include ':app'

include ':fijkplayer'
project(":fijkplayer").projectDir = '/Users/*/Documents/flutter/.pub-cache/hosted/pub.dartlang.org/fijkplayer-0.8.7/android'

include ':shared_preferences'
project(":shared_preferences").projectDir = '/Users/*/Documents/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.5.12+4/android'

include ':sqflite'
project(":sqflite").projectDir = '/Users/*/Documents/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-1.3.2+3/android'

include ':url_launcher'
project(":url_launcher").projectDir = '/Users/*/Documents/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-5.7.10/android'

總結:settings.gradle中完成了所有依賴的module的配置竞帽。

Project / build.gradle

我們來看看Project / build.gradle中的一些設置:

// 1.
rootProject.buildDir = '../build'
subprojects {
    project.buildDir = "${rootProject.buildDir}/${project.name}"
}
// 2.
subprojects {
    project.evaluationDependsOn(':app')
}

  1. 設置project子project的輸出路徑,路徑為和android同級的build文件夾下鸿捧。
build directory

進去瞅瞅就可以驗證:

build files

你估計發(fā)現了屹篓,iOS的編譯結果也是放在這個目錄里面的。

  1. 通過evaluationDependsOn定義了所有其他的moudule的配置都依賴于app這個moudule匙奴。即其他的所有moudule配置必須得等app這個moudule的配置完成后再進行配置堆巧。

總結:Project / build.gradle中配置了各個moudule的編譯輸出路徑和moudule間的依賴關系。

app / build.gradle

接下來我們來看下app / build.gradle中的內容:

// 1. 
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

// 2.
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

// 3.
flutter {
    source '../..'
}

代碼解釋:

  1. 第一大段的作用是從local.properties文件中讀取版本編號和版本名稱泼菌,設置為Android App的版本編號和版本名稱谍肤;
  2. 根據從local.properties文件中讀取到的$flutterRoot路徑導入$flutterRoot/packages/flutter_tools/gradle/flutter.gradle的腳本運行;
  3. flutter擴展的source屬性配置為.. / ..哗伯。

flutter.gradle

flutter.gradle的目的是在Android 宿主App的編譯構建流程中執(zhí)行一些Flutter相關的任務荒揣。

flutter.gradle中有兩個重要的類,一個是FlutterPlugin笋颤,一個是FlutterTask乳附。

flutter.gradle代碼結構
FlutterPlugin

FlutterPlugin作為Gradle Plugin實現了Plugin接口,所以它的入口方法是apply()方法:

FlutterPlugin/apply
void apply(Project project) {
    ...
    // 1. 
    project.extensions.create("flutter", FlutterExtension)
    // 2
    project.afterEvaluate this.&addFlutterTasks
    // 3
    if (shouldSplitPerAbi()) {
        project.android {
            splits {
                abi {
                    enable true
                    reset()
                    universalApk false
                }
            }
        }
    }
    getTargetPlatforms().each { targetArch ->
        String abiValue = PLATFORM_ARCH_MAP[targetArch]
        project.android {
            if (shouldSplitPerAbi()) {
                splits {
                    abi {
                        include abiValue
                    }
                }
            }
        }
    }
    
    // 4
    String flutterRootPath = resolveProperty("flutter.sdk", System.env.FLUTTER_ROOT)
    if (flutterRootPath == null) {
        throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.")
    }
    flutterRoot = project.file(flutterRootPath)
    if (!flutterRoot.isDirectory()) {
        throw new GradleException("flutter.sdk must point to the Flutter SDK directory")
    }

    engineVersion = useLocalEngine()
        ? "+" // Match any version since there's only one.
        : "1.0.0-" + Paths.get(flutterRoot.absolutePath, "bin", "internal", "engine.version").toFile().text.trim()

    String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
    flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile();

    // 5
    project.android.buildTypes {
        profile {
            initWith debug
            if (it.hasProperty("matchingFallbacks")) {
                matchingFallbacks = ["debug", "release"]
            }
        }
    }
    // 6
    if (shouldShrinkResources(project)) {
        String flutterProguardRules = Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools",
                    "gradle", "flutter_proguard_rules.pro")
        project.android.buildTypes {
            release {
                shrinkResources isBuiltAsApp(project)
                proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro"
            }
        }
    }
    // 7
    if (useLocalEngine()) {
        String engineOutPath = project.property('local-engine-out')
        File engineOut = project.file(engineOutPath)
        if (!engineOut.isDirectory()) {
            throw new GradleException('local-engine-out must point to a local engine build')
        }
        localEngine = engineOut.name
        localEngineSrcPath = engineOut.parentFile.parent
    }
    // 8
    project.android.buildTypes.each this.&addFlutterDependencies
    project.android.buildTypes.whenObjectAdded this.&addFlutterDependencies
}

代碼解釋:

  1. 創(chuàng)建一個FlutterExtension擴展内地,這個擴展有兩個屬性,source-Flutter APP工程的路徑赋除,target- Flutter APP的執(zhí)行入口,不設置就默認lib/main.dart阱缓;
  2. app module的其他Task完成后執(zhí)行addFlutterTasks方法;
  3. 決定是否開啟abi分包举农;
  4. 獲取一些系統(tǒng)環(huán)境變量荆针;
    • flutterRootPath --- /Users/*/Documents/flutter
    • flutterRoot --- /Users/*/Documents/flutter
    • engineVersion --- 1.0.0-2c956a31c0a3d350827aee6c56bb63337c5b4e6e
    • flutterExecutable --- flutter(mac), flutter.bat(windows)
  5. 默認有debugrelease兩個模式,這里參照debug模式又添加了一個profile構建模式颁糟,所以現在變成了debug,releaseprofile三個構建模式航背;
  6. 是否啟動shrinkResources資源縮減;
  7. 是否設置本地maven倉庫棱貌;
  8. 給每個構建模式添加Flutter依賴addFlutterDependencies的調用玖媚。

接下來我們看看addFlutterDependencies中的實現:

FlutterPlugin/addFlutterDependencies
void addFlutterDependencies(buildType) {
    ...
    // 1. 
    String hostedRepository = System.env.FLUTTER_STORAGE_BASE_URL ?: DEFAULT_MAVEN_HOST
    String repository = useLocalEngine()
        ? project.property('local-engine-repo')
        : "$hostedRepository/download.flutter.io"
    project.rootProject.allprojects {
        repositories {
            maven {
                url repository
            }
        }
    }
    // 2
    addApiDependencies(project, buildType.name,
            "io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")
            print("io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion\n");

    // 3
    List<String> platforms = getTargetPlatforms().collect()
    if (flutterBuildMode == "debug" && !useLocalEngine()) {
        platforms.add("android-x86")
        platforms.add("android-x64")
    }
    platforms.each { platform ->
        String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_")
        addApiDependencies(project, buildType.name,
                "io.flutter:${arch}_$flutterBuildMode:$engineVersion")
                            print("io.flutter:${arch}_$flutterBuildMode:$engineVersion\n");
    }
}

代碼解釋:

  1. 設置maven倉庫的url地址,默認是https://storage.googleapis.com/download.flutter.io, 如果網速不太理想也可以配置FLUTTER_STORAGE_BASE_URL環(huán)境變量婚脱,讓其指向國內的鏡像地址https://storage.flutter-io.cn/download.flutter.io今魔;
  2. 添加嵌入式的依賴io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion, 這個依賴與構建模式Flutter Engine版本有關系。例子-io.flutter:flutter_embedding_debug:1.0.0-2c956a31c0a3d350827aee6c56bb63337c5b4e6e
  3. 添加libflutter.so依賴障贸,這個依賴和架構Flutter Engine版本有關系错森。例子-io.flutter:armeabi_v7a_debug:1.0.0-2c956a31c0a3d350827aee6c56bb63337c5b4e6e

提示:

  1. flutter_embedding的作用是賦予Flutter嵌入native的能力;
  2. libflutter.so就是Flutter Engine;
  3. 這兩個依賴最后調用的是project.dependencies.add(configuration, dependency, config)這個方法,所以是給project加的依賴篮洁。因為sqflite等其他的module都需要這兩個依賴涩维。

至此FlutterPlugin/apply的流程已經分析完了,接下來我們就來分析第2步遺留的addFlutterTasks方法袁波。

FlutterPlugin/addFlutterTasks

這個方法的代碼量比較大瓦阐,我們來概括總結一下。

private void addFlutterTasks(Project project) {
    // 1
    String target = project.flutter.target
    if (target == null) {
        target = 'lib/main.dart'
    }
    // 2
    def addFlutterDeps = { variant -> 
        2.1 
        FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) { ... }
        2.2
        Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) { ... }
        2.3
        addApiDependencies(project, variant.name, project.files {
            packFlutterAppAotTask
        })
        2.4 
        Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}",
                type: Copy,
            ) { ... }
    }
    // 3
    project.android.applicationVariants.all { variant -> ...}
    // 4
    configurePlugins()
}
  1. gradle.properties獲取各種參數锋叨,上面給出的一個例子---如果沒有target配置垄分,就默認設置為lib/main.dart
  2. 定義一個addFlutterDeps的函數.
  • 根據構建模式和第一步從gradle.properties獲取各種參數一起創(chuàng)建對應的FlutterTask娃磺。FlutterTask的功能則是編譯Flutter APP的代碼薄湿。
  • packFlutterAppAotTask這個task是將FlutterTask的編譯結果打包成libs.jar文件。
  • project上libs.jar的文件依賴偷卧。
  • copyFlutterAssetsTask是進行Flutter App相關的asset進行拷貝豺瘤。由于Flutter可能作為插件編譯或子項目編譯,如果是插件編譯產物編譯結果打包為AAR,子項目編譯則子項目編譯時听诸,編譯結果被打包成APK坐求,所以兩種情況下有區(qū)別處理。
  1. 為所有applicationVariantslibraryVariants添加Flutter依賴晌梨,執(zhí)行addFlutterDeps函數然后把APK拷貝到目標路徑桥嗤。
  2. 這個方法是給project添加Plugin的依賴.編譯方式不同依賴的處理方式也不一樣须妻。
FlutterTask

FlutterTaskbuild()調用的是父類的buildBundle()方法:

void buildBundle() {
    if (!sourceDir.isDirectory()) {
        throw new GradleException("Invalid Flutter source directory: ${sourceDir}")
    }

    intermediateDir.mkdirs()

    // Compute the rule name for flutter assemble. To speed up builds that contain
    // multiple ABIs, the target name is used to communicate which ones are required
    // rather than the TargetPlatform. This allows multiple builds to share the same
    // cache.
    String[] ruleNames;
    if (buildMode == "debug") {
        if (fastStart) {
            ruleNames = ["faststart_android_application"]
        } else {
            ruleNames = ["debug_android_application"]
        }
    } else {
        ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
    }
    project.exec {
        logging.captureStandardError LogLevel.ERROR
        executable flutterExecutable.absolutePath
        workingDir sourceDir
        if (localEngine != null) {
            args "--local-engine", localEngine
            args "--local-engine-src-path", localEngineSrcPath
        }
        if (verbose) {
            args "--verbose"
        } else {
            args "--quiet"
            }
        args "assemble"
        args "--depfile", "${intermediateDir}/flutter_build.d"
        args "--output", "${intermediateDir}"
        if (performanceMeasurementFile != null) {
            args "--performance-measurement-file=${performanceMeasurementFile}"
        }
        if (!fastStart || buildMode != "debug") {
            args "-dTargetFile=${targetPath}"
        } else {
            args "-dTargetFile=${Paths.get(flutterRoot.absolutePath, "examples", "splash", "lib", "main.dart")}"
        }
        args "-dTargetPlatform=android"
        args "-dBuildMode=${buildMode}"
        if (trackWidgetCreation != null) {
            args "-dTrackWidgetCreation=${trackWidgetCreation}"
        }
        if (splitDebugInfo != null) {
            args "-dSplitDebugInfo=${splitDebugInfo}"
        }
        if (treeShakeIcons == true) {
            args "-dTreeShakeIcons=true"
        }
        if (dartObfuscation == true) {
            args "-dDartObfuscation=true"
        }
        if (dartDefines != null) {
            args "--DartDefines=${dartDefines}"
        }
        if (bundleSkSLPath != null) {
            args "-iBundleSkSLPath=${bundleSkSLPath}"
        }
        if (codeSizeDirectory != null) {
            args "-dCodeSizeDirectory=${codeSizeDirectory}"
        }
        if (extraGenSnapshotOptions != null) {
            args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
        }
        if (extraFrontEndOptions != null) {
            args "--ExtraFrontEndOptions=${extraFrontEndOptions}"
        }
        args ruleNames
    }
}

這個方法其實是執(zhí)行帶一些參數的flutter build方法。

APK

最后泛领,我們來看看APK的結構荒吏。

  1. lib文件中默認包含了4種架構的支持,里面包括了Flutter Engine--- libflutter.so渊鞋,Flutter App代碼---libapp.so绰更,Flutter插件依賴的so文件---libijkffmpeg.so,libijkplayer.so,libijksdl.so

你可能會遇到couldn't find "libflutter.so",因為x86里面沒有libflutter.so和libapp.so锡宋,這是flutter的一個已知的問題儡湾。

  1. Flutter App中的資源文件都打包到了assets/flutter_assets中。
APK

AndroidManifest.xml

我們來看看AndroidManifest.xml里面都配置了些什么東東:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.jj_movie">
    // 1.
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="jj_movie"
        android:icon="@mipmap/ic_launcher">
        <activity
            ...>
            // 2
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            // 3
            <meta-data
              android:name="io.flutter.embedding.android.SplashScreenDrawable"
              android:resource="@drawable/launch_background"
              />
            
        </activity>
        // 4
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>
  1. 項目的Application的類為io.flutter.app.FlutterApplication;
  2. 默認定義了一個白色的主題执俩,在Flutter APP加載顯示前用戶可見徐钠,在Flutter APP加載顯示后作為Window的背景;
  3. 可以修改啟動圖奠滑;
  4. 使用Flutter Android Embedding V2 版本丹皱。FlutterActivity,FlutterActivity,FlutterActivityFlutterActivity等類都是在V2版本中引入的妒穴。

FlutterApplication

FlutterApplication中的代碼很簡單,就主要執(zhí)行了FlutterInjector.instance().flutterLoader().startInitialization(this)這行代碼讼油。

public class FlutterApplication extends Application {
  @Override
  @CallSuper
  public void onCreate() {
    super.onCreate();
    FlutterInjector.instance().flutterLoader().startInitialization(this);
  }
}

FlutterLoader主要的作用就是加載FLutter Engine 和加載Flutter APP的資源等杰赛。我們看看FlutterLoaderstartInitialization方法中的主要代碼:

public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
    // 1.
    if (Looper.myLooper() != Looper.getMainLooper()) {
      throw new IllegalStateException("startInitialization must be called on the main thread");
    }

    // 2.
    VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE))
        .init();

    // 3.
    Callable<InitResult> initTask =
        new Callable<InitResult>() {
          @Override
          public InitResult call() {
            ...
          }
        };
  }
  1. 確保本方法是在主線程執(zhí)行;
  2. VsyncWaiter進行初始化矮台,它的主要作用是在Android系統(tǒng)注冊并等待VSync信號乏屯。

VsyncWaiterFlutter渲染的中繼者,當收到信號后瘦赫,會通知Flutter app發(fā)起渲染調用辰晕,然后執(zhí)行一些列的layout和paint,最后提交給GPU線程合成上屏确虱。

  1. 開啟一個異步線程含友,加載一些asset資源。

總結:FlutterLoader執(zhí)行startInitialization是為Flutter app的 加載和渲染做好準備工作校辩。

FlutterActivity

我們來看看FlutterActivity的重要代碼:

public class FlutterActivity extends Activity {
    // 1
    protected FlutterActivityAndFragmentDelegate delegate;
    // 2
    private LifecycleRegistry lifecycle;

    public FlutterActivity() {
        lifecycle = new LifecycleRegistry(this);
    }
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        // 3.1
        switchLaunchThemeForNormalTheme();
    
        super.onCreate(savedInstanceState);

        // 3.2
        delegate = new FlutterActivityAndFragmentDelegate(this);
        delegate.onAttach(this);
        delegate.onActivityCreated(savedInstanceState);
        
        // 3.3
        configureWindowForTransparency();
        // 3.4
        setContentView(createFlutterView());
        // 3.5
        configureStatusBarForFullscreenFlutterExperience();
    }
    
    private View createFlutterView() {
    return delegate.onCreateView(null , null, null);
  }
}
  1. FlutterActivityAndFragmentDelegate屬性delegate可以說是負責處理FLutter app相關的絕大部分功能的對象窘问;
  2. LifecycleRegistry是一個LifeCycle,處理FlutterActivity生命周期的事情,在構造函數中初始化宜咒;
  3. onCreate方法中主要的工作是:
    • 先切換到啟動圖的主題顯示啟動圖片
    • 初始化FlutterActivityAndFragmentDelegate對象delegate
    • 然后將window的背景設置透明
    • FlutterActivityView添加由delegate對象創(chuàng)建的一個FlutterView惠赫,作為FLutter app的渲染ViewFlutterViewSurfaceView的子類故黑。
    • Android 5.0以上設置為沉浸式狀態(tài)欄

到此為止儿咱,一切都已經準備就緒了庭砍,就等FlutterActivityAndFragmentDelegate將內容加載進來了。

FlutterActivityAndFragmentDelegate

  1. 構造函數傳入FlutterActivity作為Host, 主要就是為了獲取context混埠。
FlutterActivityAndFragmentDelegate(@NonNull Host host) {
    this.host = host;
  }
  1. onAttach主要是初始化了Flutter Engine和注冊了Flutter插件;
void onAttach(@NonNull Context context) {
    ensureAlive();

    if (flutterEngine == null) {
      setupFlutterEngine();
    }
    
    host.configureFlutterEngine(flutterEngine);
  }
// FlutterActivity
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
 }

GeneratedPluginRegistrant這個注冊插件的邏輯和iOS的類似逗威。

  1. 加載Flutter App的內容

FlutterActivity執(zhí)行onStart時會調用FlutterActivityAndFragmentDelegateonStart方法,然后就從lib/main.dart入口文件開始執(zhí)行了凯旭。

void onStart() {
    doInitialFlutterViewRun();
}

private void doInitialFlutterViewRun() {
    
    if (flutterEngine.getDartExecutor().isExecutingDart()) {
      return;
    }

    if (host.getInitialRoute() != null) {
      flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute());
    }

    String appBundlePathOverride = host.getAppBundlePath();
    if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
      appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
    }

    DartExecutor.DartEntrypoint entrypoint =
        new DartExecutor.DartEntrypoint(
            appBundlePathOverride, host.getDartEntrypointFunctionName());
    flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
  }
?著作權歸作者所有,轉載或內容合作請聯系作者
禁止轉載罐呼,如需轉載請通過簡信或評論聯系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奉呛,隨后出現的幾起案子计螺,更是在濱河造成了極大的恐慌登馒,老刑警劉巖陈轿,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件麦射,死亡現場離奇詭異,居然都是意外死亡峻呛,警方通過查閱死者的電腦和手機杀饵,發(fā)現死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谜悟,“玉大人葡幸,你說我怎么就攤上這事床蜘⌒暇猓” “怎么了丹擎?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵榜苫,是天一觀的道長单刁。 經常有香客問我羔飞,道長,這世上最難降的妖魔是什么卡儒? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮擎鸠,結果婚禮上袜蚕,老公的妹妹穿的比我還像新娘牲剃。我一直安慰自己,他們只是感情好狭归,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著疚宇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪榜揖。 梳的紋絲不亂的頭發(fā)上举哟,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天秽褒,我揣著相機與錄音销斟,去河邊找鬼蚂踊。 笑死窗宇,一個胖子當著我的面吹牛军俊,可吹牛的內容都是我干的担败。 我是一名探鬼主播提前,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼笨腥,長吁一口氣:“原來是場噩夢啊……” “哼脖母!你這毒婦竟也來了烤礁?” 一聲冷哼從身側響起脚仔,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掌桩,沒想到半個月后波岛,有當地人在樹林里發(fā)現了一具尸體贡蓖,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡彻桃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驯镊。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖洒放,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情领追,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站订雾,受9級特大地震影響,放射性物質發(fā)生泄漏噩峦。R本人自食惡果不足惜族淮,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望较幌。 院中可真熱鬧,春花似錦岛琼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至义郑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喉誊。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留例获,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像蜜宪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子澳窑,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容