ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Android15 遇到的部分问题汇总

Android15 遇到的部分问题汇总 一、多用户发送的广播是根据用户来传递的如果你想在切换用户后对之前用户的应用做些保存或清理你需要获取之前用户的UserHandle并sendBroadcastAsUser这样发送的广播就可以传递给上一个用户的静态广播接收器了。项目上写了一个系统服务来记录当前的用户并接收系统切换用户的广播切换广播带有目标用户的参数这样我们就可以发送不同的广播给不同的用户来控制一些全局显示的功能。import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemProperties; import java.util.HashMap; import android.util.Log; import android.os.UserHandle; import android.os.UserManager; import android.content.BroadcastReceiver; import android.os.Handler; import android.os.HandlerThread; public class MultipleUsersService extends IMultipleUsers.Stub { private static final String TAG MultipleUsersService.class.getSimpleName(); private static MultipleUsersService sInstance; private Context mContext; private UserManager userManager; private UserHandle mCurrentUserHandle; private Handler mHandler; public MultipleUsersService(Context mContext){ this.mContext mContext; sInstance this; userManager (UserManager) mContext.getSystemService(Context.USER_SERVICE); mCurrentUserHandle userManager.getUserForSerialNumber(0); HandlerThread thread new HandlerThread(TAG-1); thread.start(); mHandler new Handler(thread.getLooper()); mContext.registerReceiverAsUser(new DeviceAdminReceiver2(), UserHandle.ALL, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler); Log.i(TAG,init ok); } public static MultipleUsersService getInstance(){ return sInstance; } Override public int getCurrentUserSerialNumber() throws RemoteException { return mCurrentUserHandle.getIdentifier(); } public UserHandle getCurrentUser() throws RemoteException { Log.v(TAG,getCurrentUsermCurrentUserHandle); return mCurrentUserHandle; } private class DeviceAdminReceiver2 extends BroadcastReceiver{ Override public void onReceive(Context context,Intent intent) { String action intent.getAction(); //Log.v(TAG, getClass().getName() .onReceive(): received action on user context.getUserId()); if(Intent.ACTION_USER_SWITCHED.equals(action)){ UserHandle switchedUser intent.getParcelableExtra(Intent.EXTRA_USER, android.os.UserHandle.class); Log.i(TAG,onUserSwitched oldmCurrentUserHandle newswitchedUser context.getUserId()); Intent gloAnnIntent new Intent(); gloIntent.setAction(com.lpw.test.action.HIDE_GLOBAL_ANNOTATION); gloIntent.setPackage(com.lpw.test); intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); mContext.sendBroadcastAsUser(gloIntent, mCurrentUserHandle); Log.i(TAG,sendBroadcastAsUser hide glo); Intent showGloAnnIntent new Intent(); showGloIntent.setAction(com.lpw.test.action.SHOW_GLOBAL_ANNOTATION); showGloIntent.setPackage(com.lpw.test); intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); mContext.sendBroadcastAsUser(showGloIntent, mCurrentUserHandle); Log.i(TAG,sendBroadcastAsUser show glo); } } } }二、无障碍服务使用无障碍服务监听时监听到的事件节点以及它的子节点可能没有包含我们想要的组件因此保险的方法是每次监听到事件都从根节点递归去查找对应的节点。这里考虑到部分应用会自定义view这样view的名称就不是原本的了因此需要比较父类对象是否是需要寻找的名称。import android.accessibilityservice.AccessibilityService; import android.app.ActivityManager; import android.content.Context; import android.os.Message; import android.os.SystemClock; import android.text.TextUtils; import android.util.SparseArray; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.widget.EditText; import java.util.List; public class MyAccessibilityService extends AccessibilityService { public static ActivityManager mActivityManager; private int lastEventType; private long lastTime; private static final int HANDLER_DELAY 150; Override public void onAccessibilityEvent(AccessibilityEvent event) { int eventType event.getEventType(); LogUtil.d(input_test, eventType: eventType); if (eventType AccessibilityEvent.TYPE_VIEW_FOCUSED || eventType AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { LogUtil.d(input_test, TYPE_VIEW_FOCUSED!); AccessibilityNodeInfo source event.getSource(); AccessibilityNodeInfo root getRootInActiveWindow(); boolean hasFocus refreshNode(source, 1); if(hasFocus) { //识别到目标执行操作 } else { AccessibilityNodeInfo focusEdit getFocusEdit(root); if(focusEdit ! null) { Log.d(input_test, MSG_REC); } else { Log.d(input_test, MSG_DEF); } } lastTime System.currentTimeMillis(); } else { if(Math.abs(System.currentTimeMillis() - lastTime) 80) { Log.d(input_test, TYPE_OTHER! return for time!); return; } if(Math.abs(System.currentTimeMillis() - lastTime) 200 eventType lastEventType) { Log.d(input_test, TYPE_OTHER! return for time and event!); return; } if(AccessibilityUtil.mAccessibilityNodeInfo ! null) { boolean refresh AccessibilityUtil.mAccessibilityNodeInfo.refresh(); if(refresh) { CharSequence orgText AccessibilityUtil.mAccessibilityNodeInfo.getText(); CharSequence hintText AccessibilityUtil.mAccessibilityNodeInfo.getHintText(); CharSequence packageName AccessibilityUtil.mAccessibilityNodeInfo.getPackageName(); int textSelectionStart AccessibilityUtil.mAccessibilityNodeInfo.getTextSelectionStart(); int textSelectionEnd AccessibilityUtil.mAccessibilityNodeInfo.getTextSelectionEnd(); boolean focused AccessibilityUtil.mAccessibilityNodeInfo.isFocused(); if(!focused) { //焦点丢失切换到默认 } } else { //没有找到目标节点默认操作 } } lastEventType eventType; lastTime System.currentTimeMillis(); } } private boolean refreshNode(AccessibilityNodeInfo nodeInfo, int number) { boolean set false; try { if (nodeInfo null || nodeInfo.getPackageName().equals(com.google.android.apps.docs.editors.docs)) { return false; } Class? superClass EditText.class; // 父类类对象 Class? subClass Class.forName((String) nodeInfo.getClassName()); // 根据类名获取子类类对象 if (superClass.isAssignableFrom(subClass) nodeInfo.isFocused() nodeInfo.getInputType() ! 129) { LogUtil.d(input_test, refreshNode - number - text nodeInfo.getText() - class nodeInfo.getClassName() - inputType nodeInfo.getInputType()); AccessibilityUtil.setAccessibilityNodeInfo(nodeInfo); set true; } } catch (Exception e) { e.printStackTrace(); } return set; } Override protected void onServiceConnected() { super.onServiceConnected(); } private AccessibilityNodeInfo getFocusEdit(AccessibilityNodeInfo source) { if(source null) return null; if(refreshNode(source,3) source.getInputType() ! 129) { return source; } int childCount source.getChildCount(); // 递归检查子节点 for (int i 0; i childCount; i) { AccessibilityNodeInfo child source.getChild(i); if(child null) continue; AccessibilityNodeInfo focusEdit getFocusEdit(child); child.recycle(); if(focusEdit ! null) { return focusEdit; } } source.recycle(); return null; } Override public void onInterrupt() { // 服务中断时的处理 } }三、图层信任C中使用createSurface创建的图层如果显示在GMS部分安全应用上并且能够传递触摸时谷歌应用将不会处理这部分触摸事件例如Google Play登录时就会弹出“可能有应用阻挡Google Play”。这时需要给创建的图层添加信任不然权限相关的部分功能都没法触摸点击。SurfaceComposerClient::Transaction{} .setLayer(mSurfaceControl, z_order) .setPosition(mSurfaceControl, left, top) .setSize(mSurfaceControl, mSurfaceWidth, mSurfaceHeight) .setLayerStack(mSurfaceControl, ui::LayerStack::fromValue(layerStack)) .setTrustedOverlay(mSurfaceControl, true) //创建图层时设置可信任 .show(mSurfaceControl) .apply();四、SDK部分Android15应用的SDK版本至少需要34更新应用的sdk版本后部分功能使用会报错需要做修改。//1、注册广播需要显式的声明是否EXPORT registerReceiver(mReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED); //2、pendingIntent需要声明不可变FLAG_IMMUTABLE或直接指定具体的包名 if (android.os.Build.VERSION.SDK_INT android.os.Build.VERSION_CODES.S) { postAuthorizationIntent.setPackage(com.lpw.filemanager); pendingIntent PendingIntent.getActivity(getContext(), request.hashCode(), postAuthorizationIntent, PendingIntent.FLAG_MUTABLE); } else { pendingIntent PendingIntent.getActivity(getContext(), request.hashCode(), postAuthorizationIntent, 0); }五、OUT镜像图层C创建的加速图层Surface需要配置对应的镜像图层才能显示到OUT连接的副屏上多个副屏需要配置多个镜像图层否则只有最近接入的副屏才会显示加速图像。//代码由AI生成具体需要根据实际可以接入的副屏数量来设置 // 创建主SurfaceControl mSurfaceControl composerClient-createSurface( String8(handwrite_win), mSurfaceWidth, mSurfaceHeight, PIXEL_FORMAT_RGBA_8888, 0); // 创建镜像SurfaceControl mMirrorControl composerClient-MirrorSurface(mSurfaceControl.get()); mParentControl composerClient-createSurface(String8(MirrorParent), 0, 0, PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceContainer, nullptr); const std::vectorPhysicalDisplayId ids SurfaceComposerClient::getPhysicalDisplayIds(); const size_t size ids.size(); if (size 1) { for (size_t i 0; i size; i) { spIComposerClient token SurfaceComposerClient::getPhysicalDisplayToken(ids[i]); if (i ! 0 token ! nullptr) { ui::DisplayState displayState; auto err SurfaceComposerClient::getDisplayState(token, displayState); if (err ! NO_ERROR) { ALOGE(get display state fail...); } else { // 为第一个副屏创建并设置镜像图层 if (i 1) { SurfaceComposerClient::Transaction t; t.setLayer(mParentControl, std::numeric_limitsint32_t::max()) .setLayerStack(mParentControl, displayState.layerStack) .show(mParentControl) .apply(); SurfaceComposerClient::Transaction t_mirror; t_mirror.reparent(mMirrorControl, mParentControl) .setLayer(mMirrorControl, std::numeric_limitsint32_t::max()) .setLayerStack(mMirrorControl, displayState.layerStack) .show(mMirrorControl) .apply(); } // 为第二个副屏创建并设置镜像图层 if (i 2) { // 创建新的镜像SurfaceControl用于第二个副屏 spSurfaceControl mMirrorControl2 composerClient-MirrorSurface(mSurfaceControl.get()); spSurfaceControl mParentControl2 composerClient-createSurface(String8(MirrorParent2), 0, 0, PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceContainer, nullptr); SurfaceComposerClient::Transaction t2; t2.setLayer(mParentControl2, std::numeric_limitsint32_t::max()) .setLayerStack(mParentControl2, displayState.layerStack) .show(mParentControl2) .apply(); SurfaceComposerClient::Transaction t_mirror2; t_mirror2.reparent(mMirrorControl2, mParentControl2) .setLayer(mMirrorControl2, std::numeric_limitsint32_t::max()) .setLayerStack(mMirrorControl2, displayState.layerStack) .show(mMirrorControl2) .apply(); } } } } } // 双屏或更多屏 SurfaceComposerClient::Transaction t; if (LayerStack 0) { // 设置主图层 t.setLayer(mSurfaceControl, LayerStack) .apply(); }六、OUT触控笔部分PEN协议的副屏上传的事件不是特别规范这就导致安卓系统可能报错重启这时需要将错误等级降低。// frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp // This pointer was already sent to the window. Use ACTION_HOVER_MOVE. if (CC_UNLIKELY(maskedAction ! AMOTION_EVENT_ACTION_HOVER_MOVE)) { //这里将原本的FATAL等级改为ERROR避免系统重启 android::base::LogSeverity severity android::base::LogSeverity::ERROR; if (!input_flags::a11y_crash_on_inconsistent_event_stream() entry.flags AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT) { // The Accessibility injected touch exploration event stream // has known inconsistencies, so log ERROR instead of // crashing the device with FATAL. severity android::base::LogSeverity::ERROR; } dump(); LOG(severity) Expected ACTION_HOVER_MOVE instead of entry.getDescription(); } touchedWindow.dispatchMode InputTarget::DispatchMode::AS_IS;异常的触控笔输入流程可能导致本机的窗口抛掉后续的输入事件根据log找到源码发现有个判断是flag配置的。/** * Return: * true if the incoming event was correctly tracked, * false if the incoming event should be dropped. */ bool InputState::trackMotion(const MotionEntry entry, int32_t flags) { // Dont track non-pointer events if (!isFromSource(entry.source, AINPUT_SOURCE_CLASS_POINTER)) { // This is a focus-dispatched event; we dont track its state. return true; } // 添加打印后发现抛掉输入的情况大部分走这这个判断入口是flag控制的 // 仓库中查询到这个配置是在build目录下 if (!input_flags::enable_multi_device_same_window_stream()) { if (!mMotionMementos.empty()) { const MotionMemento lastMemento mMotionMementos.back(); if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) !isStylusEvent(entry.source, entry.pointerProperties)) { // We already have a stylus stream, and the new event is not from stylus. LOG(INFO) We already have a stylus stream, and the new event is not from stylus --lpw.; return false; } } } int32_t actionMasked entry.action AMOTION_EVENT_ACTION_MASK; switch (actionMasked) { case AMOTION_EVENT_ACTION_UP: case AMOTION_EVENT_ACTION_CANCEL: { ssize_t index findMotionMemento(entry, /*hovering*/false); if (index 0) { mMotionMementos.erase(mMotionMementos.begin() index); return true; } LOG(INFO) AMOTION_EVENT_ACTION_UP findMotionMemento fail --lpw.; return false; } case AMOTION_EVENT_ACTION_DOWN: { ssize_t index findMotionMemento(entry, /*hovering*/false); if (index 0) { mMotionMementos.erase(mMotionMementos.begin() index); } addMotionMemento(entry, flags, /*hovering*/false); return true; } case AMOTION_EVENT_ACTION_POINTER_UP: case AMOTION_EVENT_ACTION_POINTER_DOWN: case AMOTION_EVENT_ACTION_MOVE: { if (entry.source AINPUT_SOURCE_CLASS_NAVIGATION) { // Trackballs can send MOVE events with a corresponding DOWN or UP. Theres no need // to generate cancellation events for these since theyre based in relative rather // than absolute units. return true; } ssize_t index findMotionMemento(entry, /*hovering*/false); if (entry.source AINPUT_SOURCE_CLASS_JOYSTICK) { // Joysticks can send MOVE events without a corresponding DOWN or UP. Since all // joystick axes are normalized to [-1, 1] we can trust that 0 means its neutral. // Any other value and we need to track the motion so we can send cancellation // events for anything generating fallback events (e.g. DPad keys for joystick // movements). if (index 0) { if (entry.pointerCoords[0].isEmpty()) { mMotionMementos.erase(mMotionMementos.begin() index); } else { MotionMemento memento mMotionMementos[index]; memento.setPointers(entry); } } else if (!entry.pointerCoords[0].isEmpty()) { addMotionMemento(entry, flags, /*hovering*/false); } // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP. return true; } if (index 0) { MotionMemento memento mMotionMementos[index]; if (memento.firstNewPointerIdx 0) { memento.setPointers(entry); return true; } } LOG(INFO) AMOTION_EVENT_ACTION_MOVE findMotionMemento fail --lpw.; return false; } case AMOTION_EVENT_ACTION_HOVER_EXIT: { ssize_t index findMotionMemento(entry, /*hovering*/true); if (index 0) { mMotionMementos.erase(mMotionMementos.begin() index); return true; } LOG(INFO) AMOTION_EVENT_ACTION_HOVER_EXIT findMotionMemento fail --lpw.; return false; } case AMOTION_EVENT_ACTION_HOVER_ENTER: case AMOTION_EVENT_ACTION_HOVER_MOVE: { ssize_t index findMotionMemento(entry, /*hovering*/true); if (index 0) { mMotionMementos.erase(mMotionMementos.begin() index); } addMotionMemento(entry, flags, /*hovering*/true); return true; } default: LOG(INFO) default --lpw.; return true; } }enable_multi_device_same_window_stream_flag_values.textproto 这个是build目录下具体的配置文件原本没有这个文件这里我添加上文件并设置enable触控笔的操作就不会导致主设备无法触摸了。flag_value { package: com.android.input.flags name: enable_multi_device_same_window_stream state: ENABLED permission: READ_WRITE }七、视图残留滑动ScrollView使得子View一半显示在边界处子View执行动画后ScrollView顶上有一条蓝线显示后续如何滑动ScrollView都不会消失看上去是子View的部分残留。解决方式子View设置android:layerTypesoftware分析应该是硬件加速导致部分动画显示残留。八、开机自启动设置开机后自动跳转某个应用但是设置后开机跳转会有一个滑动切换的动画这个动画默认背景是白色与主题不符合并且如果主页起来后再切换就没有这个动画了。解决方式给主页和FallbackHome都设置无动画发现动画还在查看自动跳转的逻辑是在主页onCreate时判断跳转的此时主页的window可能还没准备好就会导致window动画异常因此在onAttachToWindow中进行自动跳转就没有问题了。九、弹窗背景点击确认按钮后弹窗自动渐变消失但是会有应用的名称在中间一直显示。分析代码应用原逻辑是启动一个透明activity统一管理所有弹窗。解决方式activity预设了一个theme主题NoAnimationDialog导致有默认背景在AndroidManifest.xml将android:themestyle/NoAnimationDialog一行删去即可。
返回列表