[Android] Use java for looking up Android API level. (#163558)

android_get_device_api_level is an API 24 API, whereas flutter supports
down to API 21. We have other places in the engine that use this API but
they're not on paths that work < 29. Even though the ndk API seems to
work on older devices we should still probably not rely on it...
This commit is contained in:
Jonah Williams 2025-02-27 17:59:42 -08:00 committed by GitHub
parent 6f71aa9901
commit cd7a24c1b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 96 additions and 32 deletions

View File

@ -95,7 +95,8 @@ void FlutterMain::Init(JNIEnv* env,
jstring kernelPath,
jstring appStoragePath,
jstring engineCachesPath,
jlong initTimeMillis) {
jlong initTimeMillis,
jint api_level) {
std::vector<std::string> args;
args.push_back("flutter");
for (auto& arg : fml::jni::StringArrayToVector(env, jargs)) {
@ -118,8 +119,12 @@ void FlutterMain::Init(JNIEnv* env,
"Dart DevTools.");
}
}
// The API level must be provided from java, as the NDK function
// android_get_device_api_level() is only available on API 24 and greater, and
// Flutter still supports 21, 22, and 23.
AndroidRenderingAPI android_rendering_api = SelectedRenderingAPI(settings);
AndroidRenderingAPI android_rendering_api =
SelectedRenderingAPI(settings, api_level);
switch (android_rendering_api) {
case AndroidRenderingAPI::kSoftware:
case AndroidRenderingAPI::kSkiaOpenGLES:
@ -233,7 +238,7 @@ bool FlutterMain::Register(JNIEnv* env) {
{
.name = "nativeInit",
.signature = "(Landroid/content/Context;[Ljava/lang/String;Ljava/"
"lang/String;Ljava/lang/String;Ljava/lang/String;J)V",
"lang/String;Ljava/lang/String;Ljava/lang/String;JI)V",
.fnPtr = reinterpret_cast<void*>(&Init),
},
{
@ -271,7 +276,8 @@ bool FlutterMain::IsKnownBadSOC(std::string_view hardware) {
// static
AndroidRenderingAPI FlutterMain::SelectedRenderingAPI(
const flutter::Settings& settings) {
const flutter::Settings& settings,
int api_level) {
if (settings.enable_software_rendering) {
FML_CHECK(!settings.enable_impeller)
<< "Impeller does not support software rendering. Either disable "
@ -301,7 +307,6 @@ AndroidRenderingAPI FlutterMain::SelectedRenderingAPI(
// Even if this check returns true, Impeller may determine it cannot use
// Vulkan for some other reason, such as a missing required extension or
// feature.
int api_level = android_get_device_api_level();
if (api_level < kMinimumAndroidApiLevelForVulkan) {
return kVulkanUnsupportedFallback;
}

View File

@ -26,7 +26,8 @@ class FlutterMain {
flutter::AndroidRenderingAPI GetAndroidRenderingAPI();
static AndroidRenderingAPI SelectedRenderingAPI(
const flutter::Settings& settings);
const flutter::Settings& settings,
int api_level);
static bool IsDeviceEmulator(std::string_view product_model);
@ -47,7 +48,8 @@ class FlutterMain {
jstring kernelPath,
jstring appStoragePath,
jstring engineCachesPath,
jlong initTimeMillis);
jlong initTimeMillis,
jint api_level);
void SetupDartVMServiceUriCallback(JNIEnv* env);

View File

@ -180,7 +180,8 @@ public class FlutterJNI {
@Nullable String bundlePath,
@NonNull String appStoragePath,
@NonNull String engineCachesPath,
long initTimeMillis);
long initTimeMillis,
int apiLevel);
/**
* Perform one time initialization of the Dart VM and Flutter engine.
@ -193,6 +194,7 @@ public class FlutterJNI {
* @param appStoragePath The path to the application data directory.
* @param engineCachesPath The path to the application cache directory.
* @param initTimeMillis The time, in milliseconds, taken for initialization.
* @param apiLevel The current Android API level.
*/
public void init(
@NonNull Context context,
@ -200,13 +202,14 @@ public class FlutterJNI {
@Nullable String bundlePath,
@NonNull String appStoragePath,
@NonNull String engineCachesPath,
long initTimeMillis) {
long initTimeMillis,
int apiLevel) {
if (FlutterJNI.initCalled) {
Log.w(TAG, "FlutterJNI.init called more than once");
}
FlutterJNI.nativeInit(
context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis);
context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis, apiLevel);
FlutterJNI.initCalled = true;
}
@ -247,7 +250,7 @@ public class FlutterJNI {
* VM Service URI for the VM instance.
*
* <p>Its value is set by the native engine once {@link #init(Context, String[], String, String,
* String, long)} is run.
* String, long, int)} is run.
*/
@Nullable
public static String getVMServiceUri() {
@ -258,7 +261,7 @@ public class FlutterJNI {
* VM Service URI for the VM instance.
*
* <p>Its value is set by the native engine once {@link #init(Context, String[], String, String,
* String, long)} is run.
* String, long, int)} is run.
*
* @deprecated replaced by {@link #getVMServiceUri()}.
*/
@ -448,7 +451,8 @@ public class FlutterJNI {
* #attachToNative()}.
*
* <p>Static methods that should be only called once such as {@link #init(Context, String[],
* String, String, String, long)} shouldn't be called again on the spawned FlutterJNI instance.
* String, String, String, long, int)} shouldn't be called again on the spawned FlutterJNI
* instance.
*/
@UiThread
@NonNull

View File

@ -390,7 +390,8 @@ public class FlutterLoader {
kernelPath,
result.appStoragePath,
result.engineCachesPath,
initTimeMillis);
initTimeMillis,
Integer.valueOf(android.os.Build.VERSION.SDK_INT));
initialized = true;
} catch (Exception e) {

View File

@ -21,15 +21,10 @@ TEST(AndroidPlatformView, DISABLED_SelectsVulkanBasedOnApiLevel) {
settings.enable_software_rendering = false;
settings.enable_impeller = true;
int api_level = android_get_device_api_level();
EXPECT_GT(api_level, 0);
if (api_level >= 29) {
EXPECT_EQ(FlutterMain::SelectedRenderingAPI(settings),
AndroidRenderingAPI::kImpellerVulkan);
} else {
EXPECT_EQ(FlutterMain::SelectedRenderingAPI(settings),
AndroidRenderingAPI::kImpellerOpenGLES);
}
EXPECT_EQ(FlutterMain::SelectedRenderingAPI(settings, 29),
AndroidRenderingAPI::kImpellerVulkan);
EXPECT_EQ(FlutterMain::SelectedRenderingAPI(settings, 24),
AndroidRenderingAPI::kImpellerOpenGLES);
}
TEST(AndroidPlatformView, SoftwareRenderingNotSupportedWithImpeller) {
@ -37,7 +32,7 @@ TEST(AndroidPlatformView, SoftwareRenderingNotSupportedWithImpeller) {
settings.enable_software_rendering = true;
settings.enable_impeller = true;
ASSERT_DEATH(FlutterMain::SelectedRenderingAPI(settings), "");
ASSERT_DEATH(FlutterMain::SelectedRenderingAPI(settings, 29), "");
}
TEST(AndroidPlatformView, FallsBackToGLESonEmulator) {

View File

@ -9,6 +9,7 @@ import static io.flutter.Build.API_LEVELS;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.eq;
@ -99,7 +100,14 @@ public class FlutterLoaderTest {
final String oldGenHeapArg = "--old-gen-heap-size=" + oldGenHeapSizeMegaBytes;
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong());
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertTrue(arguments.contains(oldGenHeapArg));
}
@ -122,7 +130,14 @@ public class FlutterLoaderTest {
"--resource-cache-max-bytes-threshold=" + resourceCacheMaxBytesThreshold;
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong());
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertTrue(arguments.contains(resourceCacheMaxBytesThresholdArg));
}
@ -140,7 +155,14 @@ public class FlutterLoaderTest {
final String leakVMArg = "--leak-vm=true";
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong());
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertTrue(arguments.contains(leakVMArg));
}
@ -162,7 +184,14 @@ public class FlutterLoaderTest {
final String leakVMArg = "--leak-vm=false";
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong());
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertTrue(arguments.contains(leakVMArg));
}
@ -191,7 +220,14 @@ public class FlutterLoaderTest {
final String enableImpellerArg = "--enable-impeller";
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong());
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertFalse(arguments.contains(enableImpellerArg));
}
@ -209,7 +245,14 @@ public class FlutterLoaderTest {
final String enableVulkanValidationArg = "--enable-vulkan-validation";
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong());
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertFalse(arguments.contains(enableVulkanValidationArg));
}
@ -231,7 +274,14 @@ public class FlutterLoaderTest {
final String enableImpellerArg = "--enable-impeller=true";
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong());
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertTrue(arguments.contains(enableImpellerArg));
}
@ -253,7 +303,14 @@ public class FlutterLoaderTest {
final String disabledControlArg = "--enable-surface-control";
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong());
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertTrue(arguments.contains(disabledControlArg));
}