outOfMemoryError通过切换许多活动和使用相机?

时间:2011-08-04 08:46:11

标签: android garbage-collection bitmap camera out-of-memory

我正在开发一款适用于Android的游戏。这是一个我必须经常改变活动的游戏。实际上这是一个活动循环。在谷歌搜索了一下后,我发现了这篇文章:

http://ttlnews.blogspot.com/2010/01/attacking-memory-problems-on-android.html

一行说: “但对于任何其他”普通“应用程序,你可以在使用它一段时间后运行OOM。例如,即使你只是打开/关闭同一个屏幕(活动)20次。”

我的应用每周都需要更多内存。但是没有可能避免这个问题吗?我正在安静很多Bitmaps。但我不认为这太多了。最多的玩家是8.而且everey玩家获得了2个Bitmap。在X10上,一个是120x120,另一个是180x180。如果我玩了几轮然后回到屏幕截图来开始一个完整的新游戏,那么堆大小可能已经很大了。通过开始一个新的游戏,我有可能用相机拍照,我可以用作玩家的位图。如果我到达OnPictureTaken我的应用程序将在短时间内需要7MB以上。这导致MMO有时取决于我之前玩过多少。

但是..这里有一些我用来避免OOM的代码片段。

在游戏开始之前,我必须进行一些设置。其中我必须设定球员数量。如果我达到此活动,我会尝试删除目前为止创建的播放器的所有位图(如果我想在终止另一个游戏后玩一个全新的游戏,则会存在)

    for (int i = 0; i < StbApp.getPlayer().size(); i++){
        StbApp.getPlayer().get(i).getAvaterBig().recycle();
        StbApp.getPlayer().get(i).getAvatar().recycle();
        StbApp.getPlayer().get(i).setAvatarBig(null);
        StbApp.getPlayer().get(i).setAvatar(null);
    }
    System.gc();
    StbApp.getPlayer().clear();

以下是拍摄照片的方式: 首先是参数:

Camera.Parameters parameters = camera.getParameters();
List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes();
Camera.Size previewSize = previewSizes.get(0);
params.setPreviewSize(previewSize.width, previewSize.height);
params.setPictureFormat(PixelFormat.JPEG);
params.setJpegQuality(50);
camera.setParameters(params);
camera.startPreview();

这是我的OnPictureTaken方法:

camera.takePicture(null, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                try {
                    StbApp.logHeapSizeInfo();
                    //the name of the file
                    Log.d(TAG, "GC_ test2");
                    fileName = String.format("avatar_"+System.currentTimeMillis()+".jpg");
                    Log.d(TAG, "GC_ test3");
                    //will save
                    FileOutputStream fos = openFileOutput(fileName, Context.MODE_PRIVATE);
                    Log.d(TAG, "GC_ test4");
                    fos.write(data);
                    Log.d(TAG, "GC_ test5");
                    fos.close();
                    Log.d(TAG, "GC_ test6");
                } 
                catch(Exception e) {
                  Log.e(TAG, "saveJPEGBitmapToMediaStore: failed to save image", e);
                } 
            }
        });

比我使用setReult:

if (view == takeAndUseBtn && takeAndUseBtn.getText().toString().equalsIgnoreCase(getResources().getString(R.string.use))) {
        if (fileName != null){
            Intent intent = new Intent();
            intent.putExtra("fileName", fileName);
            setResult(RESULT_OK, intent);
            this.finish();
        } else {
            Toast.makeText(this, "wait until picture is saved", Toast.LENGTH_SHORT).show();
            Log.d(TAG, "not saved yet");
        }
    }

如果用户对图片感到满意并想在游戏中使用它,就会发生这种情况:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.d(TAG, "requestCode: " +requestCode);
    if (resultCode == RESULT_OK){
        String file = data.getExtras().getString("fileName");
        FileInputStream fis = null;
        try {
            fis = openFileInput(file);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "File not found :(");
            e.printStackTrace();
        }
        if (fis != null){
            StbApp.logHeapSizeInfo();
            Options opt = new Options();
            opt.inSampleSize = 5;
            opt.inPurgeable = true;
            Bitmap bm = BitmapFactory.decodeStream(fis, null, opt);

            bm = Bitmap.createScaledBitmap(bm, getResources().getDrawable(R.drawable.avatar1_big).getMinimumWidth(), getResources().getDrawable(R.drawable.avatar1_big).getMinimumHeight(), false);
            StbApp.getPlayer().get(requestCode).setAvatarBig(bm);

            bm = Bitmap.createScaledBitmap(bm, getResources().getDrawable(R.drawable.avatar1).getMinimumWidth(), getResources().getDrawable(R.drawable.avatar1).getMinimumHeight(), false);
            StbApp.getPlayer().get(requestCode).setAvatar(bm);

            bm.setDensity(DisplayMetrics.DENSITY_MEDIUM);
            Drawable drawable = new BitmapDrawable(bm);
            avatarBtn[requestCode].setBackgroundDrawable(drawable);

            bm.setDensity(getResources().getDisplayMetrics().densityDpi);
            deleteFile(file);
            try {
                fis.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            StbApp.logHeapSizeInfo();
        }
    }
    super.onActivityResult(requestCode, resultCode, data);
}

每次我开始新一轮时,我都会显示某项活动。在开始活动之前,我让它用FLAG_CLEAR_TOP启动titlescreen我真的要开始确保没有任何不必要的活动在运行:

    if (view == startBtn){
        StbApp.setRound(StbApp.getRound()+1);
        Intent intent = new Intent(this, SpinTheBottle.class);
        Intent intentToClear = new Intent(this, TitleScreen.class);
        intentToClear.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + Intent.FLAG_ACTIVITY_SINGLE_TOP);
        startActivity(intentToClear);
        startActivity(intent);
    }

现在这里有一些来自GC的日志: 游戏刚刚开始,我还没有拍照:

08-04 08:34:44.355: DEBUG/dalvikvm(317): GC_CONCURRENT freed 1194K, 53% free 3377K/7175K, external 2615K/2699K, paused 1ms+11ms

我拍照后(在调用OnPictureTaken之前)(如果用户玩了一段时间并且想要开始一个全新的游戏,应用程序就崩溃了。

08-04 08:36:21.875: DEBUG/dalvikvm(4216): GC_FOR_MALLOC freed 1473K, 60% free 2813K/6983K, external 10938K/12508K, paused 17ms

从相机活动返回到previos活动后使用picutre

08-04 08:37:28.365: DEBUG/dalvikvm(317): GC_EXTERNAL_ALLOC freed 221K, 59% free 2992K/7175K, external 2875K/2877K, paused 100ms

第一轮开始:

08-04 08:38:54.005: DEBUG/dalvikvm(4216): GC_EXTERNAL_ALLOC freed 608K, 56% free 3742K/8391K, external 14229K/15604K, paused 28ms

经过几轮(它继续增加直到它崩溃):

08-04 08:40:07.845: DEBUG/dalvikvm(4216): GC_CONCURRENT freed 1486K, 49% free 4945K/9671K, external 16187K/17938K, paused 2ms+7ms

有时候如果这一轮开始我会得到类似的东西(我用canvas.drawBitmap

在画布上绘制球员位图的活动
08-04 08:40:07.425: DEBUG/dalvikvm(317): GC_CONCURRENT freed 1312K, 54% free 3379K/7303K, external 2139K/2671K, paused 2ms+14ms

但是在这一行之后会有一条像我之前发布过的那条线。它最后不断增加。

那就是如果用户想要开始新游戏并且即将选择这次将加入游戏的玩家数量会发生什么。 (这就是我尝试重新编写位图并手动使用GC的地方)

08-04 08:43:46.635: DEBUG/dalvikvm(4216): GC_EXPLICIT freed 69K, 70% free 3402K/11079K, external 18337K/20004K, paused 31ms

正如你所看到的......发生的事情并不多。

我知道这几乎是代码。但我只是不知道在哪里可以尝试改进代码以获得更多内存。如果有人知道一本关于android内存管理的好书我会很感激他/她是否可以推荐我。

2 个答案:

答案 0 :(得分:2)

我在使用经常切换活动的应用时遇到了同样的问题。我通过杀死进程并在每次迭代中再次启动来解决它。

    PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, tartActivity.class), 0);
    AlarmManager mgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
    mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 50, intent);
    android.os.Process.killProcess(android.os.Process.myPid());

我知道这是一个黑客攻击。但我还没有找到另一种解决方案。

顺便说一下,Bitmap对象在本机堆上,但在托管堆中却没有。 GC没有帮助。 看这里http://maximbogatov.wordpress.com/2011/08/03/bitmaps-in-android/

在我的应用程序中,托管堆的一切正常,但本机堆正在增长。我回收了我创建的所有位图。我相当确定。但无论如何,原生堆逐渐增长。

答案 1 :(得分:0)

最后我发现我在几个layout.xml中设置的小部件中的大图像(例如android:background:“@ drawable / resourceId”)已经分配了大部分堆大小。我给小部件提供了一个大图像ID,所以如果我不需要它我可以删除它。如果用户返回到屏幕,我必须再次设置图像。

boyImg = (ImageView) findViewById(R.id.imgv_boy);
if (boyImg.getDrawable() != null){
boyImg.getDrawable().setCallback(null);
boyImg.setImageDrawable(null);
}
System.gc();