如何解决SearchView中createVoiceAppSearchIntent引发的PendingIntent崩溃?
问题情况
使用SearchView的createVoiceAppSearchIntent方法时,该方法仍沿用PendingIntent.FLAG_ONE_SHOT,在Target SDK 31及以上版本会触发崩溃,报错提示:
Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
崩溃日志:
Fatal Exception: java.lang.IllegalArgumentException: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles. at android.app.PendingIntent.checkFlags(PendingIntent.java:382) at android.app.PendingIntent.getActivityAsUser(PendingIntent.java:465) at android.app.PendingIntent.getActivity(PendingIntent.java:451) at android.app.PendingIntent.getActivity(PendingIntent.java:415) at androidx.appcompat.widget.SearchView.createVoiceAppSearchIntent(SearchView.java:1594) at androidx.appcompat.widget.SearchView.onVoiceClicked(SearchView.java:1251) at androidx.appcompat.widget.SearchView$5.onClick(SearchView.java:991) at android.view.View.performClick(View.java:7792) at android.view.View.performClickInternal(View.java:7769) at android.view.View.access$3800(View.java:910) at android.view.View$PerformClick.run(View.java:30218) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.app.ActivityThread.main(ActivityThread.java:8669) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
解决办法
1. 升级AppCompat库版本
这是最推荐的方案,旧版AppCompat库的SearchView未适配Android 12+的PendingIntent强制要求。将AppCompat库升级到1.6.0及以上版本,新版会自动在创建PendingIntent时添加FLAG_IMMUTABLE,修复该崩溃问题。
在build.gradle(Module级)中更新依赖:
implementation 'androidx.appcompat:appcompat:1.6.0' // 或更新到最新稳定版
2. 自定义SearchView重写方法
如果暂时无法升级库,可自定义SearchView并重写createVoiceAppSearchIntent方法,手动添加适配Android 12+的Flags:
public class FixedSearchView extends SearchView { public FixedSearchView(Context context) { super(context); } public FixedSearchView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected PendingIntent createVoiceAppSearchIntent() { Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, getContext().getString(R.string.search_voice_prompt)); int flags = PendingIntent.FLAG_ONE_SHOT; // 适配Android 12+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { flags |= PendingIntent.FLAG_IMMUTABLE; } return PendingIntent.getActivity(getContext(), 0, voiceIntent, flags); } }
之后在布局文件中使用自定义的FixedSearchView替代原SearchView即可。
3. 反射临时修复(不推荐)
若以上两种方案都无法实施,可通过反射修改SearchView内部的PendingIntent,但反射存在兼容性风险,仅作临时应急:
// 在SearchView初始化后调用 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { try { Field field = SearchView.class.getDeclaredField("mVoicePendingIntent"); field.setAccessible(true); PendingIntent oldPi = (PendingIntent) field.get(yourSearchViewInstance); Intent intent = oldPi.getIntent(); // 重新创建带FLAG_IMMUTABLE的PendingIntent PendingIntent newPi = PendingIntent.getActivity( getContext(), 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE ); field.set(yourSearchViewInstance, newPi); } catch (Exception e) { e.printStackTrace(); } }
内容的提问来源于stack exchange,提问作者Kazunari

