logo
Development
検索
Android 側でのワークスペース統合ガイド

Android 側でのワークスペース統合ガイド

はじめに

本ガイドでは、Android アプリで GPTBots ワークスペースを統合する詳細な方法について説明します。権限申請、ネイティブと H5 のインタラクション、その他の関連設定を含みます。

GPTBots では、すぐに始められるようワークスペースの DEMO プロジェクトも提供しています。Android 側の DEMO プロジェクト:android-webview-bridge


権限一覧

基本権限

ワークスペース関連の機能を正常に利用できるようにするため、AndroidManifest.xml に以下の権限を設定する必要があります:

<!-- 网络权限 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 录音权限 --> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <!-- 存储权限 --> <!-- Android 13以下 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Android 13及以上 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- 其他辅助权限 --> <uses-permission android:name="android.permission.WAKE_LOCK" />
                      
                      <!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- 录音权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

<!-- 存储权限 -->
<!-- Android 13以下 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android 13及以上 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- 其他辅助权限 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

                    
このコードブロックをポップアップで表示

権限処理

アプリが実行時にマイク、カメラ、アルバムなどの機密権限に正常にアクセスできるようにするため、アプリ起動時に必要なランタイム権限を動的にリクエストする必要があります:

// Android 13及以上请求的权限 private final String[] permissionsForAndroid13 = { Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_VIDEO }; // Android 13以下请求的权限 private final String[] permissionsForBelowAndroid13 = { Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE };
                      
                      // Android 13及以上请求的权限
private final String[] permissionsForAndroid13 = {
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.READ_MEDIA_IMAGES,
    Manifest.permission.READ_MEDIA_AUDIO,
    Manifest.permission.READ_MEDIA_VIDEO
};

// Android 13以下请求的权限
private final String[] permissionsForBelowAndroid13 = {
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.WRITE_EXTERNAL_STORAGE
};

                    
このコードブロックをポップアップで表示

WebView 設定

基本設定

APP で WebView インスタンスを作成し、必要な設定を行います:

WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); // 启用 JavaScript webSettings.setDomStorageEnabled(true); // 启用 DOM 存储 API webSettings.setAllowFileAccess(true); // 允许访问文件 webSettings.setAllowContentAccess(true); // 允许访问内容 URL webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); // 允许混合内容 webSettings.setMediaPlaybackRequiresUserGesture(false); // 不需要用户手势即可播放媒体
                      
                      WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true); // 启用 JavaScript
webSettings.setDomStorageEnabled(true); // 启用 DOM 存储 API
webSettings.setAllowFileAccess(true); // 允许访问文件
webSettings.setAllowContentAccess(true); // 允许访问内容 URL
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); // 允许混合内容
webSettings.setMediaPlaybackRequiresUserGesture(false); // 不需要用户手势即可播放媒体

                    
このコードブロックをポップアップで表示

録音関連の設定

webSettings.setAllowFileAccessFromFileURLs(true); // 允许文件 URL 访问文件 webSettings.setAllowUniversalAccessFromFileURLs(true); // 允许通用访问 webSettings.setDatabaseEnabled(true); // 启用数据库 webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); // 设置缓存模式
                      
                      webSettings.setAllowFileAccessFromFileURLs(true); // 允许文件 URL 访问文件
webSettings.setAllowUniversalAccessFromFileURLs(true); // 允许通用访问
webSettings.setDatabaseEnabled(true); // 启用数据库
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); // 设置缓存模式

                    
このコードブロックをポップアップで表示

User-Agent の設定

String userAgent = webSettings.getUserAgentString(); webSettings.setUserAgentString(userAgent + " WebViewApp/1.0");
                      
                      String userAgent = webSettings.getUserAgentString();
webSettings.setUserAgentString(userAgent + " WebViewApp/1.0");

                    
このコードブロックをポップアップで表示

ワークスペースへのアクセス

URL の構築

ワークスペースへのアクセスには Base URL と AiToken パラメータが必要です:

String baseUrl = "https://gptbots.ai/space/h5/home"; String aiToken = "YOUR_AI_TOKEN"; // 替换为实际的AiToken String fullUrl = baseUrl + "?AiToken=" + aiToken;
                      
                      String baseUrl = "https://gptbots.ai/space/h5/home";
String aiToken = "YOUR_AI_TOKEN"; // 替换为实际的AiToken
String fullUrl = baseUrl + "?AiToken=" + aiToken;

                    
このコードブロックをポップアップで表示

アプリにはデフォルトの AiToken があらかじめ設定されています。変更が必要な場合は、ログインページで入力できます。

AiToken の生成方法については AiToken 暗号化 を参照してください。

ページの読み込み

webView.loadUrl(fullUrl);
                      
                      webView.loadUrl(fullUrl);

                    
このコードブロックをポップアップで表示

WebView とネイティブのインタラクション

JavaScript インターフェースの登録

webView.addJavascriptInterface(new JSBridge(), "agentWebBridge");
                      
                      webView.addJavascriptInterface(new JSBridge(), "agentWebBridge");

                    
このコードブロックをポップアップで表示

H5 ページはグローバルオブジェクト agentWebBridge を通じてネイティブメソッドを呼び出します。


権限処理

WebView の権限リクエスト

H5 ページが特別な権限(録音など)をリクエストした場合、WebView の権限リクエストを処理する必要があります:

webView.setWebChromeClient(new WebChromeClient() { @Override public void onPermissionRequest(PermissionRequest request) { String[] requestedResources = request.getResources(); boolean hasAudioPermission = false; for (String resource : requestedResources) { if (PermissionRequest.RESOURCE_AUDIO_CAPTURE.equals(resource)) { hasAudioPermission = true; break; } } if (hasAudioPermission) { // 检查应用是否有录音权限 if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { request.grant(requestedResources); } else { // 请求系统权限 requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_CODE); // 存储权限请求,在获得权限后处理 pendingPermissionRequest = request; } } else { // 其他权限直接授予 request.grant(requestedResources); } } });
                      
                      webView.setWebChromeClient(new WebChromeClient() {
    @Override
    public void onPermissionRequest(PermissionRequest request) {
        String[] requestedResources = request.getResources();
        boolean hasAudioPermission = false;

        for (String resource : requestedResources) {
            if (PermissionRequest.RESOURCE_AUDIO_CAPTURE.equals(resource)) {
                hasAudioPermission = true;
                break;
            }
        }

        if (hasAudioPermission) {
            // 检查应用是否有录音权限
            if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
                request.grant(requestedResources);
            } else {
                // 请求系统权限
                requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_CODE);
                // 存储权限请求,在获得权限后处理
                pendingPermissionRequest = request;
            }
        } else {
            // 其他权限直接授予
            request.grant(requestedResources);
        }
    }
});

                    
このコードブロックをポップアップで表示

ファイル選択の処理

H5 ページのファイル選択操作を処理します:

webView.setWebChromeClient(new WebChromeClient() { @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { // 保存回调 this.filePathCallback = filePathCallback; // 创建文件选择 Intent Intent intent = fileChooserParams.createIntent(); startActivityForResult(intent, REQUEST_FILE_CHOOSER); return true; } }); @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_FILE_CHOOSER) { if (filePathCallback != null) { Uri[] results = null; if (resultCode == RESULT_OK && data != null) { // 处理选择结果 // ... } filePathCallback.onReceiveValue(results); filePathCallback = null; } } }
                      
                      webView.setWebChromeClient(new WebChromeClient() {
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
        // 保存回调
        this.filePathCallback = filePathCallback;

        // 创建文件选择 Intent
        Intent intent = fileChooserParams.createIntent();
        startActivityForResult(intent, REQUEST_FILE_CHOOSER);
        return true;
    }
});

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == REQUEST_FILE_CHOOSER) {
        if (filePathCallback != null) {
            Uri[] results = null;

            if (resultCode == RESULT_OK && data != null) {
                // 处理选择结果
                // ...
            }

            filePathCallback.onReceiveValue(results);
            filePathCallback = null;
        }
    }
}

                    
このコードブロックをポップアップで表示

機能例

WebView を閉じる

H5 ページは以下の方法で WebView を閉じるようリクエストできます:

var message = { eventType: "click", data: { value: "close" } }; agentWebBridge.callNative(JSON.stringify(message));
                      
                      var message = {
    eventType: "click",
    data: {
        value: "close"
    }
};
agentWebBridge.callNative(JSON.stringify(message));

                    
このコードブロックをポップアップで表示

ネイティブ側の処理:

public void onClick(JSONObject data) { String value = data.optString("value"); if (TextUtils.equals(value, "close")) { closeWeb(data); } } public void closeWeb(JSONObject data) { // 通知 H5 即将关闭 JSONObject willCloseData = new JSONObject(); willCloseData.put("value", data.optString("value")); willCloseData.put("reason", "user_request"); willCloseData.put("delay", 1000); willCloseData.put("timestamp", System.currentTimeMillis()); webViewBridge.callH5(WebViewBridge.EVENT_CLICK, willCloseData); // 延迟关闭 Activity webView.postDelayed(() -> { finish(); }, 1000); }
                      
                      public void onClick(JSONObject data) {
    String value = data.optString("value");
    if (TextUtils.equals(value, "close")) {
        closeWeb(data);
    }
}

public void closeWeb(JSONObject data) {
    // 通知 H5 即将关闭
    JSONObject willCloseData = new JSONObject();
    willCloseData.put("value", data.optString("value"));
    willCloseData.put("reason", "user_request");
    willCloseData.put("delay", 1000);
    willCloseData.put("timestamp", System.currentTimeMillis());

    webViewBridge.callH5(WebViewBridge.EVENT_CLICK, willCloseData);
    // 延迟关闭 Activity
    webView.postDelayed(() -> {
        finish();
    }, 1000);
}

                    
このコードブロックをポップアップで表示

関連ドキュメント