提交 ec8cbe0f 编写于 作者: T Todd Volkert 提交者: GitHub

Refactor FlutterActivity to be more composable (#3748)

This factors the functionality that was in `FlutterActivity`
to live in `FlutterActivityDelegate`. This will allow the creation of a
`FlutterFragmentActivity` that has the same core functionality, which in
turn unlocks certain Android plugins that choose to require the v4
support library (like Google Sign-In).

https://github.com/flutter/flutter/issues/10072
上级 c9bbcbec
......@@ -78,6 +78,8 @@ java_library("flutter_shell_java") {
java_files = [
"io/flutter/app/FlutterActivity.java",
"io/flutter/app/FlutterActivityDelegate.java",
"io/flutter/app/FlutterActivityEvents.java",
"io/flutter/app/FlutterApplication.java",
"io/flutter/plugin/common/ActivityLifecycleListener.java",
"io/flutter/plugin/common/BasicMessageChannel.java",
......@@ -100,6 +102,7 @@ java_library("flutter_shell_java") {
"io/flutter/plugin/editing/TextInputPlugin.java",
"io/flutter/plugin/platform/PlatformPlugin.java",
"io/flutter/util/PathUtils.java",
"io/flutter/util/Preconditions.java",
"io/flutter/view/AccessibilityBridge.java",
"io/flutter/view/FlutterMain.java",
"io/flutter/view/FlutterView.java",
......
......@@ -5,277 +5,138 @@
package io.flutter.app;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.app.FlutterActivityDelegate.ViewFactory;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.view.FlutterMain;
import io.flutter.view.FlutterView;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Base class for activities that use Flutter.
*/
public class FlutterActivity extends Activity implements PluginRegistry {
private final Map<String, Object> pluginMap = new LinkedHashMap<>(0);
private final List<RequestPermissionResultListener> requestPermissionResultListeners = new ArrayList<>(0);
private final List<ActivityResultListener> activityResultListeners = new ArrayList<>(0);
private final List<NewIntentListener> newIntentListeners = new ArrayList<>(0);
private final List<UserLeaveHintListener> userLeaveHintListeners = new ArrayList<>(0);
private FlutterView flutterView;
public class FlutterActivity extends Activity implements FlutterView.Provider, PluginRegistry, ViewFactory {
private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);
private String[] getArgsFromIntent(Intent intent) {
// Before adding more entries to this list, consider that arbitrary
// Android applications can generate intents with extra data and that
// there are many security-sensitive args in the binary.
ArrayList<String> args = new ArrayList<String>();
if (intent.getBooleanExtra("trace-startup", false)) {
args.add("--trace-startup");
}
if (intent.getBooleanExtra("start-paused", false)) {
args.add("--start-paused");
}
if (intent.getBooleanExtra("use-test-fonts", false)) {
args.add("--use-test-fonts");
}
if (intent.getBooleanExtra("enable-dart-profiling", false)) {
args.add("--enable-dart-profiling");
}
if (intent.getBooleanExtra("enable-software-rendering", false)) {
args.add("--enable-software-rendering");
}
if (!args.isEmpty()) {
String[] argsArray = new String[args.size()];
return args.toArray(argsArray);
}
return null;
}
// These aliases ensure that the methods we forward to the delegate adhere
// to relevant interfaces versus just existing in FlutterActivityDelegate.
private final FlutterActivityEvents eventDelegate = delegate;
private final FlutterView.Provider viewProvider = delegate;
private final PluginRegistry pluginRegistry = delegate;
/**
* @see android.app.Activity#onCreate(android.os.Bundle)
* Returns the Flutter view used by this activity; will be null before
* {@link #onCreate(Bundle)} is called.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public FlutterView getFlutterView() {
return viewProvider.getFlutterView();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(0x40000000);
window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
}
/**
* Hook for subclasses to customize their startup behavior.
*
* @deprecated Just override {@link #onCreate(Bundle)} instead, and add your
* logic after calling {@code super.onCreate()}.
*/
@Deprecated
protected void onFlutterReady() {}
String[] args = getArgsFromIntent(getIntent());
FlutterMain.ensureInitializationComplete(getApplicationContext(), args);
flutterView = new FlutterView(this);
setContentView(flutterView);
/**
* Hook for subclasses to customize the creation of the
* {@code FlutterView}.
* <p/>
* The default implementation returns {@code null}, which will cause the
* activity to use a newly instantiated full-screen view.
*/
@Override
public FlutterView createFlutterView(Context context) {
return null;
}
onFlutterReady();
@Override
public final boolean hasPlugin(String key) {
return pluginRegistry.hasPlugin(key);
}
@Override
public boolean hasPlugin(String key) {
return pluginMap.containsKey(key);
public final <T> T valuePublishedByPlugin(String pluginKey) {
return pluginRegistry.valuePublishedByPlugin(pluginKey);
}
@Override
@SuppressWarnings("unchecked")
public <T> T valuePublishedByPlugin(String pluginKey) {
return (T) pluginMap.get(pluginKey);
public final Registrar registrarFor(String pluginKey) {
return pluginRegistry.registrarFor(pluginKey);
}
@Override
public Registrar registrarFor(String pluginKey) {
if (pluginMap.containsKey(pluginKey)) {
throw new IllegalStateException("Plugin key " + pluginKey + " is already in use");
}
pluginMap.put(pluginKey, null);
return new FlutterRegistrar(pluginKey);
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
eventDelegate.onCreate(savedInstanceState);
onFlutterReady();
}
/**
* @see android.app.Activity#onDestroy()
*/
@Override
protected void onDestroy() {
if (flutterView != null) {
flutterView.destroy();
}
eventDelegate.onDestroy();
super.onDestroy();
}
@Override
public void onBackPressed() {
if (flutterView != null) {
flutterView.popRoute();
return;
if (!eventDelegate.onBackPressed()) {
super.onBackPressed();
}
super.onBackPressed();
}
@Override
protected void onPause() {
super.onPause();
if (flutterView != null) {
flutterView.onPause();
}
eventDelegate.onPause();
}
@Override
protected void onPostResume() {
super.onPostResume();
if (flutterView != null) {
flutterView.onPostResume();
}
}
/**
* Override this function to customize startup behavior.
*/
protected void onFlutterReady() {
if (loadIntent(getIntent())) {
return;
}
String appBundlePath = FlutterMain.findAppBundlePath(getApplicationContext());
if (appBundlePath != null) {
flutterView.runFromBundle(appBundlePath, null);
return;
}
eventDelegate.onPostResume();
}
// @Override - added in API level 23
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
for (RequestPermissionResultListener listener : requestPermissionResultListeners) {
if (listener.onRequestPermissionResult(requestCode, permissions, grantResults)) {
return;
}
}
eventDelegate.onRequestPermissionResult(requestCode, permissions, grantResults);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
for (ActivityResultListener listener : activityResultListeners) {
if (listener.onActivityResult(requestCode, resultCode, data)) {
return;
}
if (!eventDelegate.onActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
protected void onNewIntent(Intent intent) {
if (!loadIntent(intent)) {
for (NewIntentListener listener : newIntentListeners) {
if (listener.onNewIntent(intent)) {
return;
}
}
}
eventDelegate.onNewIntent(intent);
}
@Override
public void onUserLeaveHint() {
for (UserLeaveHintListener listener : userLeaveHintListeners) {
listener.onUserLeaveHint();
}
eventDelegate.onUserLeaveHint();
}
public boolean loadIntent(Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_RUN.equals(action)) {
String route = intent.getStringExtra("route");
String appBundlePath = intent.getDataString();
if (appBundlePath == null) {
// Fall back to the installation path if no bundle path
// was specified.
appBundlePath =
FlutterMain.findAppBundlePath(getApplicationContext());
}
flutterView.runFromBundle(appBundlePath,
intent.getStringExtra("snapshot"));
if (route != null)
flutterView.pushRoute(route);
return true;
}
return false;
@Override
public void onTrimMemory(int level) {
eventDelegate.onTrimMemory(level);
}
/**
* Returns the Flutter view used by this activity, may be null before
* onCreate was called.
* @return The FlutterView.
*/
public FlutterView getFlutterView() {
return flutterView;
@Override
public void onLowMemory() {
eventDelegate.onLowMemory();
}
@Override
public void onTrimMemory(int level) {
// Use a trim level delivered while the application is running so the
// framework has a chance to react to the notification.
if (level == TRIM_MEMORY_RUNNING_LOW)
flutterView.onMemoryPressure();
public void onConfigurationChanged(Configuration newConfig) {
eventDelegate.onConfigurationChanged(newConfig);
}
private class FlutterRegistrar implements Registrar {
private final String pluginKey;
FlutterRegistrar(String pluginKey) {
this.pluginKey = pluginKey;
}
public Activity activity() {
return FlutterActivity.this;
}
public BinaryMessenger messenger() {
return getFlutterView();
}
/**
* Publishes a value associated with the plugin being registered.
*
* <p>The published value is available to interested clients via
* {@link PluginRegistry#valuePublishedByPlugin(String)}.</p>
*
* <p>Publication should be done only when there is an interesting value
* to be shared with other code. This would typically be an instance of
* the plugin's main class itself that must be wired up to receive
* notifications or events from an Android API.
*
* <p>Overwrites any previously published value.</p>
*/
public Registrar publish(Object value) {
pluginMap.put(pluginKey, value);
return this;
}
public Registrar addRequestPermissionResultListener(RequestPermissionResultListener listener) {
requestPermissionResultListeners.add(listener);
return this;
}
public Registrar addActivityResultListener(ActivityResultListener listener) {
activityResultListeners.add(listener);
return this;
}
public Registrar addNewIntentListener(NewIntentListener listener) {
newIntentListeners.add(listener);
return this;
}
public Registrar addUserLeaveHintListener(UserLeaveHintListener listener) {
userLeaveHintListeners.add(listener);
return this;
}
};
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.app;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.ActivityResultListener;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugin.common.PluginRegistry.RequestPermissionResultListener;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.util.Preconditions;
import io.flutter.view.FlutterMain;
import io.flutter.view.FlutterView;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Class that performs the actual work of tying Android {@link Activity}
* instances to Flutter.
* <p/>
* This exists as a dedicated class (as opposed to being integrated directly
* into {@link FlutterActivity}) to facilitate applications that don't wish
* to subclass {@code FlutterActivity}. The most obvious example of when this
* may come in handy is if an application wishes to subclass the Android v4
* support library's {@code FragmentActivity}.
* <p/>
* <h3>Usage:</h3>
* To wire this class up to your activity, simply forward the events defined
* in {@link FlutterActivityEvents} from your activity to an instance of this
* class. Optionally, you can make your activity implement
* {@link PluginRegistry} and/or {@link FlutterView.Provider} and forward those
* methods to this class as well.
*/
public final class FlutterActivityDelegate
implements FlutterActivityEvents,
FlutterView.Provider,
PluginRegistry {
/**
* Specifies the mechanism by which Flutter views are created during the
* operation of a {@code FlutterActivityDelegate}.
* <p/>
* A delegate's view factory will be consulted during
* {@link #onCreate(Bundle)}. If it returns {@code null}, then the delegate
* will fall back to instantiating a new full-screen {@code FlutterView}.
*/
public interface ViewFactory {
FlutterView createFlutterView(Context context);
}
private final Activity activity;
private final ViewFactory viewFactory;
private final Map<String, Object> pluginMap = new LinkedHashMap<>(0);
private final List<RequestPermissionResultListener> requestPermissionResultListeners = new ArrayList<>(0);
private final List<ActivityResultListener> activityResultListeners = new ArrayList<>(0);
private final List<NewIntentListener> newIntentListeners = new ArrayList<>(0);
private final List<UserLeaveHintListener> userLeaveHintListeners = new ArrayList<>(0);
private FlutterView flutterView;
public FlutterActivityDelegate(Activity activity, ViewFactory viewFactory) {
this.activity = Preconditions.checkNotNull(activity);
this.viewFactory = Preconditions.checkNotNull(viewFactory);
}
@Override
public FlutterView getFlutterView() {
return flutterView;
}
@Override
public boolean hasPlugin(String key) {
return pluginMap.containsKey(key);
}
@Override
@SuppressWarnings("unchecked")
public <T> T valuePublishedByPlugin(String pluginKey) {
return (T) pluginMap.get(pluginKey);
}
@Override
public Registrar registrarFor(String pluginKey) {
if (pluginMap.containsKey(pluginKey)) {
throw new IllegalStateException("Plugin key " + pluginKey + " is already in use");
}
pluginMap.put(pluginKey, null);
return new FlutterRegistrar(pluginKey);
}
@Override
public boolean onRequestPermissionResult(
int requestCode, String[] permissions, int[] grantResults) {
for (RequestPermissionResultListener listener : requestPermissionResultListeners) {
if (listener.onRequestPermissionResult(requestCode, permissions, grantResults)) {
return true;
}
}
return false;
}
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
for (ActivityResultListener listener : activityResultListeners) {
if (listener.onActivityResult(requestCode, resultCode, data)) {
return true;
}
}
return false;
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
window.addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(0x40000000);
window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
}
String[] args = getArgsFromIntent(activity.getIntent());
FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args);
flutterView = viewFactory.createFlutterView(activity);
if (flutterView == null) {
flutterView = new FlutterView(activity);
flutterView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
activity.setContentView(flutterView);
}
if (loadIntent(activity.getIntent())) {
return;
}
String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
if (appBundlePath != null) {
flutterView.runFromBundle(appBundlePath, null);
}
}
@Override
public void onNewIntent(Intent intent) {
if (!loadIntent(intent)) {
for (NewIntentListener listener : newIntentListeners) {
if (listener.onNewIntent(intent)) {
return;
}
}
}
}
@Override
public void onPause() {
if (flutterView != null) {
flutterView.onPause();
}
}
@Override
public void onResume() {
}
@Override
public void onPostResume() {
if (flutterView != null) {
flutterView.onPostResume();
}
}
@Override
public void onDestroy() {
if (flutterView != null) {
flutterView.destroy();
}
}
@Override
public boolean onBackPressed() {
if (flutterView != null) {
flutterView.popRoute();
return true;
}
return false;
}
@Override
public void onUserLeaveHint() {
}
@Override
public void onTrimMemory(int level) {
// Use a trim level delivered while the application is running so the
// framework has a chance to react to the notification.
if (level == TRIM_MEMORY_RUNNING_LOW) {
flutterView.onMemoryPressure();
}
}
@Override
public void onLowMemory() {
flutterView.onMemoryPressure();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
}
private static String[] getArgsFromIntent(Intent intent) {
// Before adding more entries to this list, consider that arbitrary
// Android applications can generate intents with extra data and that
// there are many security-sensitive args in the binary.
ArrayList<String> args = new ArrayList<String>();
if (intent.getBooleanExtra("trace-startup", false)) {
args.add("--trace-startup");
}
if (intent.getBooleanExtra("start-paused", false)) {
args.add("--start-paused");
}
if (intent.getBooleanExtra("use-test-fonts", false)) {
args.add("--use-test-fonts");
}
if (intent.getBooleanExtra("enable-dart-profiling", false)) {
args.add("--enable-dart-profiling");
}
if (intent.getBooleanExtra("enable-software-rendering", false)) {
args.add("--enable-software-rendering");
}
if (!args.isEmpty()) {
String[] argsArray = new String[args.size()];
return args.toArray(argsArray);
}
return null;
}
private boolean loadIntent(Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_RUN.equals(action)) {
String route = intent.getStringExtra("route");
String appBundlePath = intent.getDataString();
if (appBundlePath == null) {
// Fall back to the installation path if no bundle path
// was specified.
appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
}
flutterView.runFromBundle(appBundlePath, intent.getStringExtra("snapshot"));
if (route != null) {
flutterView.pushRoute(route);
}
return true;
}
return false;
}
private class FlutterRegistrar implements Registrar {
private final String pluginKey;
FlutterRegistrar(String pluginKey) {
this.pluginKey = pluginKey;
}
@Override
public Activity activity() {
return activity;
}
@Override
public BinaryMessenger messenger() {
return flutterView;
}
/**
* Publishes a value associated with the plugin being registered.
*
* <p>The published value is available to interested clients via
* {@link PluginRegistry#valuePublishedByPlugin(String)}.</p>
*
* <p>Publication should be done only when there is an interesting value
* to be shared with other code. This would typically be an instance of
* the plugin's main class itself that must be wired up to receive
* notifications or events from an Android API.
*
* <p>Overwrites any previously published value.</p>
*/
@Override
public Registrar publish(Object value) {
pluginMap.put(pluginKey, value);
return this;
}
@Override
public Registrar addRequestPermissionResultListener(
RequestPermissionResultListener listener) {
requestPermissionResultListeners.add(listener);
return this;
}
@Override
public Registrar addActivityResultListener(ActivityResultListener listener) {
activityResultListeners.add(listener);
return this;
}
@Override
public Registrar addNewIntentListener(NewIntentListener listener) {
newIntentListeners.add(listener);
return this;
}
@Override
public Registrar addUserLeaveHintListener(UserLeaveHintListener listener) {
userLeaveHintListeners.add(listener);
return this;
}
}
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.app;
import android.content.ComponentCallbacks2;
import android.content.Intent;
import android.os.Bundle;
import io.flutter.plugin.common.PluginRegistry.ActivityResultListener;
import io.flutter.plugin.common.PluginRegistry.RequestPermissionResultListener;
/**
* A collection of Android {@code Activity} methods that are relevant to the
* core operation of Flutter applications.
* <p/>
* Application authors that use an activity other than {@link FlutterActivity}
* should forward all events herein from their activity to an instance of
* {@link FlutterActivityDelegate} in order to wire the activity up to the
* Flutter framework. This forwarding is already provided in
* {@code FlutterActivity}.
*/
public interface FlutterActivityEvents
extends ComponentCallbacks2, ActivityResultListener, RequestPermissionResultListener {
/**
* @see android.app.Activity#onCreate(android.os.Bundle)
*/
void onCreate(Bundle savedInstanceState);
/**
* @see android.app.Activity#onNewIntent(Intent)
*/
void onNewIntent(Intent intent);
/**
* @see android.app.Activity#onPause()
*/
void onPause();
/**
* @see android.app.Activity#onResume()
*/
void onResume();
/**
* @see android.app.Activity#onPostResume()
*/
void onPostResume();
/**
* @see android.app.Activity#onDestroy()
*/
void onDestroy();
/**
* Invoked when the activity has detected the user's press of the back key.
*
* @return {@code true} if the listener handled the event; {@code false}
* to let the activity continue with its default back button handling.
* @see android.app.Activity#onBackPressed()
*/
boolean onBackPressed();
/**
* @see android.app.Activity#onUserLeaveHint()
*/
void onUserLeaveHint();
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.util;
/**
* Static convenience methods that help a method or constructor check whether
* it was invoked correctly (that is, whether its <i>preconditions</i> were
* met).
*/
public final class Preconditions {
private Preconditions() {}
/**
* Ensures that an object reference passed as a parameter to the calling
* method is not null.
*
* @param reference an object reference
* @return the non-null reference that was validated
* @throws NullPointerException if {@code reference} is null
*/
public static <T> T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
}
return reference;
}
}
......@@ -56,6 +56,24 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class FlutterView extends SurfaceView
implements BinaryMessenger, AccessibilityManager.AccessibilityStateChangeListener {
/**
* Interface for those objects that maintain and expose a reference to a
* {@code FlutterView} (such as a full-screen Flutter activity).
* <p/>
* This indirection is provided to support applications that use an
* activity other than {@link FlutterActivity} (e.g. Android v4 support
* library's {@code FragmentActivity}). It allows Flutter plugins to deal
* in this interface and not require that the activity be a subclass of
* {@code FlutterActivity}.
*/
public interface Provider {
/**
* Returns a reference to the Flutter view maintained by this object.
* This may be {@code null}.
*/
FlutterView getFlutterView();
}
private static final String TAG = "FlutterView";
private static final String ACTION_DISCOVER = "io.flutter.view.DISCOVER";
......
......@@ -1430,6 +1430,8 @@ FILE: ../../../flutter/shell/gpu/gpu_surface_software.cc
FILE: ../../../flutter/shell/gpu/gpu_surface_software.h
FILE: ../../../flutter/shell/platform/android/android_surface_software.cc
FILE: ../../../flutter/shell/platform/android/android_surface_software.h
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityEvents.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BinaryCodec.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/EventChannel.java
......@@ -1444,6 +1446,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/StandardM
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/StandardMethodCodec.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/StringCodec.java
FILE: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java
FILE: ../../../flutter/shell/platform/android/io/flutter/util/Preconditions.java
FILE: ../../../flutter/shell/platform/android/platform_view_android_jni.cc
FILE: ../../../flutter/shell/platform/android/platform_view_android_jni.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterBinaryMessenger.h
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册