03_主线程联网问题&ANR&子线程不能修改UI
如果不使用HAXM,恐怕网页源码查看器无法获取servlet的源码。初步猜测是安卓模拟器运行速度太慢了。如果CPU不支持VT-x的话,HAXM是安装不上的。所以可以先开启VT-x.
可以参考几篇文章https://jingyan.baidu.com/article/ed15cb1b7586011be2698140.html http://www.cnblogs.com/lijc1990/archive/2013/02/07/2908969.html来解决安卓模拟器慢的问题。
安装intel的虚拟硬件加速软件“intelhaxm”,如D:\BaiduNetdiskDownload\adt-bundle-windows-x86_64_20140101\adt-bundle-windows-x86_64_20140101\sdk\extras\intel\Hardware_Accelerated_Execution_Manager
程序死了。
实际上在4.0以上的设备上它默认联网一定要开一个子线程。联网必须要开子线程。由于现在2.0以上4.0以下的设备基本上绝迹了。所以可以默认地认为联网一定要开子线程。主线程被阻塞住了,联网之后这是一个耗时的操作,联网之后经常会出现这样的问题,你这个网络环境不是很好,不是很好的话就一直在那里等,一直在那里等呢你这个线程被阻塞住之后你什么都不做不了这个用户体验是非常差的。
程序一运行起来默认它跑起来的这个线程就是主线程。所以在主线程里面不能做这种耗时的操作。所以咱们要开一个子线程。
4.0之后的设备你再做类似的操作马上就会抛出一个错误。因为它不允许在主线程当中访问网络。必须要开一个子线程。在4.0的设备也部署一下工程。
为什么没反应?因为它实际上抛异常咱们在这抓了一个大个的。有异常就都被抓住了。
多次出现ANR那你就会流失很多用户,大部分人都会把你的东西卸载掉。避免ANR异常,就是要把耗时的操作放到子线程去做。4.0之后联网必须要在子线程中否则会抛出异常。
又来异常了,程序之所以没挂是因为被catch住了。这个代码从一个错误的线程进行调用了。只有创建这个view结构的最初始的这个线程可以去操作这一个view。TextView是在主线程里面创建的。所以说想操作TextView这一个东西只能在主线程里面做不能在子线程里面做。
所以做安卓开发的第二个常识是:子线程不能修改UI/界面。未来的一周之内写代码的时候把这个忘了是无所谓的。但是两周之后敲了一两个应用程序之后联网还不开子线程或者是在子线程里面修改UI那这就说不过去了。
子线程不能修改UI这就涉及到一个多线程的问题了。你丢给子线程去做,究竟哪个线程先执行?这你就管不着了,这是CPU自己去控制。可能时间上是他先喝了一口血,然后对手按了一下打他,那究竟是这个血先喝呢还是这个对手先打他呢这就不好说了。这就涉及到线程同步的问题了。但是如果你操作这个界面的话如果是丢给线程的话这就不好说了,究竟是哪一个线程先操作这个界面让这个人倒下或者说这个血补上来那这个就很难判定了。有可能是说这个人没问题血补上了,也有可能说这个人被打倒下了然后血补上了又蹦起来了。如果多个线程操作这个界面你想让它没问题就得写这个锁,加锁,如果涉及到加锁的问题一旦这个程序员加锁处理不好可能会造成死锁的情况。所以干脆只有一个线程操作。所以强制修改UI必须在主线程进行。所以主线程又叫UI线程。
主线程叫UI线程又涉及到一个问题,我这个数据是从网上获取的,我要把这个内容交给UI线程,对TextView的持有进行操作。数据是从网上来的,但是也开子线程,所以我这个数据肯定是从子线程里获取到,但是我拿到数据还不能直接改,我还得在主线程里面进行修改。所以这个时候就出现一点悖论了。你联网必须得开子线程,子线程还不让改UI,我还要去修改这个UI.这就涉及到一个新的API:Handler。
package com.itheima.htmlcodeviewer; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import android.os.Bundle; import android.app.Activity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private EditText et_url; private Button btn_show; private TextView tv_code; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_url = (EditText) findViewById(R.id.et_url); btn_show = (Button) findViewById(R.id.btn_show); tv_code = (TextView) findViewById(R.id.tv_code); et_url.setText("http://10.0.2.2:8080/day07_01_servlethello/hello"); btn_show.setOnClickListener(new MyOnClickListenr()); String name = Thread.currentThread().getName(); Toast.makeText(this, name, Toast.LENGTH_SHORT).show(); } private class MyOnClickListenr implements OnClickListener{ @Override public void onClick(View v) { // TODO Auto-generated method stub //获取url String path = et_url.getText().toString().trim(); try { URL url = new URL(path); //URLConnection openConnection = url.openConnection(); HttpURLConnection openConnection = (HttpURLConnection) url.openConnection(); //} catch (MalformedURLException e) { //设置请求的方法 方法要大写 默认采用的是GET方式请求 openConnection.setRequestMethod("GET"); //如果联网之后网络信号不是太好,那就涉及到一直在等,一直在等 //有的时候可能会等的时间很长,等的时间很长的话那就有问题了,我究竟等到什么时候算是个结束 //设置超时的时间,一旦超过这个时间默认就是连接失败,让它停止下来 openConnection.setConnectTimeout(10000);//设置一个连接超时的时间 //获取响应码 int responseCode = openConnection.getResponseCode(); if(responseCode==200){ //联网后获得响应内容 响应是通过流的方式去返回回来的 //通过textview 展示对应的内容 InputStream inputStream = openConnection.getInputStream();//InputStream其实放的就是跟咱们HTML代码相关的内容 String stringFromStream = Utils.getStringFromStream(inputStream); //不能直接修改界面 而是要通知主线程 获取到了数据 并且把数据丢给主线程 在主线程中显示 //修改textview内容展示数据 tv_code.setText(stringFromStream); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //拿着url联网 //判断响应码 如果是200 } } }
package com.itheima.htmlcodeviewer; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import android.os.Bundle; import android.app.Activity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private EditText et_url; private Button btn_show; private TextView tv_code; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_url = (EditText) findViewById(R.id.et_url); btn_show = (Button) findViewById(R.id.btn_show); tv_code = (TextView) findViewById(R.id.tv_code); //et_url.setText("http://10.0.2.2:8080"); et_url.setText("http://10.0.2.2:8080/day07_01_servlethello/hello"); btn_show.setOnClickListener(new MyOnClickListenr()); String name = Thread.currentThread().getName(); Toast.makeText(this, name, Toast.LENGTH_SHORT).show(); } private class MyOnClickListenr implements OnClickListener{ @Override public void onClick(View v) { // TODO Auto-generated method stub new Thread(){ public void run() { //获取url String path = et_url.getText().toString().trim(); try { URL url = new URL(path); //拿着url联网 //URLConnection openConnection = url.openConnection(); HttpURLConnection openConnection = (HttpURLConnection) url.openConnection(); //} catch (MalformedURLException e) { //设置请求的方法 方法要大写 默认采用的是GET方式请求 openConnection.setRequestMethod("GET"); //如果联网之后网络信号不是太好,那就涉及到一直在等,一直在等 //有的时候可能会等的时间很长,等的时间很长的话那就有问题了,我究竟等到什么时候算是个结束 //设置超时的时间,一旦超过这个时间默认就是连接失败,让它停止下来 openConnection.setConnectTimeout(10000);//设置一个连接超时的时间 //获取响应码 int responseCode = openConnection.getResponseCode(); //判断响应码 如果是200 获取流 if(responseCode==200){ //联网后获得响应内容 响应是通过流的方式去返回回来的 //通过textview 展示对应的内容 InputStream inputStream = openConnection.getInputStream();//InputStream其实放的就是跟咱们HTML代码相关的内容 String stringFromStream = Utils.getStringFromStream(inputStream); //不能直接修改界面 而是要通知主线程 获取到了数据 并且把数据丢给主线程 在主线程中显示 //修改textview内容展示数据 tv_code.setText(stringFromStream); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }; }.start(); } } }
package com.itheima.htmlcodeviewer; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; public class Utils { //从这个流里面把它内容读出来,读出来之后把它转化成一个String类型的数据 public static String getStringFromStream(InputStream inputStream) { // TODO Auto-generated method stub //把流转化为字符串 ByteArrayOutputStream baso = new ByteArrayOutputStream(); int len = -1; byte[] buffer = new byte[1024]; try { while((len=inputStream.read(buffer))!=-1){ baso.write(buffer, 0, len); } inputStream.close(); byte[] byteArray = baso.toByteArray(); return new String(byteArray); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } }