Android apk安装过程及Java、JNI读取安装包内assets资源文件的两种方法(附源码实例)

问题背景:在PC上的程序可以轻松使用./或不用指明,默认读取的就是程序所在路径内的文件。但在Android上,应用程序被打包成apk,程序运行时无法直接获取apk(压缩包)内的文件。但在一些特殊场合,如加载图像处理训练好的分类器、模型等数据,要求每个apk到手机上都能运行,就必须解决这个问题。本文深入研究apk安装过程,给出三种方法解决这个问题。

一、android apk安装过程

Android apk文件是将AndroidManifinest.xml、应用程序代码(.dex)、资源文件和其他文件打包成的一个压缩包文件,其中的.dex文件即使android上的可执行文件,由Java代码编译后的class文件链接而成。因此可以用unzip直接将apk打开。如下图所示,将本文后面要附源码的一个apk解压后示意图下:


1、assets文件夹,这个本文后面的源码专门就讲它,暂略。

2、lib文件夹,这里放着我们jni编译后生成的so文件。

3、META-INF文件夹,这个要追溯到java的jar文件。jar文件和zip文件唯一的区别就是包含一个META-INF文件夹,详见:这里

4、res文件夹,就是所谓的资源文件,里面放的有各种图片资源(drawable文件夹下的东西)和布局xml文件。示图如下:


因此如果想借用一个apk的图片资源的话,直接解压就ok了。

5、AndroidManifinest.xml文件,这个就不多说了,每个Android工程文件都有。

6、classes.dex文件,Dex是Dalvik VM executes的全称,即Android Dalvik执行程序,并非Java ME字节码而是Dalvik字节码

7、resources.arsc文件,是编译后的二进制资源文件。

apk具体的核心安装步骤及牵涉到文件夹路径如下(以安装ReadAssets.apk为例):

第一步:复制apk文件到data/app/目录下,解压并扫描安装包,名字是以包名命名的,并不是apk的名字。如下:


第二步:将.dex文件保存到data/dalvik-cache目录,


第三步:在/data/data/目录下创建对应的应用数据目录,目录名字是apk的包名:


其中cache文件夹下的内容如下:


lib文件夹下是jni里生成的库,libReadAsset.so,如下:


参考链接1 参考链接2

二、Java和JNI读取assets文件夹内的文件

关于assets文件夹和res文件夹的区别见http://blog.sina.com.cn/s/blog_4b93170a0102dqxj.html ,即res文件夹内的东西会再R.java生成id,而assets文件夹不会生成标记,只能利用assetmanager进行访问。其中的raw文件夹也不会被编译跟assets一样。

下面的代码是我写的一个demo,从java和jni两种方式读取assets文件夹内的一个txt文件。

1、布局文件:

[html]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:paddingBottom="@dimen/activity_vertical_margin"  
  6.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  7.     android:paddingRight="@dimen/activity_horizontal_margin"  
  8.     android:paddingTop="@dimen/activity_vertical_margin"  
  9.     tools:context=".MainActivity" >  
  10.   
  11.     <TextView  
  12.         android:id="@+id/textview_show"  
  13.         android:layout_width="wrap_content"  
  14.         android:layout_height="wrap_content"  
  15.         android:layout_alignParentTop="true"  
  16.         android:text="@string/hello_world" />  
  17.   
  18.     <Button  
  19.         android:id="@+id/btn_javashow"  
  20.         android:layout_width="wrap_content"  
  21.         android:layout_height="wrap_content"  
  22.         android:layout_alignParentBottom="true"  
  23.         android:text="Java读取" />  
  24.   
  25.     <Button  
  26.         android:id="@+id/btn_jnishow"  
  27.         android:layout_width="wrap_content"  
  28.         android:layout_height="wrap_content"  
  29.         android:layout_alignParentBottom="true"  
  30.         android:layout_toRightOf="@id/btn_javashow"  
  31.         android:text="JNI读取" />  
  32.   
  33.   
  34.   
  35. </RelativeLayout></span>  

2、MainActivity.java文件

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">package org.yanzi.readassets;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.ByteArrayOutputStream;  
  5. import java.io.IOException;  
  6. import java.io.InputStream;  
  7. import java.io.InputStreamReader;  
  8.   
  9. import org.yanzi.lib.MyLib;  
  10.   
  11. import android.app.Activity;  
  12. import android.content.res.AssetManager;  
  13. import android.os.Bundle;  
  14. import android.view.Menu;  
  15. import android.view.View;  
  16. import android.widget.Button;  
  17. import android.widget.TextView;  
  18.   
  19. public class MainActivity extends Activity {  
  20.   
  21.     Button javaShowBtn;  
  22.     Button jniShowBtn;  
  23.     TextView showTextView;  
  24.       
  25.     @Override  
  26.     protected void onCreate(Bundle savedInstanceState) {  
  27.         super.onCreate(savedInstanceState);  
  28.         setContentView(R.layout.activity_main);  
  29.         initUI();  
  30.         javaShowBtn.setOnClickListener(new View.OnClickListener() {  
  31.   
  32.             @Override  
  33.             public void onClick(View v) {  
  34.                 // TODO Auto-generated method stub  
  35.   
  36.                 String str = readFromAssets("test.txt"); //notes.txt System.getProperty("file.encoding")+"\n"   
  37.                 showTextView.setText("Java读取:" + str);  
  38.             }  
  39.         });  
  40.           
  41.         jniShowBtn.setOnClickListener(new View.OnClickListener() {  
  42.               
  43.             @Override  
  44.             public void onClick(View v) {  
  45.                 // TODO Auto-generated method stub  
  46. //              AssetManager assetManager = getAssets();  
  47.                 String str = MyLib.readFromAssets(getAssets(), "test.txt"); //notes.txt  
  48.                 showTextView.setText("Jni读取:" + str);  
  49.             }  
  50.         });  
  51.           
  52.     }  
  53.   
  54.     public void initUI(){  
  55.         javaShowBtn = (Button)findViewById(R.id.btn_javashow);  
  56.         jniShowBtn = (Button)findViewById(R.id.btn_jnishow);  
  57.         showTextView = (TextView)findViewById(R.id.textview_show);  
  58.   
  59.     }  
  60.     @Override  
  61.     public boolean onCreateOptionsMenu(Menu menu) {  
  62.         // Inflate the menu; this adds items to the action bar if it is present.  
  63.         getMenuInflater().inflate(R.menu.main, menu);  
  64.         return true;  
  65.     }  
  66.   
  67.     /** 
  68.      * @param name 
  69.      * @return 
  70.      * 从Java层读取Assects文件夹内东西 
  71.      */  
  72.     public String readFromAssets(String name){  
  73.         String resultStr = "";  
  74.         try {  
  75.             InputStream inStream = this.getResources().getAssets().open(name);  
  76.             resultStr = convertStream2String(inStream);  
  77.   
  78.             //convertStreamToString(inStream);  
  79.         } catch (IOException e) {  
  80.             // TODO Auto-generated catch block  
  81.             e.printStackTrace();  
  82.         }  
  83.   
  84.   
  85.         return resultStr;  
  86.     }  
  87.     public String convertStream2String(InputStream is){  
  88.         ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  89.         int i = 0;  
  90.         String str = "";  
  91.         //      String str2 = "";  
  92.         int l = 0;  
  93.         try {  
  94.             l = is.available();  
  95.         } catch (IOException e1) {  
  96.             // TODO Auto-generated catch block  
  97.             e1.printStackTrace();  
  98.         }  
  99.         byte[] buffer = new byte[l];  
  100.         try {  
  101.             while((i = is.read()) != -1){  
  102.                 baos.write(i);  
  103.             }  
  104.             is.close();  
  105.             str = baos.toString("utf-8");  
  106.             //          str2 = new String(baos.toByteArray(), "utf-8");  
  107.             //          str2 = EncodingUtils.getString(buffer, "utf-8"); //gb2312  
  108.         } catch (IOException e) {  
  109.             // TODO Auto-generated catch block  
  110.             e.printStackTrace();  
  111.         }  
  112.         return str;  
  113.     }  
  114.   
  115.   
  116.     public String convertStreamToString(InputStream is) throws IOException {  
  117.   
  118.         /* 
  119.  
  120.           28          * 为了将InputStream转换成String我们使用函数BufferedReader.readLine(). 
  121.  
  122.           29          * 我们迭代调用BufferedReader直到其返回null, null意味着没有其他的数据要读取了. 
  123.  
  124.           30          * 每一行将会追加到StringBuilder的末尾, StringBuilder将作为String返回。 
  125.  
  126.           31          * 
  127.  
  128.           32          */  
  129.   
  130.         if (is != null) {  
  131.   
  132.             StringBuilder sb = new StringBuilder();  
  133.   
  134.             String line;  
  135.             try {  
  136.   
  137.                 BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));  
  138.   
  139.                 while ((line = reader.readLine()) != null) {  
  140.   
  141.                     sb.append(line).append("\n");  
  142.   
  143.                 }  
  144.   
  145.             } finally {  
  146.   
  147.                 is.close();  
  148.   
  149.             }  
  150.             return sb.toString();  
  151.   
  152.         } else {  
  153.   
  154.             return "";  
  155.   
  156.         }  
  157.   
  158.     }  
  159.   
  160.   
  161.   
  162.   
  163. }  
  164. </span>  

[注:关于InputStream转String可以参考http://blog.csdn.net/iplayvs2008/article/details/11484627,需注意默认的在windows下新建文本文档txt格式是gb2312,因此代码里转换时也必须指明是gb2312,如果是txt文档经过notepad++创建的,则默认的是UTF-8格式,无需指定默认的就是utf-8格式,因此转出来不乱码。]

3、MyLib.java 这个文件是加载JNI ReadAsset.so的,如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">package org.yanzi.lib;  
  2.   
  3. import android.content.res.AssetManager;  
  4.   
  5. public class MyLib {  
  6.     static{  
  7.         System.loadLibrary("ReadAsset");  
  8.     }  
  9.       
  10.     public static native String readFromAssets(AssetManager assetManager, String name);  
  11. }  
  12. </span>  

4、jni文件夹下有四个文件,分别是Android.mk、Application.mk、mylog.h、readAssets.cpp。

Android.mk文件如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS    := -lm -llog -landroid
LOCAL_MODULE    := ReadAsset
LOCAL_SRC_FILES := readAssets.cpp
#LOCAL_C_INCLUDES+= D:\ProgramFile\android-ndk-r7\platforms\android-14\arch-arm\usr\include\android
include $(BUILD_SHARED_LIBRARY) 

关于Android.mk文件的解释可以参见: http://blog.csdn.net/xuxinyl/article/details/6555762  http://www.2cto.com/kf/201310/253386.html


Application.mk文件

APP_STL:=gnustl_static
APP_CPPFLAGS:=-frtti -fexceptions 
APP_ABI:= armeabi-v7a 

关于Application.mk文件解释:http://www.52rd.com/Blog/Detail_RD.Blog_liuxijob_29763.html


readAssets.cpp

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">#include "mylog.h"  
  2. #include <jni.h>  
  3. #include <sys/types.h>  
  4. #include <stdlib.h>  
  5. #include <android/asset_manager_jni.h>  
  6. #include <android/asset_manager.h>  
  7.   
  8. //jclass clazz,  
  9. extern "C"{  
  10. JNIEXPORT  jstring  JNICALL Java_org_yanzi_lib_MyLib_readFromAssets(JNIEnv* env,jclass clazz, jobject assetManager,jstring name);  
  11.   
  12. JNIEXPORT  jstring JNICALL Java_org_yanzi_lib_MyLib_readFromAssets(JNIEnv* env, jclass clazz, jobject assetManager,jstring name){  
  13.     LOGI("readFromAssets enter..."); //jclass this,  
  14.     jstring resultStr;  
  15.     LOGI("readFromAssets enter111...");  
  16.     AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);  
  17.     LOGI("readFromAssets enter000...");  
  18.     if(mgr==NULL)  
  19.     {  
  20.         LOGI(" %s","AAssetManager==NULL");  
  21.         return 0;  
  22.     }  
  23.   
  24.     /*获取文件名并打开*/  
  25.     jboolean iscopy;  
  26.     const char *mfile = env->GetStringUTFChars(name, &iscopy); //(*env)->GetStringUTFChars(name, &iscopy); env,  
  27.     AAsset* asset = AAssetManager_open(mgr, mfile,AASSET_MODE_UNKNOWN);  
  28.     env->ReleaseStringUTFChars(name, mfile); //env,  
  29.     if(asset==NULL)  
  30.     {  
  31.         LOGI(" %s","asset==NULL");  
  32.         return 0;  
  33.     }  
  34.     /*获取文件大小*/  
  35.     off_t bufferSize = AAsset_getLength(asset);  
  36.     LOGI("file size : %d\n",bufferSize);  
  37.     char *buffer=(char *)malloc(bufferSize+1);  
  38.     buffer[bufferSize]=0;  
  39.     int numBytesRead = AAsset_read(asset, buffer, bufferSize);  
  40.     LOGI("readFromAssets: %s",buffer);  
  41.     resultStr = env->NewStringUTF(buffer);  
  42.     free(buffer);  
  43.     /*关闭文件*/  
  44.     AAsset_close(asset);  
  45.     LOGI("readFromAssets exit...");  
  46.     return resultStr;  
  47. }  
  48. }  
  49. </span>  

关于这个cpp文件,注意事项如下:

a、头文件是一个都不能少,其实是ndk安装目录下的文件:


b、关于jni里将java的string转成jstring,C文件和C++是不同的,C++里按我上面的程序就ok:

const char *mfile = env->GetStringUTFChars(name, &iscopy);

但在C文件里是:const char *mfile = (*env)->GetStringUTFChars(env, filename, &iscopy);

c、这个jni本地函数的声明如下:
JNIEXPORT  jstring  JNICALL Java_org_yanzi_lib_MyLib_readFromAssets(JNIEnv* env,jclass clazz, jobject assetManager,jstring name);
其中的jclass clazz不能去掉,尽管在代码里没有调用到。如果去掉,去找不到filed之类的错误,猜测jclass传下来的是类似包名之类的东西,AssetManager要读东西必须要它。
d、jni里将char * 或const char*类型转为jstring的代码:
resultStr = env->NewStringUTF(buffer);
e、mk文件里必须有
LOCAL_LDLIBS    := -lm -llog -landroid里的-landroid否则的话是打开不了AssetManager的!!!!


mylog.h文件是为了在jni里添加log:
#ifndef _MYLOG_H
#define _MYLOG_H
#include <android/log.h>
#define TAG "yan"
//#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
//#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__)
//#define LOGW(...) __android_log_print(ANDROID_LOG_WARN , TAG, __VA_ARGS__)
//#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , TAG, __VA_ARGS__)
#endif
要想打印log,除了这些外,还要在mk文件里添加:LOCAL_LDLIBS    := -lm -llog -landroid里的-llog

参考文档:
1、http://blog.sina.com.cn/s/blog_4a657c5a01016t2y.html
2、http://blog.sina.com.cn/s/blog_4a4f9fb5010101tb.html
这两个参考文档仅仅是代码片段,杂家根据这两篇文章搞了两晚才彻底让jni里能访问assets文件夹里的东西。西可以用java读取毕竟方便,然后传给jni以string的方式,实在不行了让jni里读。但也有例外情况,如果jni里需要频繁的读取,(assets文件里的东西只能读不能写),这种jni读取的方法就不适合了。原因是需要每次都打开assetsmanager,很不方便。为此,我们可以再java里将assets文件里的东西拷贝到sdcard中,然后将这个文件的路径传给jni,在jni里知道了文件在sdcard上的绝对路径就可以畅通的读取了,下面是示例代码:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:18px;">public String copyModelToSdcard(){  
  2.         String dir = Environment.getExternalStorageDirectory() + "/""ScanBankCard";  
  3.         File dirFile = new File(dir);  
  4.         if(!dirFile.exists()){  
  5.             dirFile.mkdir();  
  6.         }  
  7.         String modelName = "card.model";  
  8.         String modelPath = dir + "/" + modelName;  
  9.         File model = new File(modelPath);  
  10.         if(model.exists()){  
  11.             return modelPath;     
  12.         }else       {  
  13.             try {  
  14.                 InputStream in = this.getResources().getAssets().open(modelName);  
  15.                 int length = in.available();  
  16.                 byte[] buffer = new byte[length];  
  17.                 in.read(buffer);  
  18.                 OutputStream out = new FileOutputStream(modelPath);  
  19.                 out.write(buffer);  
  20.                 out.flush();  
  21.                 out.close();  
  22.                 in.close();  
  23.             } catch (IOException e) {  
  24.                 // TODO Auto-generated catch block  
  25.                 e.printStackTrace();  
  26.             }  
  27.   
  28.         }  
  29.         return modelPath;  
  30.   
  31.     }</span>  

大家想用的话再改改就行了,别忘了配置文件里加sdcard的写文件权限。

代码下载链接:http://download.csdn.net/detail/yanzi1225627/7008015

本文代码运行效果:





本文系转载,作者:yanzi1225627


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值