Thursday, April 2, 2015

How to put sprite on top in AndEngine

Intruduction

    There are no easy ways to put sprite on top of the others in AndEngine. You ether have to manipulate z index or detach/attach sprites to scene to gain what you need. Both methods are performance expensive and ugly.

Solution

    We need a method in Entity which will look something like:
  
public void setOnTop(boolean pOnTop);
The method input is boolean which controls if our sprite is on top.

First of all we need to add the code below to IEntity.java(yes, we shall modify some part of the engine):

public boolean isOnTop();
public void setOnTop(final boolean pOnTop);

Then add implementation of these methods to BaseMenuDecorator.java
@Override
public boolean isOnTop() {
    return false;
}

@Override
public void setOnTop(boolean pOnTop) {

}
Nothing interesting is in here, just a stub.

Next let's come to main part in Entity.java. Override methods you've just created in IEntity.java with something like this:

    @Override
    public boolean isOnTop() {
        return mIsOnTop;
    }

    @Override
    public void setOnTop(boolean pOnTop) {
        mIsOnTop = pOnTop;
    }
mIsOnTop is a variable that holds if an Entity is on top.
Next go to method

void onManagedDraw(final GLState pGLState, final Camera pCamera);
This method is called every time IEntity is being drawn to, well, draw it:). We should check if entityes in draw loops are on top and if so draw them last. That's it. I'm putting here the whole modifyed method so you can compare it with original:

 protected void onManagedDraw(final GLState pGLState, final Camera pCamera) {
  pGLState.pushModelViewGLMatrix();
  {
   this.onApplyTransformations(pGLState);

   final SmartList<IEntity> children = this.mChildren;
   if ((children == null) || !this.mChildrenVisible) {
    /* Draw only self. */
    this.preDraw(pGLState, pCamera);
    this.draw(pGLState, pCamera);
    this.postDraw(pGLState, pCamera);
   } else {
    if (this.mChildrenSortPending) {
     ZIndexSorter.getInstance().sort(this.mChildren);
     this.mChildrenSortPending = false;
    }

    final int childCount = children.size();
    int i = 0;

                List<IEntity> topList = new LinkedList<>();
    { /* Draw children behind this Entity. */
     for (; i < childCount; i++) {
      final IEntity child = children.get(i);
      if (child.getZIndex() < 0) {
                            if (!child.isOnTop()) {
                                child.onDraw(pGLState, pCamera);
                            } else {
                                topList.add(child);
                            }
      } else {
       break;
      }
     }
    }

    /* Draw self. */
    this.preDraw(pGLState, pCamera);
    this.draw(pGLState, pCamera);
    this.postDraw(pGLState, pCamera);

    { /* Draw children in front of this Entity. */
     for (; i < childCount; i++) {
                        final IEntity child = children.get(i);
                        if (!child.isOnTop()) {
                            children.get(i).onDraw(pGLState, pCamera);
                        } else {
                            topList.add(child);
                        }
     }
    }

                { /* Draw top Entityes */
                    for (IEntity child : topList) {
                        child.onDraw(pGLState, pCamera);
                    }
                }
   }
  }
  pGLState.popModelViewGLMatrix();
 }
Nothing special as you can see. Now if you want sprite to became on top just call setOnTop(true).
Thanks for reading and have a good day.

You can find edited files here.

Friday, March 27, 2015

Using shaders in AndEngine


Introduction

    I'd spent quite a lot time trying to  figure out how to use custom shaders in AndEngine. There are a couple of tutorials on the Internet but all of them are incomplete or have some strange solutions like engine onDraw overloading or something like this, so i decided to write this tutorial to collect useful info in one place.

First steps

Luckily  AndEngine has logical and straight architecture and we don't need to walk through the forest of code. It already has ShaderProgram class that can be used as a basis for our shader. In this example i will show how to write simple blink shader using which you can mark sprites in your game for example. So let me show you the whole BlinkShader class and then explain how it works:
package com.syouth.heli.main.shaders;

import android.opengl.GLES20;

import org.andengine.opengl.shader.ShaderProgram;
import org.andengine.opengl.shader.constants.ShaderProgramConstants;
import org.andengine.opengl.shader.exception.ShaderProgramException;
import org.andengine.opengl.shader.exception.ShaderProgramLinkException;
import org.andengine.opengl.util.GLState;
import org.andengine.opengl.vbo.attribute.VertexBufferObjectAttributes;

/**
 * Created by syouth on 25.03.15.
 */
public class BlinkShader extends ShaderProgram {
    public static final String UNIFORM_INTENCITY = "intencity";
    public static final double START_ANGLE = 60.0;

    private static BlinkShader INSTANCE = null;
    private static final float BLINKING_SPEED = 0.09f;
    private static final float MAX_ANGLE = 90;
    private static final float MIN_ANGLE = 0;
    private static final float MIN_INTENCITY = 0.5f;
    private static final float MAX_INTENCITY = 1.5f;
    private static final float mDiv = (float) ((MAX_INTENCITY - MIN_INTENCITY) /
            (Math.cos(Math.toRadians(MIN_ANGLE)) - Math.cos(Math.toRadians(MAX_ANGLE))));
    private static int mModelViewProjectionMatrixLocation = ShaderProgramConstants.LOCATION_INVALID;
    private static int mTexture0Location = ShaderProgramConstants.LOCATION_INVALID;
    private static int mIntencityLocation = ShaderProgramConstants.LOCATION_INVALID;

    private static boolean mBlink = false;
    private static double mCurAngle = 0.0f;
    private static long mPrevTime = 0;
    private static boolean mGrow = true;

    private static final String VERTEXT_SHADER =
            "uniform mat4 " + ShaderProgramConstants.UNIFORM_MODELVIEWPROJECTIONMATRIX + ";\n" +
                    "attribute vec4 " + ShaderProgramConstants.ATTRIBUTE_POSITION + ";\n" +
                    "attribute vec2 " + ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES + ";\n" +
                    "attribute vec4 " + ShaderProgramConstants.ATTRIBUTE_COLOR + ";\n" +
                    "varying vec2 " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ";\n" +
                    "varying vec4 " + ShaderProgramConstants.VARYING_COLOR + ";\n" +
                    "void main() {\n" +
                    "   " + ShaderProgramConstants.VARYING_COLOR +
                    " = " + ShaderProgramConstants.ATTRIBUTE_COLOR + ";\n" +
                    "   " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + " = " +
                    ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES + ";\n" +
                    "   gl_Position = " + ShaderProgramConstants.UNIFORM_MODELVIEWPROJECTIONMATRIX +
                    "*" + ShaderProgramConstants.ATTRIBUTE_POSITION + ";\n" +
                    "}";

    private static final String FRAGMENT_SHADER =
            "precision lowp float;\n" +
                    "uniform float " + UNIFORM_INTENCITY + ";\n" +
                    "uniform sampler2D " + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ";\n" +
                    "varying mediump vec2 " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ";\n" +
                    "varying lowp vec4 " + ShaderProgramConstants.VARYING_COLOR + ";\n" +
                    "void main() {\n" +
                    "   vec4 inten = vec4(" + UNIFORM_INTENCITY + ", " +
                    UNIFORM_INTENCITY + ", " +
                    UNIFORM_INTENCITY + ", 1.0);\n" +
                    "   gl_FragColor = inten * " + ShaderProgramConstants.VARYING_COLOR + " * " +
                    "texture2D(" + ShaderProgramConstants.UNIFORM_TEXTURE_0 + ", " + ShaderProgramConstants.VARYING_TEXTURECOORDINATES + ");\n" +
                    "}";
    private BlinkShader() {
        super(VERTEXT_SHADER, FRAGMENT_SHADER);
    }

    public static BlinkShader getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new BlinkShader();
        }

        return INSTANCE;
    }

    @Override
    protected void link(GLState pGLState) throws ShaderProgramLinkException {
        GLES20.glBindAttribLocation(mProgramID,
                ShaderProgramConstants.ATTRIBUTE_POSITION_LOCATION,
                ShaderProgramConstants.ATTRIBUTE_POSITION);
        GLES20.glBindAttribLocation(mProgramID,
                ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES_LOCATION,
                ShaderProgramConstants.ATTRIBUTE_TEXTURECOORDINATES);
        GLES20.glBindAttribLocation(mProgramID,
                ShaderProgramConstants.ATTRIBUTE_COLOR_LOCATION,
                ShaderProgramConstants.ATTRIBUTE_COLOR);
        super.link(pGLState);
        mModelViewProjectionMatrixLocation =
                getUniformLocation(ShaderProgramConstants.UNIFORM_MODELVIEWPROJECTIONMATRIX);
        mTexture0Location =
                getUniformLocation(ShaderProgramConstants.UNIFORM_TEXTURE_0);
        mIntencityLocation =
                getUniformLocation(UNIFORM_INTENCITY);
    }

    @Override
    public void bind(GLState pGLState, VertexBufferObjectAttributes pVertexBufferObjectAttributes) throws ShaderProgramException {
        super.bind(pGLState, pVertexBufferObjectAttributes);
        GLES20.glUniformMatrix4fv(
                mModelViewProjectionMatrixLocation,
                1,
                false,
                pGLState.getModelViewProjectionGLMatrix(),
                0
        );
        GLES20.glUniform1i(mTexture0Location, 0);
        if (mBlink) {
            long curTime = System.currentTimeMillis();
            long timeDelta = curTime - mPrevTime;
            mPrevTime = curTime;
            float angleDelta = timeDelta * BLINKING_SPEED;
            if (mGrow) {
                mCurAngle += angleDelta;
            } else {
                mCurAngle -= angleDelta;
            }
            if (mCurAngle > MAX_ANGLE) {
                mCurAngle -= 2 * angleDelta;
                mGrow = false;
            } else if (mCurAngle < 0) {
                mCurAngle += 2 * angleDelta;
                mGrow = true;
            }
        }
        GLES20.glUniform1f(mIntencityLocation, map(mCurAngle));
    }

    private static float map(double angle) {
        return (float) (MIN_INTENCITY + mDiv * Math.cos(Math.toRadians(angle)));
    }

    public static void setmBlink(boolean mBlink) {
        mPrevTime = System.currentTimeMillis();
        mGrow = false;
        mCurAngle = START_ANGLE;
        BlinkShader.mBlink = mBlink;
    }
}


It's a bit messy so sorry for this.
The shader itself is very easy. It just changes an intensity of the sprite according to passed variable.
ShaderProgramConstants.ATTRIBUTE_POSITION, ATTRIBUTE_TEXTURECOORDINATES, ATTRIBUTE_COLOR
are used for simplicity. They represent position, texture coordinates and color for triangle respectively. The main parts are link and bind methonds.
Link is called before the shader is linked and you have to bind your attributes here before the shader is linked.
GLES20.glBindAttribLocation()
is used for this.
ShaderProgramConstants.ATTRIBUTE_POSITION_LOCATION
is a constant that represents the location AndEngine uses for position and so on. After the linkage is completed in super.link we should get location of our unifor variables.
getUniformLocation()
is used for this. The nex inportant method is
public void bind(GLState pGLState, VertexBufferObjectAttributes pVertexBufferObjectAttributes) throws ShaderProgramExceptionpublic

It's called every time sprite is being drawn, so here we pass variables to shader.
GLES20.glUniform1i(mTexture0Location, 0)
This line means that 0's texture unit will be used for sampler.

How to attach shader to sprite

Attaching shader to sprite is really simple. You just have to write 
o.setShaderProgram(BlinkShader.getInstance());
BlinkShader.getInstance().setmBlink(true);
This will attach shader to sprite and start blinking. Also you have to add your shader to shader manager to correctly manage onResume and onPause events

mActivity.getShaderProgramManager().loadShaderProgram(BlinkShader.getInstance());

The end

Thats it. As you can see its very easy to use shaders in AndEngine if you know how to do this. Later i will show how to use multitexturing and maybe so other methods.

Wednesday, March 18, 2015

Inversion Of Control, Dependency injection and Java


What are Inversion of Control and Dependency Injection

    Inversion of control is a technique that allows to ease coupling in your programs. Dependency injection is a type of IoC that allows to get needed dependencies. 

Example

    Lets for example assume that you have the ICar and IEngine interfaces that represent abstract car and abstract engine. A car can start the engine and IEngine interface has ignite method which makes ignition. Than lets imagine that you are asked to implement that interfaces for really fast car with really powerful engine.
public interface IEngine {
    void ignite();
}

public interface ICar {
    void engineStart();
}

public class FastCar implements ICar {

    private IEngine mEngine;

    public FastCar(final IEngine engine) {
        mEngine = engine;
    }
    @Override
    public void engineStart() {
        mEngine.ignite();
    }
}

public class PowerfulEngine implements IEngine {
    @Override
    public void ignite() {
        System.out.println("Ignition");
    }
}
And to construct your car you shall do:
FastCar car = new FastCar(new PowerfulEngine());
Pretty easy. Yes. But what if you have a complex program where you have to pass your object through the hierarchy of classes. Not so pleasant. Or maybe you want to use the same engine in different cars. You will have to store and pass the same engine to every car.
That is what Inversion of Control is used.   
public class FastCar implements ICar {

    public FastCar() {
    }
    @Override
    public void engineStart() {
        try {
            IoCContainer.resolve(IEngine.class).ignite();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Much easier.

End

   Now a couple words about how to use IoCContainer that is attached to this article.
First of all it's basic realization and you can improve it a lot. For example add registration of created object. To use current realization mark constructor of your class with @ResolveAnnotation annotation to mark constructorthat must be used for object creation like this: 
public class TestClass {

    @ResolveAnnotation
    public TestClass() {}
}
Then register it and resolve when you need it:

IoCContainer.register(TestClass.class);
IoCContainer.resolve(TestClass.class);
To bind class on interface use:

IoCContatiner.register(IInterface.class, IImplementation.class);
IoCContatiner.resolve(IInterface.class);
Link to github with code for IoCContatiner

Thursday, January 29, 2015

Why algorithms matter. Quad tree example

Introduction

    Plot above shows differences between brute force collision detection algorithm and quad tree based collision detection algorithm. x axis shows number of circles in scene and y axis shows time in seconds which spent detecting collision between one circle and others.

Explanation

    Even big-O notation is a base of software engineering many programmers don't care about algorithms they use. In earlier stages of development you may not see difference between O(N) and O(logN) algorithms but later it may become a really huge problem. As you can see on the plotting above performance becomes poor if you have 2 millions points if you use brute force and quad tree shows good performance even if you have 5 million points.

Quad tree

    So basically quad tree is data structure which node has exactly four children and each child has four children and so on recursively. It's often used in 2D game development for collision detection, image processing and so on.

How it works

    Let me explain you how it works using collision detection in 2D game as an example. 
Imagine you have a game surface which holds a lot of moving objects which collide with each other. As example it may be some shooter game with upside down view with moving player and targets which hit boxes are represented as circles he must hit. In addition surface has some entities such as bushes, trees and so on. And player shoots. You have to detect the collision of a bullet and enemies, besides bullet may hit a tree and destroyed or change it's direction and so on. You may check bullet's collision with every object in game using something like this:


    public static HashSet&ltPair&ltShape, Shape&gt&gt getCollisions(List&ltshape&gt list, Shape s) {
        HashSet&ltPair&ltShape, Shape&gt> pairs = new HashSet&lt&gt();
        for (Shape c : list) {
            if (s.equals(c)) {
                continue;
            }
            Vec2d v1 = new Vec2d(c.getBounds2D().getCenterX(), c.getBounds2D().getCenterY());
            Vec2d v2 = new Vec2d(s.getBounds2D().getCenterX(), s.getBounds2D().getCenterY());
            double distance = v1.distance(v2);
            if (distance <= c.getBounds2D().getHeight() / 2 + s.getBounds2D().getHeight() / 2) {
                pairs.add(new Pair<>(s, c));
            }
        }

        return pairs;
    }
This code uses simple idea that if distance between two circles is lower than sum of their radiuses they collide. As you can see this code is't too fast by itself. It uses a lot of division, square root to get distance and so on but it's going to be ok to use it by passing all objects to the list argument if you have about 100 objects on the screen or something about this. But what if you have 10000 objects? You will have to do 100000000 operations every frame. Looks like it's not going to work. What do you have to do in this case? Well, forget about it and drink a lot of vodka thinking about your miserableness. Oh, no sorry thats an article about quad tree. Use quad tree!
    The main idea is that you don't have to check everything that cant collide at all. It's like when for example you have two boxes with eggs. Eggs in different boxes can't collide with each other. So we have to divide our game scene into quads and check only those objects that lies in the same quad as our bullet! That's it.

As you can see on the picture above pink circle in the middle which is out bullet is collided with cyan circle. Red rectangles represent quad tree.

Demo



Implementation

Insertion

    private static void insertShape(Shape shape, QuadTreeNode root) {
        if (!root.mBoundingRect.contains(shape.getBounds2D()) && !shape.intersects(root.mBoundingRect)) {
            // Cant add because shape lies completely outside of the rectangle
            return;
        }

        if (root.mChildren == null) {
            if (root.mObjects == null || root.mObjects.size() < root.getMaximumObjectsPerQuad() ||
                    root.mBoundingRect.getWidth() / 2 <= root.mMinimumSide ||
                    root.mBoundingRect.getHeight() / 2 <= root.mMinimumSide) {
                if (root.mObjects == null) {
                    root.mObjects = new ArrayList<>();
                }
                root.mObjects.add(shape);
                return;
            } else {
                divideRectangle(root);
            }
        }

        for (QuadTreeNode child : root.mChildren) {
            if (root.mObjects != null) {
                for (Shape s : root.mObjects) {
                    insertShape(s, child);
                }
            }
            insertShape(shape, child);
        }

        root.mObjects = null;
    }

    Basically insertion routine is very simple. First we have to check if shape is contained within our bounding rect(by contained i mean full continence and partial continence when only part of it is inside of rectangle) if it's not just return. Second we check conditions for insertion. Those conditions may be different, here i have getMaximumObjectsPerQuad condition which controls maximum number of shapes within quad and minimum side condition which controls minimum quad side. If conditions pass we just add shape to current node that means that current node becomes a leaf(node which contains objects). If conditions don't pass and node doesn't have any children we divide quad into four and put objects contained in current node and new shape into these four quads.

Remove

    Remove operation is a little bit more tricky than insertion.

    private static void getUniqueShapesInQuad(QuadTreeNode root, HashSet<Shape> set) {
        assert root != null;

        if (root.getObjects() != null) {
            set.addAll(root.getObjects());
            return;
        }

        if (root.mChildren != null) {
            for (QuadTreeNode n : root.mChildren) {
                getUniqueShapesInQuad(n, set);
            }
        }
    }

    private static void undivideRectangleIfNeeded(QuadTreeNode parent) {
        assert parent != null;

        HashSet<Shape> shapes = new HashSet<>();
        getUniqueShapesInQuad(parent, shapes);

        if (shapes.size() <= parent.getMaximumObjectsPerQuad()) {
            assert parent.mObjects == null;
            parent.mObjects = new ArrayList<>();
            parent.getObjects().addAll(shapes);
            for (QuadTreeNode n : parent.mChildren) {
                n.mParent = null;
            }
            parent.mChildren = null;

            if (parent.mParent != null) {
                undivideRectangleIfNeeded(parent.mParent);
            }
        }
    }

    private static void removeShape(Shape s, QuadTreeNode root) {
        ArrayList<QuadTreeNode> nodesContainingShape = new ArrayList<>();
        containsShape(s, root, nodesContainingShape);

        HashMap<QuadTreeNode, QuadTreeNode> parentChildMap = new HashMap<>();
        for (QuadTreeNode n : nodesContainingShape) {
            n.getObjects().remove(s);
            parentChildMap.put(n.mParent, n);
        }

        for (HashMap.Entry<QuadTreeNode, QuadTreeNode> e : parentChildMap.entrySet()) {
            if (e.getValue().mParent != null) {
                undivideRectangleIfNeeded(e.getKey());
            }
        }
    }
The main idea behind remove is that you need to recursively check if number of objects in parent node of the node you removed shape from is equal or less than maximum number of objects in a node. If so we have to "undivide" or merge children quads into one and repeat it recursively .

Thats it. Building of quad tree is simple - it's just adding all elements you need and updating shape is removing it and adding back.

Summing up

Quad tree is a good data structure which operates in O(logN) in average(worst case performance is O(N)) which can help you to get good performance in performance dependant tasks.

You can get the code for this article here

Thursday, May 15, 2014

How to detect if application has been updated or installed

In some cases there is a need to know if user has installed or updated your app. For example to put different texts or something else. To do this you may use code below:
static public boolean isUpdated(Context ctx) {
    PackageInfo packageInfo = null;
    try {
            packageInfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(),
                PackageManager.GET_ACTIVITIES);
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "Can't get package info");
        }
    return (packageInfo.firstInstallTime != packageInfo.lastUpdateTime);
}

Wednesday, May 14, 2014

Settings up AndEngine in Android studio


When i decided to use AndEngine in my project I discovered that it doesn't support Android studio and there is not any passable tutorial about how to make it work. So I spent some time and set it up by myself.

Let's assume that you already have created a project and it has default structure. First create folder named third_party in the root directory of the project. Then in third_party directory create subdirectories called andengine and andenginebox2d. I assume that you already downloaded or cloned AndEngine and Box2d extension for it. Put AndEngine and AndEngineBox2d in andengine and andenginebox2d directories respectively. Create file named build.gradle in andengine directory and andenginebox2d directory. Build.gradle files is a file that tells gradle how to build your project.
Next you have to put content in your build.gradle files. In file located in andengine folder put:

apply plugin: 'android-library'

android {
    compileSdkVersion 17
    buildToolsVersion "19.0.3"

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 19
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-project.txt')
        }
    }

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        instrumentTest.setRoot('tests')
    }
}
By doing so you are telling gradle that minSdkVersion is 14 and targetSdk is 19. Above all I'm using Java 7 in my project so there is
compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
block.

In file located in andenginebox2d folder put such content:
apply plugin: 'android-library'

android {
    compileSdkVersion 17
    buildToolsVersion "19.0.3"

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 19
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-project.txt')
        }
    }

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        instrumentTest.setRoot('tests')
    }
}

dependencies {
    compile project(':third_party:andengine')
}
As you can see it is almost identical to previous except dependencies block. That block tells gradle that andenginebox2d depends on andengine and gradle should include it as a library project.

We are almost done. Next step is to tell gradle that you want it to compile AndEngine with your project.
Open settings.gradle that is located in your project's root directory and add two lines to it:
include ':third_party:andengine'
include ':third_party:andenginebox2d'
Next open build.gradle which locates in app directory and add
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile project(':third_party:andenginebox2d')
    compile project(':third_party:andengine')
to dependencies block. You already know that it means that your application is dependent of andengine project.

The final step is to open AndroidManifest.xml in andegine and andenginebox2d directories and make them look like this:


    
<application /> block is needed because of bug in manifest merging tool.
That's it! Now clean your project and press run. Everything should work just fine.

Tuesday, February 12, 2013

Использование assets в Android

    В целом использование Assets в Android не представляет из себя ничего сложного: знай себе читай из InputStream. Но тут есть некоторые подводные камни. В частности производительность, которая при использовании метода list() падает ниже плинтуса. Связано это, по видимому, с тем, что ассеты хранятся в сжатом виде и при каждом считывании приходиться производить манипуляции с архивом. О том как с этим боролся я ниже.