首先。在放弃投票之前阅读所有内容。
我正试图通过谷歌市场下载OBB文件的最大挑战之一。 我已经测试/尝试过几乎每一个在这里发布的代码,并且几乎读过这里发布的每一个主题,不幸的是,他们解决了我的问题。
到目前为止我做了什么?好吧,我从SDK目录导入样本下载活动,并将其更改为我目前在Market发布的APP也创建了一个OBB文件并将其上传并保存为DRAFT模式。
所以,我一步一步地按照http://my.fit.edu/~vkepuska/ece5570/adt-bundle-windows-x86_64/sdk/docs/guide/google/play/expansion-files.html中的教程。 我的测试APP出现了,但下载没有开始。
这是我的BroadcastReceiver:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
public class DownloadFinished extends BroadcastReceiver
{
@Override
public void onReceive(Context arg0, Intent arg1)
{
try
{
DownloaderClientMarshaller.startDownloadServiceIfRequired(arg0, arg1, SampleDownloaderService.class);
}
catch (NameNotFoundException e)
{
e.printStackTrace();
}
}
}
这是我的DownloaderService:
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
public class SampleDownloaderService extends DownloaderService
{
private static final String BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQE..........";
private static final byte[] SALT = new byte[]{1, 43, -12, -1, 54, 98, -100, -12, 43, 2, -8, -4, 9, 5, -106, -108, -33, 45, -1, 84};
@Override
public String getPublicKey()
{
return BASE64_PUBLIC_KEY;
}
@Override
public byte[] getSALT()
{
return SALT;
}
@Override
public String getAlarmReceiverClassName()
{
return DownloadFinished.class.getName();
}
}
这是我的活动:
public class SplashActivity extends Activity implements IDownloaderClient
{
// OBB DOWNLOAD
static private final XAPKFile[] xAPKS = {new XAPKFile(true, 5, 58814844L)};
static private final float SMOOTHING_FACTOR = 0.005f;
private IStub mDownloaderClientStub;
private IDownloaderService mRemoteService;
private View mDashboard;
private View mCellMessage;
private ProgressBar mPB;
private TextView mStatusText;
private TextView mProgressFraction;
private TextView mProgressPercent;
private TextView mAverageSpeed;
private TextView mTimeRemaining;
private Button mPauseButton;
private Button mWiFiSettingsButton;
private boolean mCancelValidation;
private boolean mStatePaused;
private int mState;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
initializeDownloadUI();
if (!expansionFilesDelivered())
{
try
{
Intent launchIntent = SplashActivity.this.getIntent();
Intent i = new Intent(SplashActivity.this, SplashActivity.this.getClass());
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
i.setAction(launchIntent.getAction());
if (launchIntent.getCategories() != null)
for (String category : launchIntent.getCategories())
i.addCategory(category);
// Build PendingIntent used to open this activity from
// Notification
PendingIntent pendingIntent = PendingIntent.getActivity(SplashActivity.this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
// Request to start the download
int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SplashActivity.class);
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED)
{
// The DownloaderService has started downloading the files,
// show progress
initializeDownloadUI();
return;
} // otherwise, download not needed so we fall through to
// starting the movie
}
catch (NameNotFoundException e)
{
// Log.e(LOG_TAG, "Cannot find own package! MAYDAY!");
e.printStackTrace();
}
}
else
validateXAPKZipFiles();
}
@Override
protected void onStop()
{
if (null != mDownloaderClientStub)
mDownloaderClientStub.disconnect(this);
super.onStop();
}
@Override
protected void onStart()
{
if (null != mDownloaderClientStub)
mDownloaderClientStub.connect(this);
super.onStart();
}
@Override
protected void onDestroy()
{
this.mCancelValidation = true;
super.onDestroy();
}
@Override
public void onServiceConnected(Messenger m)
{
mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}
@Override
public void onDownloadStateChanged(int newState)
{
setState(newState);
boolean showDashboard = true;
boolean showCellMessage = false;
boolean paused;
boolean indeterminate;
switch (newState)
{
case IDownloaderClient.STATE_IDLE :
// STATE_IDLE means the service is listening, so it's
// safe to start making calls via mRemoteService.
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_CONNECTING :
case IDownloaderClient.STATE_FETCHING_URL :
showDashboard = true;
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_DOWNLOADING :
paused = false;
showDashboard = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_FAILED_CANCELED :
case IDownloaderClient.STATE_FAILED :
case IDownloaderClient.STATE_FAILED_FETCHING_URL :
case IDownloaderClient.STATE_FAILED_UNLICENSED :
paused = true;
showDashboard = false;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION :
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION :
showDashboard = false;
paused = true;
indeterminate = false;
showCellMessage = true;
break;
case IDownloaderClient.STATE_PAUSED_BY_REQUEST :
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_ROAMING :
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE :
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_COMPLETED :
showDashboard = false;
paused = false;
indeterminate = false;
validateXAPKZipFiles();
return;
default :
paused = true;
indeterminate = true;
showDashboard = true;
}
int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE;
if (mDashboard.getVisibility() != newDashboardVisibility)
mDashboard.setVisibility(newDashboardVisibility);
int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE;
if (mCellMessage.getVisibility() != cellMessageVisibility)
mCellMessage.setVisibility(cellMessageVisibility);
mPB.setIndeterminate(indeterminate);
setButtonPausedState(paused);
}
@Override
public void onDownloadProgress(DownloadProgressInfo progress)
{
mAverageSpeed.setText(getString(R.string.kilobytes_per_second, Helpers.getSpeedString(progress.mCurrentSpeed)));
mTimeRemaining.setText(getString(R.string.time_remaining, Helpers.getTimeRemaining(progress.mTimeRemaining)));
progress.mOverallTotal = progress.mOverallTotal;
mPB.setMax((int) (progress.mOverallTotal >> 8));
mPB.setProgress((int) (progress.mOverallProgress >> 8));
mProgressPercent.setText(Long.toString(progress.mOverallProgress * 100 / progress.mOverallTotal) + "%");
mProgressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal));
}
private void initializeDownloadUI()
{
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, SplashActivity.class);
setContentView(R.layout.splash_screen);
mPB = (ProgressBar) findViewById(R.id.progressBar);
mStatusText = (TextView) findViewById(R.id.statusText);
mProgressFraction = (TextView) findViewById(R.id.progressAsFraction);
mProgressPercent = (TextView) findViewById(R.id.progressAsPercentage);
mAverageSpeed = (TextView) findViewById(R.id.progressAverageSpeed);
mTimeRemaining = (TextView) findViewById(R.id.progressTimeRemaining);
mDashboard = findViewById(R.id.downloaderDashboard);
mCellMessage = findViewById(R.id.approveCellular);
mPauseButton = (Button) findViewById(R.id.pauseButton);
mWiFiSettingsButton = (Button) findViewById(R.id.wifiSettingsButton);
mPauseButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
if (mStatePaused)
mRemoteService.requestContinueDownload();
else
mRemoteService.requestPauseDownload();
setButtonPausedState(!mStatePaused);
}
});
mWiFiSettingsButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
}
});
Button resumeOnCell = (Button) findViewById(R.id.resumeOverCellular);
resumeOnCell.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
mRemoteService.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
mRemoteService.requestContinueDownload();
mCellMessage.setVisibility(View.GONE);
}
});
}
void validateXAPKZipFiles()
{
AsyncTask<Object, DownloadProgressInfo, Boolean> validationTask = new AsyncTask<Object, DownloadProgressInfo, Boolean>()
{
@Override
protected void onPreExecute()
{
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_verifying_download);
mPauseButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
mCancelValidation = true;
}
});
mPauseButton.setText(R.string.text_button_cancel_verify);
super.onPreExecute();
}
@Override
protected Boolean doInBackground(Object... params)
{
for (XAPKFile xf : xAPKS)
{
String fileName = Helpers.getExpansionAPKFileName(SplashActivity.this, xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(SplashActivity.this, fileName, xf.mFileSize, false))
return false;
fileName = Helpers.generateSaveFileName(SplashActivity.this, fileName);
ZipResourceFile zrf;
byte[] buf = new byte[1024 * 256];
try
{
zrf = new ZipResourceFile(fileName);
ZipEntryRO[] entries = zrf.getAllEntries();
/**
* First calculate the total compressed length
*/
long totalCompressedLength = 0;
for (ZipEntryRO entry : entries)
totalCompressedLength += entry.mCompressedLength;
float averageVerifySpeed = 0;
long totalBytesRemaining = totalCompressedLength;
long timeRemaining;
/**
* Then calculate a CRC for every file in the Zip file,
* comparing it to what is stored in the Zip directory.
* Note that for compressed Zip files we must extract
* the contents to do this comparison.
*/
for (ZipEntryRO entry : entries)
{
if (-1 != entry.mCRC32)
{
long length = entry.mUncompressedLength;
CRC32 crc = new CRC32();
DataInputStream dis = null;
try
{
dis = new DataInputStream(zrf.getInputStream(entry.mFileName));
long startTime = SystemClock.uptimeMillis();
while (length > 0)
{
int seek = (int) (length > buf.length ? buf.length : length);
dis.readFully(buf, 0, seek);
crc.update(buf, 0, seek);
length -= seek;
long currentTime = SystemClock.uptimeMillis();
long timePassed = currentTime - startTime;
if (timePassed > 0)
{
float currentSpeedSample = (float) seek / (float) timePassed;
if (0 != averageVerifySpeed)
averageVerifySpeed = SMOOTHING_FACTOR * currentSpeedSample + (1 - SMOOTHING_FACTOR) * averageVerifySpeed;
else
averageVerifySpeed = currentSpeedSample;
totalBytesRemaining -= seek;
timeRemaining = (long) (totalBytesRemaining / averageVerifySpeed);
this.publishProgress(new DownloadProgressInfo(totalCompressedLength, totalCompressedLength - totalBytesRemaining,
timeRemaining, averageVerifySpeed));
}
startTime = currentTime;
if (mCancelValidation)
return true;
}
if (crc.getValue() != entry.mCRC32)
{
System.out.println("CRC does not match for entry: " + entry.mFileName);
System.out.println("In file: " + entry.getZipFileName());
return false;
}
}
finally
{
if (null != dis)
dis.close();
}
}
}
}
catch (IOException e)
{
e.printStackTrace();
return false;
}
}
return true;
}
@Override
protected void onProgressUpdate(DownloadProgressInfo... values)
{
onDownloadProgress(values[0]);
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Boolean result)
{
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
if (result)
{
mStatusText.setText(R.string.text_validation_complete);
mPauseButton.setText(android.R.string.ok);
}
else
{
mStatusText.setText(R.string.text_validation_failed);
mPauseButton.setText(android.R.string.cancel);
}
mPauseButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
finish();
}
});
super.onPostExecute(result);
}
};
validationTask.execute(new Object());
}
boolean expansionFilesDelivered()
{
for (XAPKFile xf : xAPKS)
{
String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false))
return false;
}
return true; // Always return true;
}
private static class XAPKFile
{
public final boolean mIsMain;
public final int mFileVersion;
public final long mFileSize;
XAPKFile(boolean isMain, int fileVersion, long fileSize)
{
mIsMain = isMain;
mFileVersion = fileVersion;
mFileSize = fileSize;
}
}
private void setButtonPausedState(boolean paused)
{
mStatePaused = paused;
int stringResourceID = paused ? R.string.text_button_resume : R.string.text_button_pause;
mPauseButton.setText(stringResourceID);
}
private void setState(int newState)
{
if (mState != newState)
{
mState = newState;
mStatusText.setText(Helpers.getDownloaderStringResourceIDFromState(newState));
}
}
}
如果有人向我展示正确的方向,我真的很感激。
编辑:我不确定此输出是否与我的问题有关 12-08 11:15:12.431:D / OpenGLRenderer(27491):启用调试模式0 12-08 11:15:42.273:D / libEGL(28577):已加载/system/lib/egl/libEGL_adreno200.so 12-08 11:15:42.274:D / libEGL(28577):已加载/system/lib/egl/libGLESv1_CM_adreno200.so 12-08 11:15:42.276:D / libEGL(28577):已加载/system/lib/egl/libGLESv2_adreno200.so
但是当我按下按钮时,这肯定是相关的 12-08 11:18:58.396:E / ActivityThread(28577):活动com.sample.downloadobb.SplashActivity泄露了最初绑定的ServiceConnection com.google.android.vending.expansion.downloader.DownloaderClientMarshaller$Stub$2@411c1e40 12-08 11:18:58.396:E / ActivityThread(28577):android.app.ServiceConnectionLeaked:Activity com.sample.downloadobb.SplashActivity泄露了ServiceConnection com.google.android.vending.expansion.downloader.DownloaderClientMarshaller $ Stub $ 2 @ 411c1e40最初绑定在这里 12-08 11:18:58.396:E / ActivityThread(28577):在android.app.LoadedApk $ ServiceDispatcher。(LoadedApk.java:974) 12-08 11:18:58.396:E / ActivityThread(28577):在android.app.LoadedApk.getServiceDispatcher(LoadedApk.java:868) 12-08 11:18:58.396:E / ActivityThread(28577):在android.app.ContextImpl.bindService(ContextImpl.java:1462) 12-08 11:18:58.396:E / ActivityThread(28577):在android.app.ContextImpl.bindService(ContextImpl.java:1451) 12-08 11:18:58.396:E / ActivityThread(28577):在android.content.ContextWrapper.bindService(ContextWrapper.java:473) 12-08 11:18:58.396:E / ActivityThread(28577):at com.google.android.vending.expansion.downloader.DownloaderClientMarshaller $ Stub.connect(DownloaderClientMarshaller.java:177) 12-08 11:18:58.396:E / ActivityThread(28577):at com.sample.downloadobb.SplashActivity.onStart(SplashActivity.java:113) 12-08 11:18:58.396:E / ActivityThread(28577):在android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1164) 12-08 11:18:58.396:E / ActivityThread(28577):在android.app.Activity.performStart(Activity.java:5114) 12-08 11:18:58.396:E / ActivityThread(28577):在android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2195) 12-08 11:18:58.396:E / ActivityThread(28577):在android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2272) 12-08 11:18:58.396:E / ActivityThread(28577):在android.app.ActivityThread.access $ 600(ActivityThread.java:167) 12-08 11:18:58.396:E / ActivityThread(28577):在android.app.ActivityThread $ H.handleMessage(ActivityThread.java:1276) 12-08 11:18:58.396:E / ActivityThread(28577):在android.os.Handler.dispatchMessage(Handler.java:99) 12-08 11:18:58.396:E / ActivityThread(28577):在android.os.Looper.loop(Looper.java:137) 12-08 11:18:58.396:E / ActivityThread(28577):在android.app.ActivityThread.main(ActivityThread.java:5158) 12-08 11:18:58.396:E / ActivityThread(28577):at java.lang.reflect.Method.invokeNative(Native Method) 12-08 11:18:58.396:E / ActivityThread(28577):at java.lang.reflect.Method.invoke(Method.java:511) 12-08 11:18:58.396:E / ActivityThread(28577):at com.android.internal.os.ZygoteInit $ MethodAndArgsCaller.run(ZygoteInit.java:793) 12-08 11:18:58.396:E / ActivityThread(28577):at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560) 12-08 11:18:58.396:E / ActivityThread(28577):at dalvik.system.NativeStart.main(Native Method)