RenPy因video_play_edit_sdk导致应用商店审核不通过的问题修复,套壳隐私政策绕过检测
环境:RenPy 8.5
工作目录:renpy-8.5.0-sdk\rapt\project (就是安装完RenPy安卓环境后,对应的构建目录)
RenPy 打包安卓很简单,配置好Java环境,在 RenPy Launcher 里 Grade 下依赖的时候让自己的主机能够访问境外网络,剩下的就是点点点。
但是打包完安卓,要上线国内的应用商店,那就有点麻烦。
因为我发现,构建出来的apk启动的一瞬间就已经加载了对应的 video_play_edit_sdk 这个SDK,这个东西放在国内属于未经用户同意收集手机传感器,正经应用商店肯定是会给你打回的,必须要先同意收集,然后你才能真的收集,不能用户什么都没做一打开apk就在后台收集。
TapTap审核打回例:APP未见向用户明示个人信息收集使用的目的、方式和范围,未经用户同意,存在收集传感器的行为。
我尝试了一些方法,发现没有任何在RenPy配置内部绕过的机制,这就很尴尬。
所以我们只能在外面绕,意思就是说 在安卓层绕。
我们把RenPy的构建逻辑给他改一改,相当于hack一下原本的启动方式。
RenPy安卓端的默认机制是直接拉起 PythonSDLActivity 这个安卓Activity,不要用他这个,因为一启动,sdk就加载了。
我们就自己重新写个新的 Activity,启动的时候先启动我们的,然后这个Activity的作用就是相当于套个壳子,先给用户操作了先,让他同意隐私政策、同意我们收集传感器(你真的在收集,所以隐私政策里必须写上)
用户不同意,就直接退出,整个逻辑和RenPy原生构建出的东西没有任何关系。
等用户点击同意按钮了,我们就自己把原生的 PythonSDLActivity 给拉起来,哎这样就完美了。
做法也很简单,我这里有两个现成的类,直接复制进去即可。
1、renpy-8.5.0-sdk\rapt\project\app 子模块,src/main/AndroidManifest.xml 替换,目的是启动自己写的安卓壳子,而非启动原始的 PythonSDLActivity
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backup"
android:fullBackupOnly="true"
android:icon="@mipmap/icon"
android:label="@string/appName"
android:isGame="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true">
<activity
android:name="org.renpy.android.PrivacyActivity"
android:exported="true"
android:theme="@android:style/Theme.DeviceDefault.Light.NoActionBar"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="org.renpy.android.PythonSDLActivity"
android:label="@string/iconName"
android:alwaysRetainTaskState="true"
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:launchMode="singleTop"
android:screenOrientation="sensorLandscape"
android:exported="true"
>
</activity>
<provider
android:name="org.renpy.android.RenPyFileProvider"
android:authorities="cn.noctella.galgod.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
</application>
<uses-feature android:glEsVersion="0x00030000" android:required="true"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
2、腾笼换鸟,塞自己的具体壳子,直接新建/复制进去到对应位置
renpy-8.5.0-sdk\rapt\project\renpyandroid 子模块,新建 src/main/java/org/renpy/android/PrivacyActivity.java
package org.renpy.android;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.KeyEvent;
import android.view.ViewGroup;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.FrameLayout;
public class PrivacyActivity extends Activity {
private static final String PREFS_NAME = "PrivacyPrefs";
private static final String KEY_ACCEPTED = "hasAcceptedPrivacy";
private static final String PRIVACY_URL = "https://www.baidu.com";
private Button positiveButton;
private int countdown = 5;
private final Handler handler = new Handler(Looper.getMainLooper());
private AlertDialog privacyDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 1. 立即检查权限,已同意直接跳过
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
if (prefs.getBoolean(KEY_ACCEPTED, false)) {
startRenPyGame();
return;
}
// 2. 建立一个纯黑色的根布局,彻底杜绝系统默认的白屏
FrameLayout root = new FrameLayout(this);
root.setBackgroundColor(Color.BLACK);
setContentView(root);
// 3. 稍作延迟弹出对话框,确保窗口句柄准备就绪
handler.postDelayed(this::showPrivacyDialog, 200);
}
private void showPrivacyDialog() {
if (isFinishing() || isDestroyed()) return;
WebView webView = new WebView(this);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
// 禁用硬件加速防止部分老机型 WebView 渲染失败(可选,但稳健)
webView.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null);
webView.setWebViewClient(new WebViewClient());
// 容器设置
FrameLayout provider = new FrameLayout(this);
int padding = (int) (20 * getResources().getDisplayMetrics().density);
provider.setPadding(padding, padding, padding, padding);
provider.addView(webView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
(int) (getResources().getDisplayMetrics().heightPixels * 0.8)
));
webView.loadUrl(PRIVACY_URL);
privacyDialog = new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_DARK) // 强制使用暗色主题
.setTitle("用户协议与隐私政策")
.setView(provider)
.setCancelable(false)
.setPositiveButton("同意", null) // 后面再设置监听,防止点击即消失
.setNegativeButton("拒绝并退出", (dialog, which) -> {
finishAffinity();
System.exit(0);
})
.create();
privacyDialog.setCanceledOnTouchOutside(false);
privacyDialog.setOnKeyListener((d, keyCode, event) -> keyCode == KeyEvent.KEYCODE_BACK);
privacyDialog.show();
// 获取按钮并启动倒计时逻辑
positiveButton = privacyDialog.getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setEnabled(false);
positiveButton.setOnClickListener(v -> {
getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit().putBoolean(KEY_ACCEPTED, true).apply();
privacyDialog.dismiss();
startRenPyGame();
});
startCountdownTimer();
}
private void startCountdownTimer() {
handler.post(new Runnable() {
@Override
public void run() {
if (isFinishing() || isDestroyed()) return;
if (countdown > 0) {
positiveButton.setText("同意 (" + countdown + "s)");
countdown--;
handler.postDelayed(this, 1000);
} else {
positiveButton.setText("同意");
positiveButton.setEnabled(true);
}
}
});
}
private void startRenPyGame() {
try {
Intent intent = new Intent(this, Class.forName("org.renpy.android.PythonSDLActivity"));
startActivity(intent);
finish();
// 彻底移除转场动画,防止白光闪烁
overridePendingTransition(0, 0);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onBackPressed() {
// 禁用物理返回键
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
if (privacyDialog != null && privacyDialog.isShowing()) {
privacyDialog.dismiss();
}
}
}
这两个文件弄完就OK了,里面的百度链接是占位符,生产直接改为对应隐私政策地址即可
然后就是尽量不要点 RenPy Launcher 安卓子菜单中的【强制重新编译】,你一点,这俩类会被回滚,到时候你还得重新操作。不过有的时候修bug/调试时,是需要强制重新编译,只能届时再重新替换代码,反正只要打包前替换,最终提交生产环境的apk是正常的就行。