[android] 비트 맵 객체에 이미지를로드하는 동안 메모리 부족 문제
각 행에 몇 개의 이미지 버튼이있는 목록보기가 있습니다. 목록 행을 클릭하면 새 활동이 시작됩니다. 카메라 레이아웃에 문제가있어 탭을 직접 만들어야했습니다. 결과에 대해 시작된 활동은 맵입니다. 내 버튼을 클릭하여 이미지 미리보기를 시작하면 (이미지를 SD 카드에로드) 애플리케이션이 액티비티에서 액티비티로 다시 listview
결과 핸들러로 돌아와 이미지 위젯에 지나지 않는 새로운 액티비티를 다시 시작합니다.
커서로 및을 사용하여 목록보기의 이미지 미리보기를 수행합니다 ListAdapter
. 이것은 매우 간단하지만 크기가 조정 된 이미지를 넣을 수있는 방법을 잘 모르겠습니다 (즉 src
, 이미지 버튼 의 경우 픽셀이 아닌 작은 비트 크기) 즉 , 휴대 전화 카메라에서 나온 이미지의 크기를 조정했습니다.
문제는 돌아가서 두 번째 활동을 다시 시작하려고 할 때 메모리 부족 오류가 발생한다는 것입니다.
- 내가 쉽게 목록 어댑터를 구축 나는 비행 (에 크기를 조정할 수 행에 의해 행 할 수있는 방법이 있습니까 비트 현명한 )?
포커스 문제로 인해 터치 스크린으로 행을 선택할 수 없으므로 각 행의 위젯 / 요소 속성을 일부 변경해야하기 때문에 바람직합니다. ( 롤러 볼을 사용할 수 있습니다. )
- 대역 외 크기를 조정하고 이미지를 저장할 수 있다는 것을 알고 있지만 실제로 원하는 것은 아니지만 샘플 코드가 좋을 것입니다.
목록보기에서 이미지를 비활성화하자마자 다시 정상적으로 작동했습니다.
참고 : 이것이 내가 한 일입니다 :
String[] from = new String[] { DBHelper.KEY_BUSINESSNAME,DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY,DBHelper.KEY_GPSLONG,DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME + ""};
int[] to = new int[] {R.id.businessname,R.id.address,R.id.city,R.id.gpslong,R.id.gpslat,R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);
어디 R.id.imagefilename
입니다 ButtonImage
.
내 LogCat은 다음과 같습니다.
01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed
이미지를 표시 할 때 새로운 오류가 있습니다.
01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri:
01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
답변
비트 맵을 효율적으로 표시 하는 Android Training 클래스 는 비트 맵을로드 할 때 예외를 이해하고 처리하는 데 유용한 정보를 제공합니다 .java.lang.OutOfMemoryError: bitmap size exceeds VM budget
비트 맵 치수 및 유형 읽기
BitmapFactory
클래스 (여러 복호화 방법을 제공한다 decodeByteArray()
, decodeFile()
, decodeResource()
의 작성 등) Bitmap
다양한 소스. 이미지 데이터 소스에 따라 가장 적합한 디코딩 방법을 선택하십시오. 이러한 메소드는 구성된 비트 맵에 메모리를 할당하려고 시도하므로 쉽게 OutOfMemory
예외가 발생할 수 있습니다 . 각 유형의 디코딩 방법에는 BitmapFactory.Options
클래스 를 통해 디코딩 옵션을 지정할 수있는 추가 서명이 있습니다 . 설정 inJustDecodeBounds
에 속성 true
복귀 피할 메모리 할당을 디코딩 할 때 null
비트 맵 오브젝트에 대해 설정되지만 outWidth
, outHeight
및 outMimeType
. 이 기술을 사용하면 비트 맵을 구성 (및 메모리 할당)하기 전에 이미지 데이터의 크기와 유형을 읽을 수 있습니다.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
java.lang.OutOfMemory
예외 를 피하려면 사용 가능한 메모리에 편안하게 맞는 예측 가능한 크기의 이미지 데이터를 제공하도록 소스를 절대 신뢰하지 않는 한 비트 맵을 디코딩하기 전에 비트 맵의 크기를 확인하십시오.
축소 된 버전을 메모리에로드
이제 이미지 크기를 알았으므로 전체 이미지를 메모리에로드해야하는지 또는 서브 샘플링 된 버전을로드해야하는지 결정하는 데 사용할 수 있습니다. 고려해야 할 몇 가지 요소는 다음과 같습니다.
- 메모리에 전체 이미지를로드하는 예상 메모리 사용량입니다.
- 응용 프로그램의 다른 메모리 요구 사항이 주어지면이 이미지를로드하려고 할 메모리 양입니다.
- 이미지를로드 할 대상 ImageView 또는 UI 구성 요소의 크기입니다.
- 현재 장치의 화면 크기 및 밀도
예를 들어 1024×768 픽셀 이미지를 메모리에 128×96 픽셀 썸네일로 표시 할 경우 메모리에로드 할 가치가 없습니다 ImageView
.
메모리 설정에 작은 버전을로드 이미지를 표본을 디코더에게 inSampleSize
로 true
당신의 BitmapFactory.Options
객체입니다. 예를 들어, 해상도가 2048×1536 인 이미지 inSampleSize
는 4로 디코딩 된 경우 약 512×384의 비트 맵이 생성됩니다. 이것을 메모리에로드 할 때는 전체 이미지에 12MB가 아닌 0.75MB가 사용됩니다 (비트 맵 구성이 가정 됨 ARGB_8888
). 목표 너비와 높이를 기준으로 2의 거듭 제곱 인 표본 크기 값을 계산하는 방법은 다음과 같습니다.
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;
}
참고 : 두 값의 거듭 제곱은 디코더가
inSampleSize
설명서 에 따라 가장 가까운 2의 거듭 제곱으로 반올림하여 최종 값을 사용하므로 계산
됩니다.
이 방법을 사용하려면 먼저 inJustDecodeBounds
set to true
로 디코딩하고 옵션을 통과 한 다음 새 inSampleSize
값을 사용하여 다시 디코딩하고 다음으로 inJustDecodeBounds
설정하십시오 false
.
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
이 방법을 사용 ImageView
하면 다음 예제 코드와 같이 임의로 큰 크기의 비트 맵을 100×100 픽셀 축소판을 표시하는 비트 맵에 쉽게로드 할 수 있습니다.
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
유사한 프로세스를 BitmapFactory.decode*
따라 필요에 따라 적절한 방법을 대체하여 다른 소스의 비트 맵을 디코딩 할 수 있습니다 .
답변
OutOfMemory 오류를 해결하려면 다음과 같이해야합니다.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);
이 inSampleSize
옵션은 메모리 소비를 줄입니다.
다음은 완전한 방법입니다. 먼저 내용 자체를 디코딩하지 않고 이미지 크기를 읽습니다. 그런 다음 가장 좋은 inSampleSize
값을 찾고 2의 거듭 제곱이어야하며 마지막으로 이미지가 디코딩됩니다.
// Decodes image and scales it to reduce memory consumption
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);
// The new size we want to scale to
final int REQUIRED_SIZE=70;
// Find the correct scale value. It should be the power of 2.
int scale = 1;
while(o.outWidth / scale / 2 >= REQUIRED_SIZE &&
o.outHeight / scale / 2 >= REQUIRED_SIZE) {
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;
}
답변
Fedor의 코드를 약간 개선했습니다. 기본적으로는 동일하지만 (제 생각에는) 추악한 while 루프가 없으면 항상 2의 거듭 제곱이됩니다. 원래 솔루션을 만들기 위해 Fedor에게 Kudos를 찾았습니다.
private Bitmap decodeFile(File f){
Bitmap b = null;
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
int scale = 1;
if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /
(double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
}
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
return b;
}
답변
나는 iOS 경험에서 왔으며 이미지로드 및 표시와 같은 기본적인 문제를 발견하는 데 좌절했습니다. 결국,이 문제가있는 모든 사람들은 합리적인 크기의 이미지를 표시하려고합니다. 어쨌든, 내 문제를 해결하고 내 앱을 매우 반응 적으로 만든 두 가지 변경 사항이 있습니다.
1) 할 때마다 로 설정 BitmapFactory.decodeXYZ()
하고 BitmapFactory.Options
로 inPurgeable
설정하십시오 true
(및 바람직하게 inInputShareable
는로 설정 true
).
2) 절대 사용하지 마십시오 Bitmap.createBitmap(width, height, Config.ARGB_8888)
. 나는 절대 의미하지 않는다! 몇 번의 통과 후에도 메모리 오류가 발생하지 않는 것은 없었습니다. 아무리 recycle()
, System.gc()
무엇이든은 도움이되지 않습니다. 항상 예외가 발생했습니다. 실제로 작동하는 또 다른 방법은 드로어 블에 더미 이미지 (또는 위의 1 단계를 사용하여 디코딩 한 다른 비트 맵)를 가지고 원하는 크기로 조정 한 다음 결과 비트 맵을 조작하는 것입니다 (예 : 캔버스에 전달하는 것) 더 재미있게). 따라서 대신 사용해야하는 것은 다음과 같습니다 Bitmap.createScaledBitmap(srcBitmap, width, height, false)
. 어떤 이유로 든 무차별 강제 작성 방법을 사용해야하는 경우 최소한을 전달하십시오 Config.ARGB_4444
.
며칠이 아니라면 시간을 절약 할 수 있습니다. 이미지 크기 조정 등에 대한 모든 이야기는 실제로 작동하지 않습니다 (잘못된 크기를 얻거나 이미지의 해상도가 떨어지는 것을 고려하지 않는 한).
답변
그것은이다 알려진 버그 는하지 때문에 대용량 파일의입니다. Android는 Drawables를 캐시하므로 이미지를 거의 사용하지 않으면 메모리가 부족합니다. 그러나 안드로이드 기본 캐시 시스템을 건너 뛰어 대체 방법을 찾았습니다.
해결책 : 이미지를 “자산”폴더로 이동하고 다음 기능을 사용하여 BitmapDrawable을 가져 오십시오.
public static Drawable getAssetImage(Context context, String filename) throws IOException {
AssetManager assets = context.getResources().getAssets();
InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
Bitmap bitmap = BitmapFactory.decodeStream(buffer);
return new BitmapDrawable(context.getResources(), bitmap);
}
답변
나는이 같은 문제가 있었고 BitmapFactory.decodeStream 또는 decodeFile 함수를 피하고 대신 사용하여 해결했습니다. BitmapFactory.decodeFileDescriptor
decodeFileDescriptor
decodeStream / decodeFile과 다른 기본 메소드를 호출하는 것처럼 보입니다.
어쨌든, 효과가 있었던 것은 이것입니다 (위의 일부 옵션을 추가했지만 차이점은 아닙니다. 중요한 것은 decodeStream 또는 decodeFile 대신 BitmapFactory.decodeFileDescriptor에 대한 호출입니다 ).
private void showImage(String path) {
Log.i("showImage","loading:"+path);
BitmapFactory.Options bfOptions=new BitmapFactory.Options();
bfOptions.inDither=false; //Disable Dithering mode
bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared
bfOptions.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
bfOptions.inTempStorage=new byte[32 * 1024];
File file=new File(path);
FileInputStream fs=null;
try {
fs = new FileInputStream(file);
} catch (FileNotFoundException e) {
//TODO do something intelligent
e.printStackTrace();
}
try {
if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
} catch (IOException e) {
//TODO do something intelligent
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
im.setImageBitmap(bm);
//bm.recycle();
bm=null;
}
decodeStream / decodeFile에 사용 된 기본 함수에 문제가 있다고 생각합니다. decodeFileDescriptor를 사용할 때 다른 기본 메소드가 호출되는 것을 확인했습니다. 또한 내가 읽은 것은 “이미지 (비트 맵)는 표준 Java 방식으로 할당되지 않고 네이티브 호출을 통해 할당됩니다. 할당은 가상 힙 외부에서 이루어 지지만 이에 대해
계산됩니다! “
답변
나는 피하는 가장 좋은 방법 OutOfMemoryError
은 그것을 직면하고 이해하는 것입니다.
의도적으로을 발생 시키고 메모리 사용을 모니터링 하는 앱 을 만들었습니다 OutOfMemoryError
.
이 앱으로 많은 실험을 한 후 다음과 같은 결론을 얻었습니다.
허니 콤 전에 SDK 버전에 대해 이야기하겠습니다.
-
비트 맵은 기본 힙에 저장되지만 recycle ()을 불필요하게 호출하면 가비지가 자동으로 수집됩니다.
-
{VM 힙 크기} + {할당 된 기본 힙 메모리}> = {장치의 VM 힙 크기 제한}이고 비트 맵을 작성하려고하면 OOM이 발생합니다.
주의 사항 : VM 할당 메모리가 아니라 VM 힙 크기가 계산됩니다.
-
할당 된 VM 메모리가 축소 되더라도 VM 힙 크기는 커진 후에 축소되지 않습니다.
-
따라서 비트 맵에 사용 가능한 메모리를 절약하기 위해 VM 힙 크기가 너무 커지지 않도록 피크 VM 메모리를 최대한 낮게 유지해야합니다.
-
System.gc ()를 수동으로 호출하는 것은 의미가 없습니다. 힙 크기를 늘리기 전에 시스템에서 먼저 호출합니다.
-
기본 힙 크기도 축소되지 않지만 OOM에는 포함되지 않으므로 걱정할 필요가 없습니다.
그런 다음 Honey Comb의 SDK 시작에 대해 이야기하겠습니다.
-
비트 맵은 VM 힙에 저장되며 기본 메모리는 OOM에 포함되지 않습니다.
-
OOM의 조건은 {VM 힙 크기}> = {장치의 VM 힙 크기 제한}보다 훨씬 간단합니다.
-
따라서 동일한 힙 크기 제한으로 비트 맵을 만들 수있는 사용 가능한 메모리가 더 많으므로 OOM 발생 가능성이 줄어 듭니다.
가비지 수집 및 메모리 누수에 대한 관찰 내용은 다음과 같습니다.
앱에서 직접 볼 수 있습니다. 활동이 활동이 종료 된 후에도 여전히 실행중인 AsyncTask를 실행 한 경우 AsyncTask가 완료 될 때까지 활동이 가비지 수집되지 않습니다.
AsyncTask는 익명의 내부 클래스의 인스턴스이므로 Activity에 대한 참조를 보유합니다.
백그라운드 스레드의 IO 작업에서 작업이 차단 된 경우 AsyncTask.cancel (true)을 호출하면 실행이 중지되지 않습니다.
콜백은 익명의 내부 클래스이기도하므로 프로젝트의 정적 인스턴스가 보유하고 해제하지 않으면 메모리가 누출됩니다.
타이머와 같이 반복되거나 지연된 작업을 예약하고 onPause ()에서 cancel () 및 purge ()를 호출하지 않으면 메모리가 누출됩니다.