如何从 React Native Android 模块访问 Activity?

How to access Activity from a React Native Android module?

我正在尝试桥接 Android 将屏幕保持在 React Native 上的功能。我想我可以用一个简单的模块来做到这一点,但是我不知道如何从所述模块访问当前的 Android Activity。

我需要 Activity 参考,这样我就可以调用 .getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

我试图像这样通过转换获得 activity ((Activity)getReactApplicationContext().getBaseContext()),但这会引发 "cannot be cast to Android.app.Activity" 错误

https://github.com/facebook/react-native/blob/master/docs/NativeModulesAndroid.md 不是很清楚,但是你可以阅读 React Native 发行版中 ToastModule 的源代码,看看他们是怎么做的 github.com/facebook/react-native/blob/daba14264c9f0d29c0327440df25d81c2f58316c/ReactAndroid/src/main/java/com/facebook/react/modules/toast/ToastModule.java#L31-L33

想法是主 activity 将作为 reactContext 在 github.com/facebook/react-native/blob/3b4845f93c296ed93c348733809845d587227823/local-cli/generator-android/templates/package/MainActivity 中从 ReactInstanceManager.builder() 调用的模块的初始化程序中传递.java#L28 通过实例化 MainReactPackage github.com/facebook/react-native/blob/daba14264c9f0d29c0327440df25d81c2f58316c/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java#L49

已编辑:

问题是 getReactApplicationContext() return 是应用程序的上下文,而不是 Activity。您不能将应用程序上下文强制转换为 Activity。

这是一个简单的解决方法

由于react-native中通常只有一个activity(MainActivity),我们可以在MainActivity中写一个静态函数到return activity

private static Activity mCurrentActivity = null;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mCurrentActivity = this;
    ...
}
...
public static Activity getActivity(){
    Activity activity = new Activity();
    activity = mCurrentActivity;
    return activity;
}

然后从桥接模块

调用 MainActivity.getActivity()

CustomReactPackage.java:

public class CustomReactPackage implements ReactPackage {

    private Activity mActivity = null;

    public CustomReactPackage(Activity activity) {
        mActivity = activity;
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        // Add native modules here
        return modules;
    }

    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        List<ViewManager> modules = new ArrayList<>();
        // Add native UI components here
        modules.add(new LSPlayerManager(mActivity));
        return modules;
    }
}

LSPlayerManager 是我的原生 UI 组件。我定义了一个构造函数,以便我可以传入 activity:

public LSPlayerManager(Activity activity) {
    mActivity = activity;
}

最后在定义了 ReactInstanceManagerMainActivity.java 中,我们可以将 activity 传递给我们的自定义 React 包:

mReactInstanceManager = ReactInstanceManager.builder()
        .setApplication(getApplication())
        .setBundleAssetName("index.android.bundle")
        .setJSMainModuleName("src/index.android")
        .addPackage(new MainReactPackage())
        .addPackage(new CustomReactPackage(this)) // <--- LIKE THIS!
        .setUseDeveloperSupport(BuildConfig.DEBUG)
        .setInitialLifecycleState(LifecycleState.RESUMED)
        .build();

REACT NATIVE 0.29.0 更新

这不再是您在本机模块中访问 activity 的方式。有关迁移说明,请参阅 https://github.com/facebook/react-native/releases/tag/v0.29.0

我猜想 ReactContextBaseJavaModulegetCurrentActivity() 方法可能像下面的代码一样使用,它是从 React-Native 原始代码复制而来的;

import android.app.Activity;
import com.facebook.react.bridge.ReactContextBaseJavaModule;

public class AwesomeModule extends ReactContextBaseJavaModule {

  public AwesomeModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  @Override
  public String getName() {
    return "AwesomeAndroid";
  }

  private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
  private static final String ERROR_NO_ACTIVITY_MESSAGE = "Tried to do the something while not attached to an Activity";

  @ReactMethod
  public void doSomething(successCallback, errorCallback) {

    final Activity activity = getCurrentActivity();

    if (activity == null) {
      errorCallback(ERROR_NO_ACTIVITY, ERROR_NO_ACTIVITY_MESSAGE);
      return;
    }

  }

}

对于 0.28 及之前的版本,您可以使用相同的字段从 ReactInstanceManagerImplprivate @Nullable Activity mCurrentActivity;、0.29+ 的 ReactContextBaseJavaModule 中获取 activity。

如何将 activity 传递给来自 ReactApplication::getPackages() 的包?

我不明白。我找不到任何明确的例子

package com.bluetoothioexample;

import android.app.Application;
import android.util.Log;

import com.facebook.react.bridge.ReactContextBaseJavaModule;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;

import java.util.Arrays;
import java.util.List;

import com.subsite.bluetoothio.BluetoothIOPackage;
import com.oblador.vectoricons.VectorIconsPackage;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override
    protected boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
          new BluetoothIOPackage(this), //<- How pass the activity
                                        // from ReactApplication ?
          new VectorIconsPackage()
      );
    }
  };

  @Override
  public ReactNativeHost getReactNativeHost() {
      return mReactNativeHost;
  }
}

回答:您没有将 Activity 从 MainApplication 传递给包。

您从 ReactContextBaseJavaModule 中调用 getCurrentActivity()

我需要修改一个过时的模块 (react-android-360-video),发现这很有帮助...

android/app/src/main/java/com/webcdpmobiledemo/MainApplication.java中,我使用了new格式来添加包:

...
import com.vrvideocomponent.VrVideoViewPackage;

public class MainApplication extends Application implements ReactApplication {

    ...

    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
      ...

      @Override
      protected List<ReactPackage> getPackages() {
        return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new VrVideoViewPackage()
        );
      }

      ...
    };

    ...
}

android/app/src/main/java/com/webcdpmobiledemo/MainActivity.java本质上是空的:

package com.yourproject;

import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "YourProject";
    }
}

然后,我修改了VrVideoViewPackage文件,它需要将reactContext传递给它调用的VrVideoViewManager

...

public class VrVideoViewPackage implements ReactPackage {
    ...

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.<ViewManager>asList(
                new VrVideoViewManager(reactContext)
        );
    }

}

最后,在 VrVideoViewManager 中可以像这样访问 activity:

...

import android.app.Activity;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.bridge.ReactContext;

...

public class VrVideoViewManager extends SimpleViewManager<VrVideoView> {
    ...

    public VrVideoViewManager(ReactContext reactContext) {
        // Do not store mActivity, always getCurrentActivity when needed
        Activity mActivity = mContext.getCurrentActivity();
    }

    @Override
    protected VrVideoView createViewInstance(ThemedReactContext reactContext) {

        // You can also activity from ThemedReactContext
        Activity mActivity = reactContext.getCurrentActivity();

        VrVideoView vrView = new VrVideoView(mActivity);
        vrView.setEventListener(new ActivityEventListener(vrView));
        vrView.pauseVideo();
        return new VrVideoView(mActivity);
    }

    ...
}

使用 UI 线程上下文获取 Activity / Window / 装饰视图

在您的 class 文件中导入包括...

import android.view.WindowManager;
import android.view.Window;
import android.view.View;

在你的 class 构造函数中...

private static ReactApplicationContext reactContext;
MyModule(ReactApplicationContext context) {
  super(context);
  reactContext = context;
}

然后在 class 方法中...

@ReactMethod
public void doSomethingRequiringWindow() {
  // get access to current UI thread first
  reactContext.getCurrentActivity().runOnUiThread(new Runnable() {
    @Override
    public void run() {
      Window window = reactContext
       .getCurrentActivity().getWindow();
      View decorView = reactContext
       .getCurrentActivity().getWindow().getDecorView();
      // you have access to main ui thread so you can effect
      // immediate behavior on window and decorView
    }
  });
}

对于自定义的UI组件,可以在构造函数中获取context,之后可以通过context.getCurrentActivity()

获取activity

如果你想在 ReactContextBaseJavaModule 子类中获取前景 activity 的当前实例,请使用:

// class MyReactModule extends ReactContextBaseJavaModule
Activity activity = getCurrentActivity();

它来自 ReactContextBaseJavaModule#getCurrentActivity,它使用 reactContext.getCurrentActivity()

Note: ReactApplicationContext#getCurrentActivity is package private. So you can not use reactContext.getCurrentActivity().

  • ReactContextBaseJavaModule's 受保护。所以推导它使它可以实现:
protected @Nullable final Activity getCurrentActivity() {
    return mReactApplicationContext.getCurrentActivity();
  }
  • ReactApplicationContext 是包私有的(默认访问)。所以你拿不到:
/* package */ @Nullable Activity getCurrentActivity() {
    return mCurrentActivity;
  }

Bunos

Since it's @Nullable, make sure you check it's availability:

if (reactContext.hasCurrentActivity()) {
   // There is. So get it
   Activity theActivity = getCurrentActivity();
}