Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/225.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java I';我迷路了安卓Apk考试程序_Java_Android_Apk Expansion Files - Fatal编程技术网

Java I';我迷路了安卓Apk考试程序

Java I';我迷路了安卓Apk考试程序,java,android,apk-expansion-files,Java,Android,Apk Expansion Files,我的应用程序最近达到了100 MBs的“最大”大小,因此我必须了解apk扩展过程。我阅读了谷歌的文档,但我发现它有点糟糕,所以我环顾四周,遵循以下指南:。现在,在添加SampleDownloaderActivity、SampleDownloaderService和SampleAlarmReceiver类之后,我丢失了。我在手机的内部存储器中的这个位置创建了一个目录Android/obb/com.mypackage.example,就像谷歌文档中提到的那样,这样我就可以把扩展文件放在那里了。但我的

我的应用程序最近达到了100 MBs的“最大”大小,因此我必须了解apk扩展过程。我阅读了谷歌的文档,但我发现它有点糟糕,所以我环顾四周,遵循以下指南:。现在,在添加SampleDownloaderActivity、SampleDownloaderService和SampleAlarmReceiver类之后,我丢失了。我在手机的内部存储器中的这个位置创建了一个目录Android/obb/com.mypackage.example,就像谷歌文档中提到的那样,这样我就可以把扩展文件放在那里了。但我的问题是:

1) 我已经在应用程序的manifest.xml文件中添加了所需的权限,但我是否需要首先通过代码请求这些权限,以便接收方、下载方和下载方活动正常工作

2) 我想包含在我的主扩展文件中的文件是我的xhdpi drawable文件夹中的一些图像,但我不确定创建包含这些图像的.obb文件需要做什么。我需要用它们创建一个.zip文件吗?我只是把它们放在上面提到的我创建的目录中吗?我需要做什么

3) 假设前面的问题已经回答,如果文件已经存在于目录中或已经下载,我必须通过代码获取目录并读取文件,以便在我的应用程序中使用它们,对吗

4) 如果文件不在那里,我如何从Google Play启动下载?根据我对医生的理解,我需要让我的应用程序的主要活动成为“SampleDownloaderActivity”,对吗

5) 下载完文件后,我是否必须在SampleDownloaderActivity的onCreate方法中创建意图,以便应用程序转到使用这些文件的所需活动

下面,我发布了与apk扩展相关的代码文件,在这些文件中,我更改了我所理解的所需内容。我需要换些别的东西吗?提前感谢您的帮助

ApkExpDownloaderService.java

public class ApkExpDownloaderService extends DownloaderService {
// stuff for LVL -- MODIFY FOR YOUR APPLICATION!
private static final String BASE64_PUBLIC_KEY = "MY_KEY";
// used by the preference obfuscater
private static final byte[] SALT = new byte[] {
        // my array of bytes
};

/**
 * This public key comes from your Android Market publisher account, and it
 * used by the LVL to validate responses from Market on your behalf.
 */
@Override
public String getPublicKey() {
    return BASE64_PUBLIC_KEY;
}

/**
 * This is used by the preference obfuscater to make sure that your
 * obfuscated preferences are different than the ones used by other
 * applications.
 */
@Override
public byte[] getSALT() {
    return SALT;
}

/**
 * Fill this in with the class name for your alarm receiver. We do this
 * because receivers must be unique across all of Android (it's a good idea
 * to make sure that your receiver is in your unique package)
 */
@Override
public String getAlarmReceiverClassName() {
    return ApkExpAlarmReceiver.class.getName();
}

}
public class ApkExpDownloaderActivity extends Activity implements IDownloaderClient {
private static final String LOG_TAG = "LVLDownloader";
private ProgressBar mPB;

private TextView mStatusText;
private TextView mProgressFraction;
private TextView mProgressPercent;
private TextView mAverageSpeed;
private TextView mTimeRemaining;

private View mDashboard;
private View mCellMessage;

private Button mPauseButton;
private Button mWiFiSettingsButton;

private boolean mStatePaused;
private int mState;

private IDownloaderService mRemoteService;

private IStub mDownloaderClientStub;

private void setState(int newState) {
    if (mState != newState) {
        mState = newState;
        mStatusText.setText(Helpers.getDownloaderStringResourceIDFromState(newState));
    }
}

private void setButtonPausedState(boolean paused) {
    mStatePaused = paused;
    int stringResourceID = paused ? R.string.text_button_resume :
            R.string.text_button_pause;
    mPauseButton.setText(stringResourceID);
}

/**
 * This is a little helper class that demonstrates simple testing of an
 * Expansion APK file delivered by Market. You may not wish to hard-code
 * things such as file lengths into your executable... and you may wish to
 * turn this code off during application development.
 */
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;
    }
}

/**
 * Here is where you place the data that the validator will use to determine
 * if the file was delivered correctly. This is encoded in the source code
 * so the application can easily determine whether the file has been
 * properly delivered without having to talk to the server. If the
 * application is using LVL for licensing, it may make sense to eliminate
 * these checks and to just rely on the server.
 */
private static final XAPKFile[] xAPKS = {
        new XAPKFile(
                true, // true signifies a main file
                21, // the version of the APK that the file was uploaded
                   // against
                687801613L // the length of the file in bytes
        )
};

/**
 * Go through each of the APK Expansion files defined in the structure above
 * and determine if the files are present and match the required size. Free
 * applications should definitely consider doing this, as this allows the
 * application to be launched for the first time without having a network
 * connection present. Paid applications that use LVL should probably do at
 * least one LVL check that requires the network to be present, so this is
 * not as necessary.
 * 
 * @return true if they are present.
 */
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;
}

/**
 * Calculating a moving average for the validation speed so we don't get
 * jumpy calculations for time etc.
 */
static private final float SMOOTHING_FACTOR = 0.005f;

/**
 * Used by the async task
 */
private boolean mCancelValidation;

/**
 * Go through each of the Expansion APK files and open each as a zip file.
 * Calculate the CRC for each file and return false if any fail to match.
 * 
 * @return true if XAPKZipFile is successful
 */
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(
                        ApkExpDownloaderActivity.this,
                        xf.mIsMain, xf.mFileVersion);
                if (!Helpers.doesFileExist(ApkExpDownloaderActivity.this, fileName,
                        xf.mFileSize, false))
                    return false;
                fileName = Helpers
                        .generateSaveFileName(ApkExpDownloaderActivity.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) {
                                    Log.e(Constants.TAG,
                                            "CRC does not match for entry: "
                                                    + entry.mFileName);
                                    Log.e(Constants.TAG,
                                            "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) {
            if (result) {
                mDashboard.setVisibility(View.VISIBLE);
                mCellMessage.setVisibility(View.GONE);
                mStatusText.setText(R.string.text_validation_complete);
                mPauseButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        finish();
                    }
                });
                mPauseButton.setText(android.R.string.ok);
            } else {
                mDashboard.setVisibility(View.VISIBLE);
                mCellMessage.setVisibility(View.GONE);
                mStatusText.setText(R.string.text_validation_failed);
                mPauseButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        finish();
                    }
                });
                mPauseButton.setText(android.R.string.cancel);
            }
            super.onPostExecute(result);
        }

    };
    validationTask.execute(new Object());
}

/**
 * If the download isn't present, we initialize the download UI. This ties
 * all of the controls into the remote service calls.
 */
private void initializeDownloadUI() {
    mDownloaderClientStub = DownloaderClientMarshaller.CreateStub
            (this, ApkExpDownloaderService.class);
    setContentView(R.layout.main);

    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);
        }
    });

}

/**
 * Called when the activity is first create; we wouldn't create a layout in
 * the case where we have the file and are moving to another activity
 * without downloading.
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    /**
     * Both downloading and validation make use of the "download" UI
     */
    initializeDownloadUI();

    /**
     * Before we do anything, are the files we expect already here and
     * delivered (presumably by Market) For free titles, this is probably
     * worth doing. (so no Market request is necessary)
     */
    if (!expansionFilesDelivered()) {

        try {
            Intent launchIntent = ApkExpDownloaderActivity.this
                    .getIntent();
            Intent intentToLaunchThisActivityFromNotification = new Intent(
                    ApkExpDownloaderActivity
                    .this, ApkExpDownloaderActivity.this.getClass());
            intentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                    Intent.FLAG_ACTIVITY_CLEAR_TOP);
            intentToLaunchThisActivityFromNotification.setAction(launchIntent.getAction());

            if (launchIntent.getCategories() != null) {
                for (String category : launchIntent.getCategories()) {
                    intentToLaunchThisActivityFromNotification.addCategory(category);
                }
            }

            // Build PendingIntent used to open this activity from
            // Notification
            PendingIntent pendingIntent = PendingIntent.getActivity(
                    ApkExpDownloaderActivity.this,
                    0, intentToLaunchThisActivityFromNotification,
                    PendingIntent.FLAG_UPDATE_CURRENT);
            // Request to start the download
            int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                    pendingIntent, ApkExpDownloaderService.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();
    }

}

/**
 * Connect the stub to our service on start.
 */
@Override
protected void onStart() {
    if (null != mDownloaderClientStub) {
        mDownloaderClientStub.connect(this);
    }
    super.onStart();
}

/**
 * Disconnect the stub from our service on stop
 */
@Override
protected void onStop() {
    if (null != mDownloaderClientStub) {
        mDownloaderClientStub.disconnect(this);
    }
    super.onStop();
}

//TODO:sp need more info on this
/**
 * Critical implementation detail. In onServiceConnected we create the
 * remote service and marshaler. This is how we pass the client information
 * back to the service so the client can be properly notified of changes. We
 * must do this every time we reconnect to the service.
 */
@Override
public void onServiceConnected(Messenger m) {
    mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
    mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}

/**
 * The download state should trigger changes in the UI --- it may be useful
 * to show the state as being indeterminate at times. This sample can be
 * considered a guideline.
 */
@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);
}

/**
 * Sets the state of the various controls based on the progressinfo object
 * sent from the downloader service.
 */
@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));
}

@Override
protected void onDestroy() {
    this.mCancelValidation = true;
    super.onDestroy();
}

}
public class ApkExpAlarmReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
    try {
        DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, ApkExpDownloaderService.class);
    } catch (NameNotFoundException e) {
        e.printStackTrace();
    }       
}

}
ApkExpDownloaderActivity.java

public class ApkExpDownloaderService extends DownloaderService {
// stuff for LVL -- MODIFY FOR YOUR APPLICATION!
private static final String BASE64_PUBLIC_KEY = "MY_KEY";
// used by the preference obfuscater
private static final byte[] SALT = new byte[] {
        // my array of bytes
};

/**
 * This public key comes from your Android Market publisher account, and it
 * used by the LVL to validate responses from Market on your behalf.
 */
@Override
public String getPublicKey() {
    return BASE64_PUBLIC_KEY;
}

/**
 * This is used by the preference obfuscater to make sure that your
 * obfuscated preferences are different than the ones used by other
 * applications.
 */
@Override
public byte[] getSALT() {
    return SALT;
}

/**
 * Fill this in with the class name for your alarm receiver. We do this
 * because receivers must be unique across all of Android (it's a good idea
 * to make sure that your receiver is in your unique package)
 */
@Override
public String getAlarmReceiverClassName() {
    return ApkExpAlarmReceiver.class.getName();
}

}
public class ApkExpDownloaderActivity extends Activity implements IDownloaderClient {
private static final String LOG_TAG = "LVLDownloader";
private ProgressBar mPB;

private TextView mStatusText;
private TextView mProgressFraction;
private TextView mProgressPercent;
private TextView mAverageSpeed;
private TextView mTimeRemaining;

private View mDashboard;
private View mCellMessage;

private Button mPauseButton;
private Button mWiFiSettingsButton;

private boolean mStatePaused;
private int mState;

private IDownloaderService mRemoteService;

private IStub mDownloaderClientStub;

private void setState(int newState) {
    if (mState != newState) {
        mState = newState;
        mStatusText.setText(Helpers.getDownloaderStringResourceIDFromState(newState));
    }
}

private void setButtonPausedState(boolean paused) {
    mStatePaused = paused;
    int stringResourceID = paused ? R.string.text_button_resume :
            R.string.text_button_pause;
    mPauseButton.setText(stringResourceID);
}

/**
 * This is a little helper class that demonstrates simple testing of an
 * Expansion APK file delivered by Market. You may not wish to hard-code
 * things such as file lengths into your executable... and you may wish to
 * turn this code off during application development.
 */
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;
    }
}

/**
 * Here is where you place the data that the validator will use to determine
 * if the file was delivered correctly. This is encoded in the source code
 * so the application can easily determine whether the file has been
 * properly delivered without having to talk to the server. If the
 * application is using LVL for licensing, it may make sense to eliminate
 * these checks and to just rely on the server.
 */
private static final XAPKFile[] xAPKS = {
        new XAPKFile(
                true, // true signifies a main file
                21, // the version of the APK that the file was uploaded
                   // against
                687801613L // the length of the file in bytes
        )
};

/**
 * Go through each of the APK Expansion files defined in the structure above
 * and determine if the files are present and match the required size. Free
 * applications should definitely consider doing this, as this allows the
 * application to be launched for the first time without having a network
 * connection present. Paid applications that use LVL should probably do at
 * least one LVL check that requires the network to be present, so this is
 * not as necessary.
 * 
 * @return true if they are present.
 */
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;
}

/**
 * Calculating a moving average for the validation speed so we don't get
 * jumpy calculations for time etc.
 */
static private final float SMOOTHING_FACTOR = 0.005f;

/**
 * Used by the async task
 */
private boolean mCancelValidation;

/**
 * Go through each of the Expansion APK files and open each as a zip file.
 * Calculate the CRC for each file and return false if any fail to match.
 * 
 * @return true if XAPKZipFile is successful
 */
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(
                        ApkExpDownloaderActivity.this,
                        xf.mIsMain, xf.mFileVersion);
                if (!Helpers.doesFileExist(ApkExpDownloaderActivity.this, fileName,
                        xf.mFileSize, false))
                    return false;
                fileName = Helpers
                        .generateSaveFileName(ApkExpDownloaderActivity.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) {
                                    Log.e(Constants.TAG,
                                            "CRC does not match for entry: "
                                                    + entry.mFileName);
                                    Log.e(Constants.TAG,
                                            "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) {
            if (result) {
                mDashboard.setVisibility(View.VISIBLE);
                mCellMessage.setVisibility(View.GONE);
                mStatusText.setText(R.string.text_validation_complete);
                mPauseButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        finish();
                    }
                });
                mPauseButton.setText(android.R.string.ok);
            } else {
                mDashboard.setVisibility(View.VISIBLE);
                mCellMessage.setVisibility(View.GONE);
                mStatusText.setText(R.string.text_validation_failed);
                mPauseButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        finish();
                    }
                });
                mPauseButton.setText(android.R.string.cancel);
            }
            super.onPostExecute(result);
        }

    };
    validationTask.execute(new Object());
}

/**
 * If the download isn't present, we initialize the download UI. This ties
 * all of the controls into the remote service calls.
 */
private void initializeDownloadUI() {
    mDownloaderClientStub = DownloaderClientMarshaller.CreateStub
            (this, ApkExpDownloaderService.class);
    setContentView(R.layout.main);

    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);
        }
    });

}

/**
 * Called when the activity is first create; we wouldn't create a layout in
 * the case where we have the file and are moving to another activity
 * without downloading.
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    /**
     * Both downloading and validation make use of the "download" UI
     */
    initializeDownloadUI();

    /**
     * Before we do anything, are the files we expect already here and
     * delivered (presumably by Market) For free titles, this is probably
     * worth doing. (so no Market request is necessary)
     */
    if (!expansionFilesDelivered()) {

        try {
            Intent launchIntent = ApkExpDownloaderActivity.this
                    .getIntent();
            Intent intentToLaunchThisActivityFromNotification = new Intent(
                    ApkExpDownloaderActivity
                    .this, ApkExpDownloaderActivity.this.getClass());
            intentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                    Intent.FLAG_ACTIVITY_CLEAR_TOP);
            intentToLaunchThisActivityFromNotification.setAction(launchIntent.getAction());

            if (launchIntent.getCategories() != null) {
                for (String category : launchIntent.getCategories()) {
                    intentToLaunchThisActivityFromNotification.addCategory(category);
                }
            }

            // Build PendingIntent used to open this activity from
            // Notification
            PendingIntent pendingIntent = PendingIntent.getActivity(
                    ApkExpDownloaderActivity.this,
                    0, intentToLaunchThisActivityFromNotification,
                    PendingIntent.FLAG_UPDATE_CURRENT);
            // Request to start the download
            int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                    pendingIntent, ApkExpDownloaderService.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();
    }

}

/**
 * Connect the stub to our service on start.
 */
@Override
protected void onStart() {
    if (null != mDownloaderClientStub) {
        mDownloaderClientStub.connect(this);
    }
    super.onStart();
}

/**
 * Disconnect the stub from our service on stop
 */
@Override
protected void onStop() {
    if (null != mDownloaderClientStub) {
        mDownloaderClientStub.disconnect(this);
    }
    super.onStop();
}

//TODO:sp need more info on this
/**
 * Critical implementation detail. In onServiceConnected we create the
 * remote service and marshaler. This is how we pass the client information
 * back to the service so the client can be properly notified of changes. We
 * must do this every time we reconnect to the service.
 */
@Override
public void onServiceConnected(Messenger m) {
    mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
    mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}

/**
 * The download state should trigger changes in the UI --- it may be useful
 * to show the state as being indeterminate at times. This sample can be
 * considered a guideline.
 */
@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);
}

/**
 * Sets the state of the various controls based on the progressinfo object
 * sent from the downloader service.
 */
@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));
}

@Override
protected void onDestroy() {
    this.mCancelValidation = true;
    super.onDestroy();
}

}
public class ApkExpAlarmReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
    try {
        DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, ApkExpDownloaderService.class);
    } catch (NameNotFoundException e) {
        e.printStackTrace();
    }       
}

}
  • 权限是一个更大的主题,独立于OBB文件。我建议你不要在这里解释。简而言之,对于6.0之前的Android设备,它们只需要在清单中。对于6.0之后的Android设备,您应该在运行时的适当时刻请求它们
  • OBB顾名思义是一个“不透明”的二进制blob。你可以选择任何你想要的文件格式,谷歌只是把它当作一团比特。很多应用程序都使用zip文件,但你不必这样做,你可以使用任何你喜欢的文件格式
  • 示例下载活动是顾名思义的一个示例。您可以使用该活动,也可以在自己的应用程序中重复使用部分代码进行下载。无论您做什么,下载都可能需要一段时间,需要权限,并且脱机时无法工作,因此您需要为所有这些情况的用户显示适当的用户界面。对于不同的应用程序,合适的UI会有所不同

  • 嗨,谢谢你的回复!不过,我还有最后两个问题:我非常困惑,我需要做什么才能将我的.zip文件和前面提到的目录中的所有图像放在一起?我的意思是,我必须手动添加它吗?或者我只是通过代码在目录中查找,如果它在那里,我可以解压图像,或者开始下载丢失的图像?最后,我是否需要提供SampleDownloaderActivity类的url以便下载文件,或者它是否自动连接到Google Play中的正确位置?SampleDownloaderActivity不应该只是您使用的一个类。这是一个“示例”-您应该阅读并理解代码以了解其工作原理。如果阅读示例代码,您可以回答自己的问题。当用户安装OBB文件时,Google将始终下载并将其放在正确的位置。您需要下载代码的唯一原因是在用户选择删除文件时替换该文件,用户在尝试节省空间时通常会这样做。这些都在文档中解释了1)是的,我们知道权限是如何工作的,但是OBB应该是在下载主apk的同时下载的。因此,权限是自动授予的,还是仅适用于初始下载?2)没有真正回答,问题是目录结构是否需要与res中的相同,或者如何添加资源,以便从那里加载可绘制文件,或者应该把它当作一个需要加载实际文件的外部文件夹。你能让它工作吗?我也有同样的问题,我刚要重新开始研究。没有任何运气yetOk。如果我发现了,我会在这里发布步骤。太棒了。明天开始这里也一样