前言
如何在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的资料请关注代码网其它相关文章!
发表评论