大家好 我是akira 上一讲 我们说了利用httpClinet发送网络请求 来获取流去拿到图片这么一个过程
我们可以回顾一下具体是开一个线程 然后利用httpClient 然后发一个get请求过来 得到响应吗 如果为200说明成功
得到一个流 同时保存在sd卡中 然后写一个方法 根据url格式 来获取到name (利用path的substring的lastIndexof+1)
同时拿到bitmap之后调用compress方法 保存在文件中 然后发一个消息 展示。
OK 回顾完之后 有个问题就是如果加载大图的时候 这里必定会出现OOM异常 也就是通常所说的内存过大
说说图片内存具体怎么算 我们先要有一张图 下面我随便找了一张
看下分辨率
嗯 确实是够大
加载后出现
OK 再来找下日志
这里有个12582924的申请字节 我们可以算下
除两个1024后 发现是12M 有人说 andriod不是16M或 24M内存么 12M不超啊
这个看法有两点错误
1 加入12M仅仅为了处理一个图片 那么剩下的4M 或者12M 处理太多的东西 虚拟机受不了
2 andriod系统在设计本初 就给处理图给了8M空间 如果这么一对比肯定是超了
所以 这里所谓的OOM就是处理图片内存>8M
好 知道了这个之后 我们看下怎么解决
效果如下
缓存之后效果
看下代码如何实现的
Bitmap bits ;
/**
* 加载数据(关键方法)
* @param path
*/
private void loadData(final String path) {
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
file = new File(Environment.getExternalStorageDirectory(),getPicName(path));
if(file.exists()){
pic.setImageURI(Uri.fromFile(file));
}
else{
new Thread(){
public void run() {
try {
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(path);
HttpResponse httpResponse = client.execute(get);
if(httpResponse.getStatusLine().getStatusCode()==200){
InputStream is = httpResponse.getEntity().getContent();
bits = scalePic(is, pic.getHeight(), pic.getWidth());
OutputStream os = new FileOutputStream(file);
bits.compress(CompressFormat.JPEG, 80, os);
Message msg = Message.obtain();
msg.obj =bits;
msg.what = GETPIC;
mHandler.sendMessage(msg);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}.start();
}
}
}
我们发现这里加了一个新方法是scalePic 里面我传了三个参数 因为是流 所以传一个input 再传宽高 OK
然后看下缩放pic的方法
/**
* 缩放图片
* @param bit
* @param width
* @param height
*/
protected Bitmap scalePic(InputStream is, int imageHeight, int imageWidth) {
Options opts = new Options();
opts.inJustDecodeBounds = true;
opts.outHeight = imageHeight;
opts.outWidth = imageWidth;
opts.inSampleSize =4;// 16分之一
opts.inJustDecodeBounds = false;
Bitmap smallbit = BitmapFactory.decodeStream(is, null, opts);
return smallbit;
}
这里吐槽一下 以前大多数做法是利用bitmap的decodeFile 但这样效率很低
而decodeStream的三参方法很少人用 试了一下效果很好
这里的思路仍然是缩放 利用关键的类是options 这是bitmap的一个类 专门设置位图参数使用
我们看到 利用opts的outHeight或outWidth拿到宽高 然后去进行缩放宽高的4分之一
所以整体缩放就是16分之1 inJustDecodeBounds这个方法的作用是设置成true 就先不加载进内存 方便调用bitmap身上的参数
然后加载的时候再给false 最后我们返回的是缩小后的bitmap
PS:这里要吐槽一下 网上很多代码都是互抄一点使用价值也没有 强烈鄙视
但是这里有个问题 你会发现有趣的resovle failedXXX这么一个异常 这个原因找了一段时间 发现原来是文件名的问题
原来我们的图片文件名不宜过长 否则就无法解析
OK 我们修改获取名字的代码
/**
* 得到图片名字
* @param path
* @return
*/
private String getPicName(String path){
String newpath = path.substring(path.lastIndexOf("/")+1);
if(newpath.length()>=16){
newpath = newpath.substring(16, newpath.length());
}
return newpath;
}
这里 我们进行判断 如果长度超过16就截 说白了 OK 然后再运行就没问题了
这里截了一张效果图
原图片路径
http://imgsrc.baidu.com/forum/pic/item/4f55880a19d8bc3e1c82e62a838ba61eaad34598.jpg
最后 我们改下周围的布局 利用一个新控件ScrollView 这个就可以实现上下拉动啦
布局代码
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".MainActivity"
android:orientation="vertical"
>
<ImageView
android:id="@+id/pic"
android:layout_width="match_parent"
android:layout_height="300dp"
android:contentDescription="@null"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:layout_centerHorizontal="true"
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<!-- <Button -->
<!-- android:id="@+id/bt_next" -->
<!-- android:layout_width="wrap_content" -->
<!-- android:layout_height="wrap_content" -->
<!-- android:text="next" -->
<!-- /> -->
</RelativeLayout>
<EditText
android:text="http://imgsrc.baidu.com/forum/pic/item/4f55880a19d8bc3e1c82e62a838ba61eaad34598.jpg"
android:id="@+id/et_address"
android:hint="@string/typeuri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:layout_centerHorizontal="true"
android:id="@+id/bt_load"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/load" />
</RelativeLayout>
</LinearLayout>
</ScrollView>
但新问题来了 我们发现通常加载的时候 最好给下提示来个进度条什么的
这个时候 我们也做一个 来了一个新朋友叫做progressBar 好的 我们利用代码加上进度条
ProgressBar pb;
private RelativeLayout rl;
/**
* 显示
* @param ct
*/
private void showProgress(Context ct){
if(pb==null){
ProgressBar pb = new ProgressBar(ct);
rl.addView(pb);
pb.setVisibility(View.VISIBLE);
}
}
/**
* 隐藏
*/
private void hideProgress(){
pb.setVisibility(View.GONE);
}
这里加上两个方法 显示和隐藏 利用相对布局的addView方法 加载进去
分别在加载和显示后 调用showXXX和hideXXX
if(httpResponse.getStatusLine().getStatusCode()==200){
InputStream is = httpResponse.getEntity().getContent();
showProgress(MainActivity.this);
bits = scalePic(is, pic.getHeight(), pic.getWidth());
OutputStream os = new FileOutputStream(file);
bits.compress(CompressFormat.JPEG, 80, os);
Message msg = Message.obtain();
msg.obj =bits;
msg.what = GETPIC;
mHandler.sendMessage(msg);
}
case GETPIC:
//已经拿到图片
Bitmap bit = (Bitmap) msg.obj;
pic.setImageBitmap(bit);
name.setText(getPicName(path));
hideProgress();
showToast(MainActivity.this, "成功获取", Toast.LENGTH_SHORT);
break;
但是利用MVC思想的话 这里应该是分开业务类来写 而且我们发现加载一个图利用了这么多方法
而有种框架几行代码就能搞定全部
那么这框架究竟是什么框架呢 请看下讲
学习andriod开发之 自己动手写图片查看器(三)
本期代码下载