9

Google Play ストアの他のパズル アプリでは、最大 400 個の個別の可動パズル ピースを使用できることに気付きました

私は、少なくともパズルを表す画像を取得し、特定のセクションをトリミングし、パズルピースのデザインで残された画像スペースをマスクして、個々のパズルピースを作成する方法を学ぼうとしています

アプリで最大 20 ピースを使用したいのですが、これまでの Android スタジオのメモリ ログによると、ビットマップ ファクトリが 1 つのパズル ピースの作成を完了すると、20 個のピース​​が他のすべてのピースとともに作成された後、約 18 MB のメモリを使い果たしています。アプリの機能 400 MB 以上のメモリを使用しており、メモリが不足しないように「largeHeap=true」を使用する必要がありますが、これらの上限を超えそうになっているため、アプリは非常に遅く、十分です。アニメーション化されたアクティビティは必然的にアプリをクラッシュさせます

他の Play ストア パズル アプリが何をしていて、私が行っていないのか知りたいです。

どんな入力でも大歓迎です

参考までに、画像に PNG24 を使用しており、テスト画像の寸法は 556x720 です

これは、アニメーション化可能なパズルピースの画像を1つだけ作成した場合の例です

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.requestWindowFeature(Window.FEATURE_NO_TITLE);//Remove title bar
    this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//Hides notification bar
    this.setContentView(R.layout.activity_main);//set content view AFTER ABOVE sequence (to avoid crash)

    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

    mainDisplay = getWindowManager().getDefaultDisplay();
    mainLayout = (ViewGroup) findViewById(R.id.id_layout);

    DisplayMetrics m = new DisplayMetrics();
    this.getWindowManager().getDefaultDisplay().getMetrics(m);
    int windowHeight = m.heightPixels;
    int windowWidth = m.widthPixels;

    offsetX = (windowWidth / 1440.0f);
    offsetY = (windowHeight / 2560.0f);

    ourContext = this;

    xpos = new float[2];
    ypos = new float[2];

    iv_PuzzleOne();

    bkgdbm = BitmapFactory.decodeResource(getResources(), R.drawable.puzzleimage); 

    paintObject = new Paint();
    paintObject.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR)); 

    bm_PuzzleOne();

    thisTimerTask = new ThisClass();
    thisTimer = new Timer();
    thisTimer.scheduleAtFixedRate(thisTimerTask, 16, 16);

    touchpad = new ImageButton(this);
    SetPos(0, 0, 1440, 2560);
    touchpad.setLayoutParams(layoutPositioner);
    touchpad.getBackground().setAlpha(1);
    mainLayout.addView(touchpad);

    touchpad.setOnTouchListener(new View.OnTouchListener() {

        //@SuppressLint("NewApi")
        @Override
        public boolean onTouch(View v, MotionEvent event) {

            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                xpos[0] = event.getX(); //storing my finger's position coordinates when I first touch the screen into the 1st element
                ypos[0] = event.getY();

                if ((event.getX() > imgx1) && (event.getX() < imgx1 + imagewidth1)
                        && (event.getY() > imgy1) && (event.getY() < imgy1 + imageheight1)) {
                    touching1Puzzle = true;
                    img1.bringToFront();
                }

            }

            if (event.getAction() == MotionEvent.ACTION_MOVE) {

                xpos[1] = event.getX(); //add my finger's new current coordinates into the 2nd element
                ypos[1] = event.getY();

                if (touching1Puzzle) {
                    adjustImg();
                }
            }

            if (event.getAction() == MotionEvent.ACTION_UP) {
                if (touching1Puzzle) {
                    touching1Puzzle = false;
                }

            }

            return false;
        }

    });
}


void bm_PuzzleOne()
{
    //start of 1st puzzle

    foregdimg1 = BitmapFactory.decodeResource(getResources(), R.drawable.puzzlepieceprac);//puzzle cut out (42.48MB) +6.15
    mutableforegdimg1 = foregdimg1.copy(Bitmap.Config.ARGB_8888, true); //(48.32MB) +5.84

    compositeImage1 = Bitmap.createBitmap(mutableforegdimg1);//cuts out foreground info into bkgdimage (54.43MB) +6.11

    imgCanvas1 = new Canvas(compositeImage1); //canvas references puzzle cut out image (54.43MB) +0
    imgCanvas1.drawBitmap(croppedBmp, null, new Rect(0, 0, 1500, 2000), paintObject);//places puzzle image on canvas (54.43MB) +0

    img1.setImageBitmap(compositeImage1);

}


void iv_PuzzleOne()
{
    img1 = new ImageView(ourContext);
    SetPos(imgx1, imgy1, imagewidth1, imageheight1);
    //bkgdimg.setImageResource(R.drawable.c);
    //img1.setBackgroundColor(0xffF07305); //Orange
    img1.setLayoutParams(layoutPositioner);
    mainLayout.addView(img1);

}

void adjustImg()
{
    if (touching1Puzzle)
    {
        if (xpos[1] > xpos[0]) //if the image had slid to the right
        {
            xPositionDifference = xpos[1] - xpos[0]; // find the difference in coordinate value between where my finger was and where it currently is
            imgx1 += xPositionDifference; //add that difference to the current image position ...

            xpos[0] += xPositionDifference; // ... store that difference for the next shift in finger postion

        } else if (xpos[1] < xpos[0]) //if the image had slid to the left
        {
            xPositionDifference = xpos[0] - xpos[1]; // find the difference in coordinate value between where my finger was and where it currently is
            imgx1 -= xPositionDifference; //subtract that difference to the current image position ...

            xpos[0] -= xPositionDifference; // ... store that difference for the next shift in finger postion
        }

        if (ypos[1] > ypos[0]) //if the image had slid to the right
        {
            yPositionDifference = ypos[1] - ypos[0]; // find the difference in coordinate value between where my finger was and where it currently is
            imgy1 += yPositionDifference; //add that difference to the current image position ...

            ypos[0] += yPositionDifference; // ... store that difference for the next shift in finger postion

        } else if (ypos[1] < ypos[0]) //if the image had slid to the left
        {
            yPositionDifference = ypos[0] - ypos[1]; // find the difference in coordinate value between where my finger was and where it currently is
            imgy1 -= yPositionDifference; //subtract that difference to the current image position ...

            ypos[0] -= yPositionDifference; // ... store that difference for the next shift in finger postion

        }
    }

}

class ThisClass extends TimerTask {

    @Override
    public void run() {
        MainActivity.this.runOnUiThread(new Runnable() {
            @Override
            public void run() {

                if(touching1Puzzle)
                {SetPos(imgx1, imgy1, imagewidth1, imageheight1);
                    img1.setLayoutParams(layoutPositioner);}

            }
        });
    }
}

public void SetPos(float x, float y, float width, float height) {
    layoutPositioner = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
    layoutPositioner.topMargin = (int) (offsetY * y);
    layoutPositioner.leftMargin = (int) (offsetX * x);
    layoutPositioner.width = (int) (width * offsetX);
    layoutPositioner.height = (int) (height * offsetY);
}

}

画像を5枚読み込むとこんな感じ

http://s15.postimg.org/ymqspk77v/Screenshot_2016_02_29_07_19_26.png

4

5 に答える 5

3

よし、見てみよう。

まず、パズルのピースをこれほど大きくする必要はないので、縮小することができます。

http://developer.android.com/training/displaying-bitmaps/load-bitmap.htmlから

デコーダーにイメージをサブサンプリングして、より小さいバージョンをメモリにロードするように指示するには、BitmapFactory.Options オブジェクトで inSampleSize を true に設定します。たとえば、inSampleSize 4 でデコードされた解像度 2048x1536 の画像は、約 512x384 のビットマップを生成します。これをメモリにロードすると、画像全体に 12MB ではなく 0.75MB が使用されます (ARGB_8888 のビットマップ構成を想定)。

次に、アプリケーションがフリーズするのを防ぐために、AsyncTask にビットマップをロードしてみてください。

3 番目に、LruCache を使用してビットマップをキャッシュし、速度を上げることができます。

http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

これで十分ですが、ここにない場合は、すべてのソースといくつかの追加があります: http://developer.android.com/training/displaying-bitmaps/index.html

使ってみることもできます

void bm_PuzzleOne()
{
    //start of 1st puzzle
    BitmapFactory.Options opt = new BitmapFactory.Options();

    //makes a loaded image mutable
    opt.inMutable=true;
    //reduces density to that of screen
    opt.inTargetDensity = this.context.getResources().getDisplayMetrics().densityDpi;
    opt.inScaled=true;

    //makes bitmap half as big
    opt.inSampleSize=2;

    //foregdimg1 = BitmapFactory.decodeResource(getResources(), R.drawable.puzzlepieceprac);//puzzle cut out (42.48MB) +6.15
    mutableforegdimg1 = BitmapFactory.decodeResource(getResources(), R.drawable.puzzlepieceprac,opt)

    compositeImage1 = Bitmap.createBitmap(mutableforegdimg1);//cuts out foreground info into bkgdimage (54.43MB) +6.11

    imgCanvas1 = new Canvas(compositeImage1); //canvas references puzzle cut out image (54.43MB) +0
    imgCanvas1.drawBitmap(croppedBmp, null, new Rect(0, 0, 1500, 2000), paintObject);//places puzzle image on canvas (54.43MB) +0

    img1.setImageBitmap(compositeImage1);

}

それ以外の

void bm_PuzzleOne()
{
    //start of 1st puzzle

    foregdimg1 = BitmapFactory.decodeResource(getResources(), R.drawable.puzzlepieceprac);//puzzle cut out (42.48MB) +6.15
    mutableforegdimg1 = foregdimg1.copy(Bitmap.Config.ARGB_8888, true); //(48.32MB) +5.84

    compositeImage1 = Bitmap.createBitmap(mutableforegdimg1);//cuts out foreground info into bkgdimage (54.43MB) +6.11

    imgCanvas1 = new Canvas(compositeImage1); //canvas references puzzle cut out image (54.43MB) +0
    imgCanvas1.drawBitmap(croppedBmp, null, new Rect(0, 0, 1500, 2000), paintObject);//places puzzle image on canvas (54.43MB) +0

    img1.setImageBitmap(compositeImage1);

}
于 2016-03-02T18:40:47.753 に答える
2

画像の代わりにベクトルを使用するだけで、アプリとストレージを実行するためのメモリを大幅に節約できます。非常によくサポートされています。SVGリソースを使用できます

于 2016-03-09T15:04:26.880 に答える
1

First of all I should thank @JeD for his great answer, BUT I should mention that I think the answer is different.

Lets begin from the basics...


Each android device has an specific "heap size". it begin from 16mg to 64mg and even 128mg in some devices! (im not sure about 128 mg :D)

So whenever you use a drawable in your view, it takes a specific amount of ram.

Question: How is it calculated?!

Its by the drawables dimension, not the size. For example we have two drawables with 500 KBs of size. One of them has the dimensions 500*500, the other has 700*700 The second one takes more heap although both sizes were the same! The formula is this: WIDTH*HEIGHT*4 BYTE (4 comes from ARGB, it takes four bytes for each pixel to store data)

By now, you should understand that you cant use MANY images together! If you want to use LRUCACH as @JeD mentioned, you would lose some of your images as your heap would get full.

Question : So what should we do?!

The answer is using third party engines and canvas! Game engines like COCOS2D,BOX2D,etc... are also good examples or also using NDK which makes your app HEAP FREE!!! :D

于 2016-03-07T13:57:47.547 に答える