1,Android网络请求
1.1显示互联网的图片
①获取输入的路径
②使用API URL(统一资源定位符)
URL url = new URL(path);
③通过路径打开一个http的连接,打开通道
HttpURLConnection conn = (强转)url.openConnection();
④得到服务器的返回信息
String type = conn.getContentType();//得到服务器返回的数据类型
Int code = comm.getResponseCode()//返回状态码200ok,404找不到,500服务器内部错误
If(code == 200){
//获得服务器输入流
Is = conn.getInputStream();
Bitmap bm = BitmapFactory.decodeStream(is)//通过输入流转换成位图资源
//bitmap就是 Android下的图片对象,默认都是32位图
//decode可以转换图片文件对象,也可以转换资源文件下的图片对象
//设置到对应控件上
iv.setImageBitMap(bitmap);
Is.close();
//但是上面这种写法会报错UnknowHostException,不知道的主机异常
//是因为没有设置权限android.permission.INTERNET
}
//细节问题:
conn.setRequestMethod(“POST”);//设置请求方式,默认为GET
conn.setConnectionTimeout(50000);//设置请求服务器的超时时间,超过就不再等待
conn.getContentLength();//返回服务器资源的长度
流的释放要放到finally里面.
1.2在有的模拟器上,上面的代码会运行不了(NetworkOnMainThreadException,网络在主线程的异常)(2.3的版本可以,4.1的版本不可以,因为谷歌不允许可能要花费一段时间的代码在主线程中运行,比如在主线程中休眠10秒钟,也不会允许执行)
简单的说,就是主线程允许阻塞(大多数图形化界面都是一个死循环),
ANR错误:Application not response 应用程序无响应
谷歌设计的用户保护模式,如果主线程等待太长时间,就报 ANR错误,
所以在4.0以后,访问网络的操作,必须写在子线程里
1.3,改进版网络图片查看器
通过子线程(匿名内部类,自定义继承Thread类,自定义实现runable接口)的方式,请求图片,获取数据.
但是这样会出ViewRootImp$CalledFromWrongThreadException 从错误的线程调用的代码.后面的信息是子线程不能更新ui;
查看LogCat的时候,找自己创建的类中报出的异常.
原因:非ui线程(主线程)对ui线程的修改,在android中为非法的.
只有ui才能修改ui线程中的内容
2,主线程模型:
2.1更新ui,单一主线程,审查机制(在界面可见时,才会审查,如果是界面第一次打开,在加载的时候,是可以让子线程修改ui的,相当于此时审查机制还没有启动,子线程可以悄悄的修改,但是如果在界面开启之后想要更新ui就会报错)
2.2子线程想要更新ui:Handler消息处理器,类似主线程的代理人.
在主线程中,google设置了一个message queue消息队列,通过Handler把请求放在消息队列里(MessageQueue),在消息队列里有一个监听器(底层会自动启动):looper,当looper获取消息之后,会找到Handler,Handler里有一个handleMessage()方法处理器(要注意的是,Handler是主线程的一个内部类,就是把请求间接的放在主线程中使用)
2.3,执行步骤
①定义一个Handler消息处理器
②通过消息处理器发送消息handler.sendMessage(msg)
Message msg = new Message();//创建一个Message对象
一般开发中用Message.obtain();这个方法底层优化了Message对象,可以重复利用Message对象,减少内存消耗
把需要发送的消息放在msg.obj中(Obejct类型的成员变量可以接受一切请求,相当于一个通用型的容器,起到转发信息的作用)
msg.obj = ...........;
③looper接收到消息,找到Handler(这一步在代码中没有体现,但是google框架有这一步)
④在创建的Hadler引用指向匿名子类对象
Handler handler = new Hadler(){
//重写handleMessage方法
String text = msg.obj;
//设置ui即可
//不要把super.handleMess(message)删掉了
}
Hanlder底层发送消息的流程:
用到的类:Looper,Hanlder,Message,MessageQueue(消息队列,按时间排序)
Looper初始化过程
Activiy中初始化Looper,调用了ThreadLocal 中的get()方法,
>>Looper.prepare(),初始化了Lopper对象,同时初始化了MessageQueue对象,一个Looper对象只会有一个消息队列.
>>Looper.loop();开始轮寻,不断查看消息队列里面的内容,取到msg对象
然后拿msg.target(Handler对象).dispatchMessage()方法进行转发
>>dispatchMessage()里面调用了handler的handlerMessage(msg)方法,这样就把msg发送到了主线程中,这时候可以重写hanlderMessage(msg)方法,在主线程获取到信息再进行操作
Hanlder:发送消息的流程
sendMessage(msg)
>>sendMessageDelayed(msg,0)(第二个参数可以发送延迟信息)
>>sendMessageAtTime(msg,SystemClock.uptimeMillis + delayMissis);
>>enqueueMessage(queue,msg,uptimeMillis):MessageQueue对象方法
在这个方法里,把当前的handler对象中赋值到了我们发送的消息msg里面,同时把meg对象放入到了MessageQueue里,通过que
>>
图片查看器改进版:
如果网络异常,会抛出SocketXXXXtimeoutException Socket连接失败
//不能在子线程打印Toast,因为它的底层也是用的消息实现的,如果要在子线程中使用Toast打印,后面会讲到
Msg.what = xxx
正确的代码
Msg.what=yyy
错误的代码
通过指定msg.what类型可以在handlerMessage()中区分不同的类型
3.练习:网络源码查看器(电脑上可以右击浏览器查看源代码,这里主要是为了练习Android的网络相关API)
配置权限
①定义按钮,图片地址输入框(查看的网址),文本显示框(显示源码)
②找到控件,并且定义单击事件
③单击事件中:获取网址
④创建子线程,重写run方法,调用start()方法
创建URL对象,转换路径
这列可以开启等待框
Try{
URL url = new URL(path)
HttpURLConnection conn = url.openConnection()//打开连接
//设置请求方式,超时时间
Conn.setRequestMethod(“GET”);
Conn.setConnectionTimeOut(5000);
//获得状态吗
Int code = Conn,getResponseCode();
If(code == 200){
//获取服务器连接输入流
InputStream is = conn.getInputStream();
调用转换类,把流信息转换成字符串
//调用Handler方法
Message msg = Message.obtain();
Msg.what=xxx; //让它代表一个数字
Msg.obj=xxxx;
handler.sendMessage(msg);
}
}catch(xxx){
Msg.what=xxx
Msg = Message.obtain();
Msg.obj=xxxx;
}
⑤在handle中重写HandleMessage方法,设置对应的控件文本,并关闭等待框
额外:①定义一个能把流中内容转换成字符串的类(用的挺多,提取出来做工具)
定义方法
定义内存输出流
ByteArrayOutPutStream baos = new XXX;
Byte()buffer = new byte[1024];
Int len = -1;
While(XXX){
Baos.write(x,x,x);
}
Is.close();
②定义一个Scroll控件来显示文本
③定义一个等待框,定义在子线程中开启,在handlerMessage()中关闭,代表已经把msg发送过来了,这时候就可以关闭等待框了
4.User-Agent:
conn.setRequestProperty(“User-Agent”,xxxxx)
通过设置User-Agent头信息,可以让服务器识别这是什么浏览器
5.中文乱码问题
在模拟器中访问TomCat服务器不能写LocalHost或者127.0.0.1
通过cmd指令中的ifconfig可以查看本机ip,写这个ip地址.
5.1 常见乱码>>>>黑色菱形带问号,utf-8的数据以gb2313或gbk读取就会出现这样问题.
解决方法,判断服务器中反馈信息中包含的编码信息,从而设置解码方式
6,手机号码归属地案例 解析服务器返回的xml信息
需求:用户输入手机号,点击测试,反馈手机号,归属地,吉凶
EdtiText Button TextView TextView TextView
TextView中有一个属性,drawableLeft可以通过资源文件在文本框左边显示一个小图标,美化界面
通过手机号,访问网站,观察反馈的信息来解析,获取需要的信息
案例中反馈的是xml文件,可以用xml解析
7,天气预报,解析服务器返回的JSON信息
7.1浏览器路径中不支持中文问题,通过URLEncoder编码可以把中文进行转译
URLEncoder.encode(需要转码的数据,被转码数据的编码)
7.2 JSON对象的解析
通过Android提供的 JSONObject API 进行解析
JSONObject jsonobject = new JSONObject(json);
//获取到服务器返回的状态信息(如果有的话)
String result = jsonobject.getString(“desc”);
jsonobject.optString(“desc”);
这两个都是获取值的,但是opt不会报异常,返回空字符串
//解析jsonobject的子json
JSONObject dataJSON = jsonobject.JSONObject(“data”);
//如果Json中有数组格式的json可以获取数组形式的json
JSONArray jsonarray = dataJSON.JSONArray(“”)
小技巧,带大括号的就是JSON对象,带中括号的就是JSON数组
8,新闻客户端
8.1RSS技术:是一种订阅互联网上信息的方式.是一种特殊的xml文件
把新闻xml文件和img放到tomcat服务器
8.2 新闻项目开发流程
①MVC模式分包
M:service包新闻信息的业务代码
V:布局文件显示layout
C: MainActivity显示在主页面,就这一个界面
domain包,代表业务实体,封装的对象
② 业务bean封装 新闻的item对象
业务方法 获得所有item对象
一般路径之类的字符串都是在values文件夹下创建config.xml文件,在里面创建对应的键值对,键对应字符串的名称,值对应具体的数值
③其它跟前面差不多,重点是子线程的获取
8.3加载界面的优化显示
ProgressBar 等待旋转标志
标签的visibility属性可以用来设置标签状态,显示,不显示,无效果
TextView SingleLine属性可以控制行数
使用打气筒把xml布局文件转换成view对象
创建ListView适配器显示效果
RelativeLayout中四个边界距离属性(可以参考盒子模型)
9,利用开源框架显示效果
www.github.com开源网站
使用自定义的控件,标签名要写全包名
附上一个简易的新闻客户端
package com.zzx.news; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import com.zzx.news.domain.MyItem; import com.zzx.news.other.SmartImageView; import com.zzx.news.service.NewsService; public class MainActivity extends Activity { private List<MyItem> myitem = getNewsMessage(); private ListView lv_show; private View showView; private MyAdapter ma; private ProgressBar pb_init; private Handler handler = new Handler(){ public void handleMessage(Message msg) { pb_init.setVisibility(0x00000004); lv_show.setVisibility(0x00000000); }; }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化需要的组件 init(); //设置载入画面 new Thread(){ public void run() { try { Thread.sleep(3000); Message mes = Message.obtain(); handler.sendMessage(mes); } catch (InterruptedException e) { e.printStackTrace(); } }; }.start(); lv_show.setAdapter(ma); } /** * 这里做服务器网络信息的获取集合 */ public List<MyItem> getNewsMessage() { //要通过子线程访问网络 new Thread(){ public void run() { myitem = NewsService.getNews(); }; }.start(); return myitem; } /** * 初始化控件 */ public void init() { pb_init = (ProgressBar) findViewById(R.id.pb); lv_show = (ListView) findViewById(R.id.showNews); ma = new MyAdapter(); } /** * 自定义适配器 * @author msi * */ class MyAdapter extends BaseAdapter{ private TextView tv_nt; private TextView tv_ns; private TextView tv_ne; private SmartImageView iv_in; @Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView==null){ convertView = View.inflate(MainActivity.this, R.layout.shonews, null); } //给对应组件进行赋值 iv_in = (SmartImageView) convertView.findViewById(R.id.imgnew); tv_nt = (TextView) convertView.findViewById(R.id.newsTitle); tv_ns = (TextView) convertView.findViewById(R.id.newsMsg); tv_ne = (TextView) convertView.findViewById(R.id.newstype); //获取集合中的元素 MyItem mi = myitem.get(position); String imgurl = mi.getNewsimg(); System.out.println(imgurl); iv_in.setImageUrl(imgurl); tv_nt.setText(mi.getNewstitle()); tv_ns.setText(mi.getNewsmsg()); switch (Integer.parseInt(mi.getNewstype())) { case 2: tv_ne.setText("主题新闻"); break; case 1: tv_ne.setText("评论新闻" + mi.getNewscomment()); break; case 3: tv_ne.setText("视频新闻"); break; } //用框架进行封装图片 return convertView; } @Override public int getCount() { myitem = getNewsMessage(); return myitem.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } } }
业务处理类
package com.zzx.news.service; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.util.LinkedList; import java.util.List; import org.xmlpull.v1.XmlPullParser; import android.util.Log; import android.util.Xml; import com.zzx.news.domain.MyItem; /** * 新闻信息服务类 * @author msi * */ public class NewsService { private static int type; /** * 获取新闻信息 * @return 保存item的集合,返回为null就是没有读取到 */ public static List<MyItem> getNews(){ //定义List集合保存信息 List<MyItem> myitem = null; try { //访问网络获取数据 URL url = new URL("http://192.168.16.80:8080/Android/news.xml"); //打开网络服务 HttpURLConnection huc = (HttpURLConnection) url.openConnection(); //通过输入流解析xml文件 XmlPullParser xpp = Xml.newPullParser(); //设置信息 xpp.setInput(huc.getInputStream(),"utf-8"); type = 0; //創建條目信息 MyItem my = null; //循环遍历获取结果 while((type = xpp.getEventType())!=XmlPullParser.END_DOCUMENT){ String name = xpp.getName(); switch (type) { //当解析到开始标签的时候创建集合 case XmlPullParser.START_DOCUMENT: myitem= new LinkedList<MyItem>(); break; case XmlPullParser.START_TAG: //根據不同的节点设置不同的信息 if("item".equals(name)){ my = new MyItem(); }else if("title".equals(name)){ my.setNewstitle(xpp.nextText()); }else if("description".equals(name)){ my.setNewsmsg(xpp.nextText()); }else if("image".equals(name)){ my.setNewsimg(xpp.nextText()); }else if("type".equals(name)){ my.setNewstype(xpp.nextText()); }else if("comment".equals(name)){ my.setNewscomment(xpp.nextText()); } break; case XmlPullParser.END_TAG: //如果标签解析完了,就存放在集合中 if("item".equals(name)){ myitem.add(my); } break; } //解析下一个 xpp.next(); } } catch (Exception e) { e.printStackTrace(); } System.out.println(myitem); //返回集合 return myitem; } }