apk中的assets目录资源的访问问题
Android – 读取assets文件夹下的资源
参考:https://blog.csdn.net/qq_24382363/article/details/86480943
Android 中的资源文件
Android 资源文件大致可以分为两种:res/raw 和 assets
res/raw
res/raw 目录下存放可编译的资源文件这种资源文件系统会在 R.Java 里面自动生成该资源文件的 ID,所以访问这种资源文件比较简单,通过 R.XXX.ID 即可。
assets
assets目录下存放原生资源文件,可以存放一些图片,html,js, css等文件。因为系统在编译的时候不会编译 assets 下的资源文件,所以不能通过 R.XXX.ID 的方式访问它们。那我么能不能通过该资源的绝对路径去访问它们呢?因为apk安装之后会放在/data/app/**.apk目录下,以apk形式存在,asset/res/raw被绑定在apk里,并不会解压到/data/data/YourApp目录下去,所以无法直接获取到 assets 的绝对路径,因为它们根本就没有。
assets 文件夹资源的访问
Android 系统提供了 AssetManager 类来访问 assets 文件里的资源。
assets 文件里的文件都是保持原始的文件格式,需要使用 AssetManager 以字节流的形式读取文件。但是无法以正常的文件系统路径来访问
- 先在 Activity 里面调用 getAssets() 来获取 AssetManager 引用。
- 再用 AssetManager 的 open(String fileName, int accessMode) 方法,指定读取的文件以及访问模式,就能得到输入流 InputStream。
- 然后用已经 open file 的 inputStream 读取文件,读取完成后记得 inputStream.close() 。
- 调用 AssetManager.close() 关闭 AssetManager。
res/raw 和 assets 对比
res/raw和assets的相同点:
- 两者目录下的文件在打包后会原封不动的保存在apk中,不会被变成二进制。
res/raw和assets的不同点:
res/raw 中的文件会被映射到 R.Java 文件中,访问的时候直接使用资源 ID 即 R.XXX.ID;
assets 文件夹下的文件不会被映射到 R.Java 中,访问的时候需要 AssetManager 类。res/raw 不可以有目录结构;
而 assets 则可以有目录结构,也就是 assets 目录下可以再建立文件夹。读取文件资源方式不同:
读取 res/raw 下的文件资源:
InputStream is =getResources().openRawResource(R.id.filename);读取assets下的文件资源:
AssetManager am = getAssets();
InputStream is = am.open(“filename”);
注意1:来自 Resources 和 Assets 中的文件只可以读取而不能进行写的操作
注意2:Google 的 Android 系统处理 Assert 有个 bug,在 AssertManager 中不能处理单个超过1MB的文件,不然会报异常,raw 没这个限制,可以放个4MB的Mp3文件没问题。
注意3:assets 文件夹是存放不进行编译加工的原生文件,即该文件夹里面的文件不会像 xml, java 文件被预编译,可以存放一些图片,html,js, css 等文件。
res/raw 和 assets 使用场景
- 由于 res/raw 是Resources(res)的子目录,Android会自动的为这目录中的所有资源文件生成一个ID,这个ID会被存储在R类当中,作为一个文件的引用。这意味着这个资源文件可以很容易的被Android的类和方法访问到,甚至在Android XML文件中你也可以@raw/的形式引用到它。在Android中,使用ID是访问一个文件最快捷的方式。MP3和Ogg文件放在这个目录下是比较合适的。
- assets 目录更像一个附录类型的目录,Android不会为这个目录中的文件生成ID并保存在R类当中,因此它与Android中的一些类和方法兼容度更低。同时,由于你需要一个字符串路径来获取这个目录下的文件描述符,访问的速度会更慢。但是把一些文件放在这个目录下会使一些操作更加方便,比方说拷贝一个数据库文件到系统内存中。要注意的是,你无法在Android XML文件中引用到assets目录下的文件,只能通过AssetManager来访问这些文件。数据库文件和游戏数据等放在这个目录下是比较合适的。
如果你的应用需要预加载一些静态的、不会改变的数据,并且数据量不大,那么将一个预先填充的数据库文件放入assets文件夹,然后在应用首次运行时将其复制到应用的数据库文件夹,是一种可行的方案。
这种方法的优点是可以省去在应用运行时填充数据库的时间,特别是当插入的数据量较大时。这可以使你的应用启动更快,提供更好的用户体验。
然而,这种方法也有几个缺点:
增加了应用的大小:预填充的数据库会增加应用的安装包大小,这可能对用户是一个负担,特别是在存储空间有限的设备上。
数据更新问题:如果你的应用需要更新预加载的数据,那么必须更新整个应用,以包含新的数据库文件。为避免这个问题,一些应用会选择在运行时从服务器下载数据更新。
适应性问题:这种方法并不适合需要读写动态数据的情况。如果你的应用需要用户能够修改数据,或者数据会随着时间改变,那么你应该使用Android提供的SQLite数据库或者其他数据库方案。
go语言的mobile绑定, 如何访问安卓apk中的assets
前言
用于在gin中, 代理整个单页应用spa, 因此需要访问到我们android中的assets。 但遗憾的是, 由于android特殊的限制, 我们无法使用正常的文件系统。
Java可以访问assets目录的静态资源是因为Android系统允许Java通过特定的API直接读取APK包内的资源文件。
在Android应用开发中,所有放在assets目录下的文件都会在应用编译时被打包进APK文件中,而这部分文件在应用运行时是可以被访问的。为了让开发者能够方便地访问到这部分文件,Android系统在Java层面提供了AssetManager类。开发者可以通过AssetManager类提供的API,如open()方法,来获取assets目录下文件的输入流,从而读取文件内容。
但是,这种方式只在Java层面可用,对于运行在Native层面的代码(如C++,Go等)则无法直接访问APK包内的资源。如果Native代码需要访问assets目录的内容,通常需要通过JNI来调用Java层面的代码进行桥接。
解决方案探讨
简单来说, 就是我们无法像正常的访问文件系统那样, 访问android的sdk中的assets目录。那么我们就需要解决这个问题。
大体上可行的解决方案:
使用外部存储作为中间层, 将读取到的apk中的文件内容, 写入外部存储, 然后提供这个外部存储的字符串路径, 供gin使用。
缺点1: 可以设置每次开启后, 先删除之前的复制品, 然后再重新写入。不过这个很影响启动时间。
缺点2: 需要像用户请求存储权限, 否则无法正常使用, 对用户是一种负担。自己写, 使用JNI(Java Native Interface):Go语言可以通过CGO调用C接口,因此可以考虑实现一个利用JNI访问Asset文件的C库,然后编译为.so文件供Go调用。这种方式比较复杂,可能需要对JNI和CGO都有一定的了解。
充当资源服务提供者的Content Provider:通过这种方式,Go SDK可以像访问文件系统一样访问Content Provider提供的资源。这种方式需要在Android端实现一个Content Provider,并在其openAssetFile()方法中处理资源的访问请求。
Input Stream:给Go SDK一个可以读取数据的InputStream。在Java代码中打开asset文件得到InputStream,然后您需要找到一种方式让Go SDK读取这个InputStream。这可能会涉及到JNI或JNA。
利用go的mobile库的原生开发库(结合其绑定库使用), 其中提供了原生android中访问asset的api。按文档中的描述, 我们只要使用asset.Open函数, 就可以正常访问了。 下面只是举个例子, 不保证此例子的可用性, 当作伪代码看就好:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"golang.org/x/mobile/asset"
"io/ioutil"
)
func main() {
router := gin.Default()
router.GET("/assets/*filepath", func(c *gin.Context) {
filepath := c.Param("filepath")
if strings.HasSuffix(filepath, "/") {
filepath = filepath + "index.html"
}
file, err := asset.Open(filepath)
if err != nil {
c.String(http.StatusNotFound, "File not found.")
return
}
defer file.Close()
content, err := ioutil.ReadAll(file)
if err != nil {
c.String(http.StatusInternalServerError, "Error reading file.")
return
}
c.Data(http.StatusOK, getFileContentType(filepath), content)
})
router.Run()
}
func getFileContentType(filepath string) string {
if strings.HasSuffix(filepath, ".html") {
return "text/html"
} else if strings.HasSuffix(filepath, ".css") {
return "text/css"
} else if strings.HasSuffix(filepath, ".js") {
return "application/javascript"
} else {
return "text/plain"
}
}利用go的只读的虚拟文件系统embed, 直接将 assets在 打sdk的时候, 就打入虚拟静态文件系统中。此方案的唯一副作用就是, spa需要两份, 但是带来的好处是降低了多端适配成本。
在Go 1.16及更高版本中,当你使用embed包将文件或目录嵌入到你的程序时,你需要在嵌入指令//go:embed后面指定文件或目录的相对路径(相对于包含嵌入指令的Go文件)。
所以,assets文件在你的项目中的位置取决于你的项目的文件布局和你的embed指令。例如,如果assets目录与包含嵌入指令的Go文件在同一目录下,你可以像这样指定它://go:embed assets/*
如果assets目录处于包含嵌入指令的Go文件的子目录中,你需要包含子目录的名称,如下所示:
//go:embed mySubdir/assets/*
如果assets目录在包含嵌入指令的Go文件的上级目录中,你需要使用../前缀来指定这个路径(不过貌似不合规),如下所示:
//go:embed ../assets/*
请注意,embed指令还支持通配符和多个路径。所以如果你的assets文件被分散在项目的多个目录中,你可以在一个embed指令中包含他们所有的路径。例如:
//go:embed assets/* moreAssets/* yetMoreAssets/*
最终方案
最终使用了 embed 的方案。
此时使用gin中的静态文件服务时, 需要选择StaticFS这个。
gin中的静态文件服务科普:
router.Static
指定某个目录为静态资源目录, 可直接访问这个目录下的资源, url要具体到资源名称。 – 资源可以是目录或文件。router.StaticFS
比前面一个多了个功能, 当目录下不存在index.html文件时, 会列出该目录下的所有文件。– 基本上可以与上面的Static通用。router.StaticFile
指定某个具体的文件作为静态资源访问。– 我个人一般不怎么使用。
最终示例:
假设这是我们的实际文件目录
├── spa
| ├── assets
| ├── icons
| ├── favicon.ico
| └── index.html
└── main.go
1 | package main |
1 | package main |