网络编程-图片、Html源文件查看器、get+post

网络图片查看器

  • 确定图片的网址
  • 发送http请求(子线程)

    String address = "网络地址";
    URL url = new URL(address);
    //获取连接对象,并没有建立连接
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    //设置连接和读取超时
    conn.setConnectTimeout(5000);
    conn.setReadTimeout(5000);
    //设置请求方法,注意必须大写
    conn.setRequestMethod("GET");
    //建立连接,发送get请求
    //conn.connect();
    //建立连接,然后获取响应吗,200说明请求成功
    conn.getResponseCode();
    
  • 服务器的图片是以流的形式返回给浏览器的(子线程)

    //拿到服务器返回的输入流
    InputStream is = conn.getInputStream();
    //把流里的数据读取出来,并构造成图片(Android--API)
    Bitmap bm = BitmapFactory.decodeStream(is);
    
  • 把图片设置为ImageView的显示内容(主线程)

    ImageView iv = (ImageView) findViewById(R.id.iv);
    iv.setImageBitmap(bm);
    
  • 添加权限

    <!-- 手机访问互连网需要权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    

主线程不能被阻塞

  • 在Android中,主线程被阻塞会导致应用不能刷新ui界面,不能响应用户操作,用户体验将非常差
  • 主线程阻塞时间过长,系统会抛出ANR异常
  • ANR:Application Not Response;应用无响应
  • 任何耗时操作都不可以写在主线程
  • 因为网络交互属于耗时操作,如果网速很慢,代码会阻塞,所以网络交互的代码不能运行在主线程

只有主线程能刷新ui

  • 刷新ui的代码只能运行在主线程,运行在子线程是没有任何效果的
  • 如果需要在子线程中刷新ui,使用消息队列机制
消息队列

  • Looper一旦发现Message Queue中有消息,就会把消息取出,然后把消息扔给Handler对象,Handler会调用自己的handleMessage方法来处理这条消息
  • handleMessage方法运行在主线程
  • 主线程创建时,消息队列和轮询器对象就会被创建,但是消息处理器对象,需要使用时,自行创建

    //消息队列
    Handler handler = new Handler(){
        //主线程中有一个消息轮询器looper,不断检测消息队列中是否有新消息,如果发现有新消息,自动调用此方法,注意此方法是在主线程中运行的
        public void handleMessage(android.os.Message msg) {
    
        }
    };
    
  • 在子线程中往消息队列里发消息

    //创建消息对象
    Message msg = new Message();
    //消息的obj属性可以赋值任何对象,通过这个属性可以携带数据
    msg.obj = bm;
    //what属性相当于一个标签,用于区分出不同的消息,从而运行不能的代码
    msg.what = 1;
    //发送消息
    handler.sendMessage(msg);
    

    注意:Toast不能在子线程里面,因为

  • 通过switch语句区分不同的消息(主线程)

    public void handleMessage(android.os.Message msg) {
        switch (msg.what) {
        //如果是1,说明属于请求成功的消息
        case 1:
            ImageView iv = (ImageView) findViewById(R.id.iv);
            Bitmap bm = (Bitmap) msg.obj;
            iv.setImageBitmap(bm);
            break;
        case 2:
            Toast.makeText(MainActivity.this, "请求失败", 0).show();
            break;
        }       
    }
    

加入缓存图片的功能

  • 把服务器返回的流里的数据读取出来,然后通过文件输入流写至本地文件

    //1.拿到服务器返回的输入流
    InputStream is = conn.getInputStream();
    //2.把流里的数据读取出来,并构造成图片
    
    FileOutputStream fos = new FileOutputStream(file);
    byte[] b = new byte[1024];
    int len = 0;
    while((len = is.read(b)) != -1){
        fos.write(b, 0, len);
    }
    
  • 创建bitmap对象的代码改成

    final String path = "http://192.168.13.13:8080/dd.jpg";
    final File file = new File(getCacheDir(), getFileName(path));
    //判断,缓存中是否存在该文件
    if(file.exists()){
        //如果缓存存在,从缓存读取图片
        System.out.println("从缓存读取的");
        Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath());
        ImageView.setImageBitmap(bm);
    }
    //设置文件名:dd.jpg
    public String getFileName(String path){
        int index = path.lastIndexOf("/");
        return path.substring(index + 1);
    }
    
  • 每次发送请求前检测一下在缓存中是否存在同名图片,如果存在,则读取缓存


获取开源代码的网站

  • code.google.com
  • github.com
  • 在github搜索smart-image-view
  • 下载开源项目smart-image-view
  • 使用自定义组件时,标签名字要写包名

    <com.loopj.android.image.SmartImageView/>
    
  • SmartImageView的使用

    SmartImageView siv = (SmartImageView) findViewById(R.id.siv);
    siv.setImageUrl("http://192.168.1.102:8080/dd.jpg");
    

Html源文件查看器

  • 发送GET请求

    URL url = new URL(path);
    //获取连接对象
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    //设置连接属性
    conn.setRequestMethod("GET");
    conn.setConnectTimeout(5000);
    conn.setReadTimeout(5000);
    //建立连接,获取响应吗
    if(conn.getResponseCode() == 200){
    
    }
    
  • 获取服务器返回的流,从流中把html源码读取出来

    byte[] buf = new byte[1024];
    int len = 0;
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    while((len = is.read(buf)) != -1){
        //把读到的字节先写入字节数组输出流中存起来
        bos.write(b, 0, len);
    }
    //把字节数组输出流中的内容转换成字符串
    //默认使用utf-8
    byte[] byteArray = bos.toByteArray();
    text = new String(byteArray);
    

乱码的处理

  • 乱码的出现是因为服务器和客户端码表不一致导致

    //Andriod默认的编码是UTF-8
    String temp = new String(byteArray);
    //如果temp字符串中包含UTF-8或utf-8
    if(temp.contains("charset=UTF-8") || temp.contains("charset=utf-8")){
        return new String(byteArray,"UTF-8");
    }else if(temp.contains("charset=GBK") || temp.contains("charset=gbk")){
        return new String(byteArray,"GBK");
    }else{
        return new String(byteArray,"UTF-8");
    }
    

提交数据

GET方式提交数据

  • get方式提交的数据是直接拼接在url的末尾

    final String path = "http://192.168.1.104/Web/servlet/CheckLogin?name=" + name + "&pass=" + pass;
    
  • 发送get请求,代码和之前一样

    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.setReadTimeout(5000);
    conn.setConnectTimeout(5000);
    if(conn.getResponseCode() == 200){
    
    }
    
  • 浏览器在发送请求携带数据时会对数据进行URL编码,我们写代码时也需要为中文进行URL编码
    方法一:直接拼接

    String path = "http://192.168.1.104/Web/servlet/CheckLogin?name=" + URLEncoder.encode(name) + "&pass=" + pass;
    

    方法二:用StringBuider拼接

    // url: http://192.168.1.104/Web/servlet/CheckLogin?account=android&pwd=1234
    StringBuilder urlSb=new StringBuilder(loginUrl);
    urlSb.append("?").append("account=");
    String account1=URLEncoder.encode(account, "UTF-8");
    urlSb.append(account1).append("&pwd=").append(pwd);
    URL url=new URL(urlSb.toString());
    

POST方式提交数据

  • post提交数据是用流写给服务器的
  • 协议头中多了两个属性

    • Content-Type: application/x-www-form-urlencoded,描述提交的数据的mimetype
    • Content-Length: 32,描述提交的数据的长度

      URL url=new URL(path);
      //1.设置url
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      //2. 设置请求方式  :POST 
      conn.setRequestMethod("POST");//设置请求方式
      conn.setConnectTimeout(5000);
      //3. 设置请求头
      conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");//设置请求体的类型
      //4. 设置请求体
      String body="account="+URLEncoder.encode(account, "UTF-8")+"&pwd="+pwd;
      //设置请求体的长度
      conn.setRequestProperty("Content-Length", body.getBytes().length+"");
      conn.setDoOutput(true);//表示允许 向服务端输出数据
      //5. 把请求体传递到服务端
      conn.getOutputStream().write(body.getBytes());//向服务端写字节数组
      if(conn.getResponseCode()==HttpURLConnection.HTTP_OK){
          //6.获取结果(输入流)
          InputStream is = conn.getInputStream();//取得输入流
          ......
      }
      
  • 设置允许打开post请求的流

    conn.setDoOutput(true);
    
  • 获取连接对象的输出流,往流里写要提交给服务器的数据

    OutputStream os = conn.getOutputStream();
    os.write(data.getBytes());
    

解析已经把服务端数据处理成字符串的JSON数据(详见android_day8,VideoClient)

    /**
    * json解析
    * 1.{'success':false, 'msg':'用户名不匹配'}
    * 2.{'success':false, 'msg':'密码不匹配'}
    * 3.{'success':true, 'data':[{'id':1,'name':'故事1.0','icon':'55.png','length':120}, 
    *                            {'id':2,'name':'故事2.0','icon':'56.png','length':120}
    *                           ]
    *    }
    */

    //把返回的字符串转换为json对象(result从服务器拿到的数据并处理成字符串,过程上面已经提到)
    JSONObject jsonObject=new JSONObject(result);
    boolean loginFlag = jsonObject.getBoolean("success");
    Message message=new Message();//消息 ,子线程向主线程发送的消息
    if(loginFlag){
        //表示验证成功
        JSONArray jsonArray=jsonObject.getJSONArray("data");
        //把json数组转换为List<Map> ,通过Json的工具类,把json数组解析为List<Map>
        List<Map<String,Object>> videos=VideoManager.parseJsonArray(jsonArray);//这个是自定义业务类和方法
        message.what=LOGIN_SUCCESS;
        message.obj=videos;
    }else{
        ......
    }

问题总结:

1. 在4.4模拟器上自定义ContentProvider中出现权限的问题?
permission : 权限
exported =true : 在高版本需要设置该属性 
1.5  -- 2.2   2.3.3  
 3.0 -3.1-3.2 (平板) : fragment (片段)、ActionBar :操作栏
4.0以后: 加强安全 ,网络访问子线程、广播接收器
2. 非UI线程使用Toast报错
在子线程直接不能处理 View控件
Handler.sendMessage() 
吐司的消息是放在消息队列中,进行排队显示,用到Handler ,而Hanlder有需要依赖Looper 
在子线程使用Toast ,要开启Looper ,注意主线程默认是已经开启了Looper ,而子线程默认没有开启
Looper.prepare()
Toast.makeText(context,text,duration).show();
Looper.loop();
3. 对Context对象没有概念,不明白为什么很多地方都需要这个对象
1. 作用: 获取项目的资源,取得系统的服务,即上下文环境
2. 分类: 
 - 组件级别的上下文  : Activity、Service,它们都是继承Context
  java web :application、session、request、page :作用域,生命周期
 - 应用级别的上下文  : Application,它是Context的子类,对象是单例的
3. 使用 ,一般使用组件级别的上下文 ,对于Toast,可以用应用级别的或者组件级别的
 注意1: 假如某个对象要使用上下文,要求该对象的生命周期必须要小于或者等于该context 的生命周期
        //取得项目的资源
 注意2:不要用静态的变量来引用组件级别的上下文

    String string = this.getResources().getString(R.string.app_name);
    //取系统服务
    NotificationManager manager=(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值