关于Google Play商店中obb文件意外情况下无法下载的处理

关于Google Play商店中obb文件意外情况下无法下载的处理

九月 01, 2018

    上面讲了如何加载obb文件,相信大家应该也大致知道obb是什么了。

    在Google Play后台上传apk的时候,apk大小有一个限制,不能够超过100M(应用我不太清楚,但最起码游戏是这样的),那么对于游戏来说,这可能不是个好消息,因为游戏随随便便就可以超过100M,那么我们需要把资源抽出来,单独打包成obb文件,在后台上传apk的时候,同时上传obb文件。

    审核通过之后,用户在Google Play中可以下载到我们的app,商店中显示的实际大小应该是apk+obb的大小,也就是说实际上在帮用户下载apk的同时,Google Play也一起下载了obb。但是Google的官方文档注明,不能保证百分之百成功下载,所以需要开发者在启动app前,判断一下本地是否有相应的obb,如果没有,那么需要重新下载。

    那么如何在启动时,判断是否需要下载obb呢。首先要提到之前说的Google Play APK Expansion libraryGoogle Play Licensing Library。上篇文章中描述了如何下载这两个库,但是不推荐使用Android SDK Manager下载到的Google Play APK Expansion library库。因为官方应该已经很久没维护过了,所以有些类已经被废弃掉,不适用新版本的Android SDK。(传送门:下载)下载之后,我们需要在工程中引用apkx_libraryzip_filemarket_licensing/library三个module。《关于Google Play商店中obb文件的加载》中已经提到了zip_file,这里不再赘述。

    注意:apkx_library依赖market_licensing/library,所以需要在build.gradle中设置依赖。导入module大的时候不要忘记在setting.gradle中include。

    引用后需要修改一些文件:


com.google.android.vending.licensing.LicenseChecker类的checkAccess()


public synchronized void checkAccess(LicenseCheckerCallback callback) {
// If we have a valid recent LICENSED response, we can skip asking
// Market.
if (mPolicy.allowAccess()) {
Log.i(TAG, "Using cached license response");
callback.allow(Policy.LICENSED);
} else {
LicenseValidator validator = new LicenseValidator(mPolicy, new NullDeviceLimiter(),
callback, generateNonce(), mPackageName, mVersionCode);

if (mService == null) {
Log.i(TAG, "Binding to licensing service.");
try {
//------------------modify begin-----------------------
//delete
//这段在代码在Android5.0上会抛出IllegalArgumentException
// boolean bindResult = mContext
// .bindService(
// new Intent(
// new String(
// Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))),
// this, // ServiceConnection.
// Context.BIND_AUTO_CREATE);

//add
Intent serviceIntent = new Intent(new String(Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")));
serviceIntent.setPackage("com.android.vending");

boolean bindResult = mContext
.bindService(
serviceIntent,
this, // ServiceConnection.
Context.BIND_AUTO_CREATE);
//------------------modify begin-----------------------

if (bindResult) {
mPendingChecks.offer(validator);
} else {
Log.e(TAG, "Could not bind to service.");
handleServiceConnectionError(validator);
}
} catch (SecurityException e) {
callback.applicationError(LicenseCheckerCallback.ERROR_MISSING_PERMISSION);
} catch (Base64DecoderException e) {
e.printStackTrace();
}
} else {
mPendingChecks.offer(validator);
runChecks();
}
}
}

com.google.android.vending.expansion.downloader.DownloaderClientMarshaller.Stub类的connect()


@Override
public void connect(Context c) {
mContext = c;
Intent bindIntent = new Intent(c, mDownloaderServiceClass);
bindIntent.putExtra(PARAM_MESSENGER, mMessenger);
/**
* modify
* Context.BIND_DEBUG_UNBIND
* Context.BIND_AUTO_CREATE
* 这里会导致绑定的服务在某些情况下无法启动,服务不启动,IDownloaderClient接口的onServiceConnected()方法就不会执行,mRemoteService为null,从而导致NullPointerException。
* 虽然在使用mRemoteService前增加对其是否为null的判断可以避免crash,但是下载过程仍然无法监控,无法得到下载的结果。需要将这段代码替换成如下代码。也就是将BIND_DEBUG_UNBIND替换成BIND_AUTO_CREATE。
*/
if ( !c.bindService(bindIntent, mConnection, Context.BIND_AUTO_CREATE) ) {
if ( Constants.LOGVV ) {
Log.d(Constants.TAG, "Service Unbound");
}
} else {
mBound = true;
}

}

    修改完成后,如果要自己开发下载界面的话,按照apkx_library文件夹中的AndroidManifest.xml添加即可。本文这里使用的是apkx_sample文件夹中提供的demo,AndroidManifest.xml文件也按照demo添加即可。

    把src下的java文件和res拷贝到主工程中(如有冲突,自行合并),在主工程的build.gradle中引用这几个support库。

dependencies {
compile fileTree(include: '*.jar', dir: 'libs')

compile project(':plugin:plugins:GooglePlayObbService:downloader_obb')
compile project(':plugin:plugins:GooglePlayObbService:market_licensing')
compile project(':plugin:plugins:GooglePlayObbService:zip_file')

//-------------add-------------
compile 'com.android.support:appcompat-v7:26.0.0'
compile 'com.android.support:design:26.0.0'
compile 'com.android.support:support-compat:25.2.0'
//-------------add-------------
}

    在主工程的AndroidManifest.xml文件中添加如下代码:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!--Google Play OBB Download Begin-->
<activity
android:name="[文件所在包名].SampleDownloaderActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@style/Base.Theme.AppCompat" />
<activity
android:label="@string/app_name"
android:name="[文件所在包名].SampleVideoPlayerActivity"
android:theme="@style/VideoFullScreen"
android:screenOrientation="portrait"
android:configChanges="orientation|keyboard|keyboardHidden" >
</activity>

<!--
In order to start the service, it must be uniquely registered with
the package manager here.
-->
<service android:name="[文件所在包名].SampleDownloaderService" />

<!--
In order for the alarm manager to contact the downloader script, the receiver
must be uniquely registered with the package manager here.
-->
<receiver android:name="[文件所在包名].SampleAlarmReceiver" />

<!--
Providers must have a unique authority per application.
Change the android:authorities line to something unique to your
application (such as its package name) if you wish to use the
provider.

The format will be content://com.example.google.play.apkx/[zipfilepath].
-->
<provider
android:authorities="[文件所在包名]"
android:exported="false"
android:multiprocess="true"
android:name="[文件所在包名].SampleZipFileProvider">
<meta-data android:name="mainVersion" android:value="3"/>
</provider>
<!--Google Play OBB Download End-->

    还有几处java文件的修改:


MainActivity.java


protected void onCreate(Bundle savedInstanceState){
Log.w("uuuuuuuuuu","onCreate");

// FATE_OBB_PATH是存储obb路径的变量,getVirtualObbFileFullpath()在obb文件读取的文章中讲过
FATE_OBB_PATH = getVirtualObbFileFullpath();//这句需要放在super.onCreate上面

super.onCreate(savedInstanceState);

//------------add-------------
//如果obb文件不存在,未能在Google Play中正确下载或是用户删除,则跳转至SampleDownloaderActivity开始下载
File file = new File(FATE_OBB_PATH);
if (!file.exists()){
Intent intent = new Intent(this, SampleDownloaderActivity.class);
this.startActivity(intent);
this.finish();
}
//------------add-------------
}


SampleDownloaderActivity.java


//修改为自己需要的参数
private static final XAPKFile[] xAPKS = {
new XAPKFile(
true, //isMain:是否为主要分包
6, //fileVersion:其实就是versionCode
182118395L //fileSize:分包文件字节大小,不知道怎么看的可以在cmd中使用dir命令查看
)
// main file only
};

    在void validateXAPKZipFiles()中,validationTask对象调用execute方法时,我把它加入了UiThread。(虽然不知道有没有用,但是在别的帖子中看到有人说这个问题。于是就加上了。)

this.runOnUiThread(new Runnable() {
@Override
public void run() {
validationTask.execute(new Object());
}
});

    加入onResume方法。
/**
* Connect the stub to our service on resume.
*/
@Override
protected void onResume() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.connect(this);
}
super.onResume();
}

    startMovie是在验证文件结束后,界面上有一个确定按钮,点击事件中会调用这个方法。应该是demo中的骚操作,但我们这里不需要,所以我改成了以下内容,重新跳转回游戏activity。
private void startMovie() {
// launch the movie player activity
// Uri mediaUri = Uri.withAppendedPath(SampleZipFileProvider.ASSET_URI,
// "big_buck_bunny_720p_surround.m4v");
// Intent intent = new Intent();
// intent.setData(mediaUri);
// intent.putExtra(Intent.EXTRA_TITLE, mediaUri.getLastPathSegment());
// intent.setClass(this, SampleVideoPlayerActivity.class);
// startActivity(intent);

Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
}


SampleDownloaderService.java


//替换为自己的KEY,在Google后台查询自己的RSA公共密钥
private static final String BASE64_PUBLIC_KEY = "REPLACE THIS WITH YOUR PUBLIC KEY"

SampleZipFileProvider.java


// must match what is declared in the Zip content provider in
// the AndroidManifest.xml file
// 这里填写你在AndroidManifest.xml文件中注册此Provider时,填写的android:authorities
private static final String AUTHORITY = "com.xxx.xxx.xxx";

    在依赖这些module的时候,可能会遇到这样一个无法import的问题。

images

    请尝试在build.gradle中做如下修改。

android {
compileSdkVersion 26
buildToolsVersion '28.0.2'
useLibrary 'org.apache.http.legacy' //加上这一句
}

    到此,修改基本完成。在测试之前,请先确认不添加下载逻辑的情况下,使用本地挂载方式,把obb放到/storage/emulated/0/Android/obb/[包名]路径下是否能够正常读取。

    在测试下载逻辑之前,请先将手机安装Google Play全家桶。并将已添加为测试账号的Google Play账号在手机上登陆(因为只有测试账号才能看到内测版App),并进入Google Play中下载已提审通过的内测版App。之后如有修改,安装相同签名以及包名的apk即可直接调试,不用再次提交审核。

参考文献:

https://blog.csdn.net/androidworkor/article/details/70226726

https://www.jianshu.com/p/de3c53f69925