aee20f92b8
When appropriate, changed references to "draft" apps to alpha and beta apps. Left the "draft" info in the deprecated file v2/billing_integrate.jd, but added a note that draft apps are no longer supported. Docs are staged to: in-app billing: http://asolovay.mtv:9011/google/play/billing/billing_admin.html http://asolovay.mtv:9011/google/play/billing/billing_testing.html http://asolovay.mtv:9011/google/play/billing/v2/billing_integrate.html (deprecated doc; just noted that draft is no longer supported) LVL: http://asolovay.mtv:9011/google/play/licensing/overview.html http://asolovay.mtv:9011/google/play/licensing/licensing-reference.html OBBs http://asolovay.mtv:9011/google/play/expansion-files.html As per a request from Dirk, I added a section to the "Testing In-App Billing" page noting that draft apps are no longer supported, and linking to the section on alpha and beta channels; I put in a link to the new section in each place where we'd previously talked about draft apps. Also fixed formatting in a few code samples, where over-long lines were forcing the code box to use a scroll bar. bug: 15013770 Change-Id: I1368b9b5ebf2a0c22f84fcbcd5f37401b390c423
1282 lines
61 KiB
Plaintext
1282 lines
61 KiB
Plaintext
page.title=APK Expansion Files
|
|
page.metaDescription=If your app needs more than the 50MB APK max, use free APK expansion files from Google Play.
|
|
page.tags="apk size, apk max, large assets"
|
|
@jd:body
|
|
|
|
|
|
<div id="qv-wrapper">
|
|
<div id="qv">
|
|
<h2>Quickview</h2>
|
|
<ul>
|
|
<li>Recommended for most apps that exceed the 50MB APK limit</li>
|
|
<li>You can provide up to 4GB of additional data for each APK</li>
|
|
<li>Google Play hosts and serves the expansion files at no charge</li>
|
|
<li>The files can be any file type you want and are saved to the device's shared storage</li>
|
|
</ul>
|
|
|
|
<h2>In this document</h2>
|
|
<ol>
|
|
<li><a href="#Overview">Overview</a>
|
|
<ol>
|
|
<li><a href="#Filename">File name format</a></li>
|
|
<li><a href="#StorageLocation">Storage location</a></li>
|
|
<li><a href="#DownloadProcess">Download process</a></li>
|
|
<li><a href="#Checklist">Development checklist</a></li>
|
|
</ol>
|
|
</li>
|
|
<li><a href="#Rules">Rules and Limitations</a></li>
|
|
<li><a href="#Downloading">Downloading the Expansion Files</a>
|
|
<ol>
|
|
<li><a href="#AboutLibraries">About the Downloader Library</a></li>
|
|
<li><a href="#Preparing">Preparing to use the Downloader Library</a></li>
|
|
<li><a href="#Permissions">Declaring user permissions</a></li>
|
|
<li><a href="#DownloaderService">Implementing the downloader service</a></li>
|
|
<li><a href="#AlarmReceiver">Implementing the alarm receiver</a></li>
|
|
<li><a href="#Download">Starting the download</a></li>
|
|
<li><a href="#Progress">Receiving download progress</a></li>
|
|
</ol>
|
|
</li>
|
|
<li><a href="#ExpansionPolicy">Using APKExpansionPolicy</a></li>
|
|
<li><a href="#ReadingTheFile">Reading the Expansion File</a>
|
|
<ol>
|
|
<li><a href="#GettingFilenames">Getting the file names</a></li>
|
|
<li><a href="#ZipLib">Using the APK Expansion Zip Library</a></li>
|
|
</ol>
|
|
</li>
|
|
<li><a href="#Testing">Testing Your Expansion Files</a>
|
|
<ol>
|
|
<li><a href="#TestingReading">Testing file reads</a></li>
|
|
<li><a href="#TestingReading">Testing file downloads</a></li>
|
|
</ol>
|
|
</li>
|
|
<li><a href="#Updating">Updating Your Application</a></li>
|
|
</ol>
|
|
|
|
<h2>See also</h2>
|
|
<ol>
|
|
<li><a href="{@docRoot}google/play/licensing/index.html">Application Licensing</a></li>
|
|
<li><a href="{@docRoot}google/play/publishing/multiple-apks.html">Multiple
|
|
APK Support</a></li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<p>Google Play currently requires that your APK file be no more than 50MB. For most
|
|
applications, this is plenty of space for all the application's code and assets.
|
|
However, some apps need more space for high-fidelity graphics, media files, or other large assets.
|
|
Previously, if your app exceeded 50MB, you had to host and download the additional resources
|
|
yourself when the user opens the app. Hosting and serving the extra files can be costly, and the
|
|
user experience is often less than ideal. To make this process easier for you and more pleasant
|
|
for users, Google Play allows you to attach two large expansion files that supplement your
|
|
APK.</p>
|
|
|
|
<p>Google Play hosts the expansion files for your application and serves them to the device at
|
|
no cost to you. The expansion files are saved to the device's shared storage location (the
|
|
SD card or USB-mountable partition; also known as the "external" storage) where your app can access
|
|
them. On most devices, Google Play downloads the expansion file(s) at the same time it
|
|
downloads the APK, so your application has everything it needs when the user opens it for the
|
|
first time. In some cases, however, your application must download the files from Google Play
|
|
when your application starts.</p>
|
|
|
|
|
|
|
|
<h2 id="Overview">Overview</h2>
|
|
|
|
<p>Each time you upload an APK using the Google Play Developer Console, you have the option to
|
|
add one or two expansion files to the APK. Each file can be up to 2GB and it can be any format you
|
|
choose, but we recommend you use a compressed file to conserve bandwidth during the download.
|
|
Conceptually, each expansion file plays a different role:</p>
|
|
|
|
<ul>
|
|
<li>The <strong>main</strong> expansion file is the
|
|
primary expansion file for additional resources required by your application.</li>
|
|
<li>The <strong>patch</strong> expansion file is optional and intended for small updates to the
|
|
main expansion file.</li>
|
|
</ul>
|
|
|
|
<p>While you can use the two expansion files any way you wish, we recommend that the main
|
|
expansion file deliver the primary assets and should rarely if ever updated; the patch expansion
|
|
file should be smaller and serve as a “patch carrier,” getting updated with each major
|
|
release or as necessary.</p>
|
|
|
|
<p>However, even if your application update requires only a new patch expansion file, you still must
|
|
upload a new APK with an updated <a
|
|
href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code
|
|
versionCode}</a> in the manifest. (The
|
|
Developer Console does not allow you to upload an expansion file to an existing APK.)</p>
|
|
|
|
<p class="note"><strong>Note:</strong> The patch expansion file is semantically the same as the
|
|
main expansion file—you can use each file any way you want. The system does
|
|
not use the patch expansion file to perform patching for your app. You must perform patching
|
|
yourself or be able to distinguish between the two files.</p>
|
|
|
|
|
|
|
|
<h3 id="Filename">File name format</h3>
|
|
|
|
<p>Each expansion file you upload can be any format you choose (ZIP, PDF, MP4, etc.). You can also
|
|
use the <a href="{@docRoot}tools/help/jobb.html">JOBB</a> tool to encapsulate and encrypt a set
|
|
of resource files and subsequent patches for that set. Regardless of the file type, Google Play
|
|
considers them opaque binary blobs and renames the files using the following scheme:</p>
|
|
|
|
<pre class="classic no-pretty-print">
|
|
[main|patch].<expansion-version>.<package-name>.obb
|
|
</pre>
|
|
|
|
<p>There are three components to this scheme:</p>
|
|
|
|
<dl>
|
|
<dt>{@code main} or {@code patch}</dt>
|
|
<dd>Specifies whether the file is the main or patch expansion file. There can be
|
|
only one main file and one patch file for each APK.</dd>
|
|
<dt>{@code <expansion-version>}</dt>
|
|
<dd>This is an integer that matches the version code of the APK with which the expansion is
|
|
<em>first</em> associated (it matches the application's <a
|
|
href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code android:versionCode}</a>
|
|
value).
|
|
<p>"First" is emphasized because although the Developer Console allows you to
|
|
re-use an uploaded expansion file with a new APK, the expansion file's name does not change—it
|
|
retains the version applied to it when you first uploaded the file.</p></dd>
|
|
<dt>{@code <package-name>}</dt>
|
|
<dd>Your application's Java-style package name.</dd>
|
|
</dl>
|
|
|
|
<p>For example, suppose your APK version is 314159 and your package name is com.example.app. If you
|
|
upload a main expansion file, the file is renamed to:</p>
|
|
<pre class="classic no-pretty-print">main.314159.com.example.app.obb</pre>
|
|
|
|
|
|
<h3 id="StorageLocation">Storage location</h3>
|
|
|
|
<p>When Google Play downloads your expansion files to a device, it saves them to the system's
|
|
shared storage location. To ensure proper behavior, you must not delete, move, or rename the
|
|
expansion files. In the event that your application must perform the download from Google Play
|
|
itself, you must save the files to the exact same location.</p>
|
|
|
|
<p>The specific location for your expansion files is:</p>
|
|
|
|
<pre class="classic no-pretty-print">
|
|
<shared-storage>/Android/obb/<package-name>/
|
|
</pre>
|
|
|
|
<ul>
|
|
<li>{@code <shared-storage>} is the path to the shared storage space, available from
|
|
{@link android.os.Environment#getExternalStorageDirectory()}.</li>
|
|
<li>{@code <package-name>} is your application's Java-style package name, available
|
|
from {@link android.content.Context#getPackageName()}.</li>
|
|
</ul>
|
|
|
|
<p>For each application, there are never more than two expansion files in this directory.
|
|
One is the main expansion file and the other is the patch expansion file (if necessary). Previous
|
|
versions are overwritten when you update your application with new expansion files.</p>
|
|
|
|
<p>If you must unpack the contents of your expansion files, <strong>do not</strong> delete the
|
|
{@code .obb} expansion files afterwards and <strong>do not</strong> save the unpacked data
|
|
in the same directory. You should save your unpacked files in the directory
|
|
specified by {@link android.content.Context#getExternalFilesDir getExternalFilesDir()}. However,
|
|
if possible, it's best if you use an expansion file format that allows you to read directly from
|
|
the file instead of requiring you to unpack the data. For example, we've provided a library
|
|
project called the <a href="#ZipLib">APK Expansion Zip Library</a> that reads your data directly
|
|
from the ZIP file.</p>
|
|
|
|
<p class="note"><strong>Note:</strong> Unlike APK files, any files saved on the shared storage can
|
|
be read by the user and other applications.</p>
|
|
|
|
<p class="note"><strong>Tip:</strong> If you're packaging media files into a ZIP, you can use media
|
|
playback calls on the files with offset and length controls (such as {@link
|
|
android.media.MediaPlayer#setDataSource(FileDescriptor,long,long) MediaPlayer.setDataSource()} and
|
|
{@link android.media.SoundPool#load(FileDescriptor,long,long,int) SoundPool.load()}) without the
|
|
need to unpack your ZIP. In order for this to work, you must not perform additional compression on
|
|
the media files when creating the ZIP packages. For example, when using the <code>zip</code> tool,
|
|
you should use the <code>-n</code> option to specify the file suffixes that should not be
|
|
compressed: <br/>
|
|
<code>zip -n .mp4;.ogg main_expansion media_files</code></p>
|
|
|
|
|
|
<h3 id="DownloadProcess">Download process</h3>
|
|
|
|
<p>Most of the time, Google Play downloads and saves your expansion files at the same time it
|
|
downloads the APK to the device. However, in some cases Google Play
|
|
cannot download the expansion files or the user might have deleted previously downloaded expansion
|
|
files. To handle these situations, your app must be able to download the files
|
|
itself when the main activity starts, using a URL provided by Google Play.</p>
|
|
|
|
<p>The download process from a high level looks like this:</p>
|
|
|
|
<ol>
|
|
<li>User selects to install your app from Google Play.</li>
|
|
<li>If Google Play is able to download the expansion files (which is the case for most
|
|
devices), it downloads them along with the APK.
|
|
<p>If Google Play is unable to download the expansion files, it downloads the
|
|
APK only.</p>
|
|
</li>
|
|
<li>When the user launches your application, your app must check whether the expansion files are
|
|
already saved on the device.
|
|
<ol>
|
|
<li>If yes, your app is ready to go.</li>
|
|
<li>If no, your app must download the expansion files over HTTP from Google Play. Your app
|
|
must send a request to the Google Play client using the Google Play's <a
|
|
href="{@docRoot}google/play/licensing/index.html">Application Licensing</a> service, which
|
|
responds with the name, file size, and URL for each expansion file. With this information, you then
|
|
download the files and save them to the proper <a href="#StorageLocation">storage location</a>.</li>
|
|
</ol>
|
|
</li>
|
|
</ol>
|
|
|
|
<p class="caution"><strong>Caution:</strong> It is critical that you include the necessary code to
|
|
download the expansion files from Google Play in the event that the files are not already on the
|
|
device when your application starts. As discussed in the following section about <a
|
|
href="#Downloading">Downloading the Expansion Files</a>, we've made a library available to you that
|
|
greatly simplifies this process and performs the download from a service with a minimal amount of
|
|
code from you.</p>
|
|
|
|
|
|
|
|
|
|
<h3 id="Checklist">Development checklist</h3>
|
|
|
|
<p>Here's a summary of the tasks you should perform to use expansion files with your
|
|
application:</p>
|
|
|
|
<ol>
|
|
<li>First determine whether your application absolutely requires more than 50MB per installation.
|
|
Space is precious and you should keep your total application size as small as possible. If your app
|
|
uses more than 50MB in order to provide multiple versions of your graphic assets for multiple screen
|
|
densities, consider instead publishing <a
|
|
href="{@docRoot}google/play/publishing/multiple-apks.html">multiple APKs</a> in which each APK
|
|
contains only the assets required for the screens that it targets.</li>
|
|
<li>Determine which application resources to separate from your APK and package them in a
|
|
file to use as the main expansion file.
|
|
<p>Normally, you should only use the second patch expansion file when performing updates to
|
|
the main expansion file. However, if your resources exceed the 2GB limit for the main
|
|
expansion file, you can use the patch file for the rest of your assets.</p>
|
|
</li>
|
|
<li>Develop your application such that it uses the resources from your expansion files in the
|
|
device's <a href="#StorageLocation">shared storage location</a>.
|
|
<p>Remember that you must not delete, move, or rename the expansion files.</p>
|
|
<p>If your application doesn't demand a specific format, we suggest you create ZIP files for
|
|
your expansion files, then read them using the <a href="#ZipLib">APK Expansion Zip
|
|
Library</a>.</p>
|
|
</li>
|
|
<li>Add logic to your application's main activity that checks whether the expansion files
|
|
are on the device upon start-up. If the files are not on the device, use Google Play's <a
|
|
href="{@docRoot}google/play/licensing/index.html">Application Licensing</a> service to request URLs
|
|
for the expansion files, then download and save them.
|
|
<p>To greatly reduce the amount of code you must write and ensure a good user experience
|
|
during the download, we recommend you use the <a href="#AboutLibraries">Downloader
|
|
Library</a> to implement your download behavior.</p>
|
|
<p>If you build your own download service instead of using the library, be aware that you
|
|
must not change the name of the expansion files and must save them to the proper
|
|
<a href="#StorageLocation">storage location</a>.</p></li>
|
|
</ol>
|
|
|
|
<p>Once you've finished your application development, follow the guide to <a href="#Testing">Testing
|
|
Your Expansion Files</a>.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h2 id="Rules">Rules and Limitations</h2>
|
|
|
|
<p>Adding APK expansion files is a feature available when you upload your application using the
|
|
Developer Console. When uploading your application for the first time or updating an
|
|
application that uses expansion files, you must be aware of the following rules and limitations:</p>
|
|
|
|
<ol type="I">
|
|
<li>Each expansion file can be no more than 2GB.</li>
|
|
<li>In order to download your expansion files from Google Play, <strong>the user must have
|
|
acquired your application from Google Play</strong>. Google Play will not
|
|
provide the URLs for your expansion files if the application was installed by other means.</li>
|
|
<li>When performing the download from within your application, the URL that Google Play
|
|
provides for each file is unique for every download and each one expires shortly after it is given
|
|
to your application.</li>
|
|
<li>If you update your application with a new APK or upload <a
|
|
href="{@docRoot}google/play/publishing/multiple-apks.html">multiple APKs</a> for the same
|
|
application, you can select expansion files that you've uploaded for a previous APK. <strong>The
|
|
expansion file's name does not change</strong>—it retains the version received by the APK to
|
|
which the file was originally associated.</li>
|
|
<li>If you use expansion files in combination with <a
|
|
href="{@docRoot}google/play/publishing/multiple-apks.html">multiple APKs</a> in order to
|
|
provide different expansion files for different devices, you still must upload separate APKs
|
|
for each device in order to provide a unique <a
|
|
href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code versionCode}</a>
|
|
value and declare different <a href="{@docRoot}google/play/filters.html">filters</a> for
|
|
each APK.</li>
|
|
<li>You cannot issue an update to your application by changing the expansion files
|
|
alone—<strong>you must upload a new APK</strong> to update your app. If your changes only
|
|
concern the assets in your expansion files, you can update your APK simply by changing the <a
|
|
href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code versionCode}</a> (and
|
|
perhaps also the <a href="{@docRoot}guide/topics/manifest/manifest-element.html#vname">{@code
|
|
versionName}</a>).</p></li>
|
|
<li><strong>Do not save other data into your <code>obb/</code>
|
|
directory</strong>. If you must unpack some data, save it into the location specified by {@link
|
|
android.content.Context#getExternalFilesDir getExternalFilesDir()}.</li>
|
|
<li><strong>Do not delete or rename the {@code .obb} expansion file</strong> (unless you're
|
|
performing an update). Doing so will cause Google Play (or your app itself) to repeatedly
|
|
download the expansion file.</li>
|
|
<li>When updating an expansion file manually, you must delete the previous expansion file.</li>
|
|
</ol>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h2 id="Downloading">Downloading the Expansion Files</h2>
|
|
|
|
<p>In most cases, Google Play downloads and saves your expansion files to the device at the same
|
|
time it installs or updates the APK. This way, the expansion files are available when your
|
|
application launches for the first time. However, in some cases your app must download the
|
|
expansion files itself by requesting them from a URL provided to you in a response
|
|
from Google Play's <a
|
|
href="{@docRoot}google/play/licensing/index.html">Application Licensing</a> service.</p>
|
|
|
|
<p>The basic logic you need to download your expansion files is the following:</p>
|
|
|
|
<ol>
|
|
<li>When your application starts, look for the expansion files on the <a
|
|
href="#StorageLocation">shared storage location</a> (in the
|
|
<code>Android/obb/<package-name>/</code> directory).
|
|
<ol type="a">
|
|
<li>If the expansion files are there, you're all set and your application can continue.</li>
|
|
<li>If the expansion files are <em>not</em> there:
|
|
<ol>
|
|
<li>Perform a request using Google Play's <a
|
|
href="{@docRoot}google/play/licensing/index.html">Application Licensing</a> to get your
|
|
app's expansion file names, sizes, and URLs.</li>
|
|
<li>Use the URLs provided by Google Play to download the expansion files and save
|
|
the expansion files. You <strong>must</strong> save the files to the <a
|
|
href="#StorageLocation">shared storage location</a>
|
|
(<code>Android/obb/<package-name>/</code>) and use the exact file name provided
|
|
by Google Play's response.
|
|
<p class="note"><strong>Note:</strong> The URL that Google Play provides for your
|
|
expansion files is unique for every download and each one expires shortly after it is given to
|
|
your application.</p>
|
|
</li>
|
|
</ol>
|
|
</li>
|
|
</ol>
|
|
</li>
|
|
</ol>
|
|
|
|
|
|
<p>If your application is free (not a paid app), then you probably haven't used the <a
|
|
href="{@docRoot}google/play/licensing/index.html">Application Licensing</a> service. It's primarily
|
|
designed for you to enforce
|
|
licensing policies for your application and ensure that the user has the right to
|
|
use your app (he or she rightfully paid for it on Google Play). In order to facilitate the
|
|
expansion file functionality, the licensing service has been enhanced to provide a response
|
|
to your application that includes the URL of your application's expansion files that are hosted
|
|
on Google Play. So, even if your application is free for users, you need to include the
|
|
License Verification Library (LVL) to use APK expansion files. Of course, if your application
|
|
is free, you don't need to enforce license verification—you simply need the
|
|
library to perform the request that returns the URL of your expansion files.</p>
|
|
|
|
<p class="note"><strong>Note:</strong> Whether your application is free or not, Google Play
|
|
returns the expansion file URLs only if the user acquired your application from Google Play.</p>
|
|
|
|
<p>In addition to the LVL, you need a set of code that downloads the expansion files
|
|
over an HTTP connection and saves them to the proper location on the device's shared storage.
|
|
As you build this procedure into your application, there are several issues you should take into
|
|
consideration:</p>
|
|
|
|
<ul>
|
|
<li>The device might not have enough space for the expansion files, so you should check
|
|
before beginning the download and warn the user if there's not enough space.</li>
|
|
<li>File downloads should occur in a background service in order to avoid blocking the user
|
|
interaction and allow the user to leave your app while the download completes.</li>
|
|
<li>A variety of errors might occur during the request and download that you must
|
|
gracefully handle.</li>
|
|
<li>Network connectivity can change during the download, so you should handle such changes and
|
|
if interrupted, resume the download when possible.</li>
|
|
<li>While the download occurs in the background, you should provide a notification that
|
|
indicates the download progress, notifies the user when it's done, and takes the user back to
|
|
your application when selected.</li>
|
|
</ul>
|
|
|
|
|
|
<p>To simplify this work for you, we've built the <a href="#AboutLibraries">Downloader Library</a>,
|
|
which requests the expansion file URLs through the licensing service, downloads the expansion files,
|
|
performs all of the tasks listed above, and even allows your activity to pause and resume the
|
|
download. By adding the Downloader Library and a few code hooks to your application, almost all the
|
|
work to download the expansion files is already coded for you. As such, in order to provide the best
|
|
user experience with minimal effort on your behalf, we recommend you use the Downloader Library to
|
|
download your expansion files. The information in the following sections explain how to integrate
|
|
the library into your application.</p>
|
|
|
|
<p>If you'd rather develop your own solution to download the expansion files using the Google
|
|
Play URLs, you must follow the <a href="{@docRoot}google/play/licensing/index.html">Application
|
|
Licensing</a> documentation to perform a license request, then retrieve the expansion file names,
|
|
sizes, and URLs from the response extras. You should use the <a href="#ExpansionPolicy">{@code
|
|
APKExpansionPolicy}</a> class (included in the License Verification Library) as your licensing
|
|
policy, which captures the expansion file names, sizes, and URLs from the licensing service..</p>
|
|
|
|
|
|
|
|
<h3 id="AboutLibraries">About the Downloader Library</h3>
|
|
|
|
<p>To use APK expansion files with your application and provide the best user experience with
|
|
minimal effort on your behalf, we recommend you use the Downloader Library that's included in the
|
|
Google Play APK Expansion Library package. This library downloads your expansion files in a
|
|
background service, shows a user notification with the download status, handles network
|
|
connectivity loss, resumes the download when possible, and more.</p>
|
|
|
|
<p>To implement expansion file downloads using the Downloader Library, all you need to do is:</p>
|
|
|
|
<ul>
|
|
<li>Extend a special {@link android.app.Service} subclass and {@link
|
|
android.content.BroadcastReceiver} subclass that each require just a few
|
|
lines of code from you.</li>
|
|
<li>Add some logic to your main activity that checks whether the expansion files have
|
|
already been downloaded and, if not, invokes the download process and displays a
|
|
progress UI.</li>
|
|
<li>Implement a callback interface with a few methods in your main activity that
|
|
receives updates about the download progress.</li>
|
|
</ul>
|
|
|
|
<p>The following sections explain how to set up your app using the Downloader Library.</p>
|
|
|
|
|
|
<h3 id="Preparing">Preparing to use the Downloader Library</h3>
|
|
|
|
<p>To use the Downloader Library, you need to
|
|
download two packages from the SDK Manager and add the appropriate libraries to your
|
|
application.</p>
|
|
|
|
<p>First, open the <a href="{@docRoot}sdk/exploring.html">Android SDK Manager</a>, expand
|
|
<em>Extras</em> and download:</p>
|
|
<ul>
|
|
<li><em>Google Play Licensing Library package</em></li>
|
|
<li><em>Google Play APK Expansion Library package</em></li>
|
|
</ul>
|
|
|
|
<p>If you're using Eclipse, create a project for each library and add it to your app:</p>
|
|
<ol>
|
|
<li>Create a new Library Project for the License Verification Library and Downloader
|
|
Library. For each library:
|
|
<ol>
|
|
<li>Begin a new Android project.</li>
|
|
<li>Select <strong>Create project from existing
|
|
source</strong> and choose the library from the {@code <sdk>/extras/google/} directory
|
|
({@code market_licensing/} for the License Verification Library or {@code
|
|
market_apk_expansion/downloader_library/} for the Downloader Library).</li>
|
|
<li>Specify a <em>Project Name</em> such as "Google Play License Library" and "Google Play
|
|
Downloader
|
|
Library"</li>
|
|
<li>Click <strong>Finish</strong>.</li>
|
|
</ol>
|
|
<p class="note"><strong>Note:</strong> The Downloader Library depends on the License
|
|
Verification Library. Be sure to add the License
|
|
Verification Library to the Downloader Library's project properties (same process as
|
|
steps 2 and 3 below).</p>
|
|
</li>
|
|
<li>Right-click the Android project in which you want to use APK expansion files and
|
|
select <strong>Properties</strong>.</li>
|
|
<li>In the <em>Library</em> panel, click <strong>Add</strong> to select and add each of the
|
|
libraries to your application.</li>
|
|
</ol>
|
|
|
|
<p>Or, from a command line, update your project to include the libraries:</p>
|
|
<ol>
|
|
<li>Change directories to the <code><sdk>/tools/</code> directory.</li>
|
|
<li>Execute <code>android update project</code> with the {@code --library} option to add both the
|
|
LVL and the Downloader Library to your project. For example:
|
|
<pre class="no-pretty-print">
|
|
android update project --path ~/Android/MyApp \
|
|
--library ~/android_sdk/extras/google/market_licensing \
|
|
--library ~/android_sdk/extras/google/market_apk_expansion/downloader_library
|
|
</pre>
|
|
</li>
|
|
</ol>
|
|
|
|
<p>With both the License Verification Library and Downloader Library added to your
|
|
application, you'll be able to quickly integrate the ability to download expansion files from
|
|
Google Play. The format that you choose for the expansion files and how you read them
|
|
from the shared storage is a separate implementation that you should consider based on your
|
|
application needs.</p>
|
|
|
|
<p class="note"><strong>Tip:</strong> The Apk Expansion package includes a sample
|
|
application
|
|
that shows how to use the Downloader Library in an app. The sample uses a third library
|
|
available in the Apk Expansion package called the APK Expansion Zip Library. If
|
|
you plan on
|
|
using ZIP files for your expansion files, we suggest you also add the APK Expansion Zip Library to
|
|
your application. For more information, see the section below
|
|
about <a href="#ZipLib">Using the APK Expansion Zip Library</a>.</p>
|
|
|
|
|
|
|
|
<h3 id="Permissions">Declaring user permissions</h3>
|
|
|
|
<p>In order to download the expansion files, the Downloader Library
|
|
requires several permissions that you must declare in your application's manifest file. They
|
|
are:</p>
|
|
|
|
<pre>
|
|
<manifest ...>
|
|
<!-- Required to access Google Play Licensing -->
|
|
<uses-permission android:name="com.android.vending.CHECK_LICENSE" />
|
|
|
|
<!-- Required to download files from Google Play -->
|
|
<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-permission
|
|
android: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>
|
|
</pre>
|
|
|
|
<p class="note"><strong>Note:</strong> By default, the Downloader Library requires API
|
|
level 4, but the APK Expansion Zip Library requires API level 5.</p>
|
|
|
|
|
|
<h3 id="DownloaderService">Implementing the downloader service</h3>
|
|
|
|
<p>In order to perform downloads in the background, the Downloader Library provides its
|
|
own {@link android.app.Service} subclass called {@code DownloaderService} that you should extend. In
|
|
addition to downloading the expansion files for you, the {@code DownloaderService} also:</p>
|
|
|
|
<ul>
|
|
<li>Registers a {@link android.content.BroadcastReceiver} that listens for changes to the
|
|
device's network connectivity (the {@link android.net.ConnectivityManager#CONNECTIVITY_ACTION}
|
|
broadcast) in order to pause the download when necessary (such as due to connectivity loss) and
|
|
resume the download when possible (connectivity is acquired).</li>
|
|
<li>Schedules an {@link android.app.AlarmManager#RTC_WAKEUP} alarm to retry the download for
|
|
cases in which the service gets killed.</li>
|
|
<li>Builds a custom {@link android.app.Notification} that displays the download progress and
|
|
any errors or state changes.</li>
|
|
<li>Allows your application to manually pause and resume the download.</li>
|
|
<li>Verifies that the shared storage is mounted and available, that the files don't already exist,
|
|
and that there is enough space, all before downloading the expansion files. Then notifies the user
|
|
if any of these are not true.</li>
|
|
</ul>
|
|
|
|
<p>All you need to do is create a class in your application that extends the {@code
|
|
DownloaderService} class and override three methods to provide specific application details:</p>
|
|
|
|
<dl>
|
|
<dt>{@code getPublicKey()}</dt>
|
|
<dd>This must return a string that is the Base64-encoded RSA public key for your publisher
|
|
account, available from the profile page on the Developer Console (see <a
|
|
href="{@docRoot}google/play/licensing/setting-up.html">Setting Up for Licensing</a>).</dd>
|
|
<dt>{@code getSALT()}</dt>
|
|
<dd>This must return an array of random bytes that the licensing {@code Policy} uses to
|
|
create an <a
|
|
href="{@docRoot}google/play/licensing/adding-licensing.html#impl-Obfuscator">{@code
|
|
Obfuscator}</a>. The salt ensures that your obfuscated {@link android.content.SharedPreferences}
|
|
file in which your licensing data is saved will be unique and non-discoverable.</dd>
|
|
<dt>{@code getAlarmReceiverClassName()}</dt>
|
|
<dd>This must return the class name of the {@link android.content.BroadcastReceiver} in
|
|
your application that should receive the alarm indicating that the download should be
|
|
restarted (which might happen if the downloader service unexpectedly stops).</dd>
|
|
</dl>
|
|
|
|
<p>For example, here's a complete implementation of {@code DownloaderService}:</p>
|
|
|
|
<pre>
|
|
public class SampleDownloaderService extends DownloaderService {
|
|
// You must use the public key belonging to your publisher account
|
|
public static final String BASE64_PUBLIC_KEY = "YourLVLKey";
|
|
// You should also modify this salt
|
|
public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98,
|
|
-100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
|
|
};
|
|
|
|
@Override
|
|
public String getPublicKey() {
|
|
return BASE64_PUBLIC_KEY;
|
|
}
|
|
|
|
@Override
|
|
public byte[] getSALT() {
|
|
return SALT;
|
|
}
|
|
|
|
@Override
|
|
public String getAlarmReceiverClassName() {
|
|
return SampleAlarmReceiver.class.getName();
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<p class="caution"><strong>Notice:</strong> You must update the {@code BASE64_PUBLIC_KEY} value
|
|
to be the public key belonging to your publisher account. You can find the key in the Developer
|
|
Console under your profile information. This is necessary even when testing
|
|
your downloads.</p>
|
|
|
|
<p>Remember to declare the service in your manifest file:</p>
|
|
<pre>
|
|
<application ...>
|
|
<service android:name=".SampleDownloaderService" />
|
|
...
|
|
</application>
|
|
</pre>
|
|
|
|
|
|
|
|
<h3 id="AlarmReceiver">Implementing the alarm receiver</h3>
|
|
|
|
<p>In order to monitor the progress of the file downloads and restart the download if necessary, the
|
|
{@code DownloaderService} schedules an {@link android.app.AlarmManager#RTC_WAKEUP} alarm that
|
|
delivers an {@link android.content.Intent} to a {@link android.content.BroadcastReceiver} in your
|
|
application. You must define the {@link android.content.BroadcastReceiver} to call an API
|
|
from the Downloader Library that checks the status of the download and restarts
|
|
it if necessary.</p>
|
|
|
|
<p>You simply need to override the {@link android.content.BroadcastReceiver#onReceive
|
|
onReceive()} method to call {@code
|
|
DownloaderClientMarshaller.startDownloadServiceIfRequired()}.</p>
|
|
|
|
<p>For example:</p>
|
|
|
|
<pre>
|
|
public class SampleAlarmReceiver extends BroadcastReceiver {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
try {
|
|
DownloaderClientMarshaller.startDownloadServiceIfRequired(context,
|
|
intent, SampleDownloaderService.class);
|
|
} catch (NameNotFoundException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<p>Notice that this is the class for which you must return the name
|
|
in your service's {@code getAlarmReceiverClassName()} method (see the previous section).</p>
|
|
|
|
<p>Remember to declare the receiver in your manifest file:</p>
|
|
<pre>
|
|
<application ...>
|
|
<receiver android:name=".SampleAlarmReceiver" />
|
|
...
|
|
</application>
|
|
</pre>
|
|
|
|
|
|
|
|
<h3 id="Download">Starting the download</h3>
|
|
|
|
<p>The main activity in your application (the one started by your launcher icon) is
|
|
responsible for verifying whether the expansion files are already on the device and initiating
|
|
the download if they are not.</p>
|
|
|
|
<p>Starting the download using the Downloader Library requires the following
|
|
procedures:</p>
|
|
|
|
<ol>
|
|
<li>Check whether the files have been downloaded.
|
|
<p>The Downloader Library includes some APIs in the {@code Helper} class to
|
|
help with this process:</p>
|
|
<ul>
|
|
<li>{@code getExpansionAPKFileName(Context, c, boolean mainFile, int
|
|
versionCode)}</li>
|
|
<li>{@code doesFileExist(Context c, String fileName, long fileSize)}</li>
|
|
</ul>
|
|
<p>For example, the sample app provided in the Apk Expansion package calls the
|
|
following method in the activity's {@link android.app.Activity#onCreate onCreate()} method to check
|
|
whether the expansion files already exist on the device:</p>
|
|
|
|
<pre>
|
|
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;
|
|
}
|
|
</pre>
|
|
|
|
<p>In this case, each {@code XAPKFile} object holds the version number and file size of a known
|
|
expansion file and a boolean as to whether it's the main expansion file. (See the sample
|
|
application's {@code SampleDownloaderActivity} class for details.)</p>
|
|
<p>If this method returns false, then the application must begin the download.</p>
|
|
</li>
|
|
<li>Start the download by calling the static method {@code
|
|
DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent
|
|
notificationClient, Class<?> serviceClass)}.
|
|
<p>The method takes the following parameters:</p>
|
|
<ul>
|
|
<li><code>context</code>: Your application's {@link android.content.Context}.</li>
|
|
<li><code>notificationClient</code>: A {@link android.app.PendingIntent} to start your main
|
|
activity. This is used in the {@link android.app.Notification} that the {@code DownloaderService}
|
|
creates to show the download progress. When the user selects the notification, the system
|
|
invokes the {@link android.app.PendingIntent} you supply here and should open the activity
|
|
that shows the download progress (usually the same activity that started the download).</li>
|
|
<li><code>serviceClass</code>: The {@link java.lang.Class} object for your implementation of
|
|
{@code DownloaderService}, required to start the service and begin the download if necessary.</li>
|
|
</ul>
|
|
<p>The method returns an integer that indicates
|
|
whether or not the download is required. Possible values are:</p>
|
|
<ul>
|
|
<li>{@code NO_DOWNLOAD_REQUIRED}: Returned if the files already
|
|
exist or a download is already in progress.</li>
|
|
<li>{@code LVL_CHECK_REQUIRED}: Returned if a license verification is
|
|
required in order to acquire the expansion file URLs.</li>
|
|
<li>{@code DOWNLOAD_REQUIRED}: Returned if the expansion file URLs are already known,
|
|
but have not been downloaded.</li>
|
|
</ul>
|
|
<p>The behavior for {@code LVL_CHECK_REQUIRED} and {@code DOWNLOAD_REQUIRED} are essentially the
|
|
same and you normally don't need to be concerned about them. In your main activity that calls {@code
|
|
startDownloadServiceIfRequired()}, you can simply check whether or not the response is {@code
|
|
NO_DOWNLOAD_REQUIRED}. If the response is anything <em>other than</em> {@code NO_DOWNLOAD_REQUIRED},
|
|
the Downloader Library begins the download and you should update your activity UI to
|
|
display the download progress (see the next step). If the response <em>is</em> {@code
|
|
NO_DOWNLOAD_REQUIRED}, then the files are available and your application can start.</p>
|
|
<p>For example:</p>
|
|
|
|
<pre>
|
|
@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 = new Intent(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)
|
|
int startResult =
|
|
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
|
|
}
|
|
</pre>
|
|
|
|
</li>
|
|
<li>When the {@code startDownloadServiceIfRequired()} method returns anything <em>other
|
|
than</em> {@code NO_DOWNLOAD_REQUIRED}, create an instance of {@code IStub} by
|
|
calling {@code DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?>
|
|
downloaderService)}. The {@code IStub} provides a binding between your activity to the downloader
|
|
service such that your activity receives callbacks about the download progress.
|
|
<p>In order to instantiate your {@code IStub} by calling {@code CreateStub()}, you must pass it
|
|
an implementation of the {@code IDownloaderClient} interface and your {@code DownloaderService}
|
|
implementation. The next section about <a href="#Progress">Receiving download progress</a> discusses
|
|
the {@code IDownloaderClient} interface, which you should usually implement in your {@link
|
|
android.app.Activity} class so you can update the activity UI when the download state changes.</p>
|
|
<p>We recommend that you call {@code
|
|
CreateStub()} to instantiate your {@code IStub} during your activity's {@link
|
|
android.app.Activity#onCreate onCreate()} method, after {@code startDownloadServiceIfRequired()}
|
|
starts the download. </p>
|
|
<p>For example, in the previous code sample for {@link android.app.Activity#onCreate
|
|
onCreate()}, you can respond to the {@code startDownloadServiceIfRequired()} result like this:</p>
|
|
|
|
<pre>
|
|
// 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;
|
|
}
|
|
</pre>
|
|
|
|
<p>After the {@link android.app.Activity#onCreate onCreate()} method returns, your activity
|
|
receives a call to {@link android.app.Activity#onResume onResume()}, which is where you should then
|
|
call {@code connect()} on the {@code IStub}, passing it your application's {@link
|
|
android.content.Context}. Conversely, you should call
|
|
{@code disconnect()} in your activity's {@link android.app.Activity#onStop onStop()} callback.</p>
|
|
<pre>
|
|
@Override
|
|
protected void onResume() {
|
|
if (null != mDownloaderClientStub) {
|
|
mDownloaderClientStub.connect(this);
|
|
}
|
|
super.onResume();
|
|
}
|
|
|
|
@Override
|
|
protected void onStop() {
|
|
if (null != mDownloaderClientStub) {
|
|
mDownloaderClientStub.disconnect(this);
|
|
}
|
|
super.onStop();
|
|
}
|
|
</pre>
|
|
<p>Calling {@code connect()} on the {@code IStub} binds your activity to the {@code
|
|
DownloaderService} such that your activity receives callbacks regarding changes to the download
|
|
state through the {@code IDownloaderClient} interface.</p>
|
|
</li>
|
|
</ol>
|
|
|
|
|
|
|
|
<h3 id="Progress">Receiving download progress</h3>
|
|
|
|
<p>To receive updates regarding the download progress and to interact with the {@code
|
|
DownloaderService}, you must implement the Downloader Library's {@code IDownloaderClient} interface.
|
|
Usually, the activity you use to start the download should implement this interface in order to
|
|
display the download progress and send requests to the service.</p>
|
|
|
|
<p>The required interface methods for {@code IDownloaderClient} are:</p>
|
|
|
|
<dl>
|
|
<dt>{@code onServiceConnected(Messenger m)}</dt>
|
|
<dd>After you instantiate the {@code IStub} in your activity, you'll receive a call to this
|
|
method, which passes a {@link android.os.Messenger} object that's connected with your instance
|
|
of {@code DownloaderService}. To send requests to the service, such as to pause and resume
|
|
downloads, you must call {@code DownloaderServiceMarshaller.CreateProxy()} to receive the {@code
|
|
IDownloaderService} interface connected to the service.
|
|
<p>A recommended implementation looks like this:</p>
|
|
<pre>
|
|
private IDownloaderService mRemoteService;
|
|
...
|
|
|
|
@Override
|
|
public void onServiceConnected(Messenger m) {
|
|
mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
|
|
mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
|
|
}
|
|
</pre>
|
|
<p>With the {@code IDownloaderService} object initialized, you can send commands to the
|
|
downloader service, such as to pause and resume the download ({@code requestPauseDownload()}
|
|
and {@code requestContinueDownload()}).</p>
|
|
</dd>
|
|
<dt>{@code onDownloadStateChanged(int newState)}</dt>
|
|
<dd>The download service calls this when a change in download state occurs, such as the
|
|
download begins or completes.
|
|
<p>The <code>newState</code> value will be one of several possible values specified in
|
|
by one of the {@code IDownloaderClient} class's {@code STATE_*} constants.</p>
|
|
<p>To provide a useful message to your users, you can request a corresponding string
|
|
for each state by calling {@code Helpers.getDownloaderStringResourceIDFromState()}. This
|
|
returns the resource ID for one of the strings bundled with the Downloader
|
|
Library. For example, the string "Download paused because you are roaming" corresponds to {@code
|
|
STATE_PAUSED_ROAMING}.</p></dd>
|
|
<dt>{@code onDownloadProgress(DownloadProgressInfo progress)}</dt>
|
|
<dd>The download service calls this to deliver a {@code DownloadProgressInfo} object,
|
|
which describes various information about the download progress, including estimated time remaining,
|
|
current speed, overall progress, and total so you can update the download progress UI.</dd>
|
|
</dl>
|
|
<p class="note"><strong>Tip:</strong> For examples of these callbacks that update the download
|
|
progress UI, see the {@code SampleDownloaderActivity} in the sample app provided with the
|
|
Apk Expansion package.</p>
|
|
|
|
<p>Some public methods for the {@code IDownloaderService} interface you might find useful are:</p>
|
|
|
|
<dl>
|
|
<dt>{@code requestPauseDownload()}</dt>
|
|
<dd>Pauses the download.</dd>
|
|
<dt>{@code requestContinueDownload()}</dt>
|
|
<dd>Resumes a paused download.</dd>
|
|
<dt>{@code setDownloadFlags(int flags)}</dt>
|
|
<dd>Sets user preferences for network types on which its OK to download the files. The
|
|
current implementation supports one flag, {@code FLAGS_DOWNLOAD_OVER_CELLULAR}, but you can add
|
|
others. By default, this flag is <em>not</em> enabled, so the user must be on Wi-Fi to download
|
|
expansion files. You might want to provide a user preference to enable downloads over
|
|
the cellular network. In which case, you can call:
|
|
<pre>
|
|
mRemoteService
|
|
.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
|
|
</pre>
|
|
</dd>
|
|
</dl>
|
|
|
|
|
|
|
|
|
|
<h2 id="ExpansionPolicy">Using APKExpansionPolicy</h2>
|
|
|
|
<p>If you decide to build your own downloader service instead of using the Google Play
|
|
<a href="#AboutLibraries">Downloader Library</a>, you should still use the {@code
|
|
APKExpansionPolicy} that's provided in the License Verification Library. The {@code
|
|
APKExpansionPolicy} class is nearly identical to {@code ServerManagedPolicy} (available in the
|
|
Google Play License Verification Library) but includes additional handling for the APK expansion
|
|
file response extras.</p>
|
|
|
|
<p class="note"><strong>Note:</strong> If you <em>do use</em> the <a
|
|
href="#AboutLibraries">Downloader Library</a> as discussed in the previous section, the
|
|
library performs all interaction with the {@code APKExpansionPolicy} so you don't have to use
|
|
this class directly.</p>
|
|
|
|
<p>The class includes methods to help you get the necessary information about the available
|
|
expansion files:</p>
|
|
|
|
<ul>
|
|
<li>{@code getExpansionURLCount()}</li>
|
|
<li>{@code getExpansionURL(int index)}</li>
|
|
<li>{@code getExpansionFileName(int index)}</li>
|
|
<li>{@code getExpansionFileSize(int index)}</li>
|
|
</ul>
|
|
|
|
<p>For more information about how to use the {@code APKExpansionPolicy} when you're <em>not</em>
|
|
using the <a
|
|
href="#AboutLibraries">Downloader Library</a>, see the documentation for <a
|
|
href="{@docRoot}google/play/licensing/adding-licensing.html">Adding Licensing to Your App</a>,
|
|
which explains how to implement a license policy such as this one.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h2 id="ReadingTheFile">Reading the Expansion File</h2>
|
|
|
|
<p>Once your APK expansion files are saved on the device, how you read your files
|
|
depends on the type of file you've used. As discussed in the <a href="#Overview">overview</a>, your
|
|
expansion files can be any kind of file you
|
|
want, but are renamed using a particular <a href="#Filename">file name format</a> and are saved to
|
|
{@code <shared-storage>/Android/obb/<package-name>/}.</p>
|
|
|
|
<p>Regardless of how you read your files, you should always first check that the external
|
|
storage is available for reading. There's a chance that the user has the storage mounted to a
|
|
computer over USB or has actually removed the SD card.</p>
|
|
|
|
<p class="note"><strong>Note:</strong> When your application starts, you should always check whether
|
|
the external storage space is available and readable by calling {@link
|
|
android.os.Environment#getExternalStorageState()}. This returns one of several possible strings
|
|
that represent the state of the external storage. In order for it to be readable by your
|
|
application, the return value must be {@link android.os.Environment#MEDIA_MOUNTED}.</p>
|
|
|
|
|
|
<h3 id="GettingFilenames">Getting the file names</h3>
|
|
|
|
<p>As described in the <a href="#Overview">overview</a>, your APK expansion files are saved
|
|
using a specific file name format:</p>
|
|
|
|
<pre class="classic no-pretty-print">
|
|
[main|patch].<expansion-version>.<package-name>.obb
|
|
</pre>
|
|
|
|
<p>To get the location and names of your expansion files, you should use the
|
|
{@link android.os.Environment#getExternalStorageDirectory()} and {@link
|
|
android.content.Context#getPackageName()} methods to construct the path to your files.</p>
|
|
|
|
<p>Here's a method you can use in your application to get an array containing the complete path
|
|
to both your expansion files:</p>
|
|
|
|
<pre>
|
|
// The shared path to all app expansion files
|
|
private final static String EXP_PATH = "/Android/obb/";
|
|
|
|
static String[] getAPKExpansionFiles(Context ctx, int mainVersion,
|
|
int patchVersion) {
|
|
String packageName = ctx.getPackageName();
|
|
Vector<String> ret = new Vector<String>();
|
|
if (Environment.getExternalStorageState()
|
|
.equals(Environment.MEDIA_MOUNTED)) {
|
|
// Build the full path to the app's expansion files
|
|
File root = Environment.getExternalStorageDirectory();
|
|
File expPath = new File(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 = new File(strMainPath);
|
|
if ( main.isFile() ) {
|
|
ret.add(strMainPath);
|
|
}
|
|
}
|
|
if ( patchVersion > 0 ) {
|
|
String strPatchPath = expPath + File.separator + "patch." +
|
|
mainVersion + "." + packageName + ".obb";
|
|
File main = new File(strPatchPath);
|
|
if ( main.isFile() ) {
|
|
ret.add(strPatchPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
String[] retArray = new String[ret.size()];
|
|
ret.toArray(retArray);
|
|
return retArray;
|
|
}
|
|
</pre>
|
|
|
|
<p>You can call this method by passing it your application {@link android.content.Context}
|
|
and the desired expansion file's version.</p>
|
|
|
|
<p>There are many ways you could determine the expansion file version number. One simple way is to
|
|
save the version in a {@link android.content.SharedPreferences} file when the download begins, by
|
|
querying the expansion file name with the {@code APKExpansionPolicy} class's {@code
|
|
getExpansionFileName(int index)} method. You can then get the version code by reading the {@link
|
|
android.content.SharedPreferences} file when you want to access the expansion
|
|
file.</p>
|
|
|
|
<p>For more information about reading from the shared storage, see the <a
|
|
href="{@docRoot}guide/topics/data/data-storage.html#filesExternal">Data Storage</a>
|
|
documentation.</p>
|
|
|
|
|
|
|
|
<h3 id="ZipLib">Using the APK Expansion Zip Library</h3>
|
|
|
|
<div class="sidebox-wrapper">
|
|
<div class="sidebox">
|
|
<h3>Reading media files from a ZIP</h3>
|
|
<p>If you're using your expansion files to store media files, a ZIP file still allows you to
|
|
use Android media playback calls that provide offset and length controls (such as {@link
|
|
android.media.MediaPlayer#setDataSource(FileDescriptor,long,long) MediaPlayer.setDataSource()} and
|
|
{@link android.media.SoundPool#load(FileDescriptor,long,long,int) SoundPool.load()}). In order for
|
|
this to work, you must not perform additional compression on the media files when creating the ZIP
|
|
packages. For example, when using the <code>zip</code> tool, you should use the <code>-n</code>
|
|
option to specify the file suffixes that should not be compressed:</p>
|
|
<p><code>zip -n .mp4;.ogg main_expansion media_files</code></p>
|
|
</div>
|
|
</div>
|
|
|
|
<p>The Google Market Apk Expansion package includes a library called the APK
|
|
Expansion Zip Library (located in {@code
|
|
<sdk>/extras/google/google_market_apk_expansion/zip_file/}). This is an optional library that
|
|
helps you read your expansion
|
|
files when they're saved as ZIP files. Using this library allows you to easily read resources from
|
|
your ZIP expansion files as a virtual file system.</p>
|
|
|
|
<p>The APK Expansion Zip Library includes the following classes and APIs:</p>
|
|
|
|
<dl>
|
|
<dt>{@code APKExpansionSupport}</dt>
|
|
<dd>Provides some methods to access expansion file names and ZIP files:
|
|
|
|
<dl style="margin-top:1em">
|
|
<dt>{@code getAPKExpansionFiles()}</dt>
|
|
<dd>The same method shown above that returns the complete file path to both expansion
|
|
files.</dd>
|
|
<dt>{@code getAPKExpansionZipFile(Context ctx, int mainVersion, int
|
|
patchVersion)}</dt>
|
|
<dd>Returns a {@code ZipResourceFile} representing the sum of both the main file and
|
|
patch file. That is, if you specify both the <code>mainVersion</code> and the
|
|
<code>patchVersion</code>, this returns a {@code ZipResourceFile} that provides read access to
|
|
all the data, with the patch file's data merged on top of the main file.</dd>
|
|
</dl>
|
|
</dd>
|
|
|
|
<dt>{@code ZipResourceFile}</dt>
|
|
<dd>Represents a ZIP file on the shared storage and performs all the work to provide a virtual
|
|
file system based on your ZIP files. You can get an instance using {@code
|
|
APKExpansionSupport.getAPKExpansionZipFile()} or with the {@code ZipResourceFile} by passing it the
|
|
path to your expansion file. This class includes a variety of useful methods, but you generally
|
|
don't need to access most of them. A couple of important methods are:
|
|
|
|
<dl style="margin-top:1em">
|
|
<dt>{@code getInputStream(String assetPath)}</dt>
|
|
<dd>Provides an {@link java.io.InputStream} to read a file within the ZIP file. The
|
|
<code>assetPath</code> must be the path to the desired file, relative to
|
|
the root of the ZIP file contents.</dd>
|
|
<dt>{@code getAssetFileDescriptor(String assetPath)}</dt>
|
|
<dd>Provides an {@link android.content.res.AssetFileDescriptor} for a file within the
|
|
ZIP file. The <code>assetPath</code> must be the path to the desired file, relative to
|
|
the root of the ZIP file contents. This is useful for certain Android APIs that require an {@link
|
|
android.content.res.AssetFileDescriptor}, such as some {@link android.media.MediaPlayer} APIs.</dd>
|
|
</dl>
|
|
</dd>
|
|
|
|
<dt>{@code APEZProvider}</dt>
|
|
<dd>Most applications don't need to use this class. This class defines a {@link
|
|
android.content.ContentProvider} that marshals the data from the ZIP files through a content
|
|
provider {@link android.net.Uri} in order to provide file access for certain Android APIs that
|
|
expect {@link android.net.Uri} access to media files. For example, this is useful if you want to
|
|
play a video with {@link android.widget.VideoView#setVideoURI VideoView.setVideoURI()}.</p></dd>
|
|
</dl>
|
|
|
|
<h4>Reading from a ZIP file</h4>
|
|
|
|
<p>When using the APK Expansion Zip Library, reading a file from your ZIP usually requires the
|
|
following:</p>
|
|
|
|
<pre>
|
|
// 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);
|
|
</pre>
|
|
|
|
<p>The above code provides access to any file that exists in either your main expansion file or
|
|
patch expansion file, by reading from a merged map of all the files from both files. All you
|
|
need to provide the {@code getAPKExpansionFile()} method is your application {@code
|
|
android.content.Context} and the version number for both the main expansion file and patch
|
|
expansion file.</p>
|
|
|
|
<p>If you'd rather read from a specific expansion file, you can use the {@code
|
|
ZipResourceFile} constructor with the path to the desired expansion file:</p>
|
|
|
|
<pre>
|
|
// 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);
|
|
</pre>
|
|
|
|
<p>For more information about using this library for your expansion files, look at
|
|
the sample application's {@code SampleDownloaderActivity} class, which includes additional code to
|
|
verify the downloaded files using CRC. Beware that if you use this sample as the basis for
|
|
your own implementation, it requires that you <strong>declare the byte size of your expansion
|
|
files</strong> in the {@code xAPKS} array.</p>
|
|
|
|
|
|
|
|
|
|
<h2 id="Testing">Testing Your Expansion Files</h2>
|
|
|
|
<p>Before publishing your application, there are two things you should test: Reading the
|
|
expansion files and downloading the files.</p>
|
|
|
|
|
|
<h3 id="TestingReading">Testing file reads</h3>
|
|
|
|
<p>Before you upload your application to Google Play, you
|
|
should test your application's ability to read the files from the shared storage. All you need to do
|
|
is add the files to the appropriate location on the device shared storage and launch your
|
|
application:</p>
|
|
|
|
<ol>
|
|
<li>On your device, create the appropriate directory on the shared storage where Google
|
|
Play will save your files.
|
|
<p>For example, if your package name is {@code com.example.android}, you need to create
|
|
the directory {@code Android/obb/com.example.android/} on the shared storage space. (Plug in
|
|
your test device to your computer to mount the shared storage and manually create this
|
|
directory.)</p>
|
|
</li>
|
|
<li>Manually add the expansion files to that directory. Be sure that you rename your files to
|
|
match the <a href="#Filename">file name format</a> that Google Play will use.
|
|
<p>For example, regardless of the file type, the main expansion file for the {@code
|
|
com.example.android} application should be {@code main.0300110.com.example.android.obb}.
|
|
The version code can be whatever value you want. Just remember:</p>
|
|
<ul>
|
|
<li>The main expansion file always starts with {@code main} and the patch file starts with
|
|
{@code patch}.</li>
|
|
<li>The package name always matches that of the APK to which the file is attached on
|
|
Google Play.
|
|
</ul>
|
|
</li>
|
|
<li>Now that the expansion file(s) are on the device, you can install and run your application to
|
|
test your expansion file(s).</li>
|
|
</ol>
|
|
|
|
<p>Here are some reminders about handling the expansion files:</p>
|
|
<ul>
|
|
<li><strong>Do not delete or rename</strong> the {@code .obb} expansion files (even if you unpack
|
|
the data to a different location). Doing so will cause Google Play (or your app itself) to
|
|
repeatedly download the expansion file.</li>
|
|
<li><strong>Do not save other data into your <code>obb/</code>
|
|
directory</strong>. If you must unpack some data, save it into the location specified by {@link
|
|
android.content.Context#getExternalFilesDir getExternalFilesDir()}.</li>
|
|
</ul>
|
|
|
|
|
|
|
|
<h3 id="TestingReading">Testing file downloads</h3>
|
|
|
|
<p>Because your application must sometimes manually download the expansion files when it first
|
|
opens, it's important that you test this process to be sure your application can successfully query
|
|
for the URLs, download the files, and save them to the device.</p>
|
|
|
|
<p>To test your application's implementation of the manual download procedure,
|
|
you can publish it to the alpha or beta channel, so it will only be available to
|
|
authorized testers.
|
|
If everything works as expected, your application should begin downloading the expansion
|
|
files as soon as the main activity starts.</p>
|
|
|
|
<p class="note"><strong>Note:</strong> Previously you could test an app by
|
|
uploading an unpublished "draft" version. This functionality is no longer
|
|
supported; instead, you must publish it to the alpha or beta distribution
|
|
channel. For more information, see <a
|
|
href="{@docRoot}google/play/billing/billing_testing.html#draft_apps">Draft Apps
|
|
are No Longer Supported</a>.
|
|
|
|
<h2 id="Updating">Updating Your Application</h2>
|
|
|
|
<p>One of the great benefits to using expansion files on Google Play is the ability to
|
|
update your application without re-downloading all of the original assets. Because Google Play
|
|
allows you to provide two expansion files with each APK, you can use the second file as a "patch"
|
|
that provides updates and new assets. Doing so avoids the
|
|
need to re-download the main expansion file which could be large and expensive for users.</p>
|
|
|
|
<p>The patch expansion file is technically the same as the main expansion file and neither
|
|
the Android system nor Google Play perform actual patching between your main and patch expansion
|
|
files. Your application code must perform any necessary patches itself.</p>
|
|
|
|
<p>If you use ZIP files as your expansion files, the <a href="#ZipLib">APK Expansion Zip
|
|
Library</a> that's included with the Apk Expansion package includes the ability to merge
|
|
your
|
|
patch file with the main expansion file.</p>
|
|
|
|
<p class="note"><strong>Note:</strong> Even if you only need to make changes to the patch
|
|
expansion file, you must still update the APK in order for Google Play to perform an update.
|
|
If you don't require code changes in the application, you should simply update the <a
|
|
href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code versionCode}</a> in the
|
|
manifest.</p>
|
|
|
|
<p>As long as you don't change the main expansion file that's associated with the APK
|
|
in the Developer Console, users who previously installed your application will not
|
|
download the main expansion file. Existing users receive only the updated APK and the new patch
|
|
expansion file (retaining the previous main expansion file).</p>
|
|
|
|
<p>Here are a few issues to keep in mind regarding updates to expansion files:</p>
|
|
|
|
<ul>
|
|
<li>There can be only two expansion files for your application at a time. One main expansion
|
|
file and one patch expansion file. During an update to a file, Google Play deletes the
|
|
previous version (and so must your application when performing manual updates).</li>
|
|
<li>When adding a patch expansion file, the Android system does not actually patch your
|
|
application or main expansion file. You must design your application to support the patch data.
|
|
However, the Apk Expansion package includes a library for using ZIP files
|
|
as expansion files, which merges the data from the patch file into the main expansion file so
|
|
you can easily read all the expansion file data.</li>
|
|
</ul>
|
|
|
|
|
|
|
|
<!-- Tools are not ready.
|
|
|
|
<h3>Using OBB tool and APIs</h3>
|
|
|
|
<pre>
|
|
$ mkobb.sh -d /data/myfiles -k my_secret_key -o /data/data.obb
|
|
$ obbtool a -n com.example.myapp -v 1 -s seed_from_mkobb /data/data.obb
|
|
</pre>
|
|
|
|
<pre>
|
|
storage = (StorageManager) getSystemService( STORAGE_SERVICE );
|
|
storage.mountObb( obbFilepath, "my_secret_key", myListener );
|
|
obbContentPath = storage.getMountedObbPath( obbFilepath );
|
|
</pre>
|
|
-->
|