Cocos游戏集成Android原生隐私弹窗开发指南 1. Cocos项目集成Android原生隐私弹窗的必要性在移动应用开发领域隐私合规已经成为不可忽视的关键环节。去年某知名游戏因隐私政策不合规被下架的事件给整个行业敲响了警钟。对于使用Cocos引擎开发的游戏或应用虽然引擎本身提供了跨平台能力但涉及到平台强制的隐私合规要求时我们必须深入原生层实现定制化解决方案。Android平台自Android 10起逐步强化了隐私政策要求2023年最新统计显示Google Play因隐私问题拒绝上架的应用中有32%是由于隐私弹窗实现不规范。这不仅仅是技术实现问题更关系到产品能否顺利发布和运营。通过Android Studio创建原生隐私弹窗的优势在于完全遵循Android设计规范避免因UI/UX不符合平台要求被拒可以精准控制弹窗出现时机确保在数据收集前获得用户授权能够深度集成系统级隐私API如权限请求、数据访问记录等当政策变化时只需修改原生代码即可快速响应无需重新编译Cocos部分2. 开发环境准备与项目结构调整2.1 基础环境配置在开始之前请确保你的开发环境满足以下要求Cocos Creator 3.7推荐3.8.1及以上版本Android Studio Giraffe | 2022.3.1注意版本兼容性JDK 17Android Studio新版默认配置Android SDK API Level 33Gradle 8.0建议使用Android Studio自动管理的版本重要提示避免使用汉化版Android Studio某些汉化包会导致gradle同步异常。如果必须使用中文界面建议通过官方设置切换语言而非安装第三方汉化包。2.2 Cocos项目导出设置在Cocos Creator中执行以下操作打开项目设置 → 功能裁剪确保勾选Android平台下的使用APK打包选项在构建发布面板中设置目标平台为Android点击构建生成Android工程建议输出目录命名为android-build构建完成后你会在输出目录看到以下关键文件结构android-build/ ├── app/ │ ├── build.gradle │ ├── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ └── res/ ├── gradle/ └── settings.gradle3. 创建原生隐私弹窗Activity3.1 在Android Studio中导入项目启动Android Studio选择Open而非Import Project导航到android-build目录选择settings.gradle文件等待Gradle同步完成首次可能较慢可配置阿里云镜像加速3.2 创建隐私弹窗Activity右键app模块 → New → Activity → Empty Activity设置以下参数Activity Name:PrivacyPolicyActivityLayout Name:activity_privacy_policyPackage Name: 保持与主Activity相同通常为com.example.yourgame取消勾选Generate Layout File我们将手动创建更复杂的布局在生成的Java文件中修改基类为AppCompatActivitypublic class PrivacyPolicyActivity extends AppCompatActivity { // 后续代码将在这里添加 }3.3 设计弹窗布局在res/layout/下新建activity_privacy_policy.xmlLinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:background#80000000 android:gravitycenter LinearLayout android:layout_width300dp android:layout_heightwrap_content android:orientationvertical android:backgrounddrawable/dialog_bg android:padding20dp TextView android:idid/title android:layout_widthmatch_parent android:layout_heightwrap_content android:text隐私政策 android:textSize20sp android:textColor#333333 android:gravitycenter/ ScrollView android:layout_widthmatch_parent android:layout_height200dp android:layout_marginTop15dp android:layout_marginBottom15dp TextView android:idid/content android:layout_widthmatch_parent android:layout_heightwrap_content android:textColor#666666 android:textSize14sp/ /ScrollView LinearLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationhorizontal android:gravitycenter Button android:idid/btn_disagree android:layout_width120dp android:layout_height40dp android:text不同意 android:backgrounddrawable/btn_bg_normal/ View android:layout_width20dp android:layout_height1dp/ Button android:idid/btn_agree android:layout_width120dp android:layout_height40dp android:text同意 android:backgrounddrawable/btn_bg_primary/ /LinearLayout /LinearLayout /LinearLayout同时创建对应的drawable资源在res/drawable/下创建dialog_bg.xmlshape xmlns:androidhttp://schemas.android.com/apk/res/android solid android:color#FFFFFF/ corners android:radius10dp/ /shape创建按钮背景btn_bg_normal.xml和btn_bg_primary.xml!-- btn_bg_normal.xml -- shape xmlns:androidhttp://schemas.android.com/apk/res/android solid android:color#F0F0F0/ corners android:radius20dp/ stroke android:width1dp android:color#CCCCCC/ /shape !-- btn_bg_primary.xml -- shape xmlns:androidhttp://schemas.android.com/apk/res/android solid android:color#4285F4/ corners android:radius20dp/ /shape4. 实现弹窗逻辑与Cocos交互4.1 完善PrivacyPolicyActivity在PrivacyPolicyActivity.java中添加核心逻辑public class PrivacyPolicyActivity extends AppCompatActivity { private static final String PRIVACY_PREF privacy_pref; private static final String AGREED_KEY has_agreed; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_privacy_policy); // 设置为全屏透明Activity getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); TextView content findViewById(R.id.content); Button btnAgree findViewById(R.id.btn_agree); Button btnDisagree findViewById(R.id.btn_disagree); // 加载隐私政策文本实际项目应该放在strings.xml中 String privacyText 在此处放置您的隐私政策文本...; content.setText(privacyText); btnAgree.setOnClickListener(v - { saveAgreement(true); setResult(RESULT_OK); finish(); }); btnDisagree.setOnClickListener(v - { saveAgreement(false); setResult(RESULT_CANCELED); finish(); }); } private void saveAgreement(boolean agreed) { SharedPreferences pref getSharedPreferences(PRIVACY_PREF, MODE_PRIVATE); pref.edit().putBoolean(AGREED_KEY, agreed).apply(); } public static boolean hasAgreed(Context context) { SharedPreferences pref context.getSharedPreferences(PRIVACY_PREF, MODE_PRIVATE); return pref.getBoolean(AGREED_KEY, false); } }4.2 修改主Activity启动逻辑找到Cocos生成的AppActivity通常位于proj.android/app/src/org/cocos2dx/javascript修改onCreate方法Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 检查是否已同意隐私政策 if (!PrivacyPolicyActivity.hasAgreed(this)) { Intent intent new Intent(this, PrivacyPolicyActivity.class); startActivityForResult(intent, 1001); } else { initCocos(); } } Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode 1001) { if (resultCode RESULT_OK) { initCocos(); } else { // 用户不同意退出应用 finish(); } } } private void initCocos() { // 原Cocos初始化代码 setContentView(R.layout.activity_app); // ...其余初始化逻辑 }4.3 处理AndroidManifest.xml在AndroidManifest.xml中添加PrivacyPolicyActivity声明并设置为透明主题activity android:name.PrivacyPolicyActivity android:themestyle/Theme.AppCompat.Translucent android:exportedfalse/同时确保主Activity配置正确activity android:name.AppActivity android:configChangesorientation|screenSize|keyboardHidden android:screenOrientationportrait android:exportedtrue android:launchModesingleTask intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity5. 进阶优化与常见问题解决5.1 多语言支持实现在res/values/下创建不同语言的strings.xml文件英文版res/values/strings.xmlstring nameprivacy_titlePrivacy Policy/string string nameprivacy_agreeAgree/string string nameprivacy_disagreeDisagree/string string nameprivacy_contentYour privacy policy content in English.../string中文版res/values-zh/strings.xmlstring nameprivacy_title隐私政策/string string nameprivacy_agree同意/string string nameprivacy_disagree不同意/string string nameprivacy_content此处放置中文版隐私政策内容.../string然后在Activity中使用setTitle(R.string.privacy_title); content.setText(R.string.privacy_content); btnAgree.setText(R.string.privacy_agree); btnDisagree.setText(R.string.privacy_disagree);5.2 用户选择不同意时的处理策略在实际项目中直接退出应用可能过于粗暴。建议采用以下策略第一次拒绝展示解释性提示说明必须同意才能使用核心功能第二次拒绝跳转到简化版应用仅保留账户注销等必要功能第三次拒绝真正退出应用修改PrivacyPolicyActivity中的处理逻辑private int disagreeCount 0; btnDisagree.setOnClickListener(v - { disagreeCount; if (disagreeCount 1) { Toast.makeText(this, 请阅读并同意隐私政策以使用完整功能, Toast.LENGTH_LONG).show(); } else if (disagreeCount 2) { startLimitedFunctionMode(); } else { saveAgreement(false); setResult(RESULT_CANCELED); finish(); } }); private void startLimitedFunctionMode() { // 跳转到仅包含基本功能的界面 Intent intent new Intent(this, LimitedFunctionActivity.class); startActivity(intent); finish(); }5.3 常见问题排查问题1弹窗显示黑边或位置不正确解决方案确保根布局背景设置为半透明色android:background#80000000检查内层布局的宽度/高度是否使用固定dp值而非match_parent确认Activity主题设置为透明style nameAppTheme.Translucent parentTheme.AppCompat.Light.NoActionBar item nameandroid:windowIsTranslucenttrue/item item nameandroid:windowBackgroundandroid:color/transparent/item /style问题2Cocos界面在弹窗后出现异常解决方案确保在用户同意后才初始化Cocos引擎在AppActivity中添加Override protected void onResume() { super.onResume(); if (PrivacyPolicyActivity.hasAgreed(this)) { // 恢复Cocos渲染 } }问题3Gradle同步失败解决方案修改gradle-wrapper.propertiesdistributionUrlhttps\://services.gradle.org/distributions/gradle-8.0-bin.zip在build.gradle中添加阿里云镜像repositories { maven { url https://maven.aliyun.com/repository/public } maven { url https://maven.aliyun.com/repository/google } google() mavenCentral() }6. 隐私政策内容最佳实践6.1 内容结构建议一个完整的隐私政策应包含以下部分数据收集类型精确到具体字段数据使用目的每个收集项对应具体用途数据存储方式与期限第三方共享情况如广告SDK、分析工具用户权利修改、删除、导出数据的方法政策更新机制6.2 动态加载方案对于需要频繁更新的政策内容建议采用网络加载本地缓存的方案在PrivacyPolicyActivity中添加private void loadPrivacyContent() { String cachedContent loadCachedContent(); if (cachedContent ! null) { content.setText(cachedContent); } // 异步获取最新内容 new Thread(() - { String latestContent fetchLatestContent(); runOnUiThread(() - { content.setText(latestContent); cacheContent(latestContent); }); }).start(); }实现网络请求和缓存方法private String fetchLatestContent() { try { URL url new URL(https://yourdomain.com/privacy/latest); HttpURLConnection conn (HttpURLConnection) url.openConnection(); InputStream in conn.getInputStream(); BufferedReader reader new BufferedReader(new InputStreamReader(in)); StringBuilder content new StringBuilder(); String line; while ((line reader.readLine()) ! null) { content.append(line); } return content.toString(); } catch (Exception e) { return getString(R.string.privacy_content); // 回退到本地默认 } } private void cacheContent(String text) { FileOutputStream fos null; try { fos openFileOutput(privacy_cache.txt, MODE_PRIVATE); fos.write(text.getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { if (fos ! null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } }6.3 版本控制与用户重新同意当隐私政策有重大更新时应要求用户重新同意在PrivacyPolicyActivity中添加版本检查private static final int CURRENT_VERSION 2; public static boolean needShowAgain(Context context) { SharedPreferences pref context.getSharedPreferences(PRIVACY_PREF, MODE_PRIVATE); int savedVersion pref.getInt(version, 0); boolean hasAgreed pref.getBoolean(AGREED_KEY, false); return !hasAgreed || savedVersion CURRENT_VERSION; }修改保存逻辑private void saveAgreement(boolean agreed) { SharedPreferences pref getSharedPreferences(PRIVACY_PREF, MODE_PRIVATE); pref.edit() .putBoolean(AGREED_KEY, agreed) .putInt(version, CURRENT_VERSION) .apply(); }更新主Activity检查逻辑if (PrivacyPolicyActivity.needShowAgain(this)) { // 显示弹窗 }7. 与Cocos端的深度集成7.1 通过JSBridge传递用户选择在AppActivity中添加Native方法public class AppActivity extends Cocos2dxActivity { private static AppActivity instance; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); instance this; } public static boolean hasPrivacyAgreed() { return PrivacyPolicyActivity.hasAgreed(instance); } }在Cocos的TypeScript代码中通过反射调用declare namespace jsb { namespace reflection { function callStaticMethod(className: string, methodName: string, ...args: any[]): any; } } function checkPrivacyAgreement(): boolean { if (cc.sys.isNative cc.sys.os cc.sys.OS_ANDROID) { try { return jsb.reflection.callStaticMethod( org/cocos2dx/javascript/AppActivity, hasPrivacyAgreed, ()Z ); } catch (e) { console.error(e); return true; // 默认同意避免阻塞 } } return true; // 非Android平台直接返回true }7.2 处理Cocos中的权限请求在用户同意隐私政策后再请求必要的权限if (checkPrivacyAgreement()) { requestAndroidPermissions([ android.permission.READ_EXTERNAL_STORAGE, android.permission.WRITE_EXTERNAL_STORAGE ]).then(results { // 处理权限结果 }); } function requestAndroidPermissions(permissions: string[]): Promiseboolean[] { return new Promise(resolve { if (cc.sys.isNative cc.sys.os cc.sys.OS_ANDROID) { jsb.reflection.callStaticMethod( org/cocos2dx/javascript/AppActivity, requestPermissions, ([Ljava/lang/String;)V, permissions ); // 需要实现结果回调机制 } else { resolve(permissions.map(() true)); } }); }对应的Java端实现private static PermissionCallback permissionCallback; public static void requestPermissions(String[] permissions) { if (Build.VERSION.SDK_INT Build.VERSION_CODES.M) { instance.requestPermissions(permissions, 1002); } } Override public void onRequestPermissionsResult(int code, String[] permissions, int[] results) { if (code 1002 permissionCallback ! null) { boolean[] granted new boolean[results.length]; for (int i 0; i results.length; i) { granted[i] results[i] PackageManager.PERMISSION_GRANTED; } permissionCallback.onResult(granted); } } interface PermissionCallback { void onResult(boolean[] granted); }7.3 用户撤回同意的处理当用户通过设置界面撤回同意时应停止所有数据收集创建PrivacyManager单例public class PrivacyManager { private static PrivacyManager instance; private boolean dataCollectionEnabled true; public static PrivacyManager getInstance() { if (instance null) { instance new PrivacyManager(); } return instance; } public void setDataCollectionEnabled(boolean enabled) { this.dataCollectionEnabled enabled; // 通知所有数据收集模块 } public boolean isDataCollectionEnabled() { return dataCollectionEnabled PrivacyPolicyActivity.hasAgreed(AppActivity.getInstance()); } }在所有数据收集点添加检查if (PrivacyManager.getInstance().isDataCollectionEnabled()) { // 执行数据收集 }在Cocos端提供设置界面入口function openPrivacySettings() { if (cc.sys.isNative cc.sys.os cc.sys.OS_ANDROID) { jsb.reflection.callStaticMethod( org/cocos2dx/javascript/AppActivity, openPrivacySettings, ()V ); } }Java端实现public static void openPrivacySettings() { Intent intent new Intent(instance, PrivacyPolicyActivity.class); intent.putExtra(from_settings, true); instance.startActivity(intent); }8. 测试与发布注意事项8.1 自动化测试方案创建Espresso测试用例验证隐私弹窗RunWith(AndroidJUnit4.class) public class PrivacyPolicyTest { Rule public ActivityScenarioRuleAppActivity rule new ActivityScenarioRule(AppActivity.class); Test public void testPrivacyDialogShow() { // 模拟首次启动 Context context InstrumentationRegistry.getInstrumentation().getTargetContext(); SharedPreferences pref context.getSharedPreferences( PrivacyPolicyActivity.PRIVACY_PREF, MODE_PRIVATE); pref.edit().clear().apply(); // 验证弹窗Activity是否启动 Intents.init(); onView(withId(R.id.title)).check(matches(isDisplayed())); Intents.release(); } Test public void testAgreeFlow() { // 点击同意按钮 onView(withId(R.id.btn_agree)).perform(click()); // 验证Cocos Activity已初始化 onView(withId(R.id.cocos2d_gl_surface_view)) .check(matches(isDisplayed())); } }8.2 发布前检查清单合规性验证弹窗必须在数据收集前显示不同意选项必须真实有效政策文本包含所有收集的数据类型提供政策更新历史记录功能验证旋转屏幕后布局正常低内存情况下弹窗不消失从后台返回应用时不再重复显示已同意的弹窗多语言切换显示正确性能考量弹窗显示时间不超过300ms不阻塞主线程不显著增加APK大小8.3 监控与统计实现在用户同意后初始化统计SDKprivate void initAnalytics() { if (!PrivacyManager.getInstance().isDataCollectionEnabled()) { return; } // 示例初始化Firebase FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(true); // 示例初始化Umeng MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.AUTO); MobclickAgent.init(this); }在适当位置添加统计点public static void logEvent(String event, Bundle params) { if (!PrivacyManager.getInstance().isDataCollectionEnabled()) { return; } FirebaseAnalytics.getInstance(instance).logEvent(event, params); }9. 扩展功能与未来演进9.1 支持HTML格式隐私政策修改PrivacyPolicyActivity使用WebViewWebView webView findViewById(R.id.webview); webView.loadUrl(file:///android_asset/privacy.html); // 添加本地HTML文件到assets目录 // 需要修改布局文件用WebView替代TextView9.2 区域差异化策略根据用户IP或语言显示不同政策String countryCode Locale.getDefault().getCountry(); if (CN.equals(countryCode)) { // 加载符合中国法规的版本 } else if (EU.equals(countryCode)) { // 加载GDPR版本 } else { // 国际通用版本 }9.3 与后台系统的联动实现政策版本检查APIpublic interface PrivacyService { GET(api/v1/privacy/latest) CallPrivacyResponse getLatestPolicy( Query(appVersion) String appVersion, Query(lang) String language); } public class PrivacyResponse { public int version; public String content; public boolean forceUpdate; }在Activity中调用Retrofit retrofit new Retrofit.Builder() .baseUrl(https://your-api.com/) .addConverterFactory(GsonConverterFactory.create()) .build(); PrivacyService service retrofit.create(PrivacyService.class); CallPrivacyResponse call service.getLatestPolicy( BuildConfig.VERSION_NAME, Locale.getDefault().getLanguage()); call.enqueue(new CallbackPrivacyResponse() { Override public void onResponse(CallPrivacyResponse call, ResponsePrivacyResponse response) { if (response.isSuccessful() response.body() ! null) { int latestVersion response.body().version; if (latestVersion CURRENT_VERSION) { CURRENT_VERSION latestVersion; updateContent(response.body().content); if (response.body().forceUpdate) { btnDisagree.setVisibility(View.GONE); } } } } Override public void onFailure(CallPrivacyResponse call, Throwable t) { // 处理失败情况 } });10. 维护与更新策略10.1 版本迭代管理建议采用语义化版本控制隐私政策主版本号重大内容或结构变更需用户重新同意次版本号新增数据处理类型说明修订号文字修正或格式调整建立版本变更日志## 隐私政策版本历史 ### v2.1.0 (2023-11-15) - 新增关于生物识别数据使用的说明 - 更新数据保留期限至180天 ### v2.0.0 (2023-07-01) [重大更新] - 重构整个政策结构 - 新增第三方数据共享详情 - 需要用户重新同意10.2 紧急更新机制对于必须立即生效的政策变更可采用热更新方案在AppActivity中添加检查private void checkEmergencyUpdate() { PrivacyEmergencyUpdate.check(this, new PrivacyEmergencyUpdate.Callback() { Override public void onUpdateRequired(String policyUrl) { runOnUiThread(() - { Intent intent new Intent(AppActivity.this, EmergencyUpdateActivity.class); intent.putExtra(policy_url, policyUrl); startActivity(intent); }); } }); }实现紧急更新检查器public class PrivacyEmergencyUpdate { public interface Callback { void onUpdateRequired(String policyUrl); } public static void check(Context context, Callback callback) { // 从配置服务器检查紧急更新标志 if (shouldShowEmergencyUpdate(context)) { callback.onUpdateRequired(getEmergencyPolicyUrl()); } } }10.3 A/B测试策略对不同用户群体展示不同风格的弹窗public class PrivacyABTest { public static final int STYLE_BASIC 0; public static final int STYLE_DETAILED 1; public static final int STYLE_INTERACTIVE 2; public static int getStyleForUser(String userId) { // 简单的哈希分桶算法 int bucket Math.abs(userId.hashCode()) % 100; if (bucket 60) return STYLE_BASIC; // 60%基础版 if (bucket 85) return STYLE_DETAILED; // 25%详细版 return STYLE_INTERACTIVE; // 15%交互版 } }在Activity中应用switch (PrivacyABTest.getStyleForUser(currentUserId)) { case STYLE_DETAILED: setContentView(R.layout.activity_privacy_detailed); break; case STYLE_INTERACTIVE: setContentView(R.layout.activity_privacy_interactive); break; default: setContentView(R.layout.activity_privacy_policy); }11. 性能优化技巧11.1 布局渲染优化对于复杂隐私弹窗采用以下优化措施使用ConstraintLayout减少布局层级androidx.constraintlayout.widget.ConstraintLayout android:layout_width300dp android:layout_heightwrap_content TextView android:idid/title app:layout_constraintTop_toTopOfparent app:layout_constraintStart_toStartOfparent app:layout_constraintEnd_toEndOfparent/ ScrollView app:layout_constraintTop_toBottomOfid/title app:layout_constraintBottom_toTopOfid/button_group !-- 内容 -- /ScrollView LinearLayout android:idid/button_group app:layout_constraintBottom_toBottomOfparent !-- 按钮 -- /LinearLayout /androidx.constraintlayout.widget.ConstraintLayout启用硬件加速getWindow().setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);11.2 内存管理及时释放资源Override protected void onDestroy() { super.onDestroy(); if (webView ! null) { webView.destroy(); webView null; } }使用弱引用避免内存泄漏private static class WeakPrivacyCallback implements PrivacyCallback { private WeakReferencePrivacyPolicyActivity activityRef; WeakPrivacyCallback(PrivacyPolicyActivity activity) { this.activityRef new WeakReference(activity); } Override public void onComplete() { PrivacyPolicyActivity activity activityRef.get(); if (activity ! null !activity.isFinishing()) { activity.handleCallback(); } } }11.3 启动时间优化异步加载政策内容private void loadContentAsync() { new Thread(() - { String content loadContent(); runOnUiThread(() - { if (!isFinishing()) { textView.setText(content); } }); }).start(); }预初始化关键组件Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); // 提前初始化WebView进程 if (Build.VERSION.SDK_INT Build.VERSION_CODES.LOLLIPOP) { WebView.setDataDirectorySuffix(privacy); } }12. 安全增强措施12.1 存储加密对用户同意记录进行加密public class PrivacyStorage { private static final String MASTER_KEY privacy_master_key_123; // 实际项目应从密钥库获取 public static void saveAgreement(Context context, boolean agreed) { try { SharedPreferences pref context.getSharedPreferences( encrypted_privacy, MODE_PRIVATE); String encrypted encrypt(MASTER_KEY, String.valueOf(agreed)); pref.edit().putString(agreement, encrypted).apply(); } catch (Exception e) { Log.e(PrivacyStorage, Encryption failed, e); } } private static String encrypt(String key, String value) throws Exception { // 实现AES加密 } }12.2 防篡改验证添加签名验证防止数据被篡改public class PrivacyVerifier { public static boolean verifyAgreement(Context context) { SharedPreferences pref context.getSharedPreferences( privacy_pref, MODE_PRIVATE); boolean agreed pref.getBoolean(AGREED_KEY, false); String signature pref.getString(signature, ); return verifySignature(agreed, signature); } private static boolean verifySignature(boolean value, String signature) { // 实现HMAC验证 } }12.3 防止逆向工程使用ProGuard混淆关键类-keep class com.yourpackage.PrivacyPolicyActivity { *; } -keepclassmembers class com.yourpackage.PrivacyManager { *; }关键方法使用NDK实现public native boolean checkAgreementStatus(); static { System.loadLibrary(privacy_native); }13. 适配不同设备类型13.1 平板设备适配针对大屏幕优化布局layout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto androidx.constraintlayout.widget.ConstraintLayout android:layout_widthdimen/privacy_dialog_width android:layout_heightwrap_content app:layout_constraintWidth_max600dp app:layout_constraintWidth_percent0.8 !-- 内容 -- /androidx.constraintlayout.widget.ConstraintLayout /layout13.2 折叠屏设备支持监听屏幕变化private WindowManager.LayoutParams params; private WindowMetricsCalculator windowMetricsCalculator; Override protected void onCreate(Bundle savedInstanceState) { windowMetricsCalculator WindowMetricsCalculator.getOrCreate(); params getWindow().getAttributes(); updateWindowMetrics(); } private void updateWindowMetrics() { WindowMetrics metrics windowMetricsCalculator.computeCurrentWindowMetrics(this); Rect bounds metrics.getBounds(); if (bounds.width() 1200) { // 大屏幕 params.width (int) (bounds.width() * 0.6); } else { params.width (int) (bounds.width() * 0.9); } getWindow().setAttributes(params); } Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); updateWindowMetrics(); }13.3 Wear OS适配创建简化版弹窗public class PrivacyWearActivity extends Activity { Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_privacy_wear); BoxInsetLayout root findViewById(R.id.root); root.setOnApplyWindowInsetsListener((v, insets) - { v.setPadding( insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); return insets; }); } }14. 无障碍访问支持14.1 屏幕阅读器适配添加内容描述和焦点顺序LinearLayout android:importantForAccessibilityyes android:focusabletrue android:focusableInTouchModetrue TextView android:idid/title android:contentDescription隐私政策标题 android:importantForAccessibilityyes/ ScrollView android:importantForAccessibilityyes TextView android:idid/content android:content