前言
如何在java层实现异步监控和阻断inputdispatching anr?我相信这是很多开发者都想要的功能。
本篇,我们会通过“探索”两种方案来实现在java层监控&阻断的方法
android版本发展已经趋于稳定,各种amp工具都已经很成熟了,甚至很多人都能背出来具体实现。但是,仍然有一些东西我们要回过头去看,过去我们认为不能或者很难实现的东西,或许是因为我们很少去质疑。
任何时候都要重新审视一下过去的方法。
有时候解决问题的方法并不只有一种,我们要质疑为什么选的是不是最好用的一种。一些人的代码,提前引入现有需求不需要的逻辑是否合理?还有就是,为了解决一个小问题,比如解决相似图片的问题,结果完整引入了opencv,引入这样一个很大的框架是否合理?这些都需要去质疑。
本篇前奏
这里,我们简单了解下事件传递和一些尝试方案,如果不看本节,其实影响不大,可直接跳至下一节。
我们回到本篇主题,我们如何才能使用java代码实现inputevent anr 监控和阻断呢,我们先来看这样一张图。我为什么选择这一张图呢,因为它很经典,虽然我在上面稍微改造了一下。
当然,上图缺少windowsesssion的角色,实际上,viewrootimpl和windowmanagerservice通信少不了windowsession,那么windowsession是如何通信的呢,我们继续往下看。
public void setview(view view, windowmanager.layoutparams attrs, view panelparentview) { synchronized (this) { ... if ((mwindowattributes.inputfeatures & windowmanager.layoutparams.input_feature_no_input_channel) == 0) { minputchannel = new inputchannel(); //创建inputchannel对象 } //通过binder调用,进入system进程的session[见小节2.4] res = mwindowsession.addtodisplay(mwindow, mseq, mwindowattributes, gethostvisibility(), mdisplay.getdisplayid(), mattachinfo.mcontentinsets, mattachinfo.mstableinsets, mattachinfo.moutsets, minputchannel); ... if (minputchannel != null) { if (minputqueuecallback != null) { minputqueue = new inputqueue(); minputqueuecallback.oninputqueuecreated(minputqueue); } //创建windowinputeventreceiver对象[见3.1] minputeventreceiver = new windowinputeventreceiver(minputchannel, looper.mylooper()); } } }
在这里我们可以看到,事件传递是通过inputchannel实现,而inputchannel负责事件发送、事件应答两部分,因此,肯定能双向通信,那么是不是binder呢?
实际上,inputchannel在底层是socket实现
status_t inputchannel::openinputchannelpair(const string8& name, sp<inputchannel>& outserverchannel, sp<inputchannel>& outclientchannel) { int sockets[2]; //真正创建socket对的地方【核心】 if (socketpair(af_unix, sock_seqpacket, 0, sockets)) { ... return result; } int buffersize = socket_buffer_size; //32k setsockopt(sockets[0], sol_socket, so_sndbuf, &buffersize, sizeof(buffersize)); setsockopt(sockets[0], sol_socket, so_rcvbuf, &buffersize, sizeof(buffersize)); setsockopt(sockets[1], sol_socket, so_sndbuf, &buffersize, sizeof(buffersize)); setsockopt(sockets[1], sol_socket, so_rcvbuf, &buffersize, sizeof(buffersize)); string8 serverchannelname = name; serverchannelname.append(" (server)"); //创建inputchannel对象 outserverchannel = new inputchannel(serverchannelname, sockets[0]); string8 clientchannelname = name; clientchannelname.append(" (client)"); //创建inputchannel对象 outclientchannel = new inputchannel(clientchannelname, sockets[1]); return ok; }
inputchannel既创建server又创建client,看着是很奇怪的行为,事实上,在linux中通信是通过fd就能实现,而inputchannel是parcelable的子类,可以把fd发送至wms.
失败的socket fd 监听方案
其实上面的这些代码和本篇关系不大,为什么要贴出代码呢,主要原因是我之前尝试过监听socket的fd,可问题是inputchannel的fd拿不到,除非channelname为空,但是上面两个都有channelname,然后我就去找有没有让name为空的方法,很遗憾也没有。
因此,这种实现只能借助native hook暴露接口,难度也有些大,因此,只能放弃这种方案了。
失败inputeventreceiver中间件方案
于是我找到另一种方案,在viewrootimple#windowinputeventreceiver 和 inputchannel之间插入一个middlewareinputeventreceiver,经过大量推断,将viewrootimple#windowinputeventreceiver dispose了,然后会发现,事件消费问题无法处理,因为viewrootimple#windowinputeventreceiver 调用finishinputevent的方法无法调用到middlewareinputeventreceiver。
为什么做这种尝试呢,主要还是下面一段代码,我们可以看到looper,这个类是可以传入looper的,inputchannel之间插入一个middlewareinputeventreceiver异步监听,然后转发给dispose后的windowinputeventreceiver。
public inputeventreceiver(inputchannel inputchannel, looper looper) { if (inputchannel == null) { throw new illegalargumentexception("inputchannel must not be null"); } if (looper == null) { throw new illegalargumentexception("looper must not be null"); } minputchannel = inputchannel; mmessagequeue = looper.getqueue(); mreceiverptr = nativeinit(new weakreference<inputeventreceiver>(this), inputchannel, mmessagequeue); mcloseguard.open("dispose"); }
anr monitor dialog方案
上面的方案中,实现复杂且稳定性很差,或许只有通过hook手段或者替换一些方法地址(artmethod)才能解决一些问题。
我们本篇利用一种比较新颖的方案,纯java实现 具体怎么实现的呢?
我们要先来确定以下几种关系。
viewrootimpl 与 windowsession关系
先来看一张图
在这张图中,我们可以清楚的看到,viewrootimpl和windowmanagerservice是多对一的关系,但是我们也要知道,他们之间的iwindow和iwindowsesssion和viewrootimpl也是一对一的关系,也就是说,一个viewrootimpl对应一个iwindow和iwindowsession。
因此,我们要明白,activity中的phonewindow和windowmanagerservice是没有任何关系的,activity中phonewindow也不负责管理如dialog、popwindow这样的组件,最终是windowmanager负责管理的。
好了,我们再看下一个知识点
window 层级
在android中,window是有层级关系的,当然这种关系被google改来改去,如果要使用的话需要处理一些兼容性问题。
目前来说,除了overlay类型外,其他的都需要window token来与activity强行绑定,但这不是本篇的重点,重点是,我们要知道为什么dialog作为activity的组件,会展示在activity的上面。
主要原因是activity的windowtype一般小于等于dialog的windowtype (dialog的为type_application_attached_dialog),因此他能展示activity上面。
注意: windowtype如果相等,那么后面加入的viewrootimpl层级也是高于前面的。
public int subwindowtypetolayerlw(int type) { switch (type) { case type_application_panel: case type_application_attached_dialog: return application_panel_sublayer;//返回值是1 case type_application_media: return application_media_sublayer;//返回值是-2 case type_application_media_overlay: return application_media_overlay_sublayer;//返回值是-1 case type_application_sub_panel: return application_sub_panel_sublayer;//返回值是2 case type_application_above_sub_panel: return application_above_sub_panel_sublayer;//返回值是3 } log.e(tag, "unknown sub-window type: " + type); return 0; }
那么展示在上面意味着什么?
我们要知道,在android系统中,window层级越高,意味着权限越大,假设你的弹窗能展示在系统弹窗(如指纹识别弹窗)的上面,那么你就可以做一些看不见的事。当然google是不会让你这么做的,google大费周折关联window token,就是为了修复此类风险。
那么,还意味着什么?
我们还知道,层级越高,surfsceflinger中展示顺序的优先级越高,主线程和renderthread线程优先级越高,同时线程调度的优先级越高,当然,和本篇有关的是,接收【事件】顺序的优先级越高。
viewrootimpl异步渲染
实际上,很多时候容易被忽略的一件事是,viewrootimpl其实是支持异步渲染的,同样choreographer也是支持异步的。为什么这样说呢?
因为现成的例子:android.app.dialog
在android系统中,dialog是支持异步弹出的,这也就是为什么其内部的handler是没有绑定主线程looper的原因。
核心原理
通过上面3个知识点,我们就可以做到一件事
在activity viewrootimpl上面加一个异步创建的dialog,然后将dialog接收的事件通过主线程handler转发给activity。
很显然,上面的方法是可行的。
那么,我们是不是可以做更多的事情呢?
答案是:是的。
阻断anr 产生
我们可以为了避免inputeventdispatcher anr,在dialog异步线程中,提前让inputeventreceiver的finishinputevent方法调用,这样就能避免anr。
延长anr 阈值
我们知道,inputeventdispatcher timeout时间为5s,我们可以主线程第4s的还没完成的时候,提前finishinputevent,然后我们自行启动异步监控,比如我们决定在第6s anr,如果主线程的任务在第6s没有结束,我们就下面的方法,来触发anr。
android.app.activitymanager#appnotresponding
public void appnotresponding(@nonnull final string reason) { try { getservice().appnotresponding(reason); } catch (remoteexception e) { throw e.rethrowfromsystemserver(); } }
监控anr
很多anr的监控都在native 层监控sig_quit信号,也有通过looper.printer进行检测到异常后,轮询ams的的相关接口。
但是这里都可以做到对anr的控制了,角色由消费者变成生产者,这种情况下自身就不需要监控了,只需要通知是否产生anr。
anrmonitordialog 实现逻辑
首先,我们我们来定义一个dialog,实际上,dialog会影响状态栏和底部导航栏的样式,因此,对于activity而言,为了避免dialog和activity的点击位置没法对齐,我们需要将activity的一些样式同步到dialog上,下面是同步了全屏和非全屏两种,实际过程可能还需要同步其他几种。
public class anrmonitordialog extends dialog { private static handlerthread anrmonitorthread = new handlerthread("anr-monitor"); static { anrmonitorthread.start(); } private static handler sanrmonitorhandler = new handler(anrmonitorthread.getlooper()); private final window.callback mhost; private final handler mainhandler; private boolean isfullscreen = false; anrmonitordialog(context context, window hostwindow) { super(context); this.mainhandler = new handler(looper.getmainlooper()); this.mhost = hostwindow.getcallback(); this.isfullscreen = (windowmanager.layoutparams.flag_fullscreen & hostwindow.getattributes().flags) != 0; } @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); window window = getwindow(); window.requestfeature(window.feature_no_title); view view = new view(getcontext()); view.setfocusableintouchmode(false); view.setfocusable(false); setcontentview(view); if (isfullscreen) { window.addflags(windowmanager.layoutparams.flag_fullscreen); } else { window.clearflags(windowmanager.layoutparams.flag_fullscreen); } windowmanager.layoutparams attributes = window.getattributes(); attributes.format = pixelformat.transparent; attributes.dimamount = 0f; attributes.flags |= flag_not_focusable; window.setbackgrounddrawable(new colordrawable(0x00000000)); window.setlayout(windowmanager.layoutparams.match_parent, windowmanager.layoutparams.match_parent); setcancelable(false); setcanceledontouchoutside(false); } public static void hidedialog(dialoginterface dialog) { if (dialog == null) return; sanrmonitorhandler.post(new runnable() { @override public void run() { dialog.dismiss(); } }); } public static void showdialog(final activity activity, final window window, onshowlistener onshowlistener) { sanrmonitorhandler.post(new runnable() { @override public void run() { if(activity.isfinishing()){ return; } anrmonitordialog anrmonitordialog = new anrmonitordialog(activity, window); anrmonitordialog.setonshowlistener(onshowlistener); anrmonitordialog.show(); } }); } // 省略一堆关键代码 }
在实现的过程中,我们可以复写dialog的一些方法,当然你还可以给dialog的window设置window.callback。这里要说的一点是,一些设备自定义了特殊的实现,如dispatchfnkeyevent,显然系统类中没有这个方法,但是如果你要实现的话无法通过super关键字调用,解决办法也是有的,就是利用java 7中的methodhandle动态invoke,这里我们暂不实现了,毕竟这个keyevent一般app也用不到。
/** * fixed lenovo/sharp device * */ @keep public boolean dispatchfnkeyevent(keyevent event) { //可以利用methodhandle调用父类的方法 return false; }
这里我们复写dialog的一些方法,我们以touchevent的传递为例子,当我们拿到motionevent的时候,我们就能将event转发给主线程。其实这里最稳妥的方法是对事件复制,因为motionevent是可以被recycle的,如果不复制就会被异步修改。
@override public boolean dispatchtouchevent(final motionevent event) { final waiter waiter = new waiter(); final motionevent targetevent = copymotionevent(event); mainhandler.post(new runnable() { @override public void run() { switch (event.getaction()) { case motionevent.action_down: break; case motionevent.action_up: case motionevent.action_cancel: break; } boolean ishandled = mhost.dispatchtouchevent(targetevent); targetevent.recycle(); //自己拷贝的事件,需要主动回收 waiter.countdown(ishandled); } }); try { if(!waiter.await(4000, timeunit.milliseconds)){ sanrmonitorhandler.postattime(manrtimeouttask, systemclock.uptimemillis() + 2000l); mainhandler.postatfrontofqueue(mcancelanrtimeouttask); } } catch (interruptedexception e) { e.printstacktrace(); } return waiter.ishandled; }
- manrtimeouttask 负责触发activitymanager#appnotresponding
- mcancelanrtimeouttask 用于取消sanrmonitorhandler的定时逻辑
private runnable manrtimeouttask = new runnable() { @override public void run() { sendappnotresponding("dispatching timeout"); } }; private runnable mcancelanrtimeouttask = new runnable() { @override public void run() { sanrmonitorhandler.removecallbacks(manrtimeouttask); } };
原理是,如果在指定的时间没有取消,说明主线程是卡住了,我们可以不抛anr,但是点击之后卡住不动,任何人的心情都会很难受,抑制anr发生并不可取,但是我们可以借助这些时间段收集一些线程状态和内存信息,以及业务信息,提高anr上报率和场景覆盖。
那么waiter是什么呢,其实是countdownlatch的子类,我们简单封装一下,来等待事件完成。
static class waiter extends countdownlatch { boolean ishandled = false; public waiter() { super(1); } public void countdown(boolean ishandled){ this.ishandled = ishandled; super.countdown(); } @override public void countdown() { throw new exception("i like along, don't call me"); } }
用法
很简单,我们在baseactivity的oncreate中加入即可
anrmonitordialog.showdialog(this, getwindow(), new dialoginterface.onshowlistener() { @override public void onshow(dialoginterface dialog) { anrmonitordialog = dialog; //由于是异步返回的的dialog,这里要做二次检测,防止inputchannel泄漏 posttomain(new runnable(){ if(actvitiyisfinish()){ anrmonitordialog.hidedialog(anrmonitordialog); anrmonitordialog = null; } }); } });
不过,我们一定要在ondestoryed中关闭dialog,避免inputchannel泄漏
@override protected void ondestroy() { anrmonitordialog.hidedialog(anrmonitordialog); super.ondestroy(); }
测试效果
经过测试,在touch event模式下,基本没有出现问题,滑动和点击都难正常,也不会出现遮挡,包括activity跳转也是正常的。
评价
通过上面的实现,我们将异步线程创建的全屏dialog覆盖到activity上面,然后通过dialog转发事件到activity,从而实现了在java层就能监控和阻断inputdispatching anr。
不过,这里也有些可能的问题,具体我们有测试,但可能会存在。
- 焦点问题:由于viewrootimpl 内部有焦点处理逻辑,如果把事件直接给window.callback可能还不合适,因此,如果是tv版本开发,还可能需要从decorview层面进一步兼容一下,不过测试过程中发现大部分走焦逻辑是正常的,暂没有发现特别严重的问题。
- 一些低级别windowtype的弹窗无法拦截事件:实际上,在android中,windowtype一样的话,后面的弹窗会覆盖到上面,但是对于一些魔改的系统,可能存在问题,但是解决办法就是调整windowtype,其次,anrmonitordialog要尽可能早一些弹出
- 仅限于对activity的事件监控: 本篇方案仅限于对activity的的监控,但如果是想支持其他dialog,那么要保证anrmonitordialog 有更高的层级,同时要能支持其他dialog的window.callback获取,当然,最好的方式就是从windowmanagerglobal中获取次一级的viewrootimpl,然后想办法获取decorview
- 输入法问题:由于部分系统输入法在dialog下面,按道理输入法层级更高才是,且输入法不属于app自身的ui,因此无法点击。我们要做2件事才能实现兼容: ①监听全局焦点,如果移动到textview或edittext上,那么需要关闭anrmonitordialog弹窗 ② hook windowmanager来判断是否有其他dialog弹出,等到其他dialog关闭后且焦点不在eidttext和textview上之后,同时判断键盘已经收起之后,再恢复anrmonitordialog 。
inputeventcompatprocessor方案
在android 10中,新增了inputeventcompatprocessor用來兼容事件,正因为如此,我们便可使用其在java层挂载hook,来绕过windowinputeventreceiver无法被复写的问题,下面是windowinputeventreceiver的源码部分
@override public void oninputevent(inputevent event) { trace.tracebegin(trace.trace_tag_view, "processinputeventforcompatibility"); list<inputevent> processedevents; try { processedevents = minputcompatprocessor.processinputeventforcompatibility(event); } finally { trace.traceend(trace.trace_tag_view); } if (processedevents != null) { if (processedevents.isempty()) { // inputevent consumed by minputcompatprocessor finishinputevent(event, true); } else { for (int i = 0; i < processedevents.size(); i++) { enqueueinputevent( processedevents.get(i), this, queuedinputevent.flag_modified_for_compatibility, true); } } } else { enqueueinputevent(event, this, 0, true); } }
上面的代码中,如果我们将windowinputeventreceiver的looper设置为异步的,然后,我们直接将后面的逻辑移动到processinputeventforcompatibility 进行处理,便能实现事件监控和阻断。
当然,为了避免重复处理,我们要返回的processedevents 为emptylist即可。
反隐藏类
显然我们需要反隐藏,我们需要反射一些方法,这里推荐使用《freeflection》开源项目去开启反射。
不过,为了能继承inputeventcompatprocessor,我们就需要一些新的手段
我们要hook被@hide标记的类实际上是不行的,因此我们可以在android studio中创建moudle,将这些被@hide类标记的空实现加入到 android.view包名下,然后通过compileonly方式引入项目中 比如viewrootimpl 的空实现
package android.view; public class viewrootimpl { }
那么inputeventcompatprocessor也是同理
package android.view; import android.content.context; import java.util.list; public class inputeventcompatprocessor { protected context mcontext; public inputeventcompatprocessor(context context) { mcontext = context; } public list<inputevent> processinputeventforcompatibility(inputevent e) { return null; } public inputevent processinputeventbeforefinish(inputevent e) { // no changes needed return e; } }
其他类如inputchannel,inputeventreceiver也是如此
inputeventcompatprocessor 事件异步转发实现
下面是核心实现,当然,anr 监控部分和dialog类似了,这里的监控和anr阻断方式和anr monitor dialog类似,就不再重复了。
public class windowinputeventcompatprocessor extends inputeventcompatprocessor { private final inputeventcompatprocessor processor; private final inputeventreceiver eventreceiver; private viewrootimpl viewrootimpl; final handler mainhandler; private static final atomicinteger mnextseq = new atomicinteger(); private sparseintarray eventmaps = new sparseintarray(); private method enqueueinputevent; public static final int flag_modified_for_compatibility = 1 << 6; private handler anrhandler; private string tag = "windowinputeventcompatprocessor"; private method finishinputevent; public windowinputeventcompatprocessor(context context, inputeventcompatprocessor processor, viewrootimpl viewrootimpl, inputeventreceiver eventreceiver) { super(context); this.processor = processor; this.mainhandler = new handler(looper.getmainlooper()); this.viewrootimpl = viewrootimpl; this.eventreceiver = eventreceiver; } @override public list<inputevent> processinputeventforcompatibility(inputevent e) { if (anrhandler == null) { anrhandler = new handler(looper.mylooper()); } inputevent copyevent = null; if(e instanceof keyevent){ copyevent = keyevent.changeflags((keyevent) e,((keyevent) e).getflags()); }else if( e instanceof motionevent){ copyevent = motionevent.obtain((motionevent) e); } if(copyevent == null){ return collections.emptylist(); } final inputevent event = copyevent; if(looper.mylooper() == looper.getmainlooper()){ anrhandler.post(new runnable() { @override public void run() { finishinputevent(e,true); } }); anrhandler.postattime(manrtimeouttask,event, systemclock.uptimemillis() + 6000l); mainhandler.post(new runnable() { @override public void run() { anrhandler.removecallbacks(manrtimeouttask,event); } }); list<inputevent> processedevents = processor.processinputeventforcompatibility(event); if(processedevents == null){ processedevents = new arraylist<>(); } if(processedevents.isempty()){ processedevents.add(event); } return processedevents; } eventmaps.append(event.hashcode(), mnextseq.getandincrement()); anrhandler.postattime(manrtimeouttask,event, systemclock.uptimemillis() + 6000l); mainhandler.post(new runnable() { @override public void run() { anrhandler.removecallbacks(manrtimeouttask,event); list<inputevent> processedevents = processor.processinputeventforcompatibility(event); if (processedevents != null) { if (processedevents.isempty()) { // inputevent consumed by minputcompatprocessor // finishinputevent(event, true); //这里一定不要调用哦,防止外部重复调用 } else { for (int i = 0; i < processedevents.size(); i++) { enqueueinputevent( processedevents.get(i), eventreceiver, flag_modified_for_compatibility, true); } } } else { //修改事件flag enqueueinputevent(event, eventreceiver, flag_modified_for_compatibility, true); } } }); return collections.emptylist(); } private void finishinputevent(inputevent event, boolean ishandled) { try { if (finishinputevent == null) { finishinputevent = class.forname(inputeventreceiver.class.getname()).getdeclaredmethod("finishinputevent", inputevent.class, boolean.class); finishinputevent.setaccessible(true); } finishinputevent.invoke(eventreceiver, event,ishandled); } catch (exception e) { e.printstacktrace(); } } private void enqueueinputevent(inputevent event, inputeventreceiver eventreceiver, int flags, boolean processimmediately) { try { if (enqueueinputevent == null) { enqueueinputevent = viewrootimpl.class.getdeclaredmethod("enqueueinputevent", inputevent.class, inputeventreceiver.class, int.class, boolean.class); enqueueinputevent.setaccessible(true); } enqueueinputevent.invoke(viewrootimpl, event, eventreceiver, flags, processimmediately); } catch (exception e) { e.printstacktrace(); } } @override public inputevent processinputeventbeforefinish(final inputevent e) { final int hashcode = e.hashcode(); runnable runnable = new runnable() { @override public void run() { int keyindex = eventmaps.indexofkey(hashcode); if (keyindex >= 0) { eventmaps.removeat(keyindex); } processor.processinputeventbeforefinish(e); } }; if(looper.mylooper() == anrhandler.getlooper()){ runnable.run(); }else { anrhandler.post(runnable); } return null; } private runnable manrtimeouttask = new runnable() { @override public void run() { appmanager.sendappnotresponding("dispatching timeout"); } }; }
注入新的inputeventreceiver
我们需要在activity的oncreate方法中进行注入,当然,这里有大量反射,我们不仅仅需要重新注入windowinputeventreceiver,还需要注入新的inputeventcompatprocessor
public class anrinterceptor { static final handlerthread handlerthread = new handlerthread("anr-looper"); static { if(build.version.sdk_int > build.version_codes.p){ handlerthread.start(); } } public static void monitor(final activity activity){ if(build.version.sdk_int <= build.version_codes.p){ return; } final view decorview = activity.getwindow().getdecorview(); decorview.addonattachstatechangelistener(new view.onattachstatechangelistener() { @override public void onviewattachedtowindow(@nonnull view decorview) { viewrootimpl viewrootimpl = (viewrootimpl) decorview.getparent(); try { class viewrootimplklass = viewrootimpl.getclass(); field minputcompatprocessorfield = viewrootimplklass.getdeclaredfield("minputcompatprocessor"); minputcompatprocessorfield.setaccessible(true); inputeventcompatprocessor inputeventcompatprocessor = (inputeventcompatprocessor) minputcompatprocessorfield.get(viewrootimpl); if(inputeventcompatprocessor instanceof windowinputeventcompatprocessor){ return; } field minputeventreceiverfield = viewrootimplklass.getdeclaredfield("minputeventreceiver"); minputeventreceiverfield.setaccessible(true); inputeventreceiver receiver = (inputeventreceiver) minputeventreceiverfield.get(viewrootimpl); class<?> windowinputeventreceiverclass = receiver.getclass(); field inputchannelfield = class.forname(inputeventreceiver.class.getname()).getdeclaredfield("minputchannel"); inputchannelfield.setaccessible(true); inputchannel inputchannel = (inputchannel) inputchannelfield.get(receiver); constructor windowinputeventreceiverconstructor = windowinputeventreceiverclass.getdeclaredconstructor(viewrootimpl.class,inputchannel.class, looper.class); windowinputeventreceiverconstructor.setaccessible(true); inputeventreceiver inputeventreceiver = (inputeventreceiver) windowinputeventreceiverconstructor.newinstance(viewrootimpl,inputchannel,handlerthread.getlooper()); inputeventcompatprocessor windowinputeventcompatprocessor = new windowinputeventcompatprocessor(activity,inputeventcompatprocessor,viewrootimpl,inputeventreceiver); minputeventreceiverfield.set(viewrootimpl,inputeventreceiver); minputcompatprocessorfield.set(viewrootimpl,windowinputeventcompatprocessor); receiver.dispose(); } catch (throwable e) { e.printstacktrace(); } } @override public void onviewdetachedfromwindow(@nonnull view v) { } }); } }
用法
在activity的oncreate方法中进行监控
override fun oncreate(savedinstancestate: bundle?) { enableedgetoedge(statusbarstyle = systembarstyle.dark(color.transparent)) super.oncreate(savedinstancestate) anrinterceptor.monitor(this) }
测试效果
可以完美兼容焦点模式和触屏两种模式,效果相对anr monitor dialog更好,不需要处理键盘、窗口层级,同时也避免了很多复杂的事件转发。
评价
相比anr monitor dialog而言,这种方法的稳定性相對差一些,同時需要大量反射,最重要的一点是无法兼容到android 10之前的版本。
总结
本篇实现了2种anr 监控方案 anr monior dialog 和inputeventcompatprocessor 各自都有优点和缺点,总体上,如果是android 10+版本的系统,建议使用后者。
目前来说,这两种方法在特定场景下还是比较实用的,比如调试环境,我们遇到一类问题,就是debug时间太长,一些系统中ams直接将app进程杀死;
还有就是一些系统,如果出现anr,连native层sigquit信号可能都来不及接收就直接force-stop进程的情况。
总之,这属于一种java层监控anr的方案,目前来说还有很多不足,但是至少来说,解决调试时anr进程被杀问题还是可以的,当然,能否线上使用,目前还有一些事情要处理。
以上就是android监控和阻断inputdispatching anr的方法的详细内容,更多关于android inputdispatching anr的资料请关注代码网其它相关文章!
发表评论