复现在主线程上做网络请求抛异常

上篇文章: java HttpURLConnection实现简单的网络请求,用java实现了简单的网络请求。我们看看在Android的主线程上做网络请求,会发生什么?

代码测试下

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getTextFromHttp();
    }

    private void getTextFromHttp() {
        try {
            // 根据地址创建URL对象(网络访问的url)
            URL url = new URL("https://publicobject.com/helloworld.txt");
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setReadTimeout(5000);
            urlConnection.setConnectTimeout(5000);
            urlConnection.connect();
            if (urlConnection.getResponseCode() == 200) {
                InputStream is = urlConnection.getInputStream();
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                int len = 0;
                byte buffer[] = new byte[1024];
                // 按照缓冲区的大小,循环读取
                while ((len = is.read(buffer)) != -1) {
                    // 根据读取的长度写入到os对象中
                    os.write(buffer, 0, len);
                }
                is.close();
//                os.close(); //无需关闭
                urlConnection.disconnect();
                String result = new String(os.toByteArray());
                System.out.println(result);
            } else {
                System.out.println("------------------连接失败-----------------");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行下,报错了:

 W/System.err: android.os.NetworkOnMainThreadException
 W/System.err:     at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1303)
 W/System.err:     at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:86)
 W/System.err:     at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:74)
 W/System.err:     at java.net.InetAddress.getAllByName(InetAddress.java:752)
 W/System.err:     at com.android.okhttp.internal.Network$1.resolveInetAddresses(Network.java:29)
 W/System.err:     at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:187)
 W/System.err:     at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:156)
 W/System.err:     at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:98)
 W/System.err:     at com.android.okhttp.internal.http.HttpEngine.createNextConnection(HttpEngine.java:345)
 W/System.err:     at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:328)
 W/System.err:     at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:246)
 W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:457)
 W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:126)
 W/System.err:     at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:89)
 W/System.err:     at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java)
 W/System.err:     at com.exp.cpdemo.MainActivity.getTextFromHttp(MainActivity.java:41)
 W/System.err:     at com.exp.cpdemo.MainActivity.onCreate(MainActivity.java:31)
 W/System.err:     at android.app.Activity.performCreate(Activity.java:6760)
 W/System.err:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
 W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2619)
 W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727)
 W/System.err:     at android.app.ActivityThread.-wrap12(ActivityThread.java)
 W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1478)
 W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
 W/System.err:     at android.os.Looper.loop(Looper.java:154)
 W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6121)
 W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
 W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
 W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)

异常是在哪个类抛出的?

/frameworks/base/core/java/android/os/StrictMode.java

public final class StrictMode {
	......
    private static class AndroidBlockGuardPolicy implements BlockGuard.Policy {
        private int mPolicyMask;
        public AndroidBlockGuardPolicy(final int policyMask) {
            mPolicyMask = policyMask;
        }        
        ......
        public void onNetwork() {
            if ((mPolicyMask & DETECT_NETWORK) == 0) {
                return;
            }
            if ((mPolicyMask & PENALTY_DEATH_ON_NETWORK) != 0) {
            //在这里抛出了异常
                throw new NetworkOnMainThreadException();
            }
            ......
        }
        ......
    }
    ......
}

HttpsURLConnectionImpl在哪里初始化的?

 W/System.err:     at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java)
 W/System.err:     at com.exp.cpdemo.MainActivity.getTextFromHttp(MainActivity.java:41)
 W/System.err:     at com.exp.cpdemo.MainActivity.onCreate(MainActivity.java:31)

从日志中,可以看到 urlConnection.connect();执行后,就跳到了HttpsURLConnectionImpl.connect()方法。HttpsURLConnectionImpl是什么?为什么会跳到HttpsURLConnectionImpl里面?

            URL url = new URL("https://publicobject.com/helloworld.txt");
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
           	...
            urlConnection.connect();

HttpURLConnection是一个抽象类,并且里面没有connect()方法。
/libcore/luni/src/main/java/java/net/HttpURLConnection.java

public abstract class HttpURLConnection extends URLConnection {
	......
}

看下父类URLConnection里面有没有connect()方法。有,但是也是抽象方法。那这个connect()方法到底在哪里实现的?

public abstract class URLConnection {
	...
    public abstract void connect() throws IOException;
    ...
}

往前面看,对象一定是url.openConnection()生成的。我们看看openConnection()方法源码。
/libcore/luni/src/main/java/java/net/URL.java

public final class URL implements Serializable {
	...
	transient URLStreamHandler streamHandler;
	...
    public URLConnection openConnection() throws IOException {
        return streamHandler.openConnection(this);
    }
}

streamHandler是 URLStreamHandler 的对象。继续看URLStreamHandler里面的openConnection()方法。
/libcore/luni/src/main/java/java/net/URLStreamHandler.java

public abstract class URLStreamHandler {
	...
    protected abstract URLConnection openConnection(URL u) throws IOException;
	...
}

URLStreamHandler是一个抽象类。那它的实现类是谁?streamHandler是URL的成员变量,从URL类里面找找streamHandler的初始化。

	//我们初始化用的是这个构造器
    public URL(String spec) throws MalformedURLException {
        this((URL) null, spec, null);
    }
	
	//这里入参,context为null,handler也为null
    public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
        if (spec == null) {
            throw new MalformedURLException();
        }
        if (handler != null) {
            streamHandler = handler;
        }
        spec = spec.trim();

        protocol = UrlUtils.getSchemePrefix(spec);
        int schemeSpecificPartStart = protocol != null ? (protocol.length() + 1) : 0;

		...

        if (context != null) {
			...
        } else if (protocol == null) {
            throw new MalformedURLException("Protocol not found: " + spec);
        }

        if (streamHandler == null) {
        	//这里是streamHandler被赋值的地方
            setupStreamHandler();
            if (streamHandler == null) {
                throw new MalformedURLException("Unknown protocol: " + protocol);
            }
        }

		...
    }
    
    void setupStreamHandler() {
		...
		
        // Fall back to a built-in stream handler if the user didn't supply one
        if (protocol.equals("file")) {
            streamHandler = new FileHandler();
        } else if (protocol.equals("ftp")) {
            streamHandler = new FtpHandler();
        } else if (protocol.equals("http")) {
            try {
                String name = "com.android.okhttp.HttpHandler";
                streamHandler = (URLStreamHandler) Class.forName(name).newInstance();
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        } else if (protocol.equals("https")) { //从这里可以看到这个streamHandler是HttpsHandler
            try {
                String name = "com.android.okhttp.HttpsHandler";
                streamHandler = (URLStreamHandler) Class.forName(name).newInstance();
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        } else if (protocol.equals("jar")) {
            streamHandler = new JarHandler();
        }
        if (streamHandler != null) {
            streamHandlers.put(protocol, streamHandler);
        }
    }

原来streamHandler是HttpsHandler的对象。继续看HttpsHandler的openConnection()方法。

/external/okhttp/android/main/java/com/squareup/okhttp/HttpsHandler.java

	public final class HttpsHandler extends HttpHandler { ...... }

HttpsHandler 里面没有openConnection()方法,接着找他的父类HttpHandler。

	public class HttpHandler extends URLStreamHandler {
		......
	    @Override protected URLConnection openConnection(URL url) throws IOException {
        return newOkUrlFactory(null /* proxy */).open(url);
    	}
    	
 		...
 		
	    protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
        OkUrlFactory okUrlFactory = createHttpOkUrlFactory(proxy);
        // For HttpURLConnections created through java.net.URL Android uses a connection pool that
        // is aware when the default network changes so that pooled connections are not re-used when
        // the default network changes.
        okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get());
        return okUrlFactory;
    }

newOkUrlFactory()方法返回了OkUrlFactory对象。我们看OkUrlFactory的open()方法。
/external/okhttp/okhttp-urlconnection/src/main/java/com/squareup/okhttp/OkUrlFactory.java

public final class OkUrlFactory implements URLStreamHandlerFactory, Cloneable {
  ...
  public HttpURLConnection open(URL url) {
    return open(url, client.getProxy());
  }

  HttpURLConnection open(URL url, Proxy proxy) {
    String protocol = url.getProtocol();
    OkHttpClient copy = client.copyWithDefaults();
    copy.setProxy(proxy);

    if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy);
    if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy);
    throw new IllegalArgumentException("Unexpected protocol: " + protocol);
  }
  ...
}

终于,我们看到了HttpURLConnection在我们程序里的实现类HttpsURLConnectionImpl。

总结下:
URLConnection是抽象类,里面定义了抽象方法connect()。HttpURLConnection也是抽象类,虽然继承了URLConnection,但是并没有实现connect()方法。URLConnection有三个子类,它们都是抽象类。HttpURLConnection,HttpsURLConnection,JarURLConnection。

HttpsURLConnectionImpl的connect()方法里面做了什么?

HttpsURLConnectionImpl中没有connect方法,去父类DelegatingHttpsURLConnection中看看。
/external/okhttp/okhttp-urlconnection/src/main/java/com/squareup/okhttp/internal/huc/HttpsURLConnectionImpl.java

public final class HttpsURLConnectionImpl extends DelegatingHttpsURLConnection { 
  ...
  //这个构造器,正是OkUrlFactory中使用的
  public HttpsURLConnectionImpl(URL url, OkHttpClient client) {
 	//注意这里传递的是HttpURLConnectionImpl
 	//不是HttpsURLConnectionImpl
    this(new HttpURLConnectionImpl(url, client)); 
  }
 
  public HttpsURLConnectionImpl(HttpURLConnectionImpl delegate) {
    super(delegate); //这里调用了父类的构造器
    this.delegate = delegate;
  }

}

/external/okhttp/okhttp-urlconnection/src/main/java/com/squareup/okhttp/internal/huc/DelegatingHttpsURLConnection.java

abstract class DelegatingHttpsURLConnection extends HttpsURLConnection {
  private final HttpURLConnection delegate;
  public DelegatingHttpsURLConnection(HttpURLConnection delegate) {
    super(delegate.getURL());
    this.delegate = delegate;
  }

 ......
  @Override public void connect() throws IOException {
    connected = true;
    delegate.connect();
  }
  ......
}

注意这里的delegate是HttpURLConnection对象,不是HttpsURLConnection对象。上面已经分析了,HttpURLConnection没有connect()方法。那这个delegate又是哪个类的对象?注意到delegate有private final修饰,所以只可能在DelegatingHttpsURLConnection类中赋值,结合上面源码,就是在构造器中赋值的。看上面HttpsURLConnectionImpl的源码,可知这个delegate其实是HttpURLConnectionImpl对象。

所以,delegate.connect();最终调用的是HttpURLConnectionImpl的connect()方法。真的能绕啊!

/external/okhttp/okhttp-urlconnection/src/main/java/com/squareup/okhttp/internal/huc/HttpURLConnectionImpl.java

public class HttpURLConnectionImpl extends HttpURLConnection {
	
	...
	
  @Override public final void connect() throws IOException {
    initHttpEngine();
    boolean success;
    do {
      success = execute(false);
    } while (!success);
  }

	...
}

后面就是execute()方法了,有时间再分析。可参考HttpURLConnection源码解读

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值