我在SD卡上有一张图片,需要在图片视图上显示
问题在于解码后,质量似乎恶化了。有没有什么方法可以保持质量,同时保留记忆?
或者,如果我使用更大的图像,是否有任何方法可以通过缩放来保留内存(避免加载过大的位图)? (我需要保持原始图像的大小)
感谢您的帮助。
public Bitmap decodeFile(String pubKey, int bookPageID, int type)
throws IOException {
Bitmap b = null;
File f = null;
String uri = null;
FileInputStream fis = null;
Log.d(TAG,"pageID to read: " + bookPageID);
IRIssue issue = Broker.model.issueDataStore.getIRIssue(pubKey);
String imageFolder = IRConstant.issueFolder(issue.year, issue.month, issue.day, issue.pubKey);
// pageID - 1 since the page is an array (start at 0) , but page ID start at 1
if (type == 2){
uri = imageFolder + issue.vol[0].pages[bookPageID - 1].graphicUri;
}else {
uri = imageFolder + issue.vol[0].pages[bookPageID - 1].textUri;
}
f = new File(uri);
Log.d(TAG,"is file: " + uri + " exist?" + f.exists());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, options);
fis.close();
return b;
}
答案 0 :(得分:3)
以下代码使用Displaying Bitmaps Efficiently中的几个概念
首先关闭位图读取是在后台线程中完成的,我在inputStream
上使用标记/重置(用BufferedInputstream
包裹),当我们试图找出大小时,不会从流中读取超过必要的内容在计算比例因子时使用的图像。下面的示例代码对图像进行子采样,以匹配320x240像素的大小。在非示例代码中,可以使用简单的回调接口将位图从onPostExecute
发送到实现类(回调接口实现器)。或者直接将视图作为AsyncTask
的成员提供,并在onPostExecute
中设置位图。
使用(我的设备上的示例下载图像)调用代码:
BitmapTask task = new BitmapTask(getContentResolver());
task.execute(Uri.parse("file:///storage/emulated/0/Download/download.jpg"));
有问题的课程
private static class BitmapTask extends AsyncTask<Uri, Void, Bitmap> {
// prevent mem leaks
private WeakReference<ContentResolver> mWeakContentResolver;
public BitmapTask(ContentResolver resolver) {
mWeakContentResolver = new WeakReference<ContentResolver>(resolver);
}
@Override
protected Bitmap doInBackground(Uri... params) {
Bitmap bitmap = null;
ContentResolver resolver = mWeakContentResolver.get();
if (resolver != null) {
BufferedInputStream stream = null;
try {
stream = new BufferedInputStream(
resolver.openInputStream(params[0]));
stream.mark(1 * 1024);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// Find out size of image
BitmapFactory.decodeStream(stream, null, options);
try {
stream.reset();
} catch (IOException e) {
Log.d(TAG, "reset failed");
}
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
Log.d(TAG, "w, h, mime " + imageWidth + " , " + imageHeight
+ " , " + imageType);
options.inJustDecodeBounds = false;
// Calculate down scale factor
options.inSampleSize = calculateInSampleSize(options, 320,
240);
return BitmapFactory.decodeStream(stream, null, options);
} catch (FileNotFoundException e) {
bitmap = null;
} finally {
IOUtils.closeStreamSilently(stream);
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
Log.d(TAG,
"bitmap result: "
+ ((result != null) ? "" + result.getByteCount()
: "0"));
result.recycle();
}
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
编辑:对于大型输入流,标记/重置技术可能存在问题,有时会在日志中看到SkImageDecoder::Factory returned null
,导致空位图,其他SO问题问题:SkImageDecoder::Factory returned null。可以通过在stream = new resolver.openInputStream(params[0]));
doInBackground
来修复此问题
编辑2:如果您必须保留图片大小但不想限制内存使用量,可以使用options.inPreferredConfig = Bitmap.Config.RGB_565;
将每个像素的内存减半,但请记住图像可能不再具有很好的质量(实验!)。
答案 1 :(得分:1)
我使用自定义BitmapHandler类来解决此问题:
public class BitmapHandler {
private static int IMAGE_MAX_SIZE = 540; //This can be set to whatever you see fit
private static String TAG = "BitmapHandler.java";
public BitmapHandler(Context ctx){
WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
Log.v(TAG, "Screen width: " + width + " height: " + height);
IMAGE_MAX_SIZE = (Math.min(width, height))*4; //Try playing with this multiplier number to get different degrees of scaling
}
public Bitmap decodeFileAsPath(String uri) {
// Create a file out of the uri
File f = null;
Log.v(TAG, "Incoming uri: " + uri);
f = new File(uri);
if (f.equals(null)){
Log.v(TAG, "File is null!");
}
return decodeFile(f);
}
private Bitmap decodeFile(File f) {
Bitmap b = null;
try {
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
o.inScaled = false;
FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
int scale = 1;
Log.v(TAG, "Decode Image height: " + o.outHeight + " and width: " + o.outWidth);
if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
scale = (int) Math.pow(
2,
(int) Math.round(Math.log(IMAGE_MAX_SIZE
/ (double) Math.max(o.outHeight, o.outWidth))
/ Math.log(0.5)));
}
Log.v(TAG, "Final scale: " + scale);
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inScaled = false;
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
} catch (IOException e) {
Log.v(TAG, e.getMessage());
}
return b;
}
}
这会在尝试阻止OutOfMemoryException时动态缩放图像
答案 2 :(得分:1)
BitmapFactory有一个inSampleSize属性,旨在解决此问题。请参阅文档:http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inSampleSize
本文写到有效处理位图:http://developer.android.com/training/displaying-bitmaps/index.html
答案 3 :(得分:1)
解码时添加options.injustdecodeBounds = true,因为这表明您只想要边界而不是整个位图。这样可以避免内存错误,因为您只会加载实际需要的图像大小。
第二件事是根据您的需要缩放该位图,并且在没有失真的情况下进行缩放,您必须以保持宽高比的方式缩放它。在单击图片时,您可以设置单击图像的固定比率,而不是仅按该比例缩放图像。如果它不在您手中,您可以使用以下方法获取样本大小,而不是将图像解码为特定大小而不会失真。
private int calculateSampleSize(int width, int height, int targetWidth, int targetHeight) {
float bitmapWidth = width;
float bitmapHeight = height;
int bitmapResolution = (int) (bitmapWidth * bitmapHeight);
int targetResolution = targetWidth * targetHeight;
int sampleSize = 1;
if (targetResolution == 0) {
return sampleSize;
}
for (int i = 1; (bitmapResolution / i) > targetResolution; i *= 2) {
sampleSize = i;
}
return sampleSize;
}
如果您发现任何改进,请提供任何反馈。
答案 4 :(得分:1)
一种高度可配置的快速方法是使用WebView
代替ImageView
:
WebView mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setAllowFileAccess(true);
mWebView.getSettings().setBuiltInZoomControls(true);
String base = Environment.getExternalStorageDirectory().getAbsolutePath().toString();
String imagePath = "file://" + base + "/myImage.png";//replace with the name of the image you are accessing
String html = "<html><head></head><body><img src=\"" + imagePath + "\"></body></html>";
mWebView.loadDataWithBaseURL("", html, "text/html","utf-8", "");
答案 5 :(得分:0)
GridViewActivity.java
public class GridViewActivity extends Activity implements OnItemClickListener {
private String[] filepathstring;
private File[] listfile;
GridView grid_sdcard;
File file;
ImageView image;
GridViewAdapter adapter;
int select;
int sele;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.gridview_activity);
image=(ImageView)convertView.findViewById(R.id.image_show);
grid_sdcard=(GridView)findViewById(R.id.grid_sdcard);
if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
{
Toast.makeText(this,"Error! No SDCARD Found!", Toast.LENGTH_LONG).show();
}
else
{
file=new File(Environment.getExternalStorageDirectory() + File.separator +"eMENU Images/");
file.mkdirs();
Toast.makeText(GridViewActivity.this,"Past Image Here:", Toast.LENGTH_LONG).show();
}
if(file.isDirectory())
{
listfile=file.listFiles();
for(int i=0;i<listfile.length;i++)
{
filepathstring[i]=listfile[i].getAbsolutePath();
}
}
adapter=new GridViewAdapter(GridViewActivity.this,filepathstring);
grid_sdcard.setAdapter(adapter);
grid_sdcard.setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id1) {
final String image=filepathstring[position];
Bitmap bitmap=BitmapFactory.decodeFile(filepathlist[position]);
imageshow.setImageBitmap(bitmap);
}
}
GridViewAdapter.java
public class GridViewAdapter extends BaseAdapter {
String[] filepathlist;
Context context;
public GridViewAdapter(Context con, String[] filepathstring) {
context=con;
filepathlist=filepathstring;
}
@Override
public int getCount() {
return filepathlist.length;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null)
{
LayoutInflater inflater=(LayoutInflater)convertView.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView=inflater.inflate(R.layout.griadview_adapter,null);
}
ImageView imageshow=(ImageView)convertView.findViewById(R.id.image_show);
Bitmap bitmap=BitmapFactory.decodeFile(filepathlist[position]);
imageshow.setImageBitmap(bitmap);
return convertView;
}
}