博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
安卓day28网络编程 handler 引用 网络文件获取 缓存图片 smartimageview框架 xml文件下载显示 网站验证密码 get post...
阅读量:5116 次
发布时间:2019-06-13

本文共 25349 字,大约阅读时间需要 84 分钟。

一、排坑

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 WeakReference
activityWeakReference;    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变量设置为null

4种引用

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 {    List
newsList; 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();    }}

 

转载于:https://www.cnblogs.com/index42/p/10451822.html

你可能感兴趣的文章
从头开始学JavaScript (九)——执行环境和作用域
查看>>
JDBC的介绍2
查看>>
任意代码执行漏洞
查看>>
MongoDB管理与监控
查看>>
HUAWEI USG6000系列 & NGFW Module V100R001 典型配置案例
查看>>
RPM仓库地址
查看>>
Underscore.js 1.3.3 中文解释
查看>>
博客园文章样式修改
查看>>
JDBC学习总结
查看>>
值域范围
查看>>
13.运算
查看>>
设计模式笔记之六:生产消费者模式
查看>>
NetBSD Make源代码阅读二:链表之创建与销毁
查看>>
docker技术之基本命令
查看>>
Android Stuido 快速设置成eclipse的快捷键习惯
查看>>
《软件需求最佳实践》——阅读笔记一
查看>>
【转载】UltraWinGrid使用心得(C#)
查看>>
[译] 用 Swift 创建自定义的键盘
查看>>
java虚拟机(八)--java性能监控与故障处理工具
查看>>
jQuery
查看>>