用 Golang 开发 Android 应用(三)—— Storage(Asset) 使用

用 Golang 开发 Android 应用 -- Storage 使用

      • 计划按以下的内容更新
      • Storage 使用
        • 关于 Asset
        • Assets 相关对象
          • AssetManager 说明
          • AssetDir 说明
        • 示例代码
        • 关于 ReadImage
        • Assets 总结

计划按以下的内容更新

  • 基本环境配置
  • 简单 UI
  • Storage 使用
  • Sensor 使用
  • Audio(openAL) 使用
  • Camera 使用
  • OpenCV 使用

Storage 使用

关于 Asset

所谓 Storage 是在 Android 里除了正常文件系统之外,它还实现了两种特殊的“文件存储”方式,一种是 OBB 另一种是 Asset, OBB 是独立于 APK 之外的资源包,而 Asset 是 APK 内置的文件。OBB 使用较少,所以 OBB 就跳过不提,只说明一下 Asset 用法。
我们解开一些 APK 可能会发现它里面有一个 assets 目录(解不开?改扩展名为.zip)。典型的如:

│  AndroidManifest.xml
│  classes.dex
│  resources.arsc
│
├─assets
│   ...
├─lib
│   ...
├─META-INF
└─res
    ...

这个目录是在 APK 内部,显然它和 APK 是一起发布的。关于它的使用场景,用一个词典应用来说,基础的词库可以放在 assets 这个目录(内置词库),而如果是要付费的一些专业词库则可以通过网络下载到手机,用正常文件系统来访问。在我们用 Golang 开发应用的情况下,它也可以用来放应用的界面资源,如图标、字串、界面布局等等。可参考示例文件。

Assets 相关对象

在应用中如何访问这个目录?
相关的对象有:

  • AssetManager
  • AssetDir
  • Asset
AssetManager 说明

它是通过调用 func (a *Activity) AssetManager() *AssetManager 得到的。
并且它只有两个函数


// Open the named directory within the asset hierarchy.  The directory can then
// be inspected with the AAssetDir functions.  To open the top-level directory,
// pass in "" as the dirName.
//
// The object returned here should be freed by calling AAssetDir_close().
func (mgr *AssetManager) OpenDir(dirName string) *AssetDir 

// Open an asset.
//
// The object returned here should be freed by calling AAsset_close().
func (mgr *AssetManager) Open(filename string, mode int) *Asset 

OpenDir 是打开 assets 下的一个目录, 要注意的是根目录是用 “” 表示, 同时这也是 assets 里文件路径的表示方式,比如 asserts/a.txt 和 assets/a/b/c.txt 在调用 Open 时,filename 分别是 “a.txt” 和 “a/b/c.txt” 。
OpenDir 会返回 AssetDir 对象,下面会详细说明。

而 Open 是返回 Asset 对象,关于 Asset 就简单了,因为它是(实现了) io.ReadSeeker 和 io.Closer,也就是几乎可当它是一个 os.File 来用。注意要调用 Close 释放资源

AssetDir 说明
// Iterate over the files in an asset directory.  A "" string is returned
// when all the file names have been returned.
//
// The string returned here is owned by the AssetDir implementation and is not
// guaranteed to remain valid if any other calls are made on this AAssetDir
// instance.
func (assetDir *AssetDir) GetNextFileName() string 

// Reset the iteration state of AAssetDir_getNextFileName() to the beginning.
func (assetDir *AssetDir) Rewind()

// Close an opened AAssetDir, freeing any related resources.
//
//void AAssetDir_close(AAssetDir* assetDir);
func (assetDir *AssetDir) Close() 

GetNextFileName 返回下一个文件名,如果返回"", 说明所有文件列举完了。请注意这里只会返回此目录下的文件,不会返回目录,不知是出于什么考虑?但对于内置文件来说,也算是合理的,理论上 APK 一发布,内置文件就是固定了的。所以从另一面来说,它是给了我们一点灵活性。

Rewind 是重置 GetNextFileName 相关状态和指针,使 GetNextFileName 又从第一个文件开始列举。

示例代码

列举目录下所有文件的代码如下:

func AssetInfo(act *app.Activity, dir string) {
	assetMgr := act.AssetManager()
	assetDir := assetMgr.OpenDir(dir)
	defer assetDir.Close()

	assetDir.Rewind() 
	for {
		// 得到下一个文件名,但不能列出子目录
		fname := assetDir.GetNextFileName()
		if fname == "" {
			return
		}
		...
		
	}
}

请参考示例,这个例子中 assets 目录树如下:

│  AndroidManifest.xml
│
├─assets
│  │  label_icon.png
│  │  readme_root.txt
│  │
│  └─a
│          readme_a.txt

因为无法列出子目录,因此对于本例的目录树,对于 assets 需调用 AssetInfo(acr, "") ,对于 assets/a 需调用 AssetInfo(act, "a")

关于 ReadImage

示例中有个 ReadImage 函数,既说明了 Asset 文件的用法,同时这个例子也演示了 imgui 中图像的显示。
用 Golang 开发 Android 应用(三)—— Storage(Asset) 使用_第1张图片

主要是三步:

  1. 图像解码
  2. 生成 OpenGL texture
  3. 调用 imgui.Image
func ReadImage(act *app.Activity, fname string) image.Image {
	// load image
	m := act.AssetManager()
	fa := m.Open(fname, storage.ASSET_MODE_BUFFER)
	buf := make([]byte, fa.Length())
	fa.Read(buf)

	img, _, err := image.Decode(bytes.NewBuffer(buf))
	if err != nil {
		log.Println("ReadImage:", err)
	}
	fa.Close()

	// 转换成 RGBA 格式
	dst := image.NewRGBA(img.Bounds())
	imagedraw.Draw(dst, dst.Bounds(), img, image.Point{}, imagedraw.Src)
	return dst
}

需要注意的是上面例子中有个 “转换成 RGBA 格式” 的操作,这个效率非常低,不建议这么做,要尽量避免数据转换。如果是 openGL 支持的格式如 RGB565 等,根据不同的图像数据格式找到对应参数直接生成 texture ,如果是不支持的数据格式则可用 GLSL 来绘制。

Assets 总结

上面已经说过 assets 目录下可以用来放应用的界面资源,如图标、字串、界面布局等等。然后是应用需要的数据,这里需要再次提醒的是因为不能列举目录,所以在目录设计的时候需要考虑这个因素,如果做成数据驱动的方式则需要把目录定下来,而以文件的变化作为区别。比如说惯用的版本区分 lite 和 pro 版,它们最好保持一致的目录结构,以文件的不同来区分版本 。

你可能感兴趣的:(Golang,Android)