Android中的网络与数据(一)
前言:在写项目的时候,由于将网络请求简单封装成了一个类,因此出了很多问题。所以简单写了这个博客
并不想系统的介绍。想用更简单能理解的方式说明可能发生的问题
背景
在Android中,我们需要大量的数据,而那些数据不能永远保存在手机本地。试想一下,当你打开淘宝的时候,你只能浏览已经写死的数据。那自然就没有了那么琳琅满目的商品。所以,大多数的数据都是从网络中获取到的。于是,就需要在程序中去访问互联网(服务器)并且请求数据。
网络与数据
不得不说,Android是一个非常全面的工具。她为网络请求基本没有要求,又到处限制。说简单,可以用原生工具类解决问题;说难,使用的条条框框又比较复杂……
简单的开始
在Android中使用网络的开始,不妨先直接在Activity中写一个试试?这里用原生的net工具类:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); URL url = null; try { // 可能报IO异常 url = new URL("https://www.baidu.com"); HttpURLConnection conn = ((HttpURLConnection) url.openConnection()); conn.setRequestMethod("POST"); OutputStream outputStream = conn.getOutputStream(); // 在这里进行网络请求、获取数据 } catch (IOException e) { throw new RuntimeException(e); } } }
于是就可以发现,他真的没跑起来……:
NetworkOnMainThreadException 翻译过就是在MainTread中使用网络,也就是说,android是不允许在主线程(UI线程)中访问网络的。
有的同学可能会问:为什么不呢?
可以想一下,如果我请求操作的时候界面直接卡死了(因为主线程在等待网络的过程中,而这个过程很有可能非常长),那这时候叫天天不灵,叫地地不应,这时候整个系统都挺蒙圈的,到底要不要继续等呢?
直接放弃吧,万一一下就好了呢;继续等吧,万一耽误其他功能了呢。所以当然不能再主线程使用了呀。那我们该怎么用呢
基本使用
不能在主线程用,那我新建一个线程呗:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 新建了一个线程 new Thread(new Runnable() { @Override public void run() { URL url = null; try { url = new URL("https://www.baidu.com"); HttpURLConnection conn = ((HttpURLConnection) url.openConnection()); conn.setRequestMethod("POST"); OutputStream outputStream = conn.getOutputStream(); // 进行网络请求、获取数据 } catch (IOException e) { throw new RuntimeException(e); } } }).start(); } }
编译,运行:
Permission denied (missing INTERNET permission?) 也就是 权限缺失(缺少网络权限?)
感慨一句:Android实在是太细啦。
权限问题其实非常简单,也很好理解:你在我的手机里就像我的一个宠物。你要的所有东西都得问问我,我不同意,你就什么也干不了。这样也能保护一些隐私数据,非常好用。
怎么解决?找到Manifast文件,在manifast标签下加入:
<uses-permission android:name="android.permission.INTERNET"/>
最终效果:
再次运行,这下终于没报错了。
更深一步
虽然这下没报错,但是似乎还没什么效果。看着界面上的Hello Word陷入了沉思。请求的数据当然不能白白请求。能看到才是真的成功。于是先对界面和请求做一些简单改造好了
界面:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="这里会放着请求来的数据" android:id="@+id/textView"/> </LinearLayout>
界面效果:
MainActivity
public class MainActivity extends AppCompatActivity { private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 得到textView控件 tv = findViewById(R.id.textView); // 从网络中请求数据 getData(); } private void getData() { new Thread(new Runnable() { @Override public void run() { URL url = null; try { url = new URL("https://www.baidu.com"); HttpURLConnection conn = ((HttpURLConnection) url.openConnection()); conn.setRequestMethod("POST"); // 获取连接的输入流(从网络写入本地) InputStream inputStream = conn.getInputStream(); byte[] buffer = new byte[1024]; StringBuilder data = new StringBuilder(); // 从输入流中不断的读取数据并拼接 while (inputStream.read(buffer) > 0) { data.append(Arrays.toString(buffer)); } setData(data.toString()); } catch (IOException e) { throw new RuntimeException(e); } } }).start(); } // 为控件设置内容 private void setData(String data) { tv.setText(data); } }
ok, 编译运行:
fine, 这次又出什么幺蛾子了:
Only the original thread that created a view hierarchy can touch its views.
也很好理解:只有在主线程(UI线程)中才能访问到主线程中控件
上边其实提到了很多次UI线程。其实可以简单理解成UI线程就是主线程。他们是一个东西
那怎么办呢?只有在主线程中才能设置控件,而主线程中又没办法进行网络请求。看上去已经没有解决方法的?
别急,Android早就想到了这个问题,于是,runOnUiThread方法出现:
private void getData() { new Thread(new Runnable() { @Override public void run() { URL url = null; try { url = new URL("https://www.baidu.com"); HttpURLConnection conn = ((HttpURLConnection) url.openConnection()); conn.setRequestMethod("POST"); // 获取连接的输入流 InputStream inputStream = conn.getInputStream(); byte[] buffer = new byte[1024]; StringBuilder data = new StringBuilder(); // 从输入流中不断的读取数据并拼接 while (inputStream.read(buffer) > 0) { data.append(Arrays.toString(buffer)); } // 在主线程中执行方法 runOnUiThread(new Runnable() { @Override public void run() { setData(data.toString()); } }); } catch (IOException e) { throw new RuntimeException(e); } } }).start(); }
runOnUiThread:翻译过来就是在UI线程中执行。编译运行试试:
OK,姑且不论这是什么。总之我们终于是得到了数据啦!
总结一下
那么我们获取数据一共需要几步呢?
- 设置网络权限
- 创建新线程
- 新线程中获取数据
- 在UI线程中展示数据