一、排坑
static Handler handler;Handler内存泄漏
Handler要解决的根本问题是Android线程并发,如果没有相应的机制约束各线程的协作并发,很容易导致开发上和运行上的混乱。Android处理多线程的方式也不是传统上的加锁机制(性能因素吧),而是MessageQueue,即消息队列,开发者可以直接控制消息队列的显示顺序和方式,这样就不会产生数据的同步混乱的问题了。
1. 内存泄露原因分析
由于这个Handler作为内部类声明在Activity内部,普通的内部类对象隐式地保存了一个指向外部类对象的引用,所以这个Handler对象保存了一个指向Activity对象的引用。而这个Handler对象的生命周期可能比Activity生命周期长,比如当有一个后台线程持有该Handler,别且该线程在执行一个长时间任务。所以当该Handler没有被JVM垃圾回收器回收时,它就阻止了它引用的外部类Activity对象的回收,这里就导致了内存泄露。
2. 如何解决这种内存泄露问题
在该内存泄露的Lint Warning中给出了解决该问题的方法。将Handler类声明为静态内部类,即解除内部类对象与其外部类对象之间的联系。创建一个外部类的WeakReference,并在实例化Handler对象时使用它。代码实现如下:
private static class MyHandler extends Handler { private WeakReferenceactivityWeakReference; public MyHandler(MainActivity activity) { activityWeakReference = new WeakReference (activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); MainActivity activity = activityWeakReference.get(); if (activity != null) { } }}
- handler造成内存泄漏是因为在Activity销毁的时候还有未执行完的任务
- 静态static可以解决内存泄漏
- 使用弱引用也可以解决内存泄漏,但是需要等到handler的中任务都执行完,才会释放activity内存,不如直接static释放的快
- handler造成内存泄漏有 两种方案:一种是业务逻辑上,在activity销毁的时候移除所有未执行的任务。一种是从GC上,通过static的Handler或者弱引用解决。但是单独的使用弱引用性能不是太高。
static MainActivity ma;
static变量所指向内存的引用,如果不把它设置为null ,GC是不会回收这个对象的,
所以为了让GC能够回收static变量 我们在使用完后将static变量设置为null4种引用
SoftReference、WeakReference和PhantomReference
SoftReference、WeakReference和PhantomReference是java.lang.ref类库中的一组类。当垃圾回收器正在考察的对象只能通过某个Reference对象才“可获得”时,这3个类为垃圾回收器提供了不同级别的间接性提示。
对象是可获得的(reachable),是指此对象可在程序中的某处找到。这意味着你在栈中有一个普通的“引用A”,而它正指向此“对象A”,也可能是“引用B”指向“对象B”,而“对象B”含有“引用C”指向“对象A”,也可能是更多的中间链接。如果一个对象是“可获得的”,垃圾回收器就不能释放它,因为它仍然为你的程序所用。如果一个对象不是“可获得的”,那么你的程序将无法使用到它,所以将其回收是安全的。
如果想继续持有某个对象的引用,想以后还能够访问到该对象,同时也想在内存消耗殆尽的时候垃圾回收器回收它,这时就应该使用Reference对象。
SoftReference、WeakReference和PhantomReference由强到弱排列,表示不同级别的“可获得性”。
SoftReference用以实现内存敏感的高速缓存。
WeakReference是为实现“规范映射”(canonicalizing mappings)而设计的,它不妨碍垃圾回收器回收映射的“键”(或“值”)。“规范映射”中对象的实例可以在程序的多处被同时使用,以节省存储空间。
PhantomReference用以调度回收前的清理工作,它比Java终止机制更加灵活。
variable xxx is accessed from within inner class; needs to be declared final
在内部类当中不能引用本地变量,需要被声明为常量
Server Tomcat v7.0 Server at localhost failed to start
可以选择删除web.xml中的映射,也可以选择将servlet自动生成的注解注释掉,这样就不会有冲突了,二者选其一留下即可。
get方式中文乱码
在Get方式请求中request.setCharacterEncoding("UTF-8")不再起作用
String name = request.getParameter("name"); name = new String(name.getBytes("ISO-8859-1"),"UTF-8");
二、网络图片查看器
主线程不能被阻塞
- 在Android中,主线程被阻塞会导致应用不能刷新ui界面,不能响应用户操作,用户体验将非常差
- 主线程阻塞时间过长,系统会抛出ANR异常
- ANR:Application Not Response;应用无响应
- 任何耗时操作都不可以写在主线程
- 因为网络交互属于耗时操作,如果网速很慢,代码会阻塞,所以网络交互的代码不能运行在主线程
只有主线程能刷新ui
- 刷新ui的代码只能运行在主线程,运行在子线程是没有任何效果的
- 如果需要在子线程中刷新ui,使用消息队列机制
消息队列
- Looper一旦发现Message Queue中有消息,就会把消息取出,然后把消息扔给Handler对象,Handler会调用自己的handleMessage方法来处理这条消息
- handleMessage方法运行在主线程
-
主线程创建时,消息队列和轮询器对象就会被创建,但是消息处理器对象,需要使用时,自行创建
public class MainActivity extends Activity { static ImageView iv; static MainActivity ma; static Handler handler = new Handler(){ //此方法在主线程中调用,可以用来刷新ui public void handleMessage(Message msg) { //处理消息时,需要知道到底是成功的消息,还是失败的消息 switch (msg.what) { case 1: //把位图对象显示至imageview iv.setImageBitmap((Bitmap)msg.obj); break; case 0: Toast.makeText(ma, "请求失败", Toast.LENGTH_SHORT).show(); break; } ma=null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); iv = (ImageView) findViewById(R.id.iv); ma = this; } public void click(View v){ getpic("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1551414968179&di=2896146dee79c07b8a5ea400d35cd9ff&imgtype=0&src=http%3A%2F%2Fa.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F5243fbf2b211931330751d8b6b380cd791238dd3.jpg"); } public void click2(View v){ getpic("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1551425560734&di=81f2206ede868b4ceb7d33a47f6f68f2&imgtype=0&src=http%3A%2F%2Fd.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2Fe7cd7b899e510fb3a4c06fa8d733c895d1430c37.jpg"); } public void getpic(String path){ final String path1=path; Thread t = new Thread(){ @Override public void run() { //下载图片 //1.确定网址 //String path = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1551414968179&di=2896146dee79c07b8a5ea400d35cd9ff&imgtype=0&src=http%3A%2F%2Fa.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F5243fbf2b211931330751d8b6b380cd791238dd3.jpg"; try { //2.把网址封装成一个url对象 URL url = new URL(path1); //3.获取客户端和服务器的连接对象,此时还没有建立连接 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //4.对连接对象进行初始化 //设置请求方法,注意大写 conn.setRequestMethod("GET"); //设置连接超时 conn.setConnectTimeout(5000); //设置读取超时 conn.setReadTimeout(5000); //5.发送请求,与服务器建立连接 conn.connect(); //如果响应码为200,说明请求成功 if(conn.getResponseCode() == 200){ //获取服务器响应头中的流,流里的数据就是客户端请求的数据 InputStream is = conn.getInputStream(); //读取出流里的数据,并构造成位图对象 Bitmap bm = BitmapFactory.decodeStream(is); // ImageView iv = (ImageView) findViewById(R.id.iv);// //把位图对象显示至imageview// iv.setImageBitmap(bm); Message msg = new Message(); //消息对象可以携带数据 msg.obj = bm; msg.what = 1; //把消息发送至主线程的消息队列 handler.sendMessage(msg); } else{// Toast.makeText(MainActivity.this, "请求失败", 0).show(); Message msg = handler.obtainMessage(); msg.what = 0; handler.sendMessage(msg); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; t.start(); }}
加入缓存图片的功能
public void getpic2(String path){ //下载图片 //1.确定网址 final String path1=path; final File file = new File(getCacheDir(), getFileName(path)); //判断,缓存中是否存在该文件 if(file.exists()){ //如果缓存存在,从缓存读取图片 Log.e(TAG, "从缓存读取的: "); Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath()); iv.setImageBitmap(bm); } else{ //如果缓存不存在,从网络下载 Log.e(TAG, "从网上下载的: "); Thread t = new Thread(){ @Override public void run() { try { //2.把网址封装成一个url对象 URL url = new URL(path1); //3.获取客户端和服务器的连接对象,此时还没有建立连接 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //4.对连接对象进行初始化 //设置请求方法,注意大写 conn.setRequestMethod("GET"); //设置连接超时 conn.setConnectTimeout(5000); //设置读取超时 conn.setReadTimeout(5000); //5.发送请求,与服务器建立连接 conn.connect(); //如果响应码为200,说明请求成功 if(conn.getResponseCode() == 200){ //获取服务器响应头中的流,流里的数据就是客户端请求的数据 InputStream is = conn.getInputStream(); //读取服务器返回的流里的数据,把数据写到本地文件,缓存起来 FileOutputStream fos = new FileOutputStream(file); byte[] b = new byte[1024]; int len = 0; while((len = is.read(b)) != -1){ fos.write(b, 0, len); } fos.close(); //读取出流里的数据,并构造成位图对象 //流里已经没有数据了// Bitmap bm = BitmapFactory.decodeStream(is); Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath()); Message msg = new Message(); //消息对象可以携带数据 msg.obj = bm; msg.what = 1; //把消息发送至主线程的消息队列 handler.sendMessage(msg); } else{// Toast.makeText(MainActivity.this, "请求失败", 0).show(); Message msg = handler.obtainMessage(); msg.what = 0; handler.sendMessage(msg); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; t.start(); } } public String getFileName(String path){ int index = path.lastIndexOf("/"); return path.substring(index + 1); }
三、SmartImageView
源码:
开源项目SmartImageView的出现主要是为了加速从网络上加载图片,它继承自ImageView类,支持根据URL地址加载图片,支持异步加载图片,支持图片缓存等。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void click(View v){ //下载图片 //1.确定网址 String path = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1551414968179&di=2896146dee79c07b8a5ea400d35cd9ff&imgtype=0&src=http%3A%2F%2Fa.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F5243fbf2b211931330751d8b6b380cd791238dd3.jpg"; //2.找到智能图片查看器对象 SmartImageView siv = (SmartImageView) findViewById(R.id.iv); //3.下载并显示图片 siv.setImageUrl(path); }}
四、Html源文件查看器
public class MainActivity extends Activity { Handler handler = new Handler(){ public void handleMessage(Message msg) { TextView tv = (TextView) findViewById(R.id.tv); tv.setText((String)msg.obj); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void click(View v){ Thread t = new Thread(){ @Override public void run() { String path = "https://www.cnblogs.com/index42/p/10438061.html"; try { URL url = new URL(path); //获取连接对象,此时还未建立连接 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); //先建立连接,然后获取响应码 if(conn.getResponseCode() == 200){ //拿到服务器返回的输入流,流里的数据就是html的源文件 InputStream is = conn.getInputStream(); //从流里把文本数据取出来 String text = Utils.getTextFromStream(is); //发送消息,让主线程刷新ui,显示源文件 Message msg = handler.obtainMessage(); msg.obj = text; handler.sendMessage(msg); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; t.start(); }}
public class Utils { public static String getTextFromStream(InputStream is){ byte[] b = new byte[1024]; int len = 0; //创建字节数组输出流,读取输入流的文本数据时,同步把数据写入数组输出流 ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { while((len = is.read(b)) != -1){ bos.write(b, 0, len); } //把字节数组输出流里的数据转换成字节数组 String text = new String(bos.toByteArray()); return text; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; }}
五、小志新闻客户端
上传文件提供下载链接
public class MainActivity extends Activity { ListnewsList; Handler handler = new Handler(){ public void handleMessage(Message msg) { ListView lv = (ListView) findViewById(R.id.lv); lv.setAdapter(new MyAdapter()); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getNewsInfo();// ListView lv = (ListView) findViewById(R.id.lv);// //要保证在设置适配器时,新闻xml文件已经解析完毕了// lv.setAdapter(new MyAdapter()); } class MyAdapter extends BaseAdapter{ //得到模型层中元素的数量,用来确定listview需要有多少个条目 @Override public int getCount() { // TODO Auto-generated method stub return newsList.size(); } @Override //返回一个View对象,作为listview的条目显示至界面 public View getView(int position, View convertView, ViewGroup parent) { News news = newsList.get(position); View v = null; ViewHolder mHolder; if(convertView == null){ v = View.inflate(MainActivity.this, R.layout.item_listview, null); mHolder = new ViewHolder(); //把布局文件中所有组件的对象封装至ViewHolder对象中 mHolder.tv_title = (TextView) v.findViewById(R.id.tv_title); mHolder.tv_detail = (TextView) v.findViewById(R.id.tv_detail); mHolder.tv_comment = (TextView) v.findViewById(R.id.tv_comment); mHolder.siv = (SmartImageView) v.findViewById(R.id.iv); //把ViewHolder对象封装至View对象中 v.setTag(mHolder); } else{ v = convertView; mHolder = (ViewHolder) v.getTag(); } //给三个文本框设置内容 mHolder.tv_title.setText(news.getTitle()); mHolder.tv_detail.setText(news.getDetail()); mHolder.tv_comment.setText(news.getComment() + "条评论"); //给新闻图片imageview设置内容 mHolder.siv.setImageUrl(news.getImageUrl()); return v; } class ViewHolder{ //条目的布局文件中有什么组件,这里就定义什么属性 TextView tv_title; TextView tv_detail; TextView tv_comment; SmartImageView siv; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } } private void getNewsInfo() { Thread t = new Thread(){ @Override public void run() { String path = "https://files.cnblogs.com/files/index42/news.xml"; try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); //发送http GET请求,获取相应码 if(conn.getResponseCode() == 200){ InputStream is = conn.getInputStream(); //使用pull解析器,解析这个流 parseNewsXml(is); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; t.start(); } private void parseNewsXml(InputStream is) { XmlPullParser xp = Xml.newPullParser(); try { xp.setInput(is, "utf-8"); //对节点的事件类型进行判断,就可以知道当前节点是什么节点 int type = xp.getEventType(); News news = null; while(type != XmlPullParser.END_DOCUMENT){ switch (type) { case XmlPullParser.START_TAG: if("newslist".equals(xp.getName())){ newsList = new ArrayList (); } else if("news".equals(xp.getName())){ news = new News(); } else if("title".equals(xp.getName())){ String title = xp.nextText(); news.setTitle(title); } else if("detail".equals(xp.getName())){ String detail = xp.nextText(); news.setDetail(detail); } else if("comment".equals(xp.getName())){ String comment = xp.nextText(); news.setComment(comment); } else if("image".equals(xp.getName())){ String image = xp.nextText(); news.setImageUrl(image); } break; case XmlPullParser.END_TAG: if("news".equals(xp.getName())){ newsList.add(news); } break; } //解析完当前节点后,把指针移动至下一个节点,并返回它的事件类型 type = xp.next(); } //发消息,让主线程设置listview的适配器,如果消息不需要携带数据,可以发送空消息 handler.sendEmptyMessage(1); // for (News n : newsList) {// System.out.println(n.toString());// } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }}
public class News { private String title; private String detail; private String comment; private String imageUrl; @Override public String toString() { return "News [title=" + title + ", detail=" + detail + ", comment=" + comment + ", imageUrl=" + imageUrl + "]"; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDetail() { return detail; } public void setDetail(String detail) { this.detail = detail; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public String getImageUrl() { return imageUrl; } public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }}
六、提交数据
本地网站服务器准备工作
public class LoginServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public LoginServlet() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doPost(request,response); //response.getWriter().append("Served at: ").append(request.getContextPath()); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub //request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); String username = request.getParameter("username"); String username1 =new String(username.getBytes("ISO-8859-1"),"UTF-8"); String password = request.getParameter("password"); PrintWriter out = response.getWriter(); out.println(""+username1+"
"); out.println(""+password+"
"); if(username.equals("admin")&&password.equals("123456")){ out.println("登录成功!"+"
"); } else{ out.println("用户名或密码错误!"+"
"); } out.flush(); out.close(); }}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>Login
LoginServlet hmk.LoginServlet LoginServlet /LoginServlet index.jsp
可用电脑上的安卓模拟器调试
GET方式提交数据
浏览器在发送请求携带数据时会对数据进行URL编码,我们写代码时也需要为中文进行URL编码
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } Handler handler = new Handler(){ public void handleMessage(Message msg) { Toast.makeText(MainActivity.this, (String)msg.obj, Toast.LENGTH_SHORT).show(); } }; public void click(View v){ EditText et_name = (EditText) findViewById(R.id.et_name); EditText et_pass = (EditText) findViewById(R.id.et_pass); final String name = et_name.getText().toString(); final String pass = et_pass.getText().toString(); Thread t = new Thread(){ @Override public void run() { //提交的数据需要经过url编码,英文和数字编码后不变 @SuppressWarnings("deprecation") String path = "http://10.32.189.73:8080/d0301/LoginServlet?username=" + URLEncoder.encode(name) + "&password=" + pass; try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); if(conn.getResponseCode() == 200){ InputStream is =conn.getInputStream(); String text = Utils.getTextFromStream(is); Message msg = handler.obtainMessage(); msg.obj = text; handler.sendMessage(msg); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; t.start(); }}
POST方式提交数据
- post提交数据是用流写给服务器的
-
协议头中多了两个属性
- Content-Type: application/x-www-form-urlencoded,描述提交的数据的mimetype
-
Content-Length: 32,描述提交的数据的长度
public void click2(View v){ EditText et_name = (EditText) findViewById(R.id.et_name); EditText et_pass = (EditText) findViewById(R.id.et_pass); final String name = et_name.getText().toString(); final String pass = et_pass.getText().toString(); Thread t = new Thread(){ @Override public void run() { //提交的数据需要经过url编码,英文和数字编码后不变 @SuppressWarnings("deprecation") String path = "http://10.32.189.73:8080/d0301/LoginServlet"; try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); //拼接出要提交的数据的字符串 String data = "username=" + URLEncoder.encode(name) + "&password=" + pass; //添加post请求的两行属性 conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", data.length() + ""); //设置打开输出流 conn.setDoOutput(true); //拿到输出流 OutputStream os = conn.getOutputStream(); //使用输出流往服务器提交数据 os.write(data.getBytes()); if(conn.getResponseCode() == 200){ InputStream is = conn.getInputStream(); String text = Utils.getTextFromStream(is); Message msg = handler.obtainMessage(); msg.obj = text; handler.sendMessage(msg); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; t.start(); }}