转自:http://blog.csdn.net/myarrow/article/details/7760579

一、APK扩展文件基本知识

Android Market (Google Play Store)中每个APK文件的最大限制是50MB。如果您的程序中包含大量的数据文件,以前您只能把这些数据文件放到自己的服务器上,当用户启动程序的时候让用户去下载。现在这些数据文件可以直接上传到Android Market了。在新的Market控制台上传App的时候,可以添加扩展文件了。

下面就来看看什么时候该使用扩展文件,该如何使用?

每个APK可以有2个扩展文件,每个文件最大限制是2GB。为了减少用户的带宽消耗,最好使用压缩格式文件吧。 这两扩展文件具有不同的用途: 第一个被称为 main (主)扩展文件,该扩展文件保护您程序中需要用到的附加数据; 第二个被称为 patch 扩展(修补)文件,该文件是可选的,并且应该只包含一些不同版本的补丁数据。
       当然您可以按照您需要的方式来使用这两个扩展文件,不过Android官方还是推荐把这两个文件的功能分开。main扩展文件包含核心数据,并且尽量不随程序版本的升级去修改;而patch扩展文件可以随程序版本的升级做修改。为了帮助大家理解具体的含义,我们使用一个地图App来解释下:        比如 Google 地图程序需要包含一个离线地图数据包,这样可以方便用户离线查看地图,在程序发布的时候,可以把现有的离线数据包作为main扩展文件上传到Market。 然后过了半年Google地图更新了,新添加了一些刚刚修好的高速公路、新建立的商场 等信息,可以把这些新增的信息作为patch扩展文件使用。 这样Google 地图 1.0版本对应一个main扩展文件;而Google地图1.1版本对应一个main扩展文件和一个1.1版本的patch扩展文件;Google地图1.2版本对应一个main扩展文件和一个1.2版本的patch扩展文件。 这里面的main扩展文件是同一个文件而patch扩文件是随版本变化的。        这样的好处就是当程序升级的时候, 用户不用重新下载main扩展文件了,只需要下载少量的新增文件即可,节省用户流量。

二、扩展文件的命名格式

扩展文件可以使用任何文件格式(ZIP, PDF, MP4, 等)。不管任何文件格式Android都认为他们是obb(opaque binary blobs)文件,并且会根据如下文件命名规则来重命名扩展文件:

[main|patch].<expansion-version>.<package-name>.obb

main or patch            指定文件是main扩展文件还是patch扩展文件,每个APK只能有一个main扩展文件和一个patch扩展文件。

<expansion-version>        和第一次上传该扩展文件的APK文件的android:versionCode一致。后续版本的APK可以重用前面上传的扩展文件。 您程序的Java包名

<package-name>          例如程序的版本为5,程序的包名为org.goodev.expansion.downloader。则上传的main扩展文件会被重命名为:main.5.org.goodev.expansion.downloader.obb

三、扩展文件的保存位置

当Android Market下载程序的扩展文件的时候会保存到系统的共享存储区。为了确保程序正常运行,您不能删除、移动或者重命名扩展文件。在某些设备上Market无法自动下载该扩展文件,那么您应该在程序启动的时候去下载该文件并且保存到同样的位置。 扩展文件保存位置如下: <shared-storage>/Android/obb/<package-name>/

<shared-storage> 代表共享文件的目录路径,通过函数getExternalStorageDirectory()获取; <package-name> APK的Java包名。        对于每个App而言,该目录下最多只能包含2个扩展文件。一个是main扩展文件另外一个是patch扩展文件。当更新程序的时候,如果有新的扩展文件则新文件会覆盖旧的扩展文件。

如果您需要解压缩扩展文件来使用,请注意不要删除该.obb文件,并且也不要把文件解压缩到该目录。您应该把解压缩后的文件保存到getExternalFilesDir()返回的目录下面。如果有可能的话,最好使用程序能直接读取的文件格式而不用再次解压缩文件了。Android开发团队提供了一个项目( APK Expansion Zip Library)可以直接读取ZIP文件中的内容而不用解压缩该文件. 需要注意的是:保存在系统共享存储区的文件,用户和其他APP也可以访问。

四、下载扩展文件的流程

在大多数情况下,Market会在下载APK的同时去下载扩展文件。然而,在某些情况下Market无法下载扩展文件或者用户删除了以前下载的扩展文件,您的程序需要处理这种异常情况。当您的程序启动的时候,可以检测文件是否存在并且可以从Market上下载。

开发者检查清单:

您可以通过下面的清单来检查是否需要使用扩展文件

  • 1.您的程序是否真的需要超过50MB的大小限制。在移动设备上空间是非常宝贵的,您应该尽可能减少App的大小。如果您仅仅是为了提供支持多种显示设备的图片资源的话,可以考虑使用发布多个APK的方式来减少每个APK的大小。
  • 2.判断哪些数据需要打包为扩展文件发布。
  • 3.在程序中添加访问共享存储区中扩展文件的代码
  • 4.在程序的启动Activity中添加检测扩展文件是否存在,以及下载扩展文件的代码

五、扩展文件的规则和限制

  • 1.每个扩展文件最大为2GB
  • 2.用户必需要从Android Market获取您的程序才能自动从Market中下载扩展文件
  • 3.当在您的程序中下载扩展文件的时候,Market每次都会为每个文件生成一个唯一的下载URL,该URL会在短期内失效。
  • 4.当你上传一个新的APK的时候,可以选择使用以前上传的扩展文件
  • 5.如果您使用多个APK文件来适配不同的设备,并且也希望使用多个扩展文件。为了获取一个唯一的versionCode和不同的Market filter, 您需要分别为每个设备上传不同的APK文件。
  • 6. 不能通过更新扩展文件来发布一个新的版本。
  • 7. 不要在obb/文件夹中保存其他数据
  • 8.不要删除或者重命名.obb文件

六、APK扩展文件使用实例

要在App中使用扩展文件,需要两个附加的Android库项目:

1)Google Market Licensing package

2)Google Market APK Expansion Library package

可以通过Android SDK Manager来下载,也可以直接通过如下链接下载:

https://dl-ssl.google.com/android/repository/market_licensing-r02.zip

https://dl-ssl.google.com/android/repository/market_apk_expansion-r01.zip

下载完成后使用market_licensing-r02.zip文件中的目录google_market_licensing\library来创建一个库项目;
然后使用market_apk_expansion-r01.zip中的google_market_apk_expansion\downloader_library来创建另外一个库项目。
同时为了简化对ZIP格式扩展文件的处理,在market_apk_expansion-r01.zip文件中还包含了一个对ZIP文件处理的库项目:google_market_apk_expansion\zip_file。 如果您使用的扩展文件格式是ZIP,那么也可以创建这个库项目。

1. 声明需要的权限

<manifest...>

    <!-- Required to access Android Market Licensing -->
<uses-permission android:name="com.android.vending.CHECK_LICENSE"/> <!-- Required to download files from Android Market -->
<uses-permission android:name="android.permission.INTERNET"/> <!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) -->
<uses-permission android:name="android.permission.WAKE_LOCK"/> <!-- Required to poll the state of the network connection and respond to changes -->
<uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/> <!-- Required to check whether Wi-Fi is enabled -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <!-- Required to read and write the expansion files on shared storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
... </manifest>

注意:默认情况下,下载库项目需要的API level为4 而APK扩展ZIP库项目需要API level为5.

准备工作完成后,下面来具体看看如何使用扩展文件。

2. 实现下载服务(Implementing the downloader service)

为了实现在后台下载文件,下载库项目提供了一个Service实现,名称为DownloaderService。您应该继承自这个文件来实现您的下载服务。为了简化下载服务的开发,该DownloaderService还实现了如下功能:

  • 注册一个BroadcastReceiver来监听设备的网络连接状态的改变。如果网络连接断开就暂停下载;如果网络连接恢复就继续下载。
  • 安排一个 RTC_WAKEUP 通知,当下载服务被终结的时候可以通过该通知来启动下载服务
  • 生成一个通知(Notification )来显示下载的进度以及下载错误等状态
  • 允许您的程序手工的暂停和恢复下载
  • 检测共享存储区挂载了并且可用,在下载文件之前检测 文件是否已经存在、存储空间是否足够。如果出现问题就通知用户。

您仅仅需要创建一个继承自DownloaderService的类,并且实现如下三个函数即可:

getPublicKey():您Market账号的 Base64 编码 RSA 公共密钥,可以通过如下网址获取: https://market.android.com/publish/Home#ProfileEditorPlace:

getSALT(): 许可策略用来生成混淆器(Obfuscator)的一组随机bytes。

getAlarmReceiverClassName(): 返回您程序中用来重启下载进程的BroadcastReceiver类名称。当某些情况下,下载服务被意外终止的时候通过该BroadcastReceiver类来重新下载。比如 进程管理的程序终止了下载服务。

下面是一个DownloaderService类的实现代码:、

public  class SampleDownloaderService extends DownloaderService {
// You must use the public key belonging to your publisher account
publicstaticfinalString BASE64_PUBLIC_KEY ="YourAndroidMarketLVLKey"; // You should also modify this salt
publicstaticfinalbyte[] SALT =newbyte[] {1, 42, -12, -1, 54, 98,
-100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
}; @Override
publicString getPublicKey() {
returnBASE64_PUBLIC_KEY;
} @Override
publicbyte[] getSALT() {
returnSALT;
} @Override
  publicString getAlarmReceiverClassName() {
returnSampleAlarmReceiver.class.getName();
}
}

然后在manifest文件中声明该Service即可。非常简单吧!

3. 实现 alarm receiver

为了检测下载进程和重启下载服务,DownloaderService会安排一个RTC_WAKEUP Alarm来发送一个Intent到程序的 BroadcastReceiver。你必需定义这个 BroadcastReceiver 来调用 Downloader Library提供的函数,通过该函数来检测下载状态和在必要的情况下重启下载服务。

实现这个类也是非常简单的,一般来说只要覆写onReceive()函数并且调用DownloaderClientMarshaller.startDownloadServiceIfRequired()函数即可。如下所示:

public  class SampleAlarmReceiver extendsBroadcastReceiver {
@Override
publicvoidonReceive(Context context, Intent intent) {
try{
DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent,
SampleDownloaderService.class);
}catch(NameNotFoundException e) {
e.printStackTrace();
}
}
}

注意这个类的名字就是前面getAlarmReceiverClassName()函数返回的名称。然后在manifest文件中声明该receiver即可。 同样很简单吧!

4. 开始下载扩展文件

程序的主Activity(通过Launcher图标启动的Activity)应该负责检查扩展文件是否存在、如果不存在就启动下载服务。 使用Downloader Library来下载需要遵守如下步骤:

1)检查文件是否已经下载了 Downloader Library中的Helper类中包含了一些函数来简化这个步骤: getExtendedAPKFileName(Context, c, boolean mainFile, int versionCode) doesFileExist(Context c, String fileName, long fileSize) 例如在示例项目中,在Activity的onCreate()函数中通过如下函数来检查文件是否存在:

boolean  expansionFilesDelivered() {
for(XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsBase, xf.mFileVersion);
if(!Helpers.doesFileExist(this, fileName, xf.mFileSize,false))
return false;
}
return true;
}

这里的XAPKFile对象保存了已知扩展文件的版本号和大小以及是否为main扩展文件。如果该函数返回false则启动下载服务。

2)通过 DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, ClassserviceClass)该函数来开始下载。 该函数的参数如下: context: Your application’s Context. notificationClient: 用来启动主Activity的PendingIntent。用在DownloaderService 创建的用来显示下载进度的通知中。当用户选择该通知,系统调用该PendingIntent来打开显示下载进度的Activity(一般而言就是启动下载的Activity)。 serviceClass: 程序中继承自DownloaderService的类。在必要的情况下会启动该服务来开始下载。

这个函数返回一个整数来表示是否有必要下载文件。有如下几个值:

  • NO_DOWNLOAD_REQUIRED: 表示文件已经存在或者当前正在下载。
  • LVL_CHECK_REQUIRED:表示需要授权验证来获取下载扩展文件的URL。
  • DOWNLOAD_REQUIRED: 表示扩展文件的URL已经获取到了,但是还没开始下载。

LVL_CHECK_REQUIRED 和 DOWNLOAD_REQUIRED 在本质上是一样的,一般而言您无需关注这个状态。在您的主Activity中调用 startDownloadServiceIfRequired(),你只需要看看返回值是否为NO_DOWNLOAD_REQUIRED即可。如果返回值不是NO_DOWNLOAD_REQUIRED, Downloader Library 开始启动下载,您应该更新程序界面来显示下载进度;如果返回值是  NO_DOWNLOAD_REQUIRED,表明该文件已经下载好了,您的程序可以正常启动了。

例如:

@Override
public void onCreate(Bundle savedInstanceState) {
// Check if expansion files are available before going any further
if(!expansionFilesDelivered()) {
// Build an Intent to start this activity from the Notification
Intent notifierIntent =newIntent(this, MainActivity.getClass());
notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP);
... PendingIntent pendingIntent = PendingIntent.getActivity(this,0,
notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); // Start the download service (if required)
intstartResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
pendingIntent, SampleDownloaderService.class); // If download has started, initialize this activity to show download progress
if(startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
// This is where you do set up to display the download progress (next step)
...
return;
}// If the download wasn't necessary, fall through to start the app
}
startApp();// Expansion files are available, start the app
}

3)当 startDownloadServiceIfRequired() 函数的返回值不是NO_DOWNLOAD_REQUIRED的时候,调用DownloaderClientMarshaller.CreateStub(IDownloaderClient client, ClassdownloaderService)函数来创建一个IStub实例。这个IStub实例提供了Activity和下载服务之前的绑定功能,这样您的Activity就可以收到下载事件了。

CreateStub()函数需要一个实现了IDownloaderClient接口的类和DownloaderService的实现类作为参数。一般而言只要让Activity实现IDownloaderClient接口即可。

Android开发团队推荐在Activity的onCreate()函数中创建IStub对象(在startDownloadServiceIfRequired()函数之后创建)。

例如:

// Start the download service (if required)
int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
pendingIntent, SampleDownloaderService.class);
// If download has started, initialize activity to show progress
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
// Instantiate a member instance of IStub
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this,
SampleDownloaderService.class); // Inflate layout that shows download progress
setContentView(R.layout.downloader_ui);
return;
}

当onCreate()函数返回以后,Activity会执行onResume()函数,在该函数中调用IStub的 connect() 函数。同样在onStop()函数中调用IStub的 disconnect()函数。

例如:

@Override
protectedvoidonResume() {
if(null!= mDownloaderClientStub) {
mDownloaderClientStub.connect(this);
}
super.onResume();
} @Override
protectedvoidonStop() {
if(null!= mDownloaderClientStub) {
mDownloaderClientStub.disconnect(this);
}
super.onStop();
}

调用connect()用来绑定Activity和DownloaderService 。

5. 处理下载进度

要接收下载进度信息,需要实现IDownloaderClient 接口。该接口有如下函数:

onServiceConnected(Messenger m)      在初始化完IStub后,会回调该函数。该函数的参数是用来访问您的DownloaderService的,通过 DownloaderServiceMarshaller.CreateProxy()函数来创建这个IDownloaderService对象,然后可以用这个对象来控制下载服务,比如 暂停、继续下载等。

推荐的实现方式:

private  IDownloaderService mRemoteService;
...
@Override
public void onServiceConnected(Messenger m) {
mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}

onDownloadStateChanged(int newState)     当下载状态发生变化的时候调用该函数,例如 开始下载或者下载完成。

参数newState的值是IDownloaderClient接口中定义的一些常量之一(以 STATE_ 开头的); 可以通过函数 Helpers.getDownloaderStringResourceIDFromState()来获取一个状态的文本描述,这样用户更容易理解。例如 STATE_PAUSED_ROAMING 对应的文本描述是: “Download paused because you are roaming/当前在漫游状态,下载停止”

onDownloadProgress(DownloadProgressInfo progress) 该函数的参数DownloadProgressInfo包含了下载进度的各种信息,例如 预计完成时间、当前下载速度、完成的百分比等。可以根据该信息来更新下载界面。

另外还有一些有用的函数: requestPauseDownload() 暂停下载 requestContinueDownload() 恢复下载 setDownloadFlags(int flags) 设置下载的网络标示。当前只支持一个标示:FLAGS_DOWNLOAD_OVER_CELLULAR。 通过移动网络下载扩展文件。默认情况下该标示没有启用,所以默认情况下只通过WIFI下载。

6. 读取扩展文件

首先要获取扩展文件的路径,可以通过如下代码完成该操作:

// The shared path to all app expansion files
private final static String EXP_PATH ="/Android/obb/"; static String[] getAPKExpansionFiles(Context ctx, intmainVersion,intpatchVersion) {
String packageName = ctx.getPackageName();
Vector<String> ret =newVector<String>();
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { // Build the full path to the app's expansion files
File root = Environment.getExternalStorageDirectory();
File expPath =newFile(root.toString() + EXP_PATH + packageName); // Check that expansion file path exists
if(expPath.exists()) {
if( mainVersion >0) {
String strMainPath = expPath + File.separator +"main."+
mainVersion +"."+ packageName +".obb";
File main =newFile(strMainPath);
if( main.isFile() ) {
ret.add(strMainPath);
}
} if( patchVersion >0) {
String strPatchPath = expPath + File.separator +"patch."+
mainVersion +"."+ packageName +".obb";
File main =newFile(strPatchPath);
if( main.isFile() ) {
ret.add(strPatchPath);
}
}
}
} String[] retArray =newString[ret.size()];
ret.toArray(retArray);
return retArray;
}

您可以在开始下载的时候,把扩展文件的版本号保存到 SharedPreferences 中,然后在这里使用。

7. 使用 APK Expansion Zip Library

APK Expansion Zip Library项目包含了对ZIP文件的处理,您可以通过该项目提供的函数来直接读取ZIP文件内容而不用解压缩扩展文件,它把ZIP扩展文件当一个虚拟文件系统来使用。

APK Expansion Zip Library项目包含如下类和函数: APKExpansionSupport 提供一些函数来访问扩展文件名称和ZIP文件。

getAPKExpansionFiles() 返回扩展文件的文件路径

getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion) 返回一个包含main扩展文件和patch扩展文件的ZipResourceFile。如果您同时提供了 mainVersion 和 patchVersion ,则该函数返回main和patch扩展文件的所有内容,如果patch中的内容和main中的有重复,则使用patch的内容覆盖main中的内容。

ZipResourceFile 用来处理ZIP文件的类 getInputStream(String assetPath) 读取ZIP文件中的具体文件,assetPath应该是相对于ZIP文件的路径信息

getAssetFileDescriptor(String assetPath) 获取ZIP文件中具体文件的 AssetFileDescriptor 信息。

APEZProvider 大多数的程序都不会用到这个类。具体情况请参考其文档。

使用APK 扩展ZIP库从ZIP文件中读取文件参考代码如下:

// Get a ZipResourceFile representing a merger of both the main and patch files
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext,
mainVersion, patchVersion); // Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

上面的代码可以读取main扩展文件或patch扩展文件(通过读取两个文件的合并文件来实现)中的任何文件

如果要读取指定的扩展文件,其方法如下:

// Get a ZipResourceFile representing a specific expansion file
ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip); // Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

8. 测试扩展文件

在发布之前要测试两个东东,下载文件和读取文件。

测试读取文件 在发布您的程序之前应该先测试下您的程序能否读取扩展文件,测试很简单,只要把扩展文件放到共享存储区的特殊位置,然后启动程序即可。 1)创建文件目录: 如果程序的包名为org.goodev,就创建如下的目录:Android/obb/org.goodev/ 2)把扩展文件添加到该目录 如果程序的包名为org.goodev,则主扩展文件名如下:main.03.org.goodev.obb。 版本号可以为大于零的任意值。

3)现在可以启动程序来测试读取扩展文件的功能了。

测试下载文件 由于在某些情况下需要在程序第一次使用的时候手工下载扩展文件。所以需要测试来确保您的程序可以成功的获取下载URL、下载文件并且保存到设备中。

您可以把程序上传到Market,同时上传扩展文件,然后不要发布程序。这样扩展文件已经可以从Market下载了。 当你测试完成后再发布您的程序。

9. 更新程序

使用扩展文件的一大好处就是每次更新App 用户不用重新下载几十上百兆的数据文件了。Android Market让你可以为每个APK提供两个扩展文件,这样可以避免每次更新App都重新下载主扩展文件数据,减少下载时间。

为了方便大家研究如下使用扩展文件,可以到这里下载示例项目代码: http://code.google.com/p/goodev-demo-code/downloads/list 文件名:Market_Downloader_Sample.zip 里面包含了所需要的各种库项目。 在Eclipse中导入即可使用。

APK扩展文件及使用的更多相关文章

  1. APK扩展文件介绍、功能及用法

    APK扩展文件介绍 Android Market (Google Play Store)中每一个APK文件的最大限制是50MB.假设您的程序中包括大量的数据文件,曾经您仅仅能把这些数据文件放到自己的s ...

  2. FileProvider N 7.0 升级 安装APK 选择文件 拍照 临时权限 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. nginx服务器在IE下载时,apk,ipa文件变成zip的解决方法

    前端时间公司官方换了服务器,由Apache换成了Nginx.大概看了下,程序运行,文件下载都没问题,过了!正常上线,OK! But,今天突然发现,在IE浏览器下下载APK和IPA的文件是会被自动识别为 ...

  4. 解决:Windows安装Composer及全局配置时提示部分.dll结尾的php扩展文件找不到指定的模板

    当安装Composer或者全局配置时出现.dll扩展文件找不到指定模板,如下图: 解决办法: 打开php.ini,将extension_dir 改为绝对路径即可 例如:

  5. Linux扩展文件分区

    **************操作之前请看章节6,看系统是否支持LVM分区管理方式*************** 1:新增磁盘 插入新的磁盘,比如物理机可以直接在卡槽插入,虚拟机可以在控制台添加磁盘或者 ...

  6. echarts地图扩展文件使用geoJson格式。

    echarts地图扩展文件使用geoJson格式. 1.在线生成 http://ecomfe.github.io/echarts-map-tool/  这里可以生成省市区的json,但是最多生成到”区 ...

  7. 如何配置IIS使其支持APK/WGT文件的下载

    1.打开Internet 信息服务(IIS)管理器: 2.选择需要配置的网站: 3.右侧功能界面双击进入“MIME类型”: 4.点击右侧“添加”按钮,在弹出窗口里文件扩展名输入:apk,MIME类型输 ...

  8. .net core 2.0 虚拟目录下载 Android Apk 等文件

    当Android 文件 Apk 放在Asp.net core wwwroot 虚拟目录下面.访问是 404,设置Content-Type类型 app.UseStaticFiles(); //设置实际目 ...

  9. Apktool反编译apk资源文件

    Android开发过程中,如何查看已经打包的APK内部xml呢,google下找到了apktool这个工具, apktool项目现在已经迁移到了github:apktool 目前最新版本2.2.2,如 ...

随机推荐

  1. SQL server2012连接不上

    数据库连接不上 其中一种可能的解决办法: 开始-所有程序-Microsoft SQL server 2012-配置工具-SQL Server 配置管理器-SQL server 服务-SQL serve ...

  2. 编译及load mydqli.so文件

    (1)cd /usr/local/php-5.2.17/ext/mysqli(2)输入/usr/local/php/bin/phpize 回车(3)./configure --prefix=/usr/ ...

  3. redmine 2.5.2 安装后邮件无法发送

    找到redmine安装目录-apps->redmine->htdocs->conflg->configuration.yml email_delivery:    delive ...

  4. nginx中针对目录进行IP限制

    一个不错的nginx中针对目录进行IP限制 ,这里我以phpmyadmin目录只能让内网IP访问,而外网不能访问的配置方法,有需要的同学可参考. nginx phpmyadmin 针对内网ip用户开放 ...

  5. 分享一些Comet开发经验

    前言 本comet技术主要用于数据库持久层的 穿越防火墙 远程访问.只要有一台中继网站,任意地点的数据库都能被访问. Comet概念介绍 WebIM.网页的客服.meebo等大家听说过了.最近还有个兄 ...

  6. When not to automate 什么时候不进行自动化

    The cornerstone of test automation is the premise that the expected application behavior is known. W ...

  7. CreateThread函数&&CString::GetBuffer函数

    对这个两个常见的windows下的函数学习了一下: //最简单的创建多线程实例 #include <stdio.h> #include <windows.h> //子线程函数 ...

  8. 桶排序-Swift

    import Foundation let b:Array = [5,2,3,1,8] var a:NSMutableArray = [] for var i in 0 ..< 11 { a[i ...

  9. 谈FTP服务器攻击技术及其展望 (修改中)

      欢迎大家给我投票: http://2010blog.51cto.com/350944 请看下篇:谈FTP服务器攻击技术及其展望 (下)              全文完http://chengua ...

  10. 数据库中使用 Synonym和openquery

    如果,你想在一台数据库服务器上,查询另一个台数据服务器的数据该如何做呢?如果,你想在同一台数据服务器上,在不同的数据库之间查询数据,又该怎么办呢?那就让我为你介绍Synonym和openquery吧. ...