网络图片查看器
- 确定图片的网址
发送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);