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 17(Android Studio新版默认配置)
- Android SDK API Level 33
- Gradle 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. 创建原生隐私弹窗Activity
3.1 在Android Studio中导入项目
- 启动Android Studio,选择"Open"而非"Import Project"
- 导航到
android-build目录,选择settings.gradle文件 - 等待Gradle同步完成(首次可能较慢,可配置阿里云镜像加速)
3.2 创建隐私弹窗Activity
右键app模块 → New → Activity → Empty Activity,设置以下参数:
- Activity Name:
PrivacyPolicyActivity - Layout Name:
activity_privacy_policy - Package Name: 保持与主Activity相同(通常为com.example.yourgame)
- 取消勾选"Generate Layout File"(我们将手动创建更复杂的布局)
在生成的Java文件中,修改基类为AppCompatActivity:
public class PrivacyPolicyActivity extends AppCompatActivity { // 后续代码将在这里添加 }3.3 设计弹窗布局
在res/layout/下新建activity_privacy_policy.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#80000000" android:gravity="center"> <LinearLayout android:layout_width="300dp" android:layout_height="wrap_content" android:orientation="vertical" android:background="@drawable/dialog_bg" android:padding="20dp"> <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="隐私政策" android:textSize="20sp" android:textColor="#333333" android:gravity="center"/> <ScrollView android:layout_width="match_parent" android:layout_height="200dp" android:layout_marginTop="15dp" android:layout_marginBottom="15dp"> <TextView android:id="@+id/content" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="#666666" android:textSize="14sp"/> </ScrollView> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center"> <Button android:id="@+id/btn_disagree" android:layout_width="120dp" android:layout_height="40dp" android:text="不同意" android:background="@drawable/btn_bg_normal"/> <View android:layout_width="20dp" android:layout_height="1dp"/> <Button android:id="@+id/btn_agree" android:layout_width="120dp" android:layout_height="40dp" android:text="同意" android:background="@drawable/btn_bg_primary"/> </LinearLayout> </LinearLayout> </LinearLayout>同时创建对应的drawable资源:
- 在
res/drawable/下创建dialog_bg.xml:
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#FFFFFF"/> <corners android:radius="10dp"/> </shape>- 创建按钮背景
btn_bg_normal.xml和btn_bg_primary.xml:
<!-- btn_bg_normal.xml --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#F0F0F0"/> <corners android:radius="20dp"/> <stroke android:width="1dp" android:color="#CCCCCC"/> </shape> <!-- btn_bg_primary.xml --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#4285F4"/> <corners android:radius="20dp"/> </shape>4. 实现弹窗逻辑与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:theme="@style/Theme.AppCompat.Translucent" android:exported="false"/>同时确保主Activity配置正确:
<activity android:name=".AppActivity" android:configChanges="orientation|screenSize|keyboardHidden" android:screenOrientation="portrait" android:exported="true" android:launchMode="singleTask"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>5. 进阶优化与常见问题解决
5.1 多语言支持实现
在res/values/下创建不同语言的strings.xml文件:
- 英文版
res/values/strings.xml:
<string name="privacy_title">Privacy Policy</string> <string name="privacy_agree">Agree</string> <string name="privacy_disagree">Disagree</string> <string name="privacy_content">Your privacy policy content in English...</string>- 中文版
res/values-zh/strings.xml:
<string name="privacy_title">隐私政策</string> <string name="privacy_agree">同意</string> <string name="privacy_disagree">不同意</string> <string name="privacy_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 name="AppTheme.Translucent" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> </style>问题2:Cocos界面在弹窗后出现异常解决方案:
- 确保在用户同意后才初始化Cocos引擎
- 在AppActivity中添加:
@Override protected void onResume() { super.onResume(); if (PrivacyPolicyActivity.hasAgreed(this)) { // 恢复Cocos渲染 } }问题3:Gradle同步失败解决方案:
- 修改
gradle-wrapper.properties:
distributionUrl=https\://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[]): Promise<boolean[]> { 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 ActivityScenarioRule<AppActivity> 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 监控与统计实现
在用户同意后初始化统计SDK:
private 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使用WebView:
WebView 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 与后台系统的联动
实现政策版本检查API:
public interface PrivacyService { @GET("api/v1/privacy/latest") Call<PrivacyResponse> 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); Call<PrivacyResponse> call = service.getLatestPolicy( BuildConfig.VERSION_NAME, Locale.getDefault().getLanguage()); call.enqueue(new Callback<PrivacyResponse>() { @Override public void onResponse(Call<PrivacyResponse> call, Response<PrivacyResponse> 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(Call<PrivacyResponse> 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_width="300dp" android:layout_height="wrap_content"> <TextView android:id="@+id/title" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> <ScrollView app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintBottom_toTopOf="@id/button_group"> <!-- 内容 --> </ScrollView> <LinearLayout android:id="@+id/button_group" app:layout_constraintBottom_toBottomOf="parent"> <!-- 按钮 --> </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 WeakReference<PrivacyPolicyActivity> 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:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="@dimen/privacy_dialog_width" android:layout_height="wrap_content" app:layout_constraintWidth_max="600dp" app:layout_constraintWidth_percent="0.8"> <!-- 内容 --> </androidx.constraintlayout.widget.ConstraintLayout> </layout>13.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:importantForAccessibility="yes" android:focusable="true" android:focusableInTouchMode="true"> <TextView android:id="@+id/title" android:contentDescription="隐私政策标题" android:importantForAccessibility="yes"/> <ScrollView android:importantForAccessibility="yes"> <TextView android:id="@+id/content" android:content