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);
}
このコードブロックをポップアップで表示
