android throw exception 原理,android NetworkOnMainThreadException原理

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

https://developer.android.com/reference/android/os/NetworkOnMainThreadException.html

android.os.NetworkOnMainThreadException众所周知是在主线程上做网络操作抛出的异常,这里通过源代码对其产生的原因做一点分析和扩展

在4.4.2手机上运行一段异常代码,日志如下E/AndroidRuntime: FATAL EXCEPTION: main

android.os.NetworkOnMainThreadException

at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1145)

at libcore.io.BlockGuardOs.recvfrom(BlockGuardOs.java:163)

at libcore.io.IoBridge.recvfrom(IoBridge.java:506)

at java.net.PlainSocketImpl.read(PlainSocketImpl.java:488)

at java.net.PlainSocketImpl.access$000(PlainSocketImpl.java:46)

at java.net.PlainSocketImpl$PlainSocketInputStream.read(PlainSocketImpl.java:240)

通过提示可以看到是在PlainSocketImpl的内部类PlainSocketInputStream读取字节流时抛出的,这里的access$000方法是java编译器为内部类访问外部方法时自动生成的1220 private static class PlainSocketInputStream extends InputStream {

221 private final PlainSocketImpl socketImpl;

222

223 public PlainSocketInputStream(PlainSocketImpl socketImpl) {

224 this.socketImpl = socketImpl;

225 }

226

227 @Override public int available() throws IOException {

228 return socketImpl.available();

229 }

230

231 @Override public void close() throws IOException {

232 socketImpl.close();

233 }

234

235 @Override public int read() throws IOException {

236 return Streams.readSingleByte(this);

237 }

238

239 @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {

240 return socketImpl.read(buffer, byteOffset, byteCount);

241 }

242 }

实际上调用了PlainSocketImpl的read方法1480 private int read(byte[] buffer, int offset, int byteCount) throws IOException {

481 if (byteCount == 0) {

482 return 0;

483 }

484 Arrays.checkOffsetAndCount(buffer.length, offset, byteCount);

485 if (shutdownInput) {

486 return -1;

487 }

488 int readCount = IoBridge.recvfrom(true, fd, buffer, offset, byteCount, 0, null, false);

489 // Return of zero bytes for a blocking socket means a timeout occurred

490 if (readCount == 0) {

491 throw new SocketTimeoutException();

492 }

493 // Return of -1 indicates the peer was closed

494 if (readCount == -1) {

495 shutdownInput = true;

496 }

497 return readCount;

498 }

从这段代码找到IoBridge.recvfrom这个方法可以看到,IoBridge作为桥接,调用Libcore1502 public static int recvfrom(boolean isRead, FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, DatagramPacket packet, boolean isConnected) throws IOException {

503 int result;

504 try {

505 InetSocketAddress srcAddress = (packet != null && !isConnected) ? new InetSocketAddress() : null;

506 result = Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress);

507 result = postRecvfrom(isRead, packet, isConnected, srcAddress, result);

508 } catch (ErrnoException errnoException) {

509 result = maybeThrowAfterRecvfrom(isRead, isConnected, errnoException);

510 }

511 return result;

512 }

再看LibCore119public final class Libcore {

20 private Libcore() { }

21

22 public static Os os = new BlockGuardOs(new Posix());

23}

可以看到出问题的在BlockGuardOs中的1162 @Override public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException {

163 BlockGuard.getThreadPolicy().onNetwork();

164 return os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress);

165 }

这里的os的实现类Posix最终通过native方法完成了网络操作,而在此之前的1BlockGuard.getThreadPolicy().onNetwork();

这行代码,抛出了NetworkOnMainThreadException异常,BlockGuard顾名思义是对可能产生阻塞的地方做预处理,BlockGuardOs其中有很多IO方法,猜测android的IO操作都是通过了这个预处理来防止主线程阻塞。

再看BlockGuard1126 private static ThreadLocal threadPolicy = new ThreadLocal() {

127 @Override protected Policy initialValue() {

128 return LAX_POLICY;

129 }

130 };

刚才用来做异常抛出的ThreadPolicy原来是一个ThreadLocal,让每个线程持有自己的Policy来做阻塞判断,可以预见这里主线程持有的Policy和其他子线程不同,对网络请求有特殊处理。

BlockGuard中的这个Policy是在StrictMode的setBlockGuardPolicy方法中设置1781 // Sets the policy in Dalvik/libcore (BlockGuard)

782 private static void setBlockGuardPolicy(final int policyMask) {

783 if (policyMask == 0) {

784 BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY);

785 return;

786 }

787 final BlockGuard.Policy policy = BlockGuard.getThreadPolicy();

788 final AndroidBlockGuardPolicy androidPolicy;

789 if (policy instanceof AndroidBlockGuardPolicy) {

790 androidPolicy = (AndroidBlockGuardPolicy) policy;

791 } else {

792 androidPolicy = threadAndroidPolicy.get();

793 BlockGuard.setThreadPolicy(androidPolicy);

794 }

795 androidPolicy.setPolicyMask(policyMask);

796 }

通常我们对StrictMode严格模式的使用是为了避免在主线程上做IO或者网络等耗时操作,优化用户体验,避免ANR

AndroidBlockGuardPolicy实现了Policy接口,其中11140 public void onNetwork() {

1141 if ((mPolicyMask & DETECT_NETWORK) == 0) {

1142 return;

1143 }

1144 if ((mPolicyMask & PENALTY_DEATH_ON_NETWORK) != 0) {

1145 throw new NetworkOnMainThreadException();//这里抛出的异常

1146 }

1147 if (tooManyViolationsThisLoop()) {

1148 return;

1149 }

1150 BlockGuard.BlockGuardPolicyException e = new StrictModeNetworkViolation(mPolicyMask);

1151 e.fillInStackTrace();

1152 startHandlingViolationException(e);

1153 }

使用位操作来判断阻塞,PENALTY_DEATH_ON_NETWORK在enableDeathOnNetwork中被打开1987 /**

988 * Used by the framework to make network usage on the main

989 * thread a fatal error.

990 *

991 * @hide

992 */

993 public static void enableDeathOnNetwork() {

994 int oldPolicy = getThreadPolicyMask();

995 int newPolicy = oldPolicy | DETECT_NETWORK | PENALTY_DEATH_ON_NETWORK;

996 setThreadPolicyMask(newPolicy);

997 }

而在ActivityThread的handleBindApplication中有这么一行代码14196 if (data.appInfo.targetSdkVersion > 9) {

4197 StrictMode.enableDeathOnNetwork();

4198 }

也就是说在app启动时,4.4.2版本默认对sdk9以上的主线程开启了不得在主线程上做网络操作

到这里可以总结一下,网络操作通过BlockGuardOs来执行,而其中的IO操作被BlockGuard预处理,BlockGuard则是通过当前线程上的policy策略来做处理,而主线程上的policy在app启动时就被设置为不得在主线程上做网络操作,所以如果我们在主线程上做将这个开关重置,即可在主线程上做网络操作而不会抛出NetworkOnMainThreadException1StrictMode.ThreadPolicy threadPolicy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();

StrictMode.setThreadPolicy(threadPolicy);

当然这里只是为了理解原理,不建议在生产环境关闭这个开关

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值