Android安全编程7大核心技巧:从数据加密到代码混淆实战指南 1. 项目概述为什么Android安全编程是开发者的必修课在移动应用开发领域Android以其开放性和庞大的市场份额成为了无数开发者的首选平台。然而这种开放性也带来了严峻的安全挑战。我见过太多因为忽视安全细节而导致用户数据泄露、应用被恶意利用甚至被应用商店下架的案例。一个看似功能完善的应用可能在安全上漏洞百出。因此掌握Android安全编程技巧绝不是锦上添花而是每一位开发者必须打好的基本功。这不仅仅是保护用户更是保护你自己的代码、声誉和商业利益。今天要聊的这7个技巧是我从多年一线开发、代码审计和事故复盘的经验中提炼出来的核心要点。它们覆盖了从数据存储到网络通信从权限控制到代码混淆的完整链条。无论你是刚入门的新手还是有一定经验的开发者系统地实践这些技巧都能显著提升你应用的安全水位。安全不是某个独立模块而是一种贯穿始终的思维方式。让我们抛开那些空洞的理论直接切入到可以立刻上手、落地见效的实操层面。2. 核心安全技巧深度解析与实战指南2.1 数据加密不止于使用AES谈到数据加密很多开发者的第一反应是“我用AES加密了。”但这远远不够。加密是一个系统工程涉及算法选择、密钥管理、模式应用和初始化向量IV处理等多个环节任何一个环节的疏忽都可能导致前功尽弃。2.1.1 算法与模式的选择为什么AES-GCM是当前首选早期我们可能使用AES/ECB或AES/CBC模式但它们各有缺陷。ECB模式相同明文块会产生相同密文块安全性低CBC模式需要处理填充和IV若IV重复使用或可预测同样存在风险。现代Android开发中我强烈推荐使用AES/GCM/NoPadding模式。GCMGalois/Counter Mode是一种认证加密模式它不仅能提供机密性加密还能提供完整性防篡改。这意味着攻击者即使截获并修改了密文在解密时也会被发现从而避免执行恶意数据。在Android中我们可以通过KeyGenerator和Cipher类来实现。// 生成一个AES密钥用于GCM模式 val keyGenerator KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore) val keyGenParameterSpec KeyGenParameterSpec.Builder( my_key_alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ).apply { setBlockModes(KeyProperties.BLOCK_MODE_GCM) setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) setKeySize(256) // 使用256位密钥 // 关键设置GCM模式需要的认证标签长度 setKeyValidityForOriginationEnd(Calendar.getInstance().apply { add(Calendar.YEAR, 1) }.time) }.build() keyGenerator.init(keyGenParameterSpec) val secretKey keyGenerator.generateKey()2.1.2 密钥的安全存储Android Keystore系统是关键加密的核心在于密钥。将密钥硬编码在代码中、存储在SharedPreferences甚至普通文件中都等同于把家门钥匙挂在门上。Android Keystore系统是解决这一问题的官方方案。它提供了一个安全的硬件或软件容器用于存储加密密钥使得密钥材料难以从设备中提取。使用Keystore时密钥的生成、存储和使用都在一个受保护的执行环境中进行。应用可以请求使用密钥进行操作如加密/解密但无法直接导出密钥的原始字节。上面的代码示例已经展示了如何生成一个存储在Keystore中的密钥。在加密和解密时我们通过别名alias来获取密钥而不是直接持有密钥对象。// 获取Keystore中的密钥进行加密 val keyStore KeyStore.getInstance(AndroidKeyStore).apply { load(null) } val secretKey keyStore.getKey(my_key_alias, null) as SecretKey val cipher Cipher.getInstance(AES/GCM/NoPadding) cipher.init(Cipher.ENCRYPT_MODE, secretKey) // **必须**获取并保存本次加密使用的IV解密时需要 val iv cipher.iv // 将iv和加密后的数据一起存储或传输 val encryptedData cipher.doFinal(plainText.toByteArray(Charsets.UTF_8))注意关于IV初始化向量GCM模式要求每次加密使用不同的IV。cipher.iv会自动生成一个安全的随机IV。你必须将这个IV与密文一起保存通常直接拼接在密文前面解密时再分离出来使用。重复使用IV会彻底破坏GCM模式的安全性。2.1.3 非对称加密的应用场景AES是对称加密加解密使用同一把密钥。在需要安全交换密钥或进行数字签名的场景就需要非对称加密如RSA。例如你的应用需要将一个小段敏感数据如一个会话密钥发送到服务器。你可以使用服务器提供的RSA公钥加密这段数据只有拥有私钥的服务器才能解密。在Android中可以使用KeyPairGenerator生成RSA密钥对并将公钥发送给服务器私钥安全地存储在Keystore中。但请注意RSA不适合加密大量数据通常只用于加密密钥本身或进行签名。2.2 权限管理最小权限原则与动态权限实战权限滥用是Android应用最常见的安全问题之一。遵循“最小权限原则”——只申请和应用功能必需的最小权限——是基本准则。Android 6.0API 23引入的动态权限模型更是将权限控制从安装时细化到了运行时。2.2.1 静态权限声明在Manifest中的精确裁剪首先在AndroidManifest.xml中声明权限要极其克制。仔细审核每一个uses-permission。例如你的应用需要选择照片是只需要READ_EXTERNAL_STORAGE还是真的需要WRITE_EXTERNAL_STORAGE需要访问网络是用INTERNET还是不小心声明了ACCESS_NETWORK_STATE虽然这个权限是normal级别但也是原则移除所有不必要的权限。对于某些敏感权限如SYSTEM_ALERT_WINDOW悬浮窗权限、WRITE_SETTINGS修改系统设置权限除了在Manifest中声明还需要引导用户到系统设置页手动开启这类权限要慎用。2.2.2 动态权限请求优雅的用户体验与后备策略对于DANGEROUS级别的权限如相机、通讯录、定位等必须在运行时动态申请。这个过程的设计直接影响用户体验和权限获取成功率。核心流程如下检查权限状态在执行需要权限的操作前使用ContextCompat.checkSelfPermission()检查是否已授权。解释必要性可选但推荐如果用户之前拒绝过该权限系统会认为用户需要你解释为什么需要这个权限。此时应该通过ActivityCompat.shouldShowRequestPermissionRationale()判断并弹出一个友好的解释对话框说明权限用途例如“需要访问相机来扫描二维码登录”然后再请求。发起权限请求使用ActivityCompat.requestPermissions()。处理请求结果在onRequestPermissionsResult()回调中处理用户的选择。// 以请求相机权限为例 private fun checkAndRequestCameraPermission() { when { ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) PackageManager.PERMISSION_GRANTED - { // 已有权限直接执行操作 openCamera() } ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) - { // 用户之前拒绝过需要解释 showRationaleDialog(需要相机权限来扫描二维码以便快速登录。) { // 用户点击“明白了”后再请求 requestCameraPermission() } } else - { // 直接请求权限 requestCameraPermission() } } } private fun requestCameraPermission() { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_CAMERA ) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Arrayout String, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { REQUEST_CODE_CAMERA - { if (grantResults.isNotEmpty() grantResults[0] PackageManager.PERMISSION_GRANTED) { openCamera() } else { // 权限被拒绝 Toast.makeText(this, 相机权限被拒绝相关功能无法使用, Toast.LENGTH_SHORT).show() // 这里可以提供后备方案例如让用户手动输入二维码内容 } } } }2.2.3 处理“不再询问”与权限后备策略如果用户在权限请求弹窗中勾选了“不再询问”下次shouldShowRequestPermissionRationale()会返回false并且直接调用requestPermissions()系统也不会再弹出对话框。此时唯一的方法是引导用户到应用设置页面手动开启权限。private fun openAppSettings() { val intent Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { data Uri.fromParts(package, packageName, null) } startActivity(intent) }更重要的是你的应用设计应该具备优雅降级的能力。如果用户拒绝提供相机权限是否可以通过相册选择图片如果拒绝定位是否可以让用户手动选择城市一个健壮的应用不应该因为单一权限被拒绝就完全崩溃。2.3 安全的网络通信告别HttpURLConnection拥抱OkHttp与证书锁定网络数据传输是敏感信息泄露的重灾区。使用HTTPS是底线但仅仅在URL前加个“https://”还远远不够。2.3.1 强制使用HTTPS并正确配置从Android 9API 28开始默认禁止明文传输Cleartext Traffic。你需要在AndroidManifest.xml中为特定域名显式允许明文传输不推荐或者更佳实践是确保所有服务器都支持HTTPS。使用现代网络库如OkHttp或Retrofit它们默认支持HTTPS。但你需要关注TLS版本和密码套件。过时的TLS 1.0/1.1已被广泛认为不安全。确保服务器配置支持TLS 1.2或更高版本。2.3.2 证书锁定Certificate Pinning防御中间人攻击的利器HTTPS依赖于证书链的信任。如果设备信任的根证书颁发机构CA被攻破或者用户手机被安装了恶意证书攻击者就可以实施中间人攻击解密你的HTTPS流量。证书锁定就是为了解决这个问题。证书锁定意味着你的应用只信任特定的、预置的服务器证书或公钥而不是信任整个CA体系。即使攻击者拥有一个由合法CA签发的伪造证书你的应用也会拒绝连接。在OkHttp中实现证书锁定非常直观val hostname your-api.example.com val certificatePinner CertificatePinner.Builder() .add(hostname, sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) // 替换为你的证书公钥哈希 .build() val okHttpClient OkHttpClient.Builder() .certificatePinner(certificatePinner) .build()如何获取这个sha256/...字符串你可以使用命令行工具从服务器获取openssl s_client -connect your-api.example.com:443 -servername your-api.example.com | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64重要提醒证书锁定是一把双刃剑。服务器证书会过期和轮换。你需要在应用中预置备用密钥如当前证书和下一个周期证书的哈希以便平滑过渡。建立可靠的证书更新机制例如通过应用商店更新应用或在一个未被锁定的、用于检查证书更新的安全端点进行动态配置需谨慎设计。2.3.3 避免自定义TrustManager带来的风险有时开发者为了调试方便或兼容某些自签名证书的测试环境会实现一个接受所有证书的X509TrustManager。这行代码绝对禁止出现在生产环境应用中// !!! 危险代码切勿在生产环境使用 !!! TrustManager[] trustAllCerts new TrustManager[] { new X509TrustManager() { Override public void checkClientTrusted(X509Certificate[] chain, String authType) {} Override public void checkServerTrusted(X509Certificate[] chain, String authType) {} // 空实现接受所有证书 Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } };这段代码会完全禁用SSL证书验证让应用暴露在巨大的风险之下。测试环境应使用正规的测试证书或通过配置网络安全策略network_security_config.xml来限定范围。2.4 安全的本地数据存储SharedPreferences、文件与数据库本地存储的数据如果保护不当在手机被root或通过备份提取后会一览无余。2.4.1 SharedPreferences的误用与正解SharedPreferences默认以明文XML格式存储。即使使用MODE_PRIVATE也只是限制了其他应用访问对于已root的设备或通过ADB备份数据是可见的。对于存储敏感信息如令牌、密码提示必须加密后再存入SharedPreferences。我们可以结合之前讲的加密方法// 加密后存储 val encryptedToken encryptData(plainToken) // 使用Keystore中的密钥加密 getSharedPreferences(secure_prefs, Context.MODE_PRIVATE) .edit() .putString(user_token, Base64.encodeToString(encryptedToken, Base64.DEFAULT)) .apply() // 读取时解密 val encryptedBase64 getSharedPreferences(...).getString(user_token, null) val decryptedToken decryptData(Base64.decode(encryptedBase64, Base64.DEFAULT))Android Jetpack Security库Security Crypto提供了更便捷的API来创建加密的SharedPreferences和文件它底层也是基于Android Keystore系统。// 使用Jetpack Security库 val masterKey MasterKey.Builder(applicationContext) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() val secureSharedPrefs EncryptedSharedPreferences.create( applicationContext, my_secure_prefs, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) secureSharedPrefs.edit().putString(api_key, sensitive_value).apply() // 读取时自动解密 val value secureSharedPrefs.getString(api_key, null)2.4.2 内部存储与外部存储内部存储(Context.getFilesDir(),getCacheDir()): 默认是应用私有的其他应用无法访问在非root设备上。这是存储敏感数据的首选位置。外部存储(Environment.getExternalStorageDirectory()): 分为私有目录和公共目录。应用私有外部目录(Context.getExternalFilesDir()): 无需权限卸载应用时会被删除相对安全适合存储应用专属的大文件。公共目录(如Downloads,Pictures): 需要权限对所有应用和用户可见。绝对不要在此存储任何敏感信息。2.4.3 SQLite数据库加密标准的SQLite数据库文件也是明文的。如果数据库中存在敏感信息必须对整个数据库文件进行加密。常用的解决方案是使用SQLCipher库。它为SQLite提供了透明的、全库的256位AES加密。集成SQLCipher后你只需要在打开数据库时提供一个密码之后的读写操作与普通SQLiteDatabase完全一样所有加解密过程在底层自动完成。// 使用SQLCipher打开加密数据库 val databaseFile getDatabasePath(my_encrypted.db) val password your_strong_password.toCharArray() // 密码应从安全渠道获取切勿硬编码 SQLiteDatabase.openOrCreateDatabase(databaseFile, password, null, null).use { db - // 像使用普通SQLiteDatabase一样操作 db.execSQL(CREATE TABLE IF NOT EXISTS secrets(id INTEGER PRIMARY KEY, data TEXT)) }同样这个密码的管理是关键可以结合Keystore来安全地生成和存储一个用于数据库加密的密钥。2.5 输入验证与WebView安全堵住代码注入的漏洞应用内外部的数据边界是攻击的主要入口。不信任任何来自外部的输入是安全编程的铁律。2.5.1 全面的输入验证这包括用户输入登录框、搜索框、表单中的所有文本输入。必须进行长度、类型、格式正则表达式的校验。例如邮箱字段必须符合邮箱格式数字字段必须只能是数字。Intent数据其他应用通过Intent传递过来的Extra数据。恶意应用可能发送畸形或超大的数据来引发你的应用崩溃DoS攻击或触发未处理的异常导致敏感信息泄露。文件内容从网络下载或外部存储读取的文件。在解压、解析如XML、JSON前应进行大小限制和格式预检。深层链接Deep Link验证URL的路径和参数避免恶意链接引导用户进入非预期页面或执行危险操作。2.5.2 WebView的安全加固WebView是一个微型的浏览器但它继承了浏览器的复杂性也带来了诸多安全风险。禁用不必要的功能除非绝对需要否则应禁用JavaScript、文件访问、插件等。WebSettings settings webView.getSettings(); settings.setJavaScriptEnabled(false); // 默认应关闭需要时再开启 settings.setAllowFileAccess(false); settings.setAllowContentAccess(false); settings.setAllowFileAccessFromFileURLs(false); settings.setAllowUniversalAccessFromFileURLs(false); // 这个尤其危险必须为false谨慎处理JavaScript桥接通过JavascriptInterface暴露给JavaScript的Java对象其所有公共方法都可能被网页调用。这些方法必须进行严格的输入验证并且不应包含敏感操作。public class WebAppInterface { JavascriptInterface public String processData(String userInput) { // 1. 验证userInput if (userInput null || userInput.length() 100) { return Invalid input; } // 2. 执行安全操作 return Processed: userInput; } }安全加载内容优先使用loadDataWithBaseURL而不是loadData并设置一个安全的、受控的BaseURL。对于加载的URL使用WebViewClient.shouldOverrideUrlLoading进行拦截和验证只允许白名单内的域名或特定的URL Scheme。绝对不要使用loadUrl加载不受信任的用户数据。注意WebView的密码保存默认情况下WebView可能会提示用户保存密码。如果你的应用内嵌了登录页面这可能不安全。可以考虑禁用settings.setSavePassword(false);(注意此API已废弃更应通过避免在WebView中进行关键登录来规避)。2.6 组件暴露与Intent过滤守好应用的四大门户Activity、Service、BroadcastReceiver、ContentProvider是Android应用的四大组件。如果它们被错误地导出android:exported”true”就可能成为其他应用攻击的入口。2.6.1 显式Intent与隐式Intent显式Intent明确指定了目标组件类名setComponent,setClass只在应用内部传递最安全。隐式Intent通过Action、Category、Data等来匹配能处理它的组件。如果组件被导出且声明了匹配的Intent Filter就可能被外部应用调用。2.6.2 组件导出原则在AndroidManifest.xml中组件的exported属性决定了它是否能被其他应用启动。如果一个组件没有声明intent-filter则exported默认为false。如果一个组件声明了intent-filter则exported默认为true这是一个巨大的陷阱。最佳实践最小化导出除非组件确实需要被其他应用包括系统调用否则显式设置android:exported”false”。对导出的组件进行权限保护如果组件必须导出使用android:permission属性为其声明一个自定义权限或系统权限限制调用者。activity android:name.ExportActivity android:exportedtrue android:permissioncom.example.myapp.permission.SHARE_DATA /然后在调用方应用的Manifest中声明并使用这个权限。对接收的Intent进行严格验证在导出的组件尤其是BroadcastReceiver和Activity中必须对接收到的Intent的Action、Data、Extras进行验证确保其符合预期。小心PendingIntentPendingIntent是将一个Intent及其执行权限授予其他应用的一种方式。创建PendingIntent时应使用FLAG_IMMUTABLEAPI 23来防止接收方修改Intent的内容。同时确保其内部Intent的目标组件是明确的或者对其数据加以限制。2.6.3 ContentProvider的安全ContentProvider是数据共享的接口风险极高。除非必要否则设置android:exported”false”。使用android:permission、android:readPermission、android:writePermission进行细粒度的读写控制。在query,insert,update,delete等方法中对所有输入参数如selection进行严格的验证和过滤防止SQL注入。永远不要直接拼接参数来构建SQL语句使用参数化查询?占位符和selectionArgs。// 危险存在SQL注入风险 String selection “id “ userInputId; cursor db.query(TABLE_NAME, null, selection, null, null, null, null); // 安全使用参数化查询 String selection “id ?”; String[] selectionArgs new String[] { userInputId }; // 系统会正确处理转义 cursor db.query(TABLE_NAME, null, selection, selectionArgs, null, null, null);2.7 代码混淆与反调试增加逆向工程的门槛虽然没有任何技术能完全阻止逆向工程但我们可以通过混淆和反调试手段大幅增加攻击者的成本和难度保护核心业务逻辑和敏感字符串。2.7.1 ProGuard/R8代码混淆这是Android构建流程中自带的工具现在默认使用R8。它主要做三件事压缩移除未使用的类、字段、方法。优化优化字节码简化逻辑。混淆将类名、方法名、字段名重命名为无意义的短字符如a, b, c。在app/build.gradle中启用android { buildTypes { release { minifyEnabled true // 启用代码压缩和混淆 proguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro } } }关键是在proguard-rules.pro文件中配置哪些内容不能被混淆所有被反射调用的类、方法、字段。实现了Parcelable接口的类及其CREATOR字段。通过JNI调用的Native方法。所有在AndroidManifest.xml中注册的组件Activity, Service等。第三方库可能需要的类参考库官方文档。混淆后使用工具如mapping.txt文件保留映射关系以便在崩溃报告中能还原堆栈信息。2.7.2 资源混淆AndResGuard进一步将资源文件图片、布局文件名也进行混淆减少通过资源索引猜测代码逻辑的可能性。2.7.3 反调试检测防止应用被附加调试器分析。可以在关键逻辑开始处或应用启动时进行检查。// 检查是否被调试器附加 public static boolean isDebuggerConnected() { return android.os.Debug.isDebuggerConnected(); } // 通过检查TracerPidLinux内核进程状态 public static boolean isBeingTraced() { try { BufferedReader reader new BufferedReader(new FileReader(/proc/self/status)); String line; while ((line reader.readLine()) ! null) { if (line.startsWith(TracerPid:)) { int tracerPid Integer.parseInt(line.substring(10).trim()); reader.close(); return tracerPid ! 0; // TracerPid不为0表示正在被跟踪 } } reader.close(); } catch (Exception e) { // 忽略异常 } return false; }如果检测到调试可以采取静默退出、触发无关逻辑或延迟崩溃等策略。但要注意这些检测本身也可能被逆向高手绕过它们的作用是提高门槛。2.7.4 敏感字符串与密钥的保护切勿在代码中硬编码API密钥、服务器地址、加密盐值等。对于必须内置的敏感字符串可以进行简单的编码或拆分但不要指望这能提供绝对安全因为解码逻辑也在代码中。更好的方式是将核心密钥、配置放在Native层C/C通过JNI调用这能增加一层逆向难度。或者使用前面提到的Android Keystore来管理运行时所需的密钥。3. 安全开发流程与自动化检查安全不是一次性工作而应融入整个开发流程。3.1 依赖库的安全审计第三方库是应用的组成部分也可能引入漏洞。定期使用如OWASP Dependency-Check等工具扫描项目依赖检查是否有已知的公共漏洞CVE。在build.gradle中尽量使用依赖库的稳定版本并关注其安全公告。3.2 使用Lint与安全扫描工具Android Studio自带的Lint工具可以检查出一些潜在的安全问题如不安全的网络配置、权限声明问题等。此外可以集成像MobSF这样的移动应用安全测试框架进行自动化动态和静态分析。3.3 建立代码审查中的安全 checklist在团队代码审查中加入安全审查环节。审查清单可以包括新增权限是否必要新增的导出组件是否经过评估和防护网络请求是否全部为HTTPS且配置正确敏感数据存储是否加密用户输入是否都有验证日志中是否可能打印敏感信息4. 常见安全问题排查与修复实录在实际开发和应急响应中会遇到各种各样的问题。这里记录几个典型场景。4.1 场景应用在Android 9设备上网络请求失败排查日志中可能看到Cleartext HTTP traffic to xxx not permitted。这是因为Android 9默认禁止明文HTTP流量。修复首选方案将服务器升级到HTTPS。临时方案仅用于测试或内部网络在res/xml/network_security_config.xml中为特定域名配置例外。network-security-config domain-config cleartextTrafficPermittedtrue domain includeSubdomainstrueinsecure.example.com/domain /domain-config /network-security-config然后在Manifest的application标签中设置android:networkSecurityConfigxml/network_security_config。生产环境慎用。4.2 场景使用EncryptedSharedPreferences时遇到“Key not found”异常排查这通常发生在应用更新、系统更新或密钥别名冲突后。Android Keystore中的密钥可能因某种原因不可用。修复设计容错机制在首次访问EncryptedSharedPreferences时捕获KeyNotFoundException。实现密钥恢复或数据迁移在捕获异常后尝试删除旧密钥如果确定不再需要并重新初始化或者将数据解密后如果还有备份用新密钥重新加密存储。对于全新的应用也可以直接清除数据让用户重新登录。预防避免频繁卸载重装测试使用MasterKey.Builder.setRequestStrongBoxBacked(true)如果设备支持可以增加密钥安全性但也要了解其限制。4.3 场景动态权限请求弹窗不显示排查检查targetSdkVersion是否 23。检查是否在onResume等可能频繁调用的生命周期中请求权限导致弹窗被快速覆盖。检查是否在非UI线程如后台线程中调用requestPermissions。对于SYSTEM_ALERT_WINDOW悬浮窗等特殊权限requestPermissions无效必须引导用户到设置页面。修复确保在主线程、合适的上下文如Activity和用户可感知的交互时机如按钮点击后请求权限。4.4 场景WebView加载的页面无法调用JavascriptInterface方法排查检查setJavaScriptEnabled(true)是否已调用。检查JavascriptInterface注解是否添加API 17以上必须添加。检查添加JavascriptInterface的对象类名和方法名在JavaScript中是否正确调用。在Android 4.2API 17以上只有添加了JavascriptInterface注解的公共方法才会暴露给JavaScript。修复确保配置正确并使用webView.addJavascriptInterface(object, “androidBridge”)后在JavaScript中用window.androidBridge.methodName()的方式调用。安全是一个持续的过程没有一劳永逸的银弹。这7个技巧是一个坚实的起点将它们内化为编码习惯结合自动化的安全扫描和团队间的安全意识培养才能构建出真正值得用户信赖的Android应用。在实际开发中每做一个涉及数据、权限、通信的决定时多问一句“这样安全吗”很多隐患就能被消灭在萌芽状态。