1.android用存到缓存的方法来保存ListView里的数据
对于这样的数据:
- <?xml version="1.0" encoding="utf-8" ?>
- <rss><sid>77f265bb46de068e78f35afbadec62af</sid><count>3</count><control>0</control>
- <mblog><uid>1195224593</uid><favid>3436952795</favid><mblogid>5xtaJR</mblogid><mblogidnum>3436952795</mblogidnum><mblogtype>0</mblogtype><mlevel>0</mlevel><feedid>5xtaJR</feedid><nick>马艳丽</nick><portrait>http://tp2.sinaimg.cn/1195224593/50/5614100014/0</portrait><vip>1</vip><vipsubtype>0</vipsubtype><member_type>13</member_type><remark></remark><level>2</level><rtnum>11</rtnum><commentnum>25</commentnum><attitudenum>0</attitudenum><attitudeid>0</attitudeid><attitudes_status>0</attitudes_status><attitudes_count>0</attitudes_count><mblogtypename></mblogtypename><visible><type>0</type><list_id>0</list_id></visible><content>婚礼在北海美丽的北海公园举行…好美好浪漫的地方… </content><pic>http://ss12.sinaimg.cn/wap240/473dae11494344debfc5b</pic><time>1288852274</time><source>彩信</source></mblog></rss>
- <?xml version="1.0" encoding="utf-8" ?>
- <rss><sid>77f265bb46de068e78f35afbadec62af</sid><count>3</count><control>0</control>
- <mblog><uid>1195224593</uid><favid>3436952795</favid><mblogid>5xtaJR</mblogid><mblogidnum>3436952795</mblogidnum><mblogtype>0</mblogtype><mlevel>0</mlevel><feedid>5xtaJR</feedid><nick>马艳丽</nick><portrait>http://tp2.sinaimg.cn/1195224593/50/5614100014/0</portrait><vip>1</vip><vipsubtype>0</vipsubtype><member_type>13</member_type><remark></remark><level>2</level><rtnum>11</rtnum><commentnum>25</commentnum><attitudenum>0</attitudenum><attitudeid>0</attitudeid><attitudes_status>0</attitudes_status><attitudes_count>0</attitudes_count><mblogtypename></mblogtypename><visible><type>0</type><list_id>0</list_id></visible><content>婚礼在北海美丽的北海公园举行…好美好浪漫的地方… </content><pic>http://ss12.sinaimg.cn/wap240/473dae11494344debfc5b</pic><time>1288852274</time><source>彩信</source></mblog></rss>
首先我们把从服务器取到的数据,里面有个主要的对象mblog,我们用一个对象来存储:
- public class MBlog implements <strong>Serializable</strong> { //保证这个对象是可以序列化的
- private static final long serialVersionUID = -3514924369786543050L;
- public String uid;
- public String favid;
- public String mblogid;
- public String nick;
- public String portrait;
- public boolean vip;
- public String content;
- public String rtrootuid;
- public String rtrootid;
- public String rtrootnick;
- public boolean rtrootvip;
- public String rtreason;
- public int rtnum;
- public int commentnum;
- public Date time;
- public String pic;
- public String src;
- public String longitude;// 经度
- public String latitude;// 纬度
- public boolean equals(Object o) {
- if (o == null) return false;
- if (o == this) return true;
- Class<?> cla = o.getClass();
- if (cla == getClass()) {
- MBlog other = (MBlog) o;
- if (other.mblogid.equals(mblogid)) return true;
- }
- return false;
- }
- public int hashCode() {
- return mblogid.hashCode() * 101 >> 12;
- }
- }
- public class MBlog implements <strong>Serializable</strong> { //保证这个对象是可以序列化的
- private static final long serialVersionUID = -3514924369786543050L;
- public String uid;
- public String favid;
- public String mblogid;
- public String nick;
- public String portrait;
- public boolean vip;
- public String content;
- public String rtrootuid;
- public String rtrootid;
- public String rtrootnick;
- public boolean rtrootvip;
- public String rtreason;
- public int rtnum;
- public int commentnum;
- public Date time;
- public String pic;
- public String src;
- public String longitude;// 经度
- public String latitude;// 纬度
- public boolean equals(Object o) {
- if (o == null) return false;
- if (o == this) return true;
- Class<?> cla = o.getClass();
- if (cla == getClass()) {
- MBlog other = (MBlog) o;
- if (other.mblogid.equals(mblogid)) return true;
- }
- return false;
- }
- public int hashCode() {
- return mblogid.hashCode() * 101 >> 12;
- }
- }
在Activity取到缓存的Path: mCacheDir = this.getCacheDir().getPath();
一般是/data/data/com.example.weibotest/cache
这个是save方法:
- public static void save(Object obj, String path) {
- try {
- File f = new File(path);
- /*if(f != null){
- f.mkdirs();
- f.createNewFile();
- }*/
- FileOutputStream fos = new FileOutputStream(f);
- ObjectOutputStream oos = new ObjectOutputStream(fos);
- oos.writeObject(obj);
- oos.flush();
- oos.close();
- }
- catch (IOException e) {
- }
- }
- public static void save(Object obj, String path) {
- try {
- File f = new File(path);
- /*if(f != null){
- f.mkdirs();
- f.createNewFile();
- }*/
- FileOutputStream fos = new FileOutputStream(f);
- ObjectOutputStream oos = new ObjectOutputStream(fos);
- oos.writeObject(obj);
- oos.flush();
- oos.close();
- }
- catch (IOException e) {
- }
- }
读取方法:
- public static Object load(String path) {
- Object obj = null;
- File file = new File(path);
- try {
- /*if(file != null){
- file.mkdirs();
- }*/
- if (file.exists()) {
- FileInputStream fis = new FileInputStream(file);
- ObjectInputStream ois = new ObjectInputStream(fis);
- try {
- obj = ois.readObject();
- }
- catch (ClassNotFoundException e) {
- }
- ois.close();
- }
- }catch (IOException e) {
- }
- return obj;
- }
- public static Object load(String path) {
- Object obj = null;
- File file = new File(path);
- try {
- /*if(file != null){
- file.mkdirs();
- }*/
- if (file.exists()) {
- FileInputStream fis = new FileInputStream(file);
- ObjectInputStream ois = new ObjectInputStream(fis);
- try {
- obj = ois.readObject();
- }
- catch (ClassNotFoundException e) {
- }
- ois.close();
- }
- }catch (IOException e) {
- }
- return obj;
- }
这样来调用:
- public void parseAssertData() {
- InputStream is = null;
- try {
- is = this.getAssets().open("11.xml", Context.MODE_PRIVATE);
- int length = is.available();
- byte[] buffer = new byte[length];
- is.read(buffer);
- String temp = new String(buffer);
- try {
- Object[] array = ParseData.getMBlogList(temp);
- List<MBlog> list = (List<MBlog>)array[1];
- FileUtils.save(list, mCacheDir+'/'+"001_fav");
- List<MBlog> list1 = (List<MBlog>)FileUtils.load(mCacheDir+'/'+"001_fav");
- MBlog blog = list1.get(1);
- System.out.println("===size="+blog.src);
- } catch (Exception e) {
- e.printStackTrace();
- }
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- public void parseAssertData() {
- InputStream is = null;
- try {
- is = this.getAssets().open("11.xml", Context.MODE_PRIVATE);
- int length = is.available();
- byte[] buffer = new byte[length];
- is.read(buffer);
- String temp = new String(buffer);
- try {
- Object[] array = ParseData.getMBlogList(temp);
- List<MBlog> list = (List<MBlog>)array[1];
- FileUtils.save(list, mCacheDir+'/'+"001_fav");
- List<MBlog> list1 = (List<MBlog>)FileUtils.load(mCacheDir+'/'+"001_fav");
- MBlog blog = list1.get(1);
- System.out.println("===size="+blog.src);
- } catch (Exception e) {
- e.printStackTrace();
- }
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
2、Android使用缓存优化ListView
ListView调用Adapter的getView方法获取每一个Item布局,将这些已经获得的Item布局放入缓存,将大大提高获取数据的效率,而且节省更多的流量,将数据进行缓存有两种方法是,一种是将内存缓存一种是sd卡缓存,在此分别进行演示。
sd卡缓存:
sd卡缓存是将下载的数据保存到sd卡中,当再次要获取数据时,首先要判断sd卡中是否存在,如果存在的话,就直接读取sd卡中的数据,如果不存在就从网上下载,然后保存到sd卡中
内存缓存:
内存优化是将获取到的数据存取到Map集合中,如果再次引用此数据,就直接从Map集合中获取,这样会导致一个问题,如果Map集合中的数据特别多,比如存取了100万条数据,这样有可能就会导致内存溢出。这是因为Map集合是强引用的集合,如何不把Map集合置为空的话,这个集合Java虚拟机就不会把它回收掉,当Map中的数据大小超过了内存大小就会导致内存溢出。为了避免这种异常我们要使用软引用softreference ,软引用和强引用的区别如下:
1. softreference 他是java虚拟机给我们提供的一个包装类型.
在包装类型里面的对象 一般情况下 ,java虚拟机会尽量长时间的保留这个对象
当java虚拟机内存不足的时候 java虚拟机就会回收 softreference里面的对象
2. hardreference 默认new出来的对象 都是这种强应用的类型
只要一个对象还保留的有引用,他就不会被垃圾回收
Map<String,Bitmap> map;
核心代码:
sd卡缓存
- public class MyReadAdapter extends BaseAdapter{
- private List<CollectionEntry> entrys;
- public MyReadAdapter(CollectionFeed feeds) {
- entrys = feeds.getEntries();
- }
- public int getCount() {
- return entrys.size();
- }
- public Object getItem(int position) {
- return entrys.get(position);
- }
- public long getItemId(int position) {
- return position;
- }
- public View getView(int position, View convertView, ViewGroup parent) {
- View view = infalter.inflate(R.layout.myread_item, null);
- final ImageView iv = (ImageView) view.findViewById(R.id.book_img);
- //获取数据实体
- CollectionEntry ce = entrys.get(position);
- //获取图片地址
- String iconurl = ce.getSubjectEntry().getLink("image", null).getHref();
- int start = iconurl.lastIndexOf("/");
- int end = iconurl.length();
- final String iconname = iconurl.substring(start, end);
- //Environment.getExternalStorageDirectory()这个是sd卡目录
- File file = new File(Environment.getExternalStorageDirectory(),iconname);
- //获取sd卡缓存
- if(file.exists()){
- iv.setImageURI(Uri.fromFile(file));
- Log.i(TAG,"使用sd卡图片");
- }else{
- new LoadImageAsynTask(new ImageTaskCallback() {
- // 图片获取之后
- public void onImageLoaded(Bitmap bitmap) {
- if(bitmap!=null){
- iv.setImageBitmap(bitmap);
- //把图片存到sd卡上
- if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
- try {
- File file = new File(Environment.getExternalStorageDirectory(),iconname);
- FileOutputStream fos = new FileOutputStream(file);
- bitmap.compress(CompressFormat.JPEG, 100, fos);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }else{
- iv.setImageResource(R.drawable.book);
- }
- }
- //图片获取之前
- public void beforeImageLoaded() {
- iv.setImageResource(R.drawable.book);
- }
- }).execute(iconurl);
- }
- return view;
- }
- }
内存缓存
- Map<String,SoftReference<Bitmap>> map;
- public class MyReadAdapter extends BaseAdapter{
- private List<CollectionEntry> entrys;
- public MyReadAdapter(CollectionFeed feeds) {
- entrys = feeds.getEntries();
- }
- public int getCount() {
- return entrys.size();
- }
- public Object getItem(int position) {
- return entrys.get(position);
- }
- public long getItemId(int position) {
- return position;
- }
- public View getView(int position, View convertView, ViewGroup parent) {
- View view = infalter.inflate(R.layout.myread_item, null);
- final ImageView iv = (ImageView) view.findViewById(R.id.book_img);
- //获取到数据的实体
- CollectionEntry ce = entrys.get(position);
- //获取到图片的Url
- String iconurl = ce.getSubjectEntry().getLink("image", null).getHref();
- int start = iconurl.lastIndexOf("/");
- int end = iconurl.length();
- final String iconname = iconurl.substring(start, end);
- //使用内存缓存
- if(map!=null && map.get(iconname)!=null){
- iv.setImageBitmap(map.get(iconname).get());
- Log.i(TAG,"使用内存缓存");
- }
- else{
- new LoadImageAsynTask(new ImageTaskCallback() {
- // 图片获取之后
- public void onImageLoaded(Bitmap bitmap) {
- if(bitmap!=null){
- iv.setImageBitmap(bitmap);
- //存放到内存中,
- //软引用类型的bitmap
- map.put(iconname, new SoftReference<Bitmap>(bitmap));
- }else{
- iv.setImageResource(R.drawable.book);
- }
- }
- //图片获取之前
- public void beforeImageLoaded() {
- iv.setImageResource(R.drawable.book);
- }
- }).execute(iconurl);
- }
- return view;
- }
- }
3、android ListView异步加载图片(双缓存)
对于ListView,相信很多人都很熟悉,因为确实太常见了,所以,做的用户体验更好,就成了我们的追求。。。
常见的ListView中很少全是文字的,一般都是图文共存的,而图片的来源是服务器端(很少有写在客户端的吧。。。考虑客户端的大小和更新的问题),所以,网络问题就成了图片是否能顺利加载成功的决定性因素了。大家都知道每次启动一个Android应用,都会启动一个UI主线程,主要是响应用户的交互,如果我们把不确定的获取网络图片的操作放在UI主线程,结果也就不确定了。。。当然,如果你网络足够好的话,应该问题不大,但是,网络谁能保证呢?所以就出现了“异步加载”的方法!
我先叙述一下异步加载的原理,说的通俗一点就是UI主线程继续做与用户交互的响应监听和操作,而加载图片的任务交到其他线程中去做,当图片加载完成之后,再跟据某种机制(比如回调)绘制到要显示的控件中。
首先,贴出AsyncBitmapLoader.java,这个类是关键,主要做的就是当加载图片的时候,去缓冲区查找,如果有的话,则立刻返回Bitmap对象,省掉再去网络服务器下载的时间和流量。
- <span style="font-size:18px;">package onerain.ald.async;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.lang.ref.SoftReference;
- import java.util.HashMap;
- import onerain.ald.common.HttpUtils;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.Handler;
- import android.os.Message;
- import android.widget.ImageView;
- /**
- * @author oneRain
- **/
- public class AsyncBitmapLoader
- {
- /**
- * 内存图片软引用缓冲
- */
- private HashMap<String, SoftReference<Bitmap>> imageCache = null;
- public AsyncBitmapLoader()
- {
- imageCache = new HashMap<String, SoftReference<Bitmap>>();
- }
- public Bitmap loadBitmap(final ImageView imageView, final String imageURL, final ImageCallBack imageCallBack)
- {
- //在内存缓存中,则返回Bitmap对象
- if(imageCache.containsKey(imageURL))
- {
- SoftReference<Bitmap> reference = imageCache.get(imageURL);
- Bitmap bitmap = reference.get();
- if(bitmap != null)
- {
- return bitmap;
- }
- }
- else
- {
- /**
- * 加上一个对本地缓存的查找
- */
- String bitmapName = imageURL.substring(imageURL.lastIndexOf("/") + 1);
- File cacheDir = new File("/mnt/sdcard/test/");
- File[] cacheFiles = cacheDir.listFiles();
- int i = 0;
- for(; i<cacheFiles.length; i++)
- {
- if(bitmapName.equals(cacheFiles[i].getName()))
- {
- break;
- }
- }
- if(i < cacheFiles.length)
- {
- return BitmapFactory.decodeFile("/mnt/sdcard/test/" + bitmapName);
- }
- }
- final Handler handler = new Handler()
- {
- /* (non-Javadoc)
- * @see android.os.Handler#handleMessage(android.os.Message)
- */
- @Override
- public void handleMessage(Message msg)
- {
- // TODO Auto-generated method stub
- imageCallBack.imageLoad(imageView, (Bitmap)msg.obj);
- }
- };
- //如果不在内存缓存中,也不在本地(被jvm回收掉),则开启线程下载图片
- new Thread()
- {
- /* (non-Javadoc)
- * @see java.lang.Thread#run()
- */
- @Override
- public void run()
- {
- // TODO Auto-generated method stub
- InputStream bitmapIs = HttpUtils.getStreamFromURL(imageURL);
- Bitmap bitmap = BitmapFactory.decodeStream(bitmapIs);
- imageCache.put(imageURL, new SoftReference<Bitmap>(bitmap));
- Message msg = handler.obtainMessage(0, bitmap);
- handler.sendMessage(msg);
- File dir = new File("/mnt/sdcard/test/");
- if(!dir.exists())
- {
- dir.mkdirs();
- }
- File bitmapFile = new File("/mnt/sdcard/test/" +
- imageURL.substring(imageURL.lastIndexOf("/") + 1));
- if(!bitmapFile.exists())
- {
- try
- {
- bitmapFile.createNewFile();
- }
- catch (IOException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- FileOutputStream fos;
- try
- {
- fos = new FileOutputStream(bitmapFile);
- bitmap.compress(Bitmap.CompressFormat.JPEG,
- 100, fos);
- fos.close();
- }
- catch (FileNotFoundException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- catch (IOException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }.start();
- return null;
- }
- /**
- * 回调接口
- * @author onerain
- *
- */
- public interface ImageCallBack
- {
- public void imageLoad(ImageView imageView, Bitmap bitmap);
- }
- }
- </span>
PS:我这里用到了两个缓冲,一是内存缓存,一个是本地缓存(即SD卡缓存),其中用到了SoftReference,这个类的主要作用是生成一个“软引用”,你可以认为是一种随时会由于JVM垃圾回收机制回收掉的Map对象(而平时我们所用到的引用不释放的话不会被JVM回收),之所以用到软引用,就是考虑到android对图片的缓存是有大小限制的,当超过这个大小时,就一定要释放,如果你用引用,保持不释放的话,那么FC(Force close)就离你不远了。。。我这里还用到了一个本地缓存的机制,是和参考博客不太一样的地方,只是提供一种思路,方法还没有完善(主要因为和服务器还没约定好关于图片的命名规则),主要作用是在用户浏览过大量图片之后(超过内存缓存容量之后),保留在本地,一是为了提高读取速度,二是可以减少流量消耗!
这个类设计好之后,在自定义的Adapter当中比之前会有些不同,先上代码再解释
- <span style="font-size:18px;">package onerain.ald.adapter;
- import java.util.List;
- import onerain.ald.R;
- import onerain.ald.async.AsyncBitmapLoader;
- import onerain.ald.async.AsyncBitmapLoader.ImageCallBack;
- import onerain.ald.entity.BaseBookEntity;
- import onerain.ald.holder.ViewHolder;
- import android.content.Context;
- import android.graphics.Bitmap;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.TextView;
- /**
- * @author oneRain
- **/
- public class ListAdapter extends BaseAdapter
- {
- private Context context = null;
- private List<BaseBookEntity> bookList = null;
- private AsyncBitmapLoader asyncLoader = null;
- public ListAdapter(Context context, List<BaseBookEntity> bookList)
- {
- this.context = context;
- this.bookList = bookList;
- this.asyncLoader = new AsyncBitmapLoader();
- }
- @Override
- public int getCount()
- {
- // TODO Auto-generated method stub
- return bookList.size();
- }
- @Override
- public Object getItem(int position)
- {
- // TODO Auto-generated method stub
- return bookList.get(position);
- }
- @Override
- public long getItemId(int position)
- {
- // TODO Auto-generated method stub
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent)
- {
- // TODO Auto-generated method stub
- ViewHolder holder = null;
- if(convertView == null)
- {
- LayoutInflater inflater = LayoutInflater.from(context);
- convertView = inflater.inflate(R.layout.item, null);
- holder = new ViewHolder((ImageView)convertView.findViewById(R.id.imageView),
- (TextView)convertView.findViewById(R.id.textView));
- convertView.setTag(holder);
- }
- else
- {
- holder = (ViewHolder) convertView.getTag();
- }
- ImageView imageView = holder.getImageView();
- <span style="color:#FF0000;">imageView.setImageBitmap(null);</span>
- //根据图片URL去查找内存缓存有没有对应的Bitmap对象,并传递回调方法,如果没有,则等下载完毕回调
- Bitmap bitmap = asyncLoader.loadBitmap(imageView,
- bookList.get(position).getBook_pic(),
- new ImageCallBack()
- {
- @Override
- public void imageLoad(ImageView imageView, Bitmap bitmap)
- {
- // TODO Auto-generated method stub
- imageView.setImageBitmap(bitmap);
- }
- });
- if(bitmap == null)
- {
- imageView.setImageResource(R.drawable.ic_launcher);
- }
- else
- {
- imageView.setImageBitmap(bitmap);
- }
- holder.getTextView().setText(bookList.get(position).getTitle());
- return convertView;
- }
- }
- </span>
在Adapter中,主要不同表现在
public View getView(int position, View convertView, ViewGroup parent)方法中(我这里用到了一些优化方面的处理,会在其他时间再与大家分享,今天先不解释),和异步加载最相关的是这一段
- <span style="font-size:18px;">ImageView imageView = holder.getImageView();
- //根据图片URL去查找内存缓存有没有对应的Bitmap对象,并传递回调方法,如果没有,则等下载完毕回调
- Bitmap bitmap = asyncLoader.loadBitmap(imageView,
- bookList.get(position).getBook_pic(),
- new ImageCallBack()
- {
- @Override
- public void imageLoad(ImageView imageView, Bitmap bitmap)
- {
- // TODO Auto-generated method stub
- imageView.setImageBitmap(bitmap);
- }
- });
- if(bitmap == null)
- {
- imageView.setImageResource(R.drawable.ic_launcher);
- }
- else
- {
- imageView.setImageBitmap(bitmap);
- }</span>
asyncLoader是我们定义的异步加载类的对象,通过这个类的
public Bitmap loadBitmap(final ImageView imageView, final String imageURL, final ImageCallBack imageCallBack)
加载图片,传递参数也与参考博客有些不同,我觉得这样更好理解一下,就是要显示图片的URL链接,和图片要显示对应的控件,当然最重要的还有这个接口实现的回调对象,是在线程中下载完图片之后,用以加载图片的回调对象。而这个回调对象在传递的时候已经实现了接口方法,即将下载好的图片绘制在对应的控件之中
4、Android公共库——图片缓存 网络缓存 下拉及底部更多ListView 公共类
介绍总结的一些android公共库,包含缓存(图片缓存、预取缓存、网络缓存)、公共View(下拉及底部加载更多ListView、底部加载更多ScrollView、滑动一页Gallery)、及Android常用工具类(网络、下载、shell、文件、json等等)。
TrineaAndroidCommon已开源,地址为TrineaAndroidCommon@Github,欢迎Star或Fork^_*
示例APK可从这些地址下载:Google Play, 360手机助手, 百度手机助手, 小米应用商店, 豌豆荚
PS:这是准备分享的三个系列之一,暂时告一段落,后续准备开始Android性能优化系列以及Java多线程系列。
一. 缓存类
1. 图片缓存
使用见:图片缓存的使用
适用:获取图片较多的应用,如新浪微博、twitter、微信头像、美丽说、蘑菇街、花瓣、淘宝等等。
主要特性:(1). 使用简单 (2). 轻松获取及预取新图片 (3). 包含二级缓存 (4). 可选择多种缓存算法(FIFO、LIFO、LRU、MRU、LFU、MFU等13种)或自定义缓存算法 (5). 可方便的保存及初始化恢复数据 (6). 省流量性能佳(有且仅有一个线程获取图片) (7). 支持不同类型网络处理 (8). 可根据系统配置初始化缓存 (9). 扩展性强 (10). 支持等待队列 (11). 包含map的大多数接口。
效果图:
2. 图片SD卡缓存
使用见:图片SD卡缓存的使用
适用:应用中获取图片较多且图片较大的情况,在微博、花瓣、美丽说、path这类应用中可以起到很好的效果。
主要特性:(1). 使用简单 (2). 轻松获取及预取新图片 (3). 包含二级缓存 (4). 可选择多种缓存算法(FIFO、LIFO、LRU、MRU、LFU、MFU等13种)或自定义缓存算法 (5). 可方便的保存及初始化恢复数据 (6). 支持文件sd卡保存及自定义文件名规则 (7). 省流量性能佳(有且仅有一个线程获取图片) (8). 支持不同类型网络处理 (9). 可根据系统配置初始化缓存 (10). 扩展性强 (11). 支持等待队列 (12). 包含map的大多数接口。
效果图:
3. 网络缓存
使用见:Android网络缓存
适用:网络获取内容不大的应用,尤其是api接口数据,如新浪微博、twitter的timeline、微信公众账号发送的内容等等。
主要特性:(1). 可同步或异步获取数据 (2). 可自动根据服务器的返回头判断是否需要缓存 (3). 可自动根据请求头信息判断是否读取缓存
效果图:
4. 预取数据缓存
使用见:预取数据缓存PreloadDataCache
主要特性:(1).使用简单 (2).可自动预取新数据 (3).可选择多种缓存算法(包括FIFO、LIFO、LRU、MRU、LFU、MFU等15种)或自定义缓存算法 (4).省流量性能佳(有且仅有一个线程获取数据) (5).支持不同类型网络处理 (6)缓存可序列化到本地 缓存可从文件中恢复 (7).扩展性强 (8). 包含map的大多数接口
缓存类关系图如下:其中HttpCache为后续计划的http缓存
二. 公用的view
1. 下拉刷新及滚动到底部加载更多的Listview
使用见: 下拉刷新及滚动到底部加载更多listview的使用
主要特性:(1). 可自定义下拉响应事件(如下拉刷新) (2).可自定义滚动到底部响应的事件(如滑动到底部加载更多) (3).可自定义丰富的样式 (4).高效(若下拉样式关闭不会加载其布局,同listView效率一致) (5). 丰富的设置
效果图:
2. 滑动一页(一个Item)的Gallery
使用及实现原理见:滑动一页(一个Item)的Gallery的使用
效果图:
3. 滑动到底部或顶部响应的ScrollView
使用及实现原理见: 滚动到底部或顶部响应的ScrollView使用
效果图:
三. 工具类
具体介绍可见:Android常用工具类
目前包括HttpUtils、DownloadManagerPro、ShellUtils、PackageUtils、PreferencesUtils、JSONUtils、FileUtils、ResourceUtils、StringUtils、ParcelUtils、RandomUtils、ArrayUtils、ImageUtils、ListUtils、MapUtils、ObjectUtils、SerializeUtils、SystemUtils、TimeUtils。
1. Android系统下载管理DownloadManager使用
使用示例见:Android系统下载管理DownloadManager功能介绍及使用示例
功能扩展:Android下载管理DownloadManager功能扩展和bug修改
2. Android APK root权限静默安装
使用示例见:Android APK root权限静默安装
3. Android root权限
直接调用ShellUtils.execCommand方法
4. 图片工具类
(1)Drawable、Bitmap、byte数组相互转换; (2)根据url获得InputStream、Drawable、Bitmap见ImageUtils。
更多工具类介绍见Android常用工具类
原文链接:http://www.trinea.cn/android/android-common-lib/
5、Android之ListView异步加载网络图片(优化缓存机制) .
网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决。下面提出一些优化:
1、采用线程池
2、内存缓存+文件缓存
3、内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4
4、对下载的图片进行按比例缩放,以减少内存的消耗
具体的代码里面说明。先放上内存缓存类的代码MemoryCache.java:
- public class MemoryCache {
- private static final String TAG = "MemoryCache";
- // 放入缓存时是个同步操作
- // LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU
- // 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率
- private Map<String, Bitmap> cache = Collections
- .synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));
- // 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存
- private long size = 0;// current allocated size
- // 缓存只能占用的最大堆内存
- private long limit = 1000000;// max memory in bytes
- public MemoryCache() {
- // use 25% of available heap size
- setLimit(Runtime.getRuntime().maxMemory() / 4);
- }
- public void setLimit(long new_limit) {
- limit = new_limit;
- Log.i(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB");
- }
- public Bitmap get(String id) {
- try {
- if (!cache.containsKey(id))
- return null;
- return cache.get(id);
- } catch (NullPointerException ex) {
- return null;
- }
- }
- public void put(String id, Bitmap bitmap) {
- try {
- if (cache.containsKey(id))
- size -= getSizeInBytes(cache.get(id));
- cache.put(id, bitmap);
- size += getSizeInBytes(bitmap);
- checkSize();
- } catch (Throwable th) {
- th.printStackTrace();
- }
- }
- /**
- * 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存
- *
- */
- private void checkSize() {
- Log.i(TAG, "cache size=" + size + " length=" + cache.size());
- if (size > limit) {
- // 先遍历最近最少使用的元素
- Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator();
- while (iter.hasNext()) {
- Entry<String, Bitmap> entry = iter.next();
- size -= getSizeInBytes(entry.getValue());
- iter.remove();
- if (size <= limit)
- break;
- }
- Log.i(TAG, "Clean cache. New size " + cache.size());
- }
- }
- public void clear() {
- cache.clear();
- }
- /**
- * 图片占用的内存
- *
- * @param bitmap
- * @return
- */
- long getSizeInBytes(Bitmap bitmap) {
- if (bitmap == null)
- return 0;
- return bitmap.getRowBytes() * bitmap.getHeight();
- }
- }
- public class MemoryCache {
- private static final String TAG = "MemoryCache";
- // 放入缓存时是个同步操作
- // LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU
- // 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率
- private Map<String, Bitmap> cache = Collections
- .synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));
- // 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存
- private long size = 0;// current allocated size
- // 缓存只能占用的最大堆内存
- private long limit = 1000000;// max memory in bytes
- public MemoryCache() {
- // use 25% of available heap size
- setLimit(Runtime.getRuntime().maxMemory() / 4);
- }
- public void setLimit(long new_limit) {
- limit = new_limit;
- Log.i(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB");
- }
- public Bitmap get(String id) {
- try {
- if (!cache.containsKey(id))
- return null;
- return cache.get(id);
- } catch (NullPointerException ex) {
- return null;
- }
- }
- public void put(String id, Bitmap bitmap) {
- try {
- if (cache.containsKey(id))
- size -= getSizeInBytes(cache.get(id));
- cache.put(id, bitmap);
- size += getSizeInBytes(bitmap);
- checkSize();
- } catch (Throwable th) {
- th.printStackTrace();
- }
- }
- /**
- * 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存
- *
- */
- private void checkSize() {
- Log.i(TAG, "cache size=" + size + " length=" + cache.size());
- if (size > limit) {
- // 先遍历最近最少使用的元素
- Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator();
- while (iter.hasNext()) {
- Entry<String, Bitmap> entry = iter.next();
- size -= getSizeInBytes(entry.getValue());
- iter.remove();
- if (size <= limit)
- break;
- }
- Log.i(TAG, "Clean cache. New size " + cache.size());
- }
- }
- public void clear() {
- cache.clear();
- }
- /**
- * 图片占用的内存
- *
- * @param bitmap
- * @return
- */
- long getSizeInBytes(Bitmap bitmap) {
- if (bitmap == null)
- return 0;
- return bitmap.getRowBytes() * bitmap.getHeight();
- }
- }
也可以使用SoftReference,代码会简单很多,但是我推荐上面的方法。
- public class MemoryCache {
- private Map<String, SoftReference<Bitmap>> cache = Collections
- .synchronizedMap(new HashMap<String, SoftReference<Bitmap>>());
- public Bitmap get(String id) {
- if (!cache.containsKey(id))
- return null;
- SoftReference<Bitmap> ref = cache.get(id);
- return ref.get();
- }
- public void put(String id, Bitmap bitmap) {
- cache.put(id, new SoftReference<Bitmap>(bitmap));
- }
- public void clear() {
- cache.clear();
- }
- }
- public class MemoryCache {
- private Map<String, SoftReference<Bitmap>> cache = Collections
- .synchronizedMap(new HashMap<String, SoftReference<Bitmap>>());
- public Bitmap get(String id) {
- if (!cache.containsKey(id))
- return null;
- SoftReference<Bitmap> ref = cache.get(id);
- return ref.get();
- }
- public void put(String id, Bitmap bitmap) {
- cache.put(id, new SoftReference<Bitmap>(bitmap));
- }
- public void clear() {
- cache.clear();
- }
- }
- public class FileCache {
- private File cacheDir;
- public FileCache(Context context) {
- // 如果有SD卡则在SD卡中建一个LazyList的目录存放缓存的图片
- // 没有SD卡就放在系统的缓存目录中
- if (android.os.Environment.getExternalStorageState().equals(
- android.os.Environment.MEDIA_MOUNTED))
- cacheDir = new File(
- android.os.Environment.getExternalStorageDirectory(),
- "LazyList");
- else
- cacheDir = context.getCacheDir();
- if (!cacheDir.exists())
- cacheDir.mkdirs();
- }
- public File getFile(String url) {
- // 将url的hashCode作为缓存的文件名
- String filename = String.valueOf(url.hashCode());
- // Another possible solution
- // String filename = URLEncoder.encode(url);
- File f = new File(cacheDir, filename);
- return f;
- }
- public void clear() {
- File[] files = cacheDir.listFiles();
- if (files == null)
- return;
- for (File f : files)
- f.delete();
- }
- }
- public class FileCache {
- private File cacheDir;
- public FileCache(Context context) {
- // 如果有SD卡则在SD卡中建一个LazyList的目录存放缓存的图片
- // 没有SD卡就放在系统的缓存目录中
- if (android.os.Environment.getExternalStorageState().equals(
- android.os.Environment.MEDIA_MOUNTED))
- cacheDir = new File(
- android.os.Environment.getExternalStorageDirectory(),
- "LazyList");
- else
- cacheDir = context.getCacheDir();
- if (!cacheDir.exists())
- cacheDir.mkdirs();
- }
- public File getFile(String url) {
- // 将url的hashCode作为缓存的文件名
- String filename = String.valueOf(url.hashCode());
- // Another possible solution
- // String filename = URLEncoder.encode(url);
- File f = new File(cacheDir, filename);
- return f;
- }
- public void clear() {
- File[] files = cacheDir.listFiles();
- if (files == null)
- return;
- for (File f : files)
- f.delete();
- }
- }
- public class ImageLoader {
- MemoryCache memoryCache = new MemoryCache();
- FileCache fileCache;
- private Map<ImageView, String> imageViews = Collections
- .synchronizedMap(new WeakHashMap<ImageView, String>());
- // 线程池
- ExecutorService executorService;
- public ImageLoader(Context context) {
- fileCache = new FileCache(context);
- executorService = Executors.newFixedThreadPool(5);
- }
- // 当进入listview时默认的图片,可换成你自己的默认图片
- final int stub_id = R.drawable.stub;
- // 最主要的方法
- public void DisplayImage(String url, ImageView imageView) {
- imageViews.put(imageView, url);
- // 先从内存缓存中查找
- Bitmap bitmap = memoryCache.get(url);
- if (bitmap != null)
- imageView.setImageBitmap(bitmap);
- else {
- // 若没有的话则开启新线程加载图片
- queuePhoto(url, imageView);
- imageView.setImageResource(stub_id);
- }
- }
- private void queuePhoto(String url, ImageView imageView) {
- PhotoToLoad p = new PhotoToLoad(url, imageView);
- executorService.submit(new PhotosLoader(p));
- }
- private Bitmap getBitmap(String url) {
- File f = fileCache.getFile(url);
- // 先从文件缓存中查找是否有
- Bitmap b = decodeFile(f);
- if (b != null)
- return b;
- // 最后从指定的url中下载图片
- try {
- Bitmap bitmap = null;
- URL imageUrl = new URL(url);
- HttpURLConnection conn = (HttpURLConnection) imageUrl
- .openConnection();
- conn.setConnectTimeout(30000);
- conn.setReadTimeout(30000);
- conn.setInstanceFollowRedirects(true);
- InputStream is = conn.getInputStream();
- OutputStream os = new FileOutputStream(f);
- CopyStream(is, os);
- os.close();
- bitmap = decodeFile(f);
- return bitmap;
- } catch (Exception ex) {
- ex.printStackTrace();
- return null;
- }
- }
- // decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的
- private Bitmap decodeFile(File f) {
- try {
- // decode image size
- BitmapFactory.Options o = new BitmapFactory.Options();
- o.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(new FileInputStream(f), null, o);
- // Find the correct scale value. It should be the power of 2.
- final int REQUIRED_SIZE = 70;
- int width_tmp = o.outWidth, height_tmp = o.outHeight;
- int scale = 1;
- while (true) {
- if (width_tmp / 2 < REQUIRED_SIZE
- || height_tmp / 2 < REQUIRED_SIZE)
- break;
- width_tmp /= 2;
- height_tmp /= 2;
- scale *= 2;
- }
- // decode with inSampleSize
- BitmapFactory.Options o2 = new BitmapFactory.Options();
- o2.inSampleSize = scale;
- return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
- } catch (FileNotFoundException e) {
- }
- return null;
- }
- // Task for the queue
- private class PhotoToLoad {
- public String url;
- public ImageView imageView;
- public PhotoToLoad(String u, ImageView i) {
- url = u;
- imageView = i;
- }
- }
- class PhotosLoader implements Runnable {
- PhotoToLoad photoToLoad;
- PhotosLoader(PhotoToLoad photoToLoad) {
- this.photoToLoad = photoToLoad;
- }
- @Override
- public void run() {
- if (imageViewReused(photoToLoad))
- return;
- Bitmap bmp = getBitmap(photoToLoad.url);
- memoryCache.put(photoToLoad.url, bmp);
- if (imageViewReused(photoToLoad))
- return;
- BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
- // 更新的操作放在UI线程中
- Activity a = (Activity) photoToLoad.imageView.getContext();
- a.runOnUiThread(bd);
- }
- }
- /**
- * 防止图片错位
- *
- * @param photoToLoad
- * @return
- */
- boolean imageViewReused(PhotoToLoad photoToLoad) {
- String tag = imageViews.get(photoToLoad.imageView);
- if (tag == null || !tag.equals(photoToLoad.url))
- return true;
- return false;
- }
- // 用于在UI线程中更新界面
- class BitmapDisplayer implements Runnable {
- Bitmap bitmap;
- PhotoToLoad photoToLoad;
- public BitmapDisplayer(Bitmap b, PhotoToLoad p) {
- bitmap = b;
- photoToLoad = p;
- }
- public void run() {
- if (imageViewReused(photoToLoad))
- return;
- if (bitmap != null)
- photoToLoad.imageView.setImageBitmap(bitmap);
- else
- photoToLoad.imageView.setImageResource(stub_id);
- }
- }
- public void clearCache() {
- memoryCache.clear();
- fileCache.clear();
- }
- public static void CopyStream(InputStream is, OutputStream os) {
- final int buffer_size = 1024;
- try {
- byte[] bytes = new byte[buffer_size];
- for (;;) {
- int count = is.read(bytes, 0, buffer_size);
- if (count == -1)
- break;
- os.write(bytes, 0, count);
- }
- } catch (Exception ex) {
- }
- }
- }
- public class ImageLoader {
- MemoryCache memoryCache = new MemoryCache();
- FileCache fileCache;
- private Map<ImageView, String> imageViews = Collections
- .synchronizedMap(new WeakHashMap<ImageView, String>());
- // 线程池
- ExecutorService executorService;
- public ImageLoader(Context context) {
- fileCache = new FileCache(context);
- executorService = Executors.newFixedThreadPool(5);
- }
- // 当进入listview时默认的图片,可换成你自己的默认图片
- final int stub_id = R.drawable.stub;
- // 最主要的方法
- public void DisplayImage(String url, ImageView imageView) {
- imageViews.put(imageView, url);
- // 先从内存缓存中查找
- Bitmap bitmap = memoryCache.get(url);
- if (bitmap != null)
- imageView.setImageBitmap(bitmap);
- else {
- // 若没有的话则开启新线程加载图片
- queuePhoto(url, imageView);
- imageView.setImageResource(stub_id);
- }
- }
- private void queuePhoto(String url, ImageView imageView) {
- PhotoToLoad p = new PhotoToLoad(url, imageView);
- executorService.submit(new PhotosLoader(p));
- }
- private Bitmap getBitmap(String url) {
- File f = fileCache.getFile(url);
- // 先从文件缓存中查找是否有
- Bitmap b = decodeFile(f);
- if (b != null)
- return b;
- // 最后从指定的url中下载图片
- try {
- Bitmap bitmap = null;
- URL imageUrl = new URL(url);
- HttpURLConnection conn = (HttpURLConnection) imageUrl
- .openConnection();
- conn.setConnectTimeout(30000);
- conn.setReadTimeout(30000);
- conn.setInstanceFollowRedirects(true);
- InputStream is = conn.getInputStream();
- OutputStream os = new FileOutputStream(f);
- CopyStream(is, os);
- os.close();
- bitmap = decodeFile(f);
- return bitmap;
- } catch (Exception ex) {
- ex.printStackTrace();
- return null;
- }
- }
- // decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的
- private Bitmap decodeFile(File f) {
- try {
- // decode image size
- BitmapFactory.Options o = new BitmapFactory.Options();
- o.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(new FileInputStream(f), null, o);
- // Find the correct scale value. It should be the power of 2.
- final int REQUIRED_SIZE = 70;
- int width_tmp = o.outWidth, height_tmp = o.outHeight;
- int scale = 1;
- while (true) {
- if (width_tmp / 2 < REQUIRED_SIZE
- || height_tmp / 2 < REQUIRED_SIZE)
- break;
- width_tmp /= 2;
- height_tmp /= 2;
- scale *= 2;
- }
- // decode with inSampleSize
- BitmapFactory.Options o2 = new BitmapFactory.Options();
- o2.inSampleSize = scale;
- return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
- } catch (FileNotFoundException e) {
- }
- return null;
- }
- // Task for the queue
- private class PhotoToLoad {
- public String url;
- public ImageView imageView;
- public PhotoToLoad(String u, ImageView i) {
- url = u;
- imageView = i;
- }
- }
- class PhotosLoader implements Runnable {
- PhotoToLoad photoToLoad;
- PhotosLoader(PhotoToLoad photoToLoad) {
- this.photoToLoad = photoToLoad;
- }
- @Override
- public void run() {
- if (imageViewReused(photoToLoad))
- return;
- Bitmap bmp = getBitmap(photoToLoad.url);
- memoryCache.put(photoToLoad.url, bmp);
- if (imageViewReused(photoToLoad))
- return;
- BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
- // 更新的操作放在UI线程中
- Activity a = (Activity) photoToLoad.imageView.getContext();
- a.runOnUiThread(bd);
- }
- }
- /**
- * 防止图片错位
- *
- * @param photoToLoad
- * @return
- */
- boolean imageViewReused(PhotoToLoad photoToLoad) {
- String tag = imageViews.get(photoToLoad.imageView);
- if (tag == null || !tag.equals(photoToLoad.url))
- return true;
- return false;
- }
- // 用于在UI线程中更新界面
- class BitmapDisplayer implements Runnable {
- Bitmap bitmap;
- PhotoToLoad photoToLoad;
- public BitmapDisplayer(Bitmap b, PhotoToLoad p) {
- bitmap = b;
- photoToLoad = p;
- }
- public void run() {
- if (imageViewReused(photoToLoad))
- return;
- if (bitmap != null)
- photoToLoad.imageView.setImageBitmap(bitmap);
- else
- photoToLoad.imageView.setImageResource(stub_id);
- }
- }
- public void clearCache() {
- memoryCache.clear();
- fileCache.clear();
- }
- public static void CopyStream(InputStream is, OutputStream os) {
- final int buffer_size = 1024;
- try {
- byte[] bytes = new byte[buffer_size];
- for (;;) {
- int count = is.read(bytes, 0, buffer_size);
- if (count == -1)
- break;
- os.write(bytes, 0, count);
- }
- } catch (Exception ex) {
- }
- }
- }
- a.runOnUiThread(...);
- a.runOnUiThread(...);
在你的程序中的基本用法:
- ImageLoader imageLoader=new ImageLoader(context);
- ...
- imageLoader.DisplayImage(url, imageView);
- ImageLoader imageLoader=new ImageLoader(context);
- ...
- imageLoader.DisplayImage(url, imageView);
6、Socket实现文件上传
实现思路:
在客户端获取到文件流,将文件流写入到通过socket指定到某服务器的输出流中,在服务器中通过socket获取到输入流,将数据写入到指定的文件夹内,为了提供多用户同时上传,这里需要将在服务器上传客户端的文件操作放在另开启一个线程去运行。
完整代码:
- import java.net.*;
- import java.io.*;
- /*
- 服务端将获取到的客户端封装到单独的线程中。
- */
- class JpgClient2
- {
- public static void main(String[] args) throws Exception
- {
- //检验文件
- if(args.length==0)
- {
- System.out.println("指定一个jpg文件先!");
- return ;
- }
- File file = new File(args[0]);
- if(!(file.exists() && file.isFile() && file.getName().endsWith(".jpg")))
- {
- System.out.println("选择文件错误,请重新选择一个正确的文件。");
- return ;
- }
- //读取文件并写入到服务器中
- Socket s = new Socket("192.168.137.199",9006);
- FileInputStream fis = new FileInputStream(file);
- OutputStream out = s.getOutputStream();
- byte[] buf = new byte[1024];
- int len = 0;
- while((len=fis.read(buf))!=-1)
- {
- out.write(buf,0,len);
- }
- //通知服务器发送数据结束
- s.shutdownOutput();
- //获取服务器响应
- InputStream in = s.getInputStream();
- byte[] bufIn = new byte[1024];
- int num = in.read(bufIn);
- String str = new String(bufIn,0,num);
- System.out.println(str);
- fis.close();
- s.close();
- }
- }
- class JpgThread implements Runnable
- {
- private Socket s;
- JpgThread(Socket s)
- {
- this.s = s;
- }
- public void run()
- {
- int count = 1;
- String ip = s.getInetAddress().getHostAddress();
- try
- {
- //获取客户端数据
- InputStream in = s.getInputStream();
- //指定文件存放路径将读取到客户提交的数据写入文件中
- File dir = new File("c:\\pic");
- File file = new File(dir,ip+"("+count+").jpg");
- while(file.exists())
- file = new File(dir,ip+"("+(count++)+").jpg");
- FileOutputStream fos = new FileOutputStream(file);
- byte[] buf = new byte[1024];
- int len = 0;
- while((len=in.read(buf))!=-1)
- {
- fos.write(buf,0,len);
- }
- //返回上传状态给客户端
- OutputStream out = s.getOutputStream();
- out.write("上传文件成功".getBytes());
- fos.close();
- s.close();
- }
- catch (Exception e)
- {
- System.out.println(e.toString());
- }
- }
- }
- class JpgServer2
- {
- public static void main(String[] args)throws Exception
- {
- ServerSocket ss = new ServerSocket(9006);
- //开启线程并发访问
- while(true)
- {
- Socket s = ss.accept();
- String ip = s.getInetAddress().getHostAddress();
- System.out.println(ip+"....connected");
- new Thread(new JpgThread(s)).start();
- }
- }
- }