「OutOfMemoryError」のテスト


2014年4月29日現在、私が理解している内容です。
間違っている場合があります。


画像を拡大してロードしたら「OutOfMemoryError」ですよ。


前回の、「画像をロード、セーブするテスト」のコードを修正して、
画像を拡大してロードするようにしてみましょう。

「ICON」で、アイコンを表示して、「SAVE」でセーブ。
 

「LOAD」で、4倍に拡大ロード。
 



SDカードに保存された、「ScreenShotFile1.png」を縦横4倍拡大のBitmapにして表示しようとするテストです。


private void LoadImage3(){ String filename = "ScreenShotFile1"; File file = Environment.getExternalStorageDirectory(); String path = file.getPath(); filename = path + "/" + filename + ".png"; Bitmap loadbitmap1 = BitmapFactory.decodeFile(filename); //ファイルが開けない if(loadbitmap1==null) return; Matrix mat = new Matrix(); mat.postScale(4,4); int x1 =loadbitmap1.getWidth(); int y1 =loadbitmap1.getHeight(); Bitmap loadbitmap2 = Bitmap.createBitmap(loadbitmap1,0,0,x1,y1,mat,true); //true=アンチエイリアス image1.setImageBitmap(loadbitmap2); }


「LoadImage1() 」を、上の「LoadImage3() 」に置き換えます。
「ScreenShot1() 」を、「ScreenShot2() 」に置き換えます。


Matrixで、「postScale(4,4)」を指定しています。
「Bitmap.createBitmap」に、Matrixを渡すと、480X800の画像をロードするとき、
1920X3200に拡大されたBitmapを作るようになります。
「true」を指定すると、アンチエイリアスがかかります。



さて、はじめは良いのですが、「SAVE」して、「LOAD」を繰り返すと、エラーが出てしまいました。

 

ログには、「OutOfMemoryError」と出ています。
でかい画像を扱ったために、メモリが足らなくなったので、
いらないメモリを開放してやらないといけないということです。

このように、でかい画像を扱うと、「OutOfMemoryError」に悩まされることがあります。




では、これをどう回避するか? ですが……。

以下のような命令が役に立ちそうです。

image1.setImageBitmap(null); image1.setImageDrawable(null); loadbitmap1.recycle(); loadbitmap1 = null; System.gc();


さて、これらの命令が有効だとして、どこに、どういうタイミングで書けばいいのか?
というのも問題です。
場所によっては、無駄だったり、効果無かったり、余計に悪影響がでたりします。

とくに、「System.gc()」は、システムのご判断にお任せして強制的にメモリ開放するという最後の手段なので、
場合によっては、なんらかの悪影響が出ることも予想されます。

メモリを圧迫してる場所を確認して、明示的にメモリを開放してあげる、という、
根本的解決にはなってない気がします。

もともと、androidは、メモリが少なくなったらガベージ・コレクションが勝手にメモリ開放してくれる仕様ということですので、
こちらはメモリ管理を気にしなくて良いはずです。

ただ、androidのガベージ・コレクションがメモリを開放するタイミングは、androidの気分次第です。
androidが、「メモリを開放しようかどうしようか、まだ開放しなくてもいいかな?」と油断してるときに、
アプリが急に大きなメモリを要求するとダメな感じかもしれません。
そんな場合は、「これから大きなメモリを扱うから、早いところメモリを開放しておいてね。」と、
「System.gc()」を要求するのは悪くないような気もします。






修正してみた。


試行錯誤の結果、一応、エラーの出ないような修正ができましたが、
結局「System.gc()」を使ってしまってるので、アレな感じです。
他にもっと良い解決策があるかもしれません。

後々の検証用のために、メモ的に残しておきます。



MainActivity.java

package com.example.test32; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import android.os.Bundle; import android.os.Environment; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Matrix; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.view.View.OnClickListener; public class MainActivity extends Activity { static final int MP = LayoutParams.MATCH_PARENT ; RelativeLayout R_layout1; ImageView image1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //----------------------------------------- //レイアウト R_layout1 = new RelativeLayout(this); setContentView(R_layout1); //背景画像用イメージビュー image1 = new ImageView(this); image1.setBackgroundColor(Color.argb(255, 255, 0, 0)); //背景の色(赤) image1.setScaleType(ImageView.ScaleType.CENTER); //image1.setScaleType(ImageView.ScaleType.FIT_CENTER); LayoutParams params1= new LayoutParams(MP,MP); image1.setLayoutParams(params1); R_layout1.addView(image1); //----------------------------------------- //ボタン LinearLayout L_layout1 = new LinearLayout(this); Button button1 = new Button(this); button1.setText("ICON"); L_layout1.addView(button1); Button button2 = new Button(this); button2.setText("SAVE"); L_layout1.addView(button2); Button button3 = new Button(this); button3.setText("LOAD"); L_layout1.addView(button3); Button button4 = new Button(this); button4.setText("CLR"); L_layout1.addView(button4); R_layout1.addView(L_layout1); //----------------------------------------- button1.setOnClickListener( new OnClickListener() { //アイコンをイメージビューにロード @Override public void onClick(View v) { image1.setImageResource(R.drawable.ic_launcher); } }); //----------------------------------------- button2.setOnClickListener( new OnClickListener() { //このアプリのスクリーンショット @Override public void onClick(View v) { ScreenShot2(); } }); //----------------------------------------- button3.setOnClickListener( new OnClickListener() { //ロード @Override public void onClick(View v) { LoadImage3(); } }); //----------------------------------------- button4.setOnClickListener( new OnClickListener() { //クリア @Override public void onClick(View v) { clearimage1(); } }); //----------------------------------------- } private void clearimage1(){ image1.setImageBitmap(null); image1.setImageDrawable(null); System.gc(); } private boolean ScreenShot2(){ //SDカードがあるか? if(To_Chk_SD1()==false) return false; View view = R_layout1.getRootView(); String filename = "ScreenShotFile1"; File file = Environment.getExternalStorageDirectory(); String path = file.getPath(); filename = path + "/" + filename; try { FileOutputStream out = new FileOutputStream(filename + ".jpg"); view.setDrawingCacheEnabled(true); Bitmap bmp = Bitmap.createBitmap(view.getDrawingCache()); view.setDrawingCacheEnabled(false); bmp.compress(Bitmap.CompressFormat.JPEG, 100, out); //JPEG保存 out.close(); //-------------------- FileOutputStream output = new FileOutputStream(filename + ".png"); bmp.compress(Bitmap.CompressFormat.PNG, 100, output); //PNG保存 output.close(); if(bmp!=null){ //開放 bmp.recycle(); bmp = null; } System.gc();//応急処置 return true; } catch (FileNotFoundException e) { e.printStackTrace(); return false; } catch (IOException e) { e.printStackTrace(); return false; } } public boolean To_Chk_SD1(){ //ファイルセーブ可能か? boolean mExternalStorageAvailable = false; boolean mExternalStorageWriteable = false; String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { mExternalStorageAvailable = mExternalStorageWriteable = true; } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { mExternalStorageAvailable = true; mExternalStorageWriteable = false; } else { mExternalStorageAvailable = mExternalStorageWriteable = false; } return mExternalStorageWriteable; } private void LoadImage3(){ String filename = "ScreenShotFile1"; File file = Environment.getExternalStorageDirectory(); String path = file.getPath(); filename = path + "/" + filename + ".png"; Bitmap loadbitmap1 = BitmapFactory.decodeFile(filename); //ファイルが開けない if(loadbitmap1==null) return; //開放//これがあると、下のSystem.gc();は不要かもしれない image1.setImageBitmap(null); //image1.setImageDrawable(null); Matrix mat = new Matrix(); mat.postScale(4,4); int x1 =loadbitmap1.getWidth(); int y1 =loadbitmap1.getHeight(); Bitmap loadbitmap2 = Bitmap.createBitmap(loadbitmap1,0,0,x1,y1,mat,true); //true=アンチエイリアス image1.setImageBitmap(loadbitmap2); //開放 loadbitmap1.recycle(); loadbitmap1 = null; //System.gc();//応急処置 } }

























戻る