Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ plugins {
alias(libs.plugins.download) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.binary.compatibility.validator) apply true
alias(libs.plugins.android.test) apply false
}

val reactAndroidProperties = java.util.Properties()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ abstract class ReactExtension @Inject constructor(val project: Project) {
val bundleAssetName: Property<String> =
objects.property(String::class.java).convention("index.android.bundle")

/**
* Whether the Bundle Asset should be compressed when packaged into a `.apk`, or not. Disabling
* compression for the `.bundle` allows it to be directly memory-mapped to RAM, hence improving
* startup time - at the cost of a larger resulting `.apk` size.
*/
val enableBundleCompression: Property<Boolean> =
objects.property(Boolean::class.java).convention(false)

/**
* Toggles the .so Cleanup step. If enabled, we will clean up all the unnecessary files before the
* bundle task. If disabled, the developers will have to manually cleanup the files. Default: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package com.facebook.react

import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.facebook.react.internal.PrivateReactExtension
Expand Down Expand Up @@ -81,6 +82,7 @@ class ReactPlugin : Plugin<Project> {
}
configureAutolinking(project, extension)
configureCodegen(project, extension, rootExtension, isLibrary = false)
configureResources(project, extension)
}

// Library Only Configuration
Expand Down Expand Up @@ -110,6 +112,17 @@ class ReactPlugin : Plugin<Project> {
}
}

/** This function configures Android resources - in this case just the bundle */
private fun configureResources(project: Project, reactExtension: ReactExtension) {
if (!reactExtension.enableBundleCompression.get()) {
// Bundle should not be compressed; add it to noCompress blacklist.
val bundleFileName = reactExtension.bundleAssetName.get()
val bundleFileExtension = bundleFileName.substringAfterLast('.', "")
val android = project.extensions.getByType(ApplicationExtension::class.java)
android.androidResources.noCompress.add(bundleFileExtension)
}
}

/** This function sets up `react-native-codegen` in our Gradle plugin. */
@Suppress("UnstableApiUsage")
private fun configureCodegen(
Expand Down
9 changes: 9 additions & 0 deletions packages/react-native/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ fmt="11.0.2"
folly="2024.11.18.00"
glog="0.3.5"
gtest="1.12.1"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
uiautomator = "2.3.0"
benchmarkMacroJunit4 = "1.3.3"

[libraries]
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
Expand Down Expand Up @@ -76,6 +80,10 @@ mockito = {module = "org.mockito:mockito-inline", version.ref = "mockito" }
mockito-kotlin = {module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito-kotlin" }
robolectric = {module = "org.robolectric:robolectric", version.ref = "robolectric" }
thoughtworks = {module = "com.thoughtworks.xstream:xstream", version.ref = "xstream" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmarkMacroJunit4" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
Expand All @@ -84,3 +92,4 @@ download = { id = "de.undercouch.download", version.ref = "download" }
nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus-publish" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
android-test = { id = "com.android.test", version.ref = "agp" }
1 change: 1 addition & 0 deletions packages/rn-tester/android/app/benchmark/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
52 changes: 52 additions & 0 deletions packages/rn-tester/android/app/benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

plugins {
alias(libs.plugins.android.test)
alias(libs.plugins.kotlin.android)
}

android {
namespace = "com.example.benchmark"
compileSdk = 35

defaultConfig {
minSdk = 24
targetSdk = 35

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
// This benchmark buildType is used for benchmarking, and should function like your
// release build (for example, with minification on). It"s signed with a debug key
// for easy local/CI testing.
create("benchmark") {
isDebuggable = true
signingConfig = getByName("debug").signingConfig
matchingFallbacks += listOf("release")
}
}

flavorDimensions += listOf("vm")
productFlavors {
create("hermes") { dimension = "vm" }
create("jsc") { dimension = "vm" }
}

targetProjectPath = ":packages:rn-tester:android:app"
experimentalProperties["android.experimental.self-instrumenting"] = true
}

dependencies {
implementation(libs.androidx.junit)
implementation(libs.androidx.espresso.core)
implementation(libs.androidx.uiautomator)
implementation(libs.androidx.benchmark.macro.junit4)
}

androidComponents { beforeVariants(selector().all()) { it.enable = it.buildType == "benchmark" } }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.example.benchmark

import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.StartupTimingMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/**
* This is an example startup benchmark.
*
* It navigates to the device's home screen, and launches the default activity.
*
* Before running this benchmark:
* 1) switch your app's active build variant in the Studio (affects Studio runs only)
* 2) add `<profileable android:shell="true" />` to your app's manifest, within the `<application>`
* tag
*
* Run this benchmark from Studio to see startup measurements, and captured system traces for
* investigating your app's performance.
*/
@RunWith(AndroidJUnit4::class)
class ExampleStartupBenchmark {
@get:Rule val benchmarkRule = MacrobenchmarkRule()

@Test
fun startup() =
benchmarkRule.measureRepeated(
packageName = "com.facebook.react.uiapp",
metrics = listOf(StartupTimingMetric()),
iterations = 10,
startupMode = StartupMode.COLD) {
pressHome()
startActivityAndWait()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this waits for the react root view to be displayed, and not just a blank activity before react rendered?

Also are these numbers from running on emulator? They look a little bit low, hence why I am suspicious.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The numbers are from running on a Pixel 8 Pro. If you run it locally you can see the RNTester home screen appearing on device 10x times

}
}
10 changes: 9 additions & 1 deletion packages/rn-tester/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ react {
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
hermesCommand = "$reactNativeDirPath/ReactAndroid/hermes-engine/build/hermes/bin/hermesc"
enableHermesOnlyInVariants = listOf("hermesDebug", "hermesRelease")
enableHermesOnlyInVariants = listOf("hermesDebug", "hermesRelease", "hermesBenchmark")

enableBundleCompression.set(false)

autolinkLibrariesWithApp()
}
Expand Down Expand Up @@ -142,6 +144,11 @@ android {
proguardFiles(getDefaultProguardFile("proguard-android.txt"))
signingConfig = signingConfigs.getByName("debug")
}
create("benchmark") {
initWith(buildTypes.getByName("release"))
matchingFallbacks += listOf("release")
isDebuggable = false
}
}
sourceSets.named("main") {
// SampleTurboModule.
Expand All @@ -165,6 +172,7 @@ dependencies {
"jscImplementation"(jscFlavor)

testImplementation(libs.junit)
implementation("androidx.profileinstaller:profileinstaller:1.4.1")
}

android {
Expand Down
169 changes: 89 additions & 80 deletions packages/rn-tester/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,88 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.hardware.location.gps"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.hardware.location.gps"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Just to show permissions example -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<!--Just to show permissions example-->
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<queries>
<package android:name="com.facebook.katana" />
<package android:name="com.facebook.lite" />
<package android:name="com.facebook.android" />

<!--
android:icon is used to display launcher icon on mobile devices.
android:banner is used to display a rectangular banned launcher icon on Android TV devices.
-->
<application
android:name=".RNTesterApplication"
android:allowBackup="true"
android:banner="@drawable/tv_banner"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:supportsRtl="true">
<activity
android:name=".RNTesterActivity"
<intent>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />

<data android:scheme="geo" />
</intent>
<intent>
<action android:name="android.intent.action.DIAL" />

<data android:scheme="tel" />
</intent>
</queries>

<application
android:name=".RNTesterApplication"
android:allowBackup="true"
android:banner="@drawable/tv_banner"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:screenOrientation="fullSensor"
android:launchMode="singleTask"
android:configChanges="orientation|screenSize|uiMode"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<!-- Needed to properly create a launch intent when running on Android TV -->
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "rntester://example” -->
<data android:scheme="rntester" android:host="example" />
</intent-filter>
</activity>
<provider
android:name="com.facebook.react.modules.blob.BlobProvider"
android:authorities="@string/blob_provider_authority"
android:exported="false"
/>
</application>
<queries>
<package android:name="com.facebook.katana" />
<package android:name="com.facebook.lite" />
<package android:name="com.facebook.android" />
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="geo" />
</intent>
<intent>
<action android:name="android.intent.action.DIAL" />
<data android:scheme="tel" />
</intent>
</queries>
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<profileable
android:shell="true"
tools:targetApi="29" />

<activity
android:name=".RNTesterActivity"
android:configChanges="orientation|screenSize|uiMode"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="fullSensor">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
<!-- Needed to properly create a launch intent when running on Android TV -->
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "rntester://example” -->
<data
android:host="example"
android:scheme="rntester" />
</intent-filter>
</activity>

<provider
android:name="com.facebook.react.modules.blob.BlobProvider"
android:authorities="@string/blob_provider_authority"
android:exported="false" />
</application>

</manifest>
Loading
Loading