LibGDX_7.1: 内存管理

本文链接: http://blog.csdn.net/xietansheng/article/details/50187933

LibGDX 基础教程(总目录)

本篇译自官方 wiki: https://github.com/libgdx/libgdx/wiki/Memory-management


游戏是非常消耗资源的应用,图片和声音会占用相当大的内存。这些资源不是由 Java 垃圾回收机制所管理,而是由本地设备来管理。让垃圾回收器来决定何时从显存中释放5MB纹理并不是一种非常好的做法。

我们希望能够细粒度控制这些资源的生命周期。在 LibGDX 中有多个类是表示这样的资源的,它们都统一实现了共同的接口 Disposable 来表明这些类在资源的生命周期结束时需要被手动处理(调用接口实现的方法 dispose())。资源释放失败将会导致严重的内存泄漏。

下面所列出的这些类需要手动释放资源(可能不完整):

  • AssetManager
  • Bitmap
  • BitmapFont
  • BitmapFontCache
  • CameraGroupStrategy
  • DecalBatch
  • ETC1Data
  • FrameBuffer
  • Mesh
  • Model
  • ModelBatch
  • ParticleEffect
  • Pixmap
  • PixmapPacker
  • Shader
  • ShaderProgram
  • Shape
  • Skin
  • SpriteBatch
  • SpriteCache
  • Stage
  • Texture
  • TextureAtlas
  • TileAtlas
  • TileMapRenderer
  • com.badlogic.gdx.physics.box2d.World
  • all bullet classes

当资源长时间不再需要时应该立即被处理以释放它们所关联的内存。访问一个已被处理的资源将会导致未定义错误,因此当处理一个资源时需要确保清除了该资源的所有引用。

当不确定一个指定类是否需要被处理时,可以查看该类是否有一个 dispose() 方法(即该类是否直接或间接实现了 Disposable 接口),如果有,则你现在正在使用本地资源。

对象池(Pool)

对象池的原理是重复使用不活跃或“死掉”的对象来代替每次重新创建对象。这是通过创建一个对象池,当你需要一个新的对象时从对象池中获取。如果对象池中已存在一个空闲的对象,则返回它。如果对象池是空的或没有空闲对象,将重新创建一个对象返回。当你长时间不再需要一个对象时,需要“释放”它,也就是说把它放回到对象池中。通过这种方式,对象分配的内存被重复利用,也减轻了垃圾回收器的压力。

在游戏中如果有对象需要频繁创建,则内存管理至关重要,例如子弹,障碍物,怪物等对象。

LibGDX 提供了一些简单对象池的实现工具:

  • Poolable 接口
  • Pool
  • Pools

实现 Poolable 接口意味着在对象中将有一个 reset() 方法,它将在你释放对象时(把对象放回池中)自动被调用。

下面是一个简单的子弹对象池的例子:

public class Bullet implements Pool.Poolable {

	/** 子弹的位置坐标 */
	public Vector2 position;
	
	/** 子弹是否“活着”的标记 */
	public boolean alive;

	/**
	 * 子弹的构造器, 仅初始化变量
	 */
	public Bullet() {
		this.position = new Vector2();
		this.alive = false;
	}

	/**
	 * 初始化子弹的位置。从对象池中获取到一个子弹对象后手动调用该方法。
	 */
	public void init(float posX, float posY) {
		position.set(posX, posY);
		alive = true;
	}

	/**
	 * 当一个子弹对象被释放时调用该方法。该方法在使用 Pool.free() 释放后被自动调用,
	 * 必须在该方法中重置子弹的所有有意义的字段。
	 */
	@Override
	public void reset() {
		position.set(0, 0);
		alive = false;
	}

	/**
	 * 该方法在每一次渲染帧的循环中调用, 在方法中更新子弹的状态
	 */
	public void update(float delta) {

		// 更行子弹的位置(在 delta 时间内增加位移增量, 60 表示速度)
		position.add(delta * 60, delta * 60);

		// 如果子弹移动到了屏幕外, 则设置子弹为死亡状态(等待被放回池中)
		if (isOutOfScreen()) {
			alive = false;
		}
	}
}

游戏世界类:

public class World {

	// 包含了所有活着的子弹的数组
	private final Array<Bullet> activeBullets = new Array<Bullet>();

	// 子弹对象池
	private final Pool<Bullet> bulletPool = new Pool<Bullet>() {
		@Override
		protected Bullet newObject() {
			// 池中无对象时自动调用该方法创建一个对象
			return new Bullet();
		}
	};
	
	public void update(float delta) {
	
		// 如果你想生成一个新子弹, 则使用如下步骤:
		Bullet item = bulletPool.obtain();
		item.init(2, 2);
		activeBullets.add(item);

		// 如果你想释放已死亡的子弹(放回到对象池中), 则使用如下步骤:
		Bullet item;
		int len = activeBullets.size;
		for (int i = len; --i >= 0;) {
			item = activeBullets.get(i);
			if (!item.alive) {
				activeBullets.removeIndex(i);	// 必须先从活着子弹数组中移除
				bulletPool.free(item);			// 释放子弹(放回到池中)
			}
		}
	}
}

上面例子通过手动创建 Pool 的子类实现对象池的创建。在 Pools 工具类中提供静态方法来动态创建任何类型(必须包含一个空参构造方法)的对象池(返回一个 ReflectionPool 实例,池中对象使用反射创建)。可以像下面的例子这样使用它:

private final Pool<Bullet> bulletPool = Pools.get(Bullet.class);

如何使用 Pool

一个 Pool<T> 只管理单一的对象类型,所有它是参数化的类型。通过具体的 Pool 实例调用 obtain() 方法获取对象,然后通过调用 free() 方法将对象放回到池中。Pool 中的类型参数 T 可以选择实现 Pool.Poolable 接口(只有需要一个 reset() 方法重置对象时才需要实现该接口),在这种情况下,当一个对象返回到池中时 Pool 将自动调用 reset() 方法重置对象。对象只有在需要时才会被初始化分配(如果你从未调用过 obtain() 方法,则 Pool 中将不包含任何对象)。

如果你是通过手动继承 Pool<T> 来创建对象池的,则必须实现 Pool<T> 中的 newObject() 抽象方法,在池中没有空闲对象时用该方法创建对象。

Pool 使用警告

谨防泄漏对象池的引用,仅仅因为你调用了 Poolfree() 方法不能保证是其他地方的引用都无效(也就是说 free 之后,除了 Pool 自己本身之外的其他任何地方都不能持有该对象的引用),如果你不小心将可能导致一些细节错误。当把对象 free 放回池中时如果没有对对象的状态进行彻底重置也可能产生细节错误。


展开阅读全文

没有更多推荐了,返回首页