mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-03 22:48:25 +08:00
Summary: Android API 15 still have 1.5~2.0% distribution (refer: [Dashboard - Android Developer](https://developer.android.com/ndk/guides/standalone_toolchain.html#creating_the_toolchain)). React Native is a good tec but many companies cannot endure loose their consumer. [Choreographer](https://developer.android.com/reference/android/view/Choreographer.html) triggered UI operation is the only reason that React Native Android sdk use minSdkVersion 16, so we can use a backward solution **only in API 15**: [Handler](https://developer.android.com/reference/android/os/Handler.html). In this PR, the biggest change is : - Make core operation of ReactChoreographer to an interface: ReactUIDriver; - Impl ReactUIDriver by Handler => UIDriverHandlerImpl, refactor ReactChoreographer to UIDriverChoreographerImpl; - Let UIDriverFactory to choose which one impl would be in use. (Only use handler in api 15). Closes https://github.com/facebook/react-native/pull/12396 Reviewed By: AaaChiuuu Differential Revision: D4588399 Pulled By: astreet fbshipit-source-id: 76408e53664314dd926e6a553cde6bafbd37779e
126 lines
4.3 KiB
Java
126 lines
4.3 KiB
Java
/**
|
|
* Copyright (c) 2014-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
package com.facebook.react.testing;
|
|
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import android.app.Instrumentation;
|
|
import android.os.SystemClock;
|
|
import android.support.test.InstrumentationRegistry;
|
|
|
|
import com.facebook.react.bridge.ReactContext;
|
|
import com.facebook.react.bridge.UiThreadUtil;
|
|
import com.facebook.react.modules.core.ChoreographerCompat;
|
|
|
|
public class ReactIdleDetectionUtil {
|
|
|
|
/**
|
|
* Waits for both the UI thread and bridge to be idle. It determines this by waiting for the
|
|
* bridge to become idle, then waiting for the UI thread to become idle, then checking if the
|
|
* bridge is idle again (if the bridge was idle before and is still idle after running the UI
|
|
* thread to idle, then there are no more events to process in either place).
|
|
* <p/>
|
|
* Also waits for any Choreographer callbacks to run after the initial sync since things like UI
|
|
* events are initiated from Choreographer callbacks.
|
|
*/
|
|
public static void waitForBridgeAndUIIdle(
|
|
ReactBridgeIdleSignaler idleSignaler,
|
|
final ReactContext reactContext,
|
|
long timeoutMs) {
|
|
UiThreadUtil.assertNotOnUiThread();
|
|
|
|
long startTime = SystemClock.uptimeMillis();
|
|
waitInner(idleSignaler, timeoutMs);
|
|
|
|
long timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime));
|
|
waitForChoreographer(timeToWait);
|
|
waitForJSIdle(reactContext);
|
|
|
|
timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime));
|
|
waitInner(idleSignaler, timeToWait);
|
|
timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime));
|
|
waitForChoreographer(timeToWait);
|
|
}
|
|
|
|
private static void waitForChoreographer(long timeToWait) {
|
|
final int waitFrameCount = 2;
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
UiThreadUtil.runOnUiThread(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
ChoreographerCompat.getInstance().postFrameCallback(
|
|
new ChoreographerCompat.FrameCallback() {
|
|
|
|
private int frameCount = 0;
|
|
|
|
@Override
|
|
public void doFrame(long frameTimeNanos) {
|
|
frameCount++;
|
|
if (frameCount == waitFrameCount) {
|
|
latch.countDown();
|
|
} else {
|
|
ChoreographerCompat.getInstance().postFrameCallback(this);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
try {
|
|
if (!latch.await(timeToWait, TimeUnit.MILLISECONDS)) {
|
|
throw new RuntimeException("Timed out waiting for Choreographer");
|
|
}
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private static void waitForJSIdle(ReactContext reactContext) {
|
|
if (!reactContext.hasActiveCatalystInstance()) {
|
|
return;
|
|
}
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
|
|
reactContext.runOnJSQueueThread(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
latch.countDown();
|
|
}
|
|
});
|
|
|
|
try {
|
|
if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
|
|
throw new RuntimeException("Timed out waiting for JS thread");
|
|
}
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private static void waitInner(ReactBridgeIdleSignaler idleSignaler, long timeToWait) {
|
|
// TODO gets broken in gradle, do we need it?
|
|
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
|
long startTime = SystemClock.uptimeMillis();
|
|
boolean bridgeWasIdle = false;
|
|
while (SystemClock.uptimeMillis() - startTime < timeToWait) {
|
|
boolean bridgeIsIdle = idleSignaler.isBridgeIdle();
|
|
if (bridgeIsIdle && bridgeWasIdle) {
|
|
return;
|
|
}
|
|
bridgeWasIdle = bridgeIsIdle;
|
|
long newTimeToWait = Math.max(1, timeToWait - (SystemClock.uptimeMillis() - startTime));
|
|
idleSignaler.waitForIdle(newTimeToWait);
|
|
instrumentation.waitForIdleSync();
|
|
}
|
|
throw new RuntimeException("Timed out waiting for bridge and UI idle!");
|
|
}
|
|
}
|