4.多线程_耗时操作/ANR异常_通过网络获取数据

来源:互联网 发布:method(i,2))用法Java 编辑:程序博客网 时间:2024/04/30 14:31
Android中的多线程
1. 每一个应用的主线程都是死循环
2. 其他线程不能修改主线程中获取的控件中的内容

耗时操作/ANR异常

为什么主线程不能阻塞太久? 
主线程需要响应界面发生的事件(响应用户操作), 接收消息, 处理消息, 更新UI, 所以不能阻塞.  耗时操作不要放在主线程中. 不是说不可以, 最好不要这样. 但是访问网络必定是个耗时操作, 从4.0开始, 主线程不可以访问网络.

为什么子线程不能更新UI, 只有主线程可以?
线程并发问题, 多线程操作UI, 可能导致UI混乱.
 
子线程获取数据后如何更新主界面?
通过 Handler 对象. Handler对象有两种使用方法:
- post
- sendMessage
-----------------
这里需要补充!!!!
-----------------
 
另: ProgressBar 是个特例, 因为只要使用 ProgressBar, 肯定是耗时操作. 所以 ProgressBar 一般都是在子线程中使用的, Google为了方便使用, 对其进行了处理, 内部已经使用了 Handler, 可以直接在子线程中使用.
另外 SeekBar, ProgressDialog 也是一样的道理.

通过网络获取数据

获取文本文件

下面演示一个从网络获取文本数据的例子, 这是android下访问网络的标准写法.
{{{class="brush:java"
public class MainActivity extends Activity {
    private EditText et_address;
    private TextView tv_content;
    // 创建一个 Handler 对象, 用于其他线程和主线程之间进行数据通信.
    private Handler handler = new Handler();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_address = (EditText) findViewById(R.id.et_address);
        tv_content = (TextView) findViewById(R.id.tv_content);
    }
 
    public void getText(View view) {
        final String path = et_address.getText().toString().trim();
        if (TextUtils.isEmpty(path)) {
            Toast.makeText(getApplicationContext(), "地址不能为空", Toast.LENGTH_SHORT).show();
        } else {
            // 主线程不能调用操作时间过长的方法(如访问网络), 所以需要另起一个线程
            new Thread() {
                public void run() {
                    try {
                        final String text = UrlService.getText(path);
                        // 子线程是不能处理主线程中的控件中的数据的, 所以需要使用一个
                        // Handler对象告诉主线程怎样处理数据.
                        // Handler 的 post 方法可以传递一个 Runnable对象, 用于让主线程处理数据
                        handler.post(new Runnable() {
                            // 这里的 run 方法是在主线程中调用的.
                            @Override
                            public void run() {
                                tv_content.setText(text);
                            }
                        });
                    } catch (Exception e) {
                        e.printStackTrace();
                            handler.post(new Runnable() {
                            // 即使是弹出吐司, 也需要使用handler交给主线程处理.
                            @Override
                            public void run() {
                                Toast.makeText(getApplicationContext(), "获取失败",
                                    Toast.LENGTH_SHORT).show();
                            }
                        });
                    }
                };
            }.start();
        }
    }
}
// 访问网络的标准方式
public class UrlService {
    public static String getText(String path) throws Exception {
        URL url = new URL(path);
        // 通过URL的 openConnection 方法得到一个 HttpURLConnection
        // 这个对象封装了程序和服务器之间的连接
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        InputStream is = conn.getInputStream();
        // 使用一个 字节数组输出流, 存放数据
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int len = 0;
        while((len = is.read(buf)) > 0) {
            baos.write(buf, 0, len);
        }
        is.close();
        byte[] bs = baos.toByteArray();
        String string = new String(bs, "UTF-8");
        return string;
    }
}
 
--------------
无法创建Handler异常, 当设置了timeout后, 网速不给力或者路径错误, 就会出这个诡异异常
这是因为在 子线程try catch 中弹出吐司了, 相当于修改了UI, 只有主线程才能更新UI
 
子线程中能否创建handler?!!!!!
能, 但是要先调用一些方法!!!
-------------
 

获取图片

大体上和获取文本一样, 只是有一些细节需要处理, 这里只给出核心的一些代码.
{{{class="brush:java"
final Bitmap img = new UrlService(getApplicationContext())
        .getImg(path);
handler.post(new Runnable() {
    @Override
    public void run() {
        iv.setImageBitmap(img);
    }
}
 
    public Bitmap getImg(String path) throws Exception {
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        //conn.setRequestMethod("GET");
        // 下面这句话设置超时时间, 如果超过了指定时间, 程序会异常退出, 很奇怪
        //conn.setConnectTimeout(5000);
        // 缓存文件
        File cacheDir = context.getCacheDir();
        File cache = new File(cacheDir, getFileName(path));
        if(cache.exists()) {
            conn.setIfModifiedSince(System.currentTimeMillis());
        }
        // 获取并判断响应码
        int code = conn.getResponseCode();
        if(304 == code ) {
            // 如果为 304, 表示要拿缓存
            // conn.setIfModifiedSince(System.currentTimeMillis());
            return BitmapFactory.decodeFile(cache.getAbsolutePath());
        }else if(200 == code) {
            // 如果为 200, 表示正常相应
            InputStream is = conn.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buf = new byte[1024];
            int len = 0;
            while((len = is.read(buf)) > 0) {
                baos.write(buf, 0, len);
            }
            is.close();
            byte[] data = baos.toByteArray();
            // 写到本地缓存中
            File file = new File(cacheDir, getFileName(path));
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(data);
            fos.close();
            // BitmapFactory 可以从各种源中解析图片.
            Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            return bitmap;
        } else {
            throw new RuntimeException("获取失败");
        }
    }
 
    private String getFileName(String path) {
        int start = path.lastIndexOf("/") + 1;
        return path.substring(start);
    }
}}}

实际开发中, 我们一般使用开源项目 SmartImageView 来获取网络上的图片.
这个项目提供了一个控件 SmartImageView , 操作起来十分方便, 内部自动开线程, 使用handler
  1. <com.loopj.android.image.SmartImageView
  2. android:id="@+id/siv"
  3. android:layout_width="wrap_content"
  4. android:layout_height="wrap_content"
  5. android:layout_below="@id/et_address"
  6. android:layout_centerHorizontal="true"
  7. android:layout_marginTop="15dp"
  8. />
  9. public void getImgSmart(View view) {
  10. SmartImageView siv = (SmartImageView) findViewById(R.id.siv);
  11. // 通过URL地址加载图片, 设置加载失败时显示的图片, 设置加载过程中显示的图片
  12. siv.setImageUrl(et_address.getText().toString().trim(),
  13. android.R.drawable.ic_delete, android.R.drawable.ic_menu_gallery);
  14. }

  

解析XML/JSON

解析XML就是pull解析, 解析JSON也有对应的工具类
  
  1. public List<Person> getXml() throws Exception {
  2. URL url = new URL("");
  3. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  4. conn.setConnectTimeout(5000);
  5. int code = conn.getResponseCode();
  6. if(200==code) {
  7. InputStream is = conn.getInputStream();
  8. List<Person> list = parseXml(is);
  9. return list;
  10. }
  11. return null;
  12. }
  13. private List<Person> parseXml(InputStream is) throws Exception {
  14. XmlPullParser parser = Xml.newPullParser();
  15. parser.setInput(is, "UTF-8");
  16. List<Person> list = new ArrayList<Person>();
  17. Person p = null;
  18. for(int type=parser.getEventType(); type!=XmlPullParser.END_DOCUMENT; type=parser.next()) {
  19. if("person".equals(parser.getName())) {
  20. p = new Person();
  21. String value = parser.getAttributeValue(0);
  22. Long id = Long.parseLong(value);
  23. p.setId(id);
  24. list.add(p);
  25. } else if("name".equals(parser.getName())) {
  26. p.setName(parser.nextText());
  27. } else if("age".equals(parser.getName())) {
  28. p.setAge(Integer.parseInt(parser.nextText()));
  29. }
  30. }
  31. return list;
  32. }

  
解析JSON: 服务器传过来的是个 js 文件, 但是要求是 utf-8 编码.
Android 中使用 JSONArray, JSONObject 解析JSON
  1. public List<Person> getJson() throws Exception {
  2. URL url = new URL("");
  3. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  4. conn.setConnectTimeout(5000);
  5. int code = conn.getResponseCode();
  6. if(200==code) {
  7. InputStream is = conn.getInputStream();
  8. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  9. byte[] buffer = new byte[1024];
  10. int len = 0;
  11. while((len=is.read(buffer))>0) {
  12. baos.write(buffer, 0, len);
  13. }
  14. byte[] bs = baos.toByteArray();
  15. String json = new String(bs, "UTF-8");
  16. List<Person> list = parseJson(json);
  17. return list;
  18. }
  19. return null;
  20. }
  21. private List<Person> parseJson(String json) throws Exception {
  22. List<Person> list = new ArrayList<Person>();
  23. Person p = null;
  24. JSONArray arr = new JSONArray(json);
  25. for(int i=0; i<arr.length(); i++) {
  26. JSONObject obj = arr.getJSONObject(i);
  27. p = new Person();
  28. p.setId(obj.getLong("id"));
  29. p.setName(obj.getString("name"));
  30. p.setAge(obj.getInt("age"));
  31. list.add(p);
  32. }
  33. return list;
  34. }
  35. }}}

 

HttpClient

  1. public static String loginByHttpClientGet(String username, String password)
  2. throws Exception {
  3. // 1.打开一个浏览器
  4. HttpClient client = new DefaultHttpClient();
  5. // 2.输入地址
  6. String path = "http://192.168.1.100:8080/web/LoginServlet?username="
  7. + URLEncoder.encode(username, "UTF-8") + "&password="
  8. + URLEncoder.encode(password, "utf-8");
  9. HttpGet httpGet = new HttpGet(path);
  10. // 3.敲回车
  11. HttpResponse response = client.execute(httpGet);
  12. int code = response.getStatusLine().getStatusCode();
  13. if (code == 200) {
  14. InputStream is = response.getEntity().getContent();
  15. // 把is里面的内容转化成 string文本.
  16. String result = StreamUtils.readStream(is);
  17. return result;
  18. }else{
  19. return null;
  20. }
  21. }
  22. /**
  23. * 采用httpclient的post方式提交数据到服务器
  24. */
  25. public static String loginByHttpClientPost(String username, String password)
  26. throws Exception {
  27. // 1.打开一个浏览器
  28. HttpClient client = new DefaultHttpClient();
  29. // 2.输入地址
  30. String path = "http://192.168.1.100:8080/web/LoginServlet";
  31. HttpPost httpPost = new HttpPost(path);
  32. //设置要提交的数据实体
  33. List<NameValuePair> parameters = new ArrayList<NameValuePair>();
  34. parameters.add(new BasicNameValuePair("username", username));
  35. parameters.add(new BasicNameValuePair("password", password));
  36. UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters,"UTF-8");
  37. // StringEntity e = new StringEntity("aaa","bbb");
  38. // httpPost.setEntity(e);
  39. httpPost.setEntity(entity);
  40. // 3.敲回车
  41. HttpResponse response = client.execute(httpPost);
  42. int code = response.getStatusLine().getStatusCode();
  43. if (code == 200) {
  44. InputStream is = response.getEntity().getContent();
  45. // 把is里面的内容转化成 string文本.
  46. String result = StreamUtils.readStream(is);
  47. return result;
  48. }else{
  49. return null;
  50. }
  51. }


关于二者的选择: 2.3之前用 HttpClient, 2.3及以后用 HttpUrlConnection

AsyncHttpClient

普通方式提交数据到服务器需要自己拼接参数, 非常麻烦. 使用开源项目 android-async-http 提供的 AsyncHttpClient 类就方便太多了.
以后一律使用 AsyncHttpClient. 而且前面所有的问题都可以用这个解决!
{{{class="brush:java"
  1. public void asyncLogin(View v) {
  2. String username = et_username.getText().toString().trim();
  3. String password = et_password.getText().toString().trim();
  4. // 把要传递的参数封装成一个对象
  5. RequestParams params = new RequestParams();
  6. params.add("username", username);
  7. params.add("password", password);
  8. // 创建异步HTTP客户端, 向指定路径发送GET请求, 传递参数,
  9. // 请求成功或失败都会执行对应的回调函数
  10. new AsyncHttpClient().post("http://192.168.1.228:8080/05.Web/LoginServlet", params,
  11. new AsyncHttpResponseHandler() {
  12. public void onSuccess(String response) {
  13. Toast.makeText(getApplicationContext(), "Async 登录成功",
  14. Toast.LENGTH_SHORT).show();
  15. }
  16. public void onFailure(Throwable error, String content) {
  17. Toast.makeText(getApplicationContext(), "Async 登录失败",
  18. Toast.LENGTH_SHORT).show();
  19. }
  20. });
  21. }
  22. public void upload(View view) throws Exception {
  23. String username = et_username.getText().toString().trim();
  24. String password = et_password.getText().toString().trim();
  25. String filepath = et_file.getText().toString().trim();
  26. RequestParams params = new RequestParams();
  27. params.add("username", username);
  28. params.add("password", password);
  29. params.put("file", new File(filepath)); // 添加文件上传字段
  30. new AsyncHttpClient().post("http://192.168.1.228:8080/05.Web/LoginServlet", params,
  31. new AsyncHttpResponseHandler() {
  32. public void onSuccess(String response) {
  33. Toast.makeText(getApplicationContext(), "Async 上传成功",
  34. Toast.LENGTH_SHORT).show();
  35. }
  36. public void onFailure(Throwable error, String content) {
  37. Toast.makeText(getApplicationContext(), "Async 上传失败",
  38. Toast.LENGTH_SHORT).show();
  39. }
  40. });
  41. }
  42. }}}

 
 

访问WebService

有些WebService可以通过HTTP的方式请求, 传回XML. 所以其实就是解析 XML.
{{{class="brush:java"
  1. String phone = et_phone.getText().toString().trim();
  2. RequestParams rp = new RequestParams();
  3. rp.put("mobileCode", phone);
  4. rp.put("userID", "");
  5. client.get("http://webservice.webxml.com.cn/xxx/getMobileCodeInfo",
  6. rp, new AsyncHttpResponseHandler() {
  7. @Override
  8. public void onSuccess(String content) {
  9. try {
  10. XmlPullParser parser = Xml.newPullParser();
  11. parser.setInput(new StringReader(content));
  12. for (int type = parser.getEventType(); type != XmlPullParser.END_DOCUMENT;
  13. type = parser.next()) {
  14. if(type == XmlPullParser.START_TAG && "string".equals(parser.getName())){
  15. tv_result.setText(parser.nextText());
  16. }
  17. }
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. });
  23. }}}

多线程断点续传下载

  1.多线程
开启多个线程, 利用请求头"Range"控制下载数据的范围, 使用RandomAccessFile.seek()方法控制写入本地文件的范围
2.断点续传
在下载的过程中, 使用一种持久化存储的方式存储每个进程的下载进度, 如果下载中断, 读取上次存储的进度, 继续
3.手机端注意
网络操作必须在新线程中, 新线程中修改界面时需要回到主线程处理
ProgressBar内部已经实现了可以在新线程操作, 而如果使用TextView, 必须主线程处理
 
 
 


来自为知笔记(Wiz)


0 0
原创粉丝点击