Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 1 | page.title=APK Expansion Files |
| 2 | @jd:body |
| 3 | |
| 4 | |
| 5 | <div id="qv-wrapper"> |
| 6 | <div id="qv"> |
| 7 | <h2>Quickview</h2> |
| 8 | <ul> |
| 9 | <li>Recommended for most apps that exceed the 50MB APK limit</li> |
| 10 | <li>You can provide up to 4GB of additional data for each APK</li> |
| 11 | <li>Google Play hosts and serves the expansion files at no charge</li> |
| 12 | <li>The files can be any file type you want and are saved to the device's shared storage</li> |
| 13 | </ul> |
| 14 | |
| 15 | <h2>In this document</h2> |
| 16 | <ol> |
| 17 | <li><a href="#Overview">Overview</a> |
| 18 | <ol> |
| 19 | <li><a href="#Filename">File name format</a></li> |
| 20 | <li><a href="#StorageLocation">Storage location</a></li> |
| 21 | <li><a href="#DownloadProcess">Download process</a></li> |
| 22 | <li><a href="#Checklist">Development checklist</a></li> |
| 23 | </ol> |
| 24 | </li> |
| 25 | <li><a href="#Rules">Rules and Limitations</a></li> |
| 26 | <li><a href="#Downloading">Downloading the Expansion Files</a> |
| 27 | <ol> |
| 28 | <li><a href="#AboutLibraries">About the Downloader Library</a></li> |
| 29 | <li><a href="#Preparing">Preparing to use the Downloader Library</a></li> |
| 30 | <li><a href="#Permissions">Declaring user permissions</a></li> |
| 31 | <li><a href="#DownloaderService">Implementing the downloader service</a></li> |
| 32 | <li><a href="#AlarmReceiver">Implementing the alarm receiver</a></li> |
| 33 | <li><a href="#Download">Starting the download</a></li> |
| 34 | <li><a href="#Progress">Receiving download progress</a></li> |
| 35 | </ol> |
| 36 | </li> |
| 37 | <li><a href="#ExpansionPolicy">Using APKExpansionPolicy</a></li> |
| 38 | <li><a href="#ReadingTheFile">Reading the Expansion File</a> |
| 39 | <ol> |
| 40 | <li><a href="#GettingFilenames">Getting the file names</a></li> |
| 41 | <li><a href="#ZipLib">Using the APK Expansion Zip Library</a></li> |
| 42 | </ol> |
| 43 | </li> |
| 44 | <li><a href="#Testing">Testing Your Expansion Files</a> |
| 45 | <ol> |
| 46 | <li><a href="#TestingReading">Testing file reads</a></li> |
| 47 | <li><a href="#TestingReading">Testing file downloads</a></li> |
| 48 | </ol> |
| 49 | </li> |
| 50 | <li><a href="#Updating">Updating Your Application</a></li> |
| 51 | </ol> |
| 52 | |
| 53 | <h2>See also</h2> |
| 54 | <ol> |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 55 | <li><a href="{@docRoot}google/play/licensing/index.html">Application Licensing</a></li> |
| 56 | <li><a href="{@docRoot}google/play/publishing/multiple-apks.html">Multiple |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 57 | APK Support</a></li> |
| 58 | </ol> |
| 59 | </div> |
| 60 | </div> |
| 61 | |
| 62 | |
| 63 | |
| 64 | <p>Google Play currently requires that your APK file be no more than 50MB. For most |
| 65 | applications, this is plenty of space for all the application's code and assets. |
| 66 | However, some apps need more space for high-fidelity graphics, media files, or other large assets. |
| 67 | Previously, if your app exceeded 50MB, you had to host and download the additional resources |
| 68 | yourself when the user opens the app. Hosting and serving the extra files can be costly, and the |
| 69 | user experience is often less than ideal. To make this process easier for you and more pleasant |
| 70 | for users, Google Play allows you to attach two large expansion files that supplement your |
| 71 | APK.</p> |
| 72 | |
| 73 | <p>Google Play hosts the expansion files for your application and serves them to the device at |
| 74 | no cost to you. The expansion files are saved to the device's shared storage location (the |
| 75 | SD card or USB-mountable partition; also known as the "external" storage) where your app can access |
| 76 | them. On most devices, Google Play downloads the expansion file(s) at the same time it |
| 77 | downloads the APK, so your application has everything it needs when the user opens it for the |
| 78 | first time. In some cases, however, your application must download the files from Google Play |
| 79 | when your application starts.</p> |
| 80 | |
| 81 | |
| 82 | |
| 83 | <h2 id="Overview">Overview</h2> |
| 84 | |
| 85 | <p>Each time you upload an APK using the Google Play Android Developer Console, you have the option to |
| 86 | add one or two expansion files to the APK. Each file can be up to 2GB and it can be any format you |
| 87 | choose, but we recommend you use a compressed file to conserve bandwidth during the download. |
| 88 | Conceptually, each expansion file plays a different role:</p> |
| 89 | |
| 90 | <ul> |
| 91 | <li>The <strong>main</strong> expansion file is the |
| 92 | primary expansion file for additional resources required by your application.</li> |
| 93 | <li>The <strong>patch</strong> expansion file is optional and intended for small updates to the |
| 94 | main expansion file.</li> |
| 95 | </ul> |
| 96 | |
| 97 | <p>While you can use the two expansion files any way you wish, we recommend that the main |
| 98 | expansion file deliver the primary assets and should rarely if ever updated; the patch expansion |
| 99 | file should be smaller and serve as a “patch carrier,” getting updated with each major |
| 100 | release or as necessary.</p> |
| 101 | |
| 102 | <p>However, even if your application update requires only a new patch expansion file, you still must |
| 103 | upload a new APK with an updated <a |
| 104 | href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code |
| 105 | versionCode}</a> in the manifest. (The |
| 106 | Developer Console does not allow you to upload an expansion file to an existing APK.)</p> |
| 107 | |
| 108 | <p class="note"><strong>Note:</strong> The patch expansion file is semantically the same as the |
| 109 | main expansion file—you can use each file any way you want. The system does |
| 110 | not use the patch expansion file to perform patching for your app. You must perform patching |
| 111 | yourself or be able to distinguish between the two files.</p> |
| 112 | |
| 113 | |
| 114 | |
| 115 | <h3 id="Filename">File name format</h3> |
| 116 | |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 117 | <p>Each expansion file you upload can be any format you choose (ZIP, PDF, MP4, etc.). You can also |
| 118 | use the <a href="{@docRoot}tools/help/jobb.html">JOBB</a> tool to encapsulate and encrypt a set |
| 119 | of resource files and subsequent patches for that set. Regardless of the file type, Google Play |
| 120 | considers them opaque binary blobs and renames the files using the following scheme:</p> |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 121 | |
| 122 | <pre class="classic no-pretty-print"> |
| 123 | [main|patch].<expansion-version>.<package-name>.obb |
| 124 | </pre> |
| 125 | |
| 126 | <p>There are three components to this scheme:</p> |
| 127 | |
| 128 | <dl> |
| 129 | <dt>{@code main} or {@code patch}</dt> |
| 130 | <dd>Specifies whether the file is the main or patch expansion file. There can be |
| 131 | only one main file and one patch file for each APK.</dd> |
| 132 | <dt>{@code <expansion-version>}</dt> |
| 133 | <dd>This is an integer that matches the version code of the APK with which the expansion is |
| 134 | <em>first</em> associated (it matches the application's <a |
| 135 | href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code android:versionCode}</a> |
| 136 | value). |
| 137 | <p>"First" is emphasized because although the Developer Console allows you to |
| 138 | re-use an uploaded expansion file with a new APK, the expansion file's name does not change—it |
| 139 | retains the version applied to it when you first uploaded the file.</p></dd> |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 140 | <dt>{@code <package-name>}</dt> |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 141 | <dd>Your application's Java-style package name.</dd> |
| 142 | </dl> |
| 143 | |
| 144 | <p>For example, suppose your APK version is 314159 and your package name is com.example.app. If you |
| 145 | upload a main expansion file, the file is renamed to:</p> |
| 146 | <pre class="classic no-pretty-print">main.314159.com.example.app.obb</pre> |
| 147 | |
| 148 | |
| 149 | <h3 id="StorageLocation">Storage location</h3> |
| 150 | |
| 151 | <p>When Google Play downloads your expansion files to a device, it saves them to the system's |
| 152 | shared storage location. To ensure proper behavior, you must not delete, move, or rename the |
| 153 | expansion files. In the event that your application must perform the download from Google Play |
| 154 | itself, you must save the files to the exact same location.</p> |
| 155 | |
| 156 | <p>The specific location for your expansion files is:</p> |
| 157 | |
| 158 | <pre class="classic no-pretty-print"> |
| 159 | <shared-storage>/Android/obb/<package-name>/ |
| 160 | </pre> |
| 161 | |
| 162 | <ul> |
| 163 | <li>{@code <shared-storage>} is the path to the shared storage space, available from |
| 164 | {@link android.os.Environment#getExternalStorageDirectory()}.</li> |
| 165 | <li>{@code <package-name>} is your application's Java-style package name, available |
| 166 | from {@link android.content.Context#getPackageName()}.</li> |
| 167 | </ul> |
| 168 | |
| 169 | <p>For each application, there are never more than two expansion files in this directory. |
| 170 | One is the main expansion file and the other is the patch expansion file (if necessary). Previous |
| 171 | versions are overwritten when you update your application with new expansion files.</p> |
| 172 | |
| 173 | <p>If you must unpack the contents of your expansion files, <strong>do not</strong> delete the |
| 174 | {@code .obb} expansion files afterwards and <strong>do not</strong> save the unpacked data |
| 175 | in the same directory. You should save your unpacked files in the directory |
| 176 | specified by {@link android.content.Context#getExternalFilesDir getExternalFilesDir()}. However, |
| 177 | if possible, it's best if you use an expansion file format that allows you to read directly from |
| 178 | the file instead of requiring you to unpack the data. For example, we've provided a library |
| 179 | project called the <a href="#ZipLib">APK Expansion Zip Library</a> that reads your data directly |
| 180 | from the ZIP file.</p> |
| 181 | |
| 182 | <p class="note"><strong>Note:</strong> Unlike APK files, any files saved on the shared storage can |
| 183 | be read by the user and other applications.</p> |
| 184 | |
| 185 | <p class="note"><strong>Tip:</strong> If you're packaging media files into a ZIP, you can use media |
| 186 | playback calls on the files with offset and length controls (such as {@link |
| 187 | android.media.MediaPlayer#setDataSource(FileDescriptor,long,long) MediaPlayer.setDataSource()} and |
| 188 | {@link android.media.SoundPool#load(FileDescriptor,long,long,int) SoundPool.load()}) without the |
| 189 | need to unpack your ZIP. In order for this to work, you must not perform additional compression on |
| 190 | the media files when creating the ZIP packages. For example, when using the <code>zip</code> tool, |
| 191 | you should use the <code>-n</code> option to specify the file suffixes that should not be |
| 192 | compressed: <br/> |
| 193 | <code>zip -n .mp4;.ogg main_expansion media_files</code></p> |
| 194 | |
| 195 | |
| 196 | <h3 id="DownloadProcess">Download process</h3> |
| 197 | |
| 198 | <p>Most of the time, Google Play downloads and saves your expansion files at the same time it |
| 199 | downloads the APK to the device. However, in some cases Google Play |
| 200 | cannot download the expansion files or the user might have deleted previously downloaded expansion |
| 201 | files. To handle these situations, your app must be able to download the files |
| 202 | itself when the main activity starts, using a URL provided by Google Play.</p> |
| 203 | |
| 204 | <p>The download process from a high level looks like this:</p> |
| 205 | |
| 206 | <ol> |
| 207 | <li>User selects to install your app from Google Play.</li> |
| 208 | <li>If Google Play is able to download the expansion files (which is the case for most |
| 209 | devices), it downloads them along with the APK. |
| 210 | <p>If Google Play is unable to download the expansion files, it downloads the |
| 211 | APK only.</p> |
| 212 | </li> |
| 213 | <li>When the user launches your application, your app must check whether the expansion files are |
| 214 | already saved on the device. |
| 215 | <ol> |
| 216 | <li>If yes, your app is ready to go.</li> |
| 217 | <li>If no, your app must download the expansion files over HTTP from Google Play. Your app |
| 218 | must send a request to the Google Play client using the Google Play's <a |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 219 | href="{@docRoot}google/play/licensing/index.html">Application Licensing</a> service, which |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 220 | responds with the name, file size, and URL for each expansion file. With this information, you then |
| 221 | download the files and save them to the proper <a href="#StorageLocation">storage location</a>.</li> |
| 222 | </ol> |
| 223 | </li> |
| 224 | </ol> |
| 225 | |
| 226 | <p class="caution"><strong>Caution:</strong> It is critical that you include the necessary code to |
| 227 | download the expansion files from Google Play in the event that the files are not already on the |
| 228 | device when your application starts. As discussed in the following section about <a |
| 229 | href="#Downloading">Downloading the Expansion Files</a>, we've made a library available to you that |
| 230 | greatly simplifies this process and performs the download from a service with a minimal amount of |
| 231 | code from you.</p> |
| 232 | |
| 233 | |
| 234 | |
| 235 | |
| 236 | <h3 id="Checklist">Development checklist</h3> |
| 237 | |
| 238 | <p>Here's a summary of the tasks you should perform to use expansion files with your |
| 239 | application:</p> |
| 240 | |
| 241 | <ol> |
| 242 | <li>First determine whether your application absolutely requires more than 50MB per installation. |
| 243 | Space is precious and you should keep your total application size as small as possible. If your app |
| 244 | uses more than 50MB in order to provide multiple versions of your graphic assets for multiple screen |
| 245 | densities, consider instead publishing <a |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 246 | href="{@docRoot}google/play/publishing/multiple-apks.html">multiple APKs</a> in which each APK |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 247 | contains only the assets required for the screens that it targets.</li> |
| 248 | <li>Determine which application resources to separate from your APK and package them in a |
| 249 | file to use as the main expansion file. |
| 250 | <p>Normally, you should only use the second patch expansion file when performing updates to |
| 251 | the main expansion file. However, if your resources exceed the 2GB limit for the main |
| 252 | expansion file, you can use the patch file for the rest of your assets.</p> |
| 253 | </li> |
| 254 | <li>Develop your application such that it uses the resources from your expansion files in the |
| 255 | device's <a href="#StorageLocation">shared storage location</a>. |
| 256 | <p>Remember that you must not delete, move, or rename the expansion files.</p> |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 257 | <p>If your application doesn't demand a specific format, we suggest you create ZIP files for |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 258 | your expansion files, then read them using the <a href="#ZipLib">APK Expansion Zip |
| 259 | Library</a>.</p> |
| 260 | </li> |
| 261 | <li>Add logic to your application's main activity that checks whether the expansion files |
| 262 | are on the device upon start-up. If the files are not on the device, use Google Play's <a |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 263 | href="{@docRoot}google/play/licensing/index.html">Application Licensing</a> service to request URLs |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 264 | for the expansion files, then download and save them. |
| 265 | <p>To greatly reduce the amount of code you must write and ensure a good user experience |
Scott Main | f05e34a | 2012-07-31 18:25:33 -0700 | [diff] [blame] | 266 | during the download, we recommend you use the <a href="#AboutLibraries">Downloader |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 267 | Library</a> to implement your download behavior.</p> |
| 268 | <p>If you build your own download service instead of using the library, be aware that you |
| 269 | must not change the name of the expansion files and must save them to the proper |
| 270 | <a href="#StorageLocation">storage location</a>.</p></li> |
| 271 | </ol> |
| 272 | |
| 273 | <p>Once you've finished your application development, follow the guide to <a href="#Testing">Testing |
| 274 | Your Expansion Files</a>.</p> |
| 275 | |
| 276 | |
| 277 | |
| 278 | |
| 279 | |
| 280 | |
| 281 | <h2 id="Rules">Rules and Limitations</h2> |
| 282 | |
| 283 | <p>Adding APK expansion files is a feature available when you upload your application using the |
| 284 | Developer Console. When uploading your application for the first time or updating an |
| 285 | application that uses expansion files, you must be aware of the following rules and limitations:</p> |
| 286 | |
| 287 | <ol type="I"> |
| 288 | <li>Each expansion file can be no more than 2GB.</li> |
| 289 | <li>In order to download your expansion files from Google Play, <strong>the user must have |
| 290 | acquired your application from Google Play</strong>. Google Play will not |
| 291 | provide the URLs for your expansion files if the application was installed by other means.</li> |
| 292 | <li>When performing the download from within your application, the URL that Google Play |
| 293 | provides for each file is unique for every download and each one expires shortly after it is given |
| 294 | to your application.</li> |
| 295 | <li>If you update your application with a new APK or upload <a |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 296 | href="{@docRoot}google/play/publishing/multiple-apks.html">multiple APKs</a> for the same |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 297 | application, you can select expansion files that you've uploaded for a previous APK. <strong>The |
| 298 | expansion file's name does not change</strong>—it retains the version received by the APK to |
| 299 | which the file was originally associated.</li> |
| 300 | <li>If you use expansion files in combination with <a |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 301 | href="{@docRoot}google/play/publishing/multiple-apks.html">multiple APKs</a> in order to |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 302 | provide different expansion files for different devices, you still must upload separate APKs |
| 303 | for each device in order to provide a unique <a |
| 304 | href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code versionCode}</a> |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 305 | value and declare different <a href="{@docRoot}google/play/filters.html">filters</a> for |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 306 | each APK.</li> |
| 307 | <li>You cannot issue an update to your application by changing the expansion files |
| 308 | alone—<strong>you must upload a new APK</strong> to update your app. If your changes only |
| 309 | concern the assets in your expansion files, you can update your APK simply by changing the <a |
| 310 | href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code versionCode}</a> (and |
| 311 | perhaps also the <a href="{@docRoot}guide/topics/manifest/manifest-element.html#vname">{@code |
| 312 | versionName}</a>).</p></li> |
| 313 | <li><strong>Do not save other data into your <code>obb/</code> |
| 314 | directory</strong>. If you must unpack some data, save it into the location specified by {@link |
| 315 | android.content.Context#getExternalFilesDir getExternalFilesDir()}.</li> |
| 316 | <li><strong>Do not delete or rename the {@code .obb} expansion file</strong> (unless you're |
| 317 | performing an update). Doing so will cause Google Play (or your app itself) to repeatedly |
| 318 | download the expansion file.</li> |
| 319 | <li>When updating an expansion file manually, you must delete the previous expansion file.</li> |
| 320 | </ol> |
| 321 | |
| 322 | |
| 323 | |
| 324 | |
| 325 | |
| 326 | |
| 327 | |
| 328 | |
| 329 | |
| 330 | <h2 id="Downloading">Downloading the Expansion Files</h2> |
| 331 | |
| 332 | <p>In most cases, Google Play downloads and saves your expansion files to the device at the same |
| 333 | time it installs or updates the APK. This way, the expansion files are available when your |
| 334 | application launches for the first time. However, in some cases your app must download the |
| 335 | expansion files itself by requesting them from a URL provided to you in a response |
| 336 | from Google Play's <a |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 337 | href="{@docRoot}google/play/licensing/index.html">Application Licensing</a> service.</p> |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 338 | |
| 339 | <p>The basic logic you need to download your expansion files is the following:</p> |
| 340 | |
| 341 | <ol> |
| 342 | <li>When your application starts, look for the expansion files on the <a |
| 343 | href="#StorageLocation">shared storage location</a> (in the |
| 344 | <code>Android/obb/<package-name>/</code> directory). |
| 345 | <ol type="a"> |
| 346 | <li>If the expansion files are there, you're all set and your application can continue.</li> |
| 347 | <li>If the expansion files are <em>not</em> there: |
| 348 | <ol> |
| 349 | <li>Perform a request using Google Play's <a |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 350 | href="{@docRoot}google/play/licensing/index.html">Application Licensing</a> to get your |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 351 | app's expansion file names, sizes, and URLs.</li> |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 352 | <li>Use the URLs provided by Google Play to download the expansion files and save |
| 353 | the expansion files. You <strong>must</strong> save the files to the <a |
| 354 | href="#StorageLocation">shared storage location</a> |
| 355 | (<code>Android/obb/<package-name>/</code>) and use the exact file name provided |
| 356 | by Google Play's response. |
| 357 | <p class="note"><strong>Note:</strong> The URL that Google Play provides for your |
| 358 | expansion files is unique for every download and each one expires shortly after it is given to |
| 359 | your application.</p> |
| 360 | </li> |
| 361 | </ol> |
| 362 | </li> |
| 363 | </ol> |
| 364 | </li> |
| 365 | </ol> |
| 366 | |
| 367 | |
| 368 | <p>If your application is free (not a paid app), then you probably haven't used the <a |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 369 | href="{@docRoot}google/play/licensing/index.html">Application Licensing</a> service. It's primarily |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 370 | designed for you to enforce |
| 371 | licensing policies for your application and ensure that the user has the right to |
| 372 | use your app (he or she rightfully paid for it on Google Play). In order to facilitate the |
| 373 | expansion file functionality, the licensing service has been enhanced to provide a response |
| 374 | to your application that includes the URL of your application's expansion files that are hosted |
| 375 | on Google Play. So, even if your application is free for users, you need to include the |
| 376 | License Verification Library (LVL) to use APK expansion files. Of course, if your application |
| 377 | is free, you don't need to enforce license verification—you simply need the |
| 378 | library to perform the request that returns the URL of your expansion files.</p> |
| 379 | |
| 380 | <p class="note"><strong>Note:</strong> Whether your application is free or not, Google Play |
| 381 | returns the expansion file URLs only if the user acquired your application from Google Play.</p> |
| 382 | |
| 383 | <p>In addition to the LVL, you need a set of code that downloads the expansion files |
| 384 | over an HTTP connection and saves them to the proper location on the device's shared storage. |
| 385 | As you build this procedure into your application, there are several issues you should take into |
| 386 | consideration:</p> |
| 387 | |
| 388 | <ul> |
| 389 | <li>The device might not have enough space for the expansion files, so you should check |
| 390 | before beginning the download and warn the user if there's not enough space.</li> |
| 391 | <li>File downloads should occur in a background service in order to avoid blocking the user |
| 392 | interaction and allow the user to leave your app while the download completes.</li> |
| 393 | <li>A variety of errors might occur during the request and download that you must |
| 394 | gracefully handle.</li> |
| 395 | <li>Network connectivity can change during the download, so you should handle such changes and |
| 396 | if interrupted, resume the download when possible.</li> |
| 397 | <li>While the download occurs in the background, you should provide a notification that |
| 398 | indicates the download progress, notifies the user when it's done, and takes the user back to |
| 399 | your application when selected.</li> |
| 400 | </ul> |
| 401 | |
| 402 | |
| 403 | <p>To simplify this work for you, we've built the <a href="#AboutLibraries">Downloader Library</a>, |
| 404 | which requests the expansion file URLs through the licensing service, downloads the expansion files, |
| 405 | performs all of the tasks listed above, and even allows your activity to pause and resume the |
| 406 | download. By adding the Downloader Library and a few code hooks to your application, almost all the |
| 407 | work to download the expansion files is already coded for you. As such, in order to provide the best |
| 408 | user experience with minimal effort on your behalf, we recommend you use the Downloader Library to |
| 409 | download your expansion files. The information in the following sections explain how to integrate |
| 410 | the library into your application.</p> |
| 411 | |
| 412 | <p>If you'd rather develop your own solution to download the expansion files using the Google |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 413 | Play URLs, you must follow the <a href="{@docRoot}google/play/licensing/index.html">Application |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 414 | Licensing</a> documentation to perform a license request, then retrieve the expansion file names, |
| 415 | sizes, and URLs from the response extras. You should use the <a href="#ExpansionPolicy">{@code |
| 416 | APKExpansionPolicy}</a> class (included in the License Verification Library) as your licensing |
| 417 | policy, which captures the expansion file names, sizes, and URLs from the licensing service..</p> |
| 418 | |
| 419 | |
| 420 | |
| 421 | <h3 id="AboutLibraries">About the Downloader Library</h3> |
| 422 | |
| 423 | <p>To use APK expansion files with your application and provide the best user experience with |
| 424 | minimal effort on your behalf, we recommend you use the Downloader Library that's included in the |
Robert Ly | dd5c182 | 2012-10-17 11:50:40 -0700 | [diff] [blame] | 425 | Google Play APK Expansion Library package. This library downloads your expansion files in a |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 426 | background service, shows a user notification with the download status, handles network |
| 427 | connectivity loss, resumes the download when possible, and more.</p> |
| 428 | |
| 429 | <p>To implement expansion file downloads using the Downloader Library, all you need to do is:</p> |
| 430 | |
| 431 | <ul> |
| 432 | <li>Extend a special {@link android.app.Service} subclass and {@link |
| 433 | android.content.BroadcastReceiver} subclass that each require just a few |
| 434 | lines of code from you.</li> |
| 435 | <li>Add some logic to your main activity that checks whether the expansion files have |
| 436 | already been downloaded and, if not, invokes the download process and displays a |
| 437 | progress UI.</li> |
| 438 | <li>Implement a callback interface with a few methods in your main activity that |
| 439 | receives updates about the download progress.</li> |
| 440 | </ul> |
| 441 | |
| 442 | <p>The following sections explain how to set up your app using the Downloader Library.</p> |
| 443 | |
| 444 | |
| 445 | <h3 id="Preparing">Preparing to use the Downloader Library</h3> |
| 446 | |
| 447 | <p>To use the Downloader Library, you need to |
| 448 | download two packages from the SDK Manager and add the appropriate libraries to your |
| 449 | application.</p> |
| 450 | |
| 451 | <p>First, open the <a href="{@docRoot}sdk/exploring.html">Android SDK Manager</a>, expand |
| 452 | <em>Extras</em> and download:</p> |
| 453 | <ul> |
Robert Ly | dd5c182 | 2012-10-17 11:50:40 -0700 | [diff] [blame] | 454 | <li><em>Google Play Licensing Library package</em></li> |
| 455 | <li><em>Google Play APK Expansion Library package</em></li> |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 456 | </ul> |
| 457 | |
| 458 | <p>If you're using Eclipse, create a project for each library and add it to your app:</p> |
| 459 | <ol> |
| 460 | <li>Create a new Library Project for the License Verification Library and Downloader |
| 461 | Library. For each library: |
| 462 | <ol> |
| 463 | <li>Begin a new Android project.</li> |
| 464 | <li>Select <strong>Create project from existing |
| 465 | source</strong> and choose the library from the {@code <sdk>/extras/google/} directory |
| 466 | ({@code market_licensing/} for the License Verification Library or {@code |
| 467 | market_apk_expansion/downloader_library/} for the Downloader Library).</li> |
| 468 | <li>Specify a <em>Project Name</em> such as "Google Play License Library" and "Google Play |
| 469 | Downloader |
| 470 | Library"</li> |
| 471 | <li>Click <strong>Finish</strong>.</li> |
| 472 | </ol> |
| 473 | <p class="note"><strong>Note:</strong> The Downloader Library depends on the License |
| 474 | Verification Library. Be sure to add the License |
| 475 | Verification Library to the Downloader Library's project properties (same process as |
| 476 | steps 2 and 3 below).</p> |
| 477 | </li> |
| 478 | <li>Right-click the Android project in which you want to use APK expansion files and |
| 479 | select <strong>Properties</strong>.</li> |
| 480 | <li>In the <em>Library</em> panel, click <strong>Add</strong> to select and add each of the |
| 481 | libraries to your application.</li> |
| 482 | </ol> |
| 483 | |
| 484 | <p>Or, from a command line, update your project to include the libraries:</p> |
| 485 | <ol> |
| 486 | <li>Change directories to the <code><sdk>/tools/</code> directory.</li> |
| 487 | <li>Execute <code>android update project</code> with the {@code --library} option to add both the |
| 488 | LVL and the Downloader Library to your project. For example: |
| 489 | <pre class="no-pretty-print"> |
| 490 | android update project --path ~/Android/MyApp \ |
| 491 | --library ~/android_sdk/extras/google/market_licensing \ |
| 492 | --library ~/android_sdk/extras/google/market_apk_expansion/downloader_library |
| 493 | </pre> |
| 494 | </li> |
| 495 | </ol> |
| 496 | |
| 497 | <p>With both the License Verification Library and Downloader Library added to your |
| 498 | application, you'll be able to quickly integrate the ability to download expansion files from |
| 499 | Google Play. The format that you choose for the expansion files and how you read them |
| 500 | from the shared storage is a separate implementation that you should consider based on your |
| 501 | application needs.</p> |
| 502 | |
| 503 | <p class="note"><strong>Tip:</strong> The Apk Expansion package includes a sample |
| 504 | application |
| 505 | that shows how to use the Downloader Library in an app. The sample uses a third library |
| 506 | available in the Apk Expansion package called the APK Expansion Zip Library. If |
| 507 | you plan on |
| 508 | using ZIP files for your expansion files, we suggest you also add the APK Expansion Zip Library to |
| 509 | your application. For more information, see the section below |
| 510 | about <a href="#ZipLib">Using the APK Expansion Zip Library</a>.</p> |
| 511 | |
| 512 | |
| 513 | |
| 514 | <h3 id="Permissions">Declaring user permissions</h3> |
| 515 | |
| 516 | <p>In order to download the expansion files, the Downloader Library |
| 517 | requires several permissions that you must declare in your application's manifest file. They |
| 518 | are:</p> |
| 519 | |
| 520 | <pre> |
| 521 | <manifest ...> |
| 522 | <!-- Required to access Google Play Licensing --> |
| 523 | <uses-permission android:name="com.android.vending.CHECK_LICENSE" /> |
| 524 | |
| 525 | <!-- Required to download files from Google Play --> |
| 526 | <uses-permission android:name="android.permission.INTERNET" /> |
| 527 | |
| 528 | <!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) --> |
| 529 | <uses-permission android:name="android.permission.WAKE_LOCK" /> |
| 530 | |
| 531 | <!-- Required to poll the state of the network connection and respond to changes --> |
| 532 | <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
| 533 | |
| 534 | <!-- Required to check whether Wi-Fi is enabled --> |
| 535 | <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> |
| 536 | |
| 537 | <!-- Required to read and write the expansion files on shared storage --> |
| 538 | <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
| 539 | ... |
| 540 | </manifest> |
| 541 | </pre> |
| 542 | |
| 543 | <p class="note"><strong>Note:</strong> By default, the Downloader Library requires API |
| 544 | level 4, but the APK Expansion Zip Library requires API level 5.</p> |
| 545 | |
| 546 | |
| 547 | <h3 id="DownloaderService">Implementing the downloader service</h3> |
| 548 | |
| 549 | <p>In order to perform downloads in the background, the Downloader Library provides its |
| 550 | own {@link android.app.Service} subclass called {@code DownloaderService} that you should extend. In |
| 551 | addition to downloading the expansion files for you, the {@code DownloaderService} also:</p> |
| 552 | |
| 553 | <ul> |
| 554 | <li>Registers a {@link android.content.BroadcastReceiver} that listens for changes to the |
| 555 | device's network connectivity (the {@link android.net.ConnectivityManager#CONNECTIVITY_ACTION} |
| 556 | broadcast) in order to pause the download when necessary (such as due to connectivity loss) and |
| 557 | resume the download when possible (connectivity is acquired).</li> |
| 558 | <li>Schedules an {@link android.app.AlarmManager#RTC_WAKEUP} alarm to retry the download for |
| 559 | cases in which the service gets killed.</li> |
| 560 | <li>Builds a custom {@link android.app.Notification} that displays the download progress and |
| 561 | any errors or state changes.</li> |
| 562 | <li>Allows your application to manually pause and resume the download.</li> |
| 563 | <li>Verifies that the shared storage is mounted and available, that the files don't already exist, |
| 564 | and that there is enough space, all before downloading the expansion files. Then notifies the user |
| 565 | if any of these are not true.</li> |
| 566 | </ul> |
| 567 | |
| 568 | <p>All you need to do is create a class in your application that extends the {@code |
| 569 | DownloaderService} class and override three methods to provide specific application details:</p> |
| 570 | |
| 571 | <dl> |
| 572 | <dt>{@code getPublicKey()}</dt> |
| 573 | <dd>This must return a string that is the Base64-encoded RSA public key for your publisher |
| 574 | account, available from the profile page on the Developer Console (see <a |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 575 | href="{@docRoot}google/play/licensing/setting-up.html">Setting Up for Licensing</a>).</dd> |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 576 | <dt>{@code getSALT()}</dt> |
| 577 | <dd>This must return an array of random bytes that the licensing {@code Policy} uses to |
| 578 | create an <a |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 579 | href="{@docRoot}google/play/licensing/adding-licensing.html#impl-Obfuscator">{@code |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 580 | Obfuscator}</a>. The salt ensures that your obfuscated {@link android.content.SharedPreferences} |
| 581 | file in which your licensing data is saved will be unique and non-discoverable.</dd> |
| 582 | <dt>{@code getAlarmReceiverClassName()}</dt> |
| 583 | <dd>This must return the class name of the {@link android.content.BroadcastReceiver} in |
| 584 | your application that should receive the alarm indicating that the download should be |
| 585 | restarted (which might happen if the downloader service unexpectedly stops).</dd> |
| 586 | </dl> |
| 587 | |
| 588 | <p>For example, here's a complete implementation of {@code DownloaderService}:</p> |
| 589 | |
| 590 | <pre> |
| 591 | public class SampleDownloaderService extends DownloaderService { |
| 592 | // You must use the public key belonging to your publisher account |
| 593 | public static final String BASE64_PUBLIC_KEY = "YourLVLKey"; |
| 594 | // You should also modify this salt |
| 595 | public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98, |
| 596 | -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84 |
| 597 | }; |
| 598 | |
| 599 | @Override |
| 600 | public String getPublicKey() { |
| 601 | return BASE64_PUBLIC_KEY; |
| 602 | } |
| 603 | |
| 604 | @Override |
| 605 | public byte[] getSALT() { |
| 606 | return SALT; |
| 607 | } |
| 608 | |
| 609 | @Override |
| 610 | public String getAlarmReceiverClassName() { |
| 611 | return SampleAlarmReceiver.class.getName(); |
| 612 | } |
| 613 | } |
| 614 | </pre> |
| 615 | |
| 616 | <p class="caution"><strong>Notice:</strong> You must update the {@code BASE64_PUBLIC_KEY} value |
| 617 | to be the public key belonging to your publisher account. You can find the key in the Developer |
| 618 | Console under your profile information. This is necessary even when testing |
| 619 | your downloads.</p> |
| 620 | |
| 621 | <p>Remember to declare the service in your manifest file:</p> |
| 622 | <pre> |
| 623 | <application ...> |
| 624 | <service android:name=".SampleDownloaderService" /> |
| 625 | ... |
| 626 | </application> |
| 627 | </pre> |
| 628 | |
| 629 | |
| 630 | |
| 631 | <h3 id="AlarmReceiver">Implementing the alarm receiver</h3> |
| 632 | |
| 633 | <p>In order to monitor the progress of the file downloads and restart the download if necessary, the |
| 634 | {@code DownloaderService} schedules an {@link android.app.AlarmManager#RTC_WAKEUP} alarm that |
| 635 | delivers an {@link android.content.Intent} to a {@link android.content.BroadcastReceiver} in your |
| 636 | application. You must define the {@link android.content.BroadcastReceiver} to call an API |
| 637 | from the Downloader Library that checks the status of the download and restarts |
| 638 | it if necessary.</p> |
| 639 | |
| 640 | <p>You simply need to override the {@link android.content.BroadcastReceiver#onReceive |
| 641 | onReceive()} method to call {@code |
| 642 | DownloaderClientMarshaller.startDownloadServiceIfRequired()}.</p> |
| 643 | |
| 644 | <p>For example:</p> |
| 645 | |
| 646 | <pre> |
| 647 | public class SampleAlarmReceiver extends BroadcastReceiver { |
| 648 | @Override |
| 649 | public void onReceive(Context context, Intent intent) { |
| 650 | try { |
| 651 | DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, |
| 652 | SampleDownloaderService.class); |
| 653 | } catch (NameNotFoundException e) { |
| 654 | e.printStackTrace(); |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 655 | } |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 656 | } |
| 657 | } |
| 658 | </pre> |
| 659 | |
| 660 | <p>Notice that this is the class for which you must return the name |
| 661 | in your service's {@code getAlarmReceiverClassName()} method (see the previous section).</p> |
| 662 | |
| 663 | <p>Remember to declare the receiver in your manifest file:</p> |
| 664 | <pre> |
| 665 | <application ...> |
| 666 | <receiver android:name=".SampleAlarmReceiver" /> |
| 667 | ... |
| 668 | </application> |
| 669 | </pre> |
| 670 | |
| 671 | |
| 672 | |
| 673 | <h3 id="Download">Starting the download</h3> |
| 674 | |
| 675 | <p>The main activity in your application (the one started by your launcher icon) is |
| 676 | responsible for verifying whether the expansion files are already on the device and initiating |
| 677 | the download if they are not.</p> |
| 678 | |
| 679 | <p>Starting the download using the Downloader Library requires the following |
| 680 | procedures:</p> |
| 681 | |
| 682 | <ol> |
| 683 | <li>Check whether the files have been downloaded. |
| 684 | <p>The Downloader Library includes some APIs in the {@code Helper} class to |
| 685 | help with this process:</p> |
| 686 | <ul> |
| 687 | <li>{@code getExtendedAPKFileName(Context, c, boolean mainFile, int |
| 688 | versionCode)}</li> |
| 689 | <li>{@code doesFileExist(Context c, String fileName, long fileSize)}</li> |
| 690 | </ul> |
| 691 | <p>For example, the sample app provided in the Apk Expansion package calls the |
| 692 | following method in the activity's {@link android.app.Activity#onCreate onCreate()} method to check |
| 693 | whether the expansion files already exist on the device:</p> |
| 694 | <pre> |
| 695 | boolean expansionFilesDelivered() { |
| 696 | for (XAPKFile xf : xAPKS) { |
| 697 | String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsBase, xf.mFileVersion); |
| 698 | if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false)) |
| 699 | return false; |
| 700 | } |
| 701 | return true; |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 702 | } |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 703 | </pre> |
| 704 | <p>In this case, each {@code XAPKFile} object holds the version number and file size of a known |
| 705 | expansion file and a boolean as to whether it's the main expansion file. (See the sample |
| 706 | application's {@code SampleDownloaderActivity} class for details.)</p> |
| 707 | <p>If this method returns false, then the application must begin the download.</p> |
| 708 | </li> |
| 709 | <li>Start the download by calling the static method {@code |
| 710 | DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent |
| 711 | notificationClient, Class<?> serviceClass)}. |
| 712 | <p>The method takes the following parameters:</p> |
| 713 | <ul> |
| 714 | <li><code>context</code>: Your application's {@link android.content.Context}.</li> |
| 715 | <li><code>notificationClient</code>: A {@link android.app.PendingIntent} to start your main |
| 716 | activity. This is used in the {@link android.app.Notification} that the {@code DownloaderService} |
| 717 | creates to show the download progress. When the user selects the notification, the system |
| 718 | invokes the {@link android.app.PendingIntent} you supply here and should open the activity |
| 719 | that shows the download progress (usually the same activity that started the download).</li> |
| 720 | <li><code>serviceClass</code>: The {@link java.lang.Class} object for your implementation of |
| 721 | {@code DownloaderService}, required to start the service and begin the download if necessary.</li> |
| 722 | </ul> |
| 723 | <p>The method returns an integer that indicates |
| 724 | whether or not the download is required. Possible values are:</p> |
| 725 | <ul> |
| 726 | <li>{@code NO_DOWNLOAD_REQUIRED}: Returned if the files already |
| 727 | exist or a download is already in progress.</li> |
| 728 | <li>{@code LVL_CHECK_REQUIRED}: Returned if a license verification is |
| 729 | required in order to acquire the expansion file URLs.</li> |
| 730 | <li>{@code DOWNLOAD_REQUIRED}: Returned if the expansion file URLs are already known, |
| 731 | but have not been downloaded.</li> |
| 732 | </ul> |
| 733 | <p>The behavior for {@code LVL_CHECK_REQUIRED} and {@code DOWNLOAD_REQUIRED} are essentially the |
| 734 | same and you normally don't need to be concerned about them. In your main activity that calls {@code |
| 735 | startDownloadServiceIfRequired()}, you can simply check whether or not the response is {@code |
| 736 | NO_DOWNLOAD_REQUIRED}. If the response is anything <em>other than</em> {@code NO_DOWNLOAD_REQUIRED}, |
| 737 | the Downloader Library begins the download and you should update your activity UI to |
| 738 | display the download progress (see the next step). If the response <em>is</em> {@code |
| 739 | NO_DOWNLOAD_REQUIRED}, then the files are available and your application can start.</p> |
| 740 | <p>For example:</p> |
| 741 | <pre> |
| 742 | @Override |
| 743 | public void onCreate(Bundle savedInstanceState) { |
| 744 | // Check if expansion files are available before going any further |
| 745 | if (!expansionFilesDelivered()) { |
| 746 | // Build an Intent to start this activity from the Notification |
| 747 | Intent notifierIntent = new Intent(this, MainActivity.getClass()); |
| 748 | notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | |
| 749 | Intent.FLAG_ACTIVITY_CLEAR_TOP); |
| 750 | ... |
| 751 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, |
| 752 | notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 753 | |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 754 | // Start the download service (if required) |
| 755 | int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, |
| 756 | pendingIntent, SampleDownloaderService.class); |
| 757 | // If download has started, initialize this activity to show download progress |
| 758 | if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { |
| 759 | // This is where you do set up to display the download progress (next step) |
| 760 | ... |
| 761 | return; |
| 762 | } // If the download wasn't necessary, fall through to start the app |
| 763 | } |
| 764 | startApp(); // Expansion files are available, start the app |
| 765 | } |
| 766 | </pre> |
| 767 | </li> |
| 768 | <li>When the {@code startDownloadServiceIfRequired()} method returns anything <em>other |
| 769 | than</em> {@code NO_DOWNLOAD_REQUIRED}, create an instance of {@code IStub} by |
| 770 | calling {@code DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> |
| 771 | downloaderService)}. The {@code IStub} provides a binding between your activity to the downloader |
| 772 | service such that your activity receives callbacks about the download progress. |
| 773 | <p>In order to instantiate your {@code IStub} by calling {@code CreateStub()}, you must pass it |
| 774 | an implementation of the {@code IDownloaderClient} interface and your {@code DownloaderService} |
| 775 | implementation. The next section about <a href="#Progress">Receiving download progress</a> discusses |
| 776 | the {@code IDownloaderClient} interface, which you should usually implement in your {@link |
| 777 | android.app.Activity} class so you can update the activity UI when the download state changes.</p> |
| 778 | <p>We recommend that you call {@code |
| 779 | CreateStub()} to instantiate your {@code IStub} during your activity's {@link |
| 780 | android.app.Activity#onCreate onCreate()} method, after {@code startDownloadServiceIfRequired()} |
| 781 | starts the download. </p> |
| 782 | <p>For example, in the previous code sample for {@link android.app.Activity#onCreate |
| 783 | onCreate()}, you can respond to the {@code startDownloadServiceIfRequired()} result like this:</p> |
| 784 | <pre> |
| 785 | // Start the download service (if required) |
| 786 | int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, |
| 787 | pendingIntent, SampleDownloaderService.class); |
| 788 | // If download has started, initialize activity to show progress |
| 789 | if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { |
| 790 | // Instantiate a member instance of IStub |
| 791 | mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, |
| 792 | SampleDownloaderService.class); |
| 793 | // Inflate layout that shows download progress |
| 794 | setContentView(R.layout.downloader_ui); |
| 795 | return; |
| 796 | } |
| 797 | </pre> |
| 798 | |
| 799 | <p>After the {@link android.app.Activity#onCreate onCreate()} method returns, your activity |
| 800 | receives a call to {@link android.app.Activity#onResume onResume()}, which is where you should then |
| 801 | call {@code connect()} on the {@code IStub}, passing it your application's {@link |
| 802 | android.content.Context}. Conversely, you should call |
| 803 | {@code disconnect()} in your activity's {@link android.app.Activity#onStop onStop()} callback.</p> |
| 804 | <pre> |
| 805 | @Override |
| 806 | protected void onResume() { |
| 807 | if (null != mDownloaderClientStub) { |
| 808 | mDownloaderClientStub.connect(this); |
| 809 | } |
| 810 | super.onResume(); |
| 811 | } |
| 812 | |
| 813 | @Override |
| 814 | protected void onStop() { |
| 815 | if (null != mDownloaderClientStub) { |
| 816 | mDownloaderClientStub.disconnect(this); |
| 817 | } |
| 818 | super.onStop(); |
| 819 | } |
| 820 | </pre> |
| 821 | <p>Calling {@code connect()} on the {@code IStub} binds your activity to the {@code |
| 822 | DownloaderService} such that your activity receives callbacks regarding changes to the download |
| 823 | state through the {@code IDownloaderClient} interface.</p> |
| 824 | </li> |
| 825 | </ol> |
| 826 | |
| 827 | |
| 828 | |
| 829 | <h3 id="Progress">Receiving download progress</h3> |
| 830 | |
| 831 | <p>To receive updates regarding the download progress and to interact with the {@code |
| 832 | DownloaderService}, you must implement the Downloader Library's {@code IDownloaderClient} interface. |
| 833 | Usually, the activity you use to start the download should implement this interface in order to |
| 834 | display the download progress and send requests to the service.</p> |
| 835 | |
| 836 | <p>The required interface methods for {@code IDownloaderClient} are:</p> |
| 837 | |
| 838 | <dl> |
| 839 | <dt>{@code onServiceConnected(Messenger m)}</dt> |
| 840 | <dd>After you instantiate the {@code IStub} in your activity, you'll receive a call to this |
| 841 | method, which passes a {@link android.os.Messenger} object that's connected with your instance |
| 842 | of {@code DownloaderService}. To send requests to the service, such as to pause and resume |
| 843 | downloads, you must call {@code DownloaderServiceMarshaller.CreateProxy()} to receive the {@code |
| 844 | IDownloaderService} interface connected to the service. |
| 845 | <p>A recommended implementation looks like this:</p> |
| 846 | <pre> |
| 847 | private IDownloaderService mRemoteService; |
| 848 | ... |
| 849 | |
| 850 | @Override |
| 851 | public void onServiceConnected(Messenger m) { |
| 852 | mRemoteService = DownloaderServiceMarshaller.CreateProxy(m); |
| 853 | mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger()); |
| 854 | } |
| 855 | </pre> |
| 856 | <p>With the {@code IDownloaderService} object initialized, you can send commands to the |
| 857 | downloader service, such as to pause and resume the download ({@code requestPauseDownload()} |
| 858 | and {@code requestContinueDownload()}).</p> |
| 859 | </dd> |
| 860 | <dt>{@code onDownloadStateChanged(int newState)}</dt> |
| 861 | <dd>The download service calls this when a change in download state occurs, such as the |
| 862 | download begins or completes. |
| 863 | <p>The <code>newState</code> value will be one of several possible values specified in |
| 864 | by one of the {@code IDownloaderClient} class's {@code STATE_*} constants.</p> |
| 865 | <p>To provide a useful message to your users, you can request a corresponding string |
| 866 | for each state by calling {@code Helpers.getDownloaderStringResourceIDFromState()}. This |
| 867 | returns the resource ID for one of the strings bundled with the Downloader |
| 868 | Library. For example, the string "Download paused because you are roaming" corresponds to {@code |
| 869 | STATE_PAUSED_ROAMING}.</p></dd> |
| 870 | <dt>{@code onDownloadProgress(DownloadProgressInfo progress)}</dt> |
| 871 | <dd>The download service calls this to deliver a {@code DownloadProgressInfo} object, |
| 872 | which describes various information about the download progress, including estimated time remaining, |
| 873 | current speed, overall progress, and total so you can update the download progress UI.</dd> |
| 874 | </dl> |
| 875 | <p class="note"><strong>Tip:</strong> For examples of these callbacks that update the download |
| 876 | progress UI, see the {@code SampleDownloaderActivity} in the sample app provided with the |
| 877 | Apk Expansion package.</p> |
| 878 | |
| 879 | <p>Some public methods for the {@code IDownloaderService} interface you might find useful are:</p> |
| 880 | |
| 881 | <dl> |
| 882 | <dt>{@code requestPauseDownload()}</dt> |
| 883 | <dd>Pauses the download.</dd> |
| 884 | <dt>{@code requestContinueDownload()}</dt> |
| 885 | <dd>Resumes a paused download.</dd> |
| 886 | <dt>{@code setDownloadFlags(int flags)}</dt> |
| 887 | <dd>Sets user preferences for network types on which its OK to download the files. The |
| 888 | current implementation supports one flag, {@code FLAGS_DOWNLOAD_OVER_CELLULAR}, but you can add |
| 889 | others. By default, this flag is <em>not</em> enabled, so the user must be on Wi-Fi to download |
| 890 | expansion files. You might want to provide a user preference to enable downloads over |
| 891 | the cellular network. In which case, you can call: |
| 892 | <pre> |
| 893 | mRemoteService.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR); |
| 894 | </pre> |
| 895 | </dd> |
| 896 | </dl> |
| 897 | |
| 898 | |
| 899 | |
| 900 | |
| 901 | <h2 id="ExpansionPolicy">Using APKExpansionPolicy</h2> |
| 902 | |
| 903 | <p>If you decide to build your own downloader service instead of using the Google Play |
| 904 | <a href="#AboutLibraries">Downloader Library</a>, you should still use the {@code |
| 905 | APKExpansionPolicy} that's provided in the License Verification Library. The {@code |
| 906 | APKExpansionPolicy} class is nearly identical to {@code ServerManagedPolicy} (available in the |
| 907 | Google Play License Verification Library) but includes additional handling for the APK expansion |
| 908 | file response extras.</p> |
| 909 | |
| 910 | <p class="note"><strong>Note:</strong> If you <em>do use</em> the <a |
| 911 | href="#AboutLibraries">Downloader Library</a> as discussed in the previous section, the |
| 912 | library performs all interaction with the {@code APKExpansionPolicy} so you don't have to use |
| 913 | this class directly.</p> |
| 914 | |
| 915 | <p>The class includes methods to help you get the necessary information about the available |
| 916 | expansion files:</p> |
| 917 | |
| 918 | <ul> |
| 919 | <li>{@code getExpansionURLCount()}</li> |
| 920 | <li>{@code getExpansionURL(int index)}</li> |
| 921 | <li>{@code getExpansionFileName(int index)}</li> |
| 922 | <li>{@code getExpansionFileSize(int index)}</li> |
| 923 | </ul> |
| 924 | |
| 925 | <p>For more information about how to use the {@code APKExpansionPolicy} when you're <em>not</em> |
| 926 | using the <a |
| 927 | href="#AboutLibraries">Downloader Library</a>, see the documentation for <a |
Dirk Dougherty | 2b286bb | 2012-11-29 17:25:09 -0800 | [diff] [blame^] | 928 | href="{@docRoot}google/play/licensing/adding-licensing.html">Adding Licensing to Your App</a>, |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 929 | which explains how to implement a license policy such as this one.</p> |
| 930 | |
| 931 | |
| 932 | |
| 933 | |
| 934 | |
| 935 | |
| 936 | |
| 937 | <h2 id="ReadingTheFile">Reading the Expansion File</h2> |
| 938 | |
| 939 | <p>Once your APK expansion files are saved on the device, how you read your files |
| 940 | depends on the type of file you've used. As discussed in the <a href="#Overview">overview</a>, your |
| 941 | expansion files can be any kind of file you |
| 942 | want, but are renamed using a particular <a href="#Filename">file name format</a> and are saved to |
| 943 | {@code <shared-storage>/Android/obb/<package-name>/}.</p> |
| 944 | |
| 945 | <p>Regardless of how you read your files, you should always first check that the external |
| 946 | storage is available for reading. There's a chance that the user has the storage mounted to a |
| 947 | computer over USB or has actually removed the SD card.</p> |
| 948 | |
| 949 | <p class="note"><strong>Note:</strong> When your application starts, you should always check whether |
| 950 | the external storage space is available and readable by calling {@link |
| 951 | android.os.Environment#getExternalStorageState()}. This returns one of several possible strings |
| 952 | that represent the state of the external storage. In order for it to be readable by your |
| 953 | application, the return value must be {@link android.os.Environment#MEDIA_MOUNTED}.</p> |
| 954 | |
| 955 | |
| 956 | <h3 id="GettingFilenames">Getting the file names</h3> |
| 957 | |
| 958 | <p>As described in the <a href="#Overview">overview</a>, your APK expansion files are saved |
| 959 | using a specific file name format:</p> |
| 960 | |
| 961 | <pre class="classic no-pretty-print"> |
| 962 | [main|patch].<expansion-version>.<package-name>.obb |
| 963 | </pre> |
| 964 | |
| 965 | <p>To get the location and names of your expansion files, you should use the |
| 966 | {@link android.os.Environment#getExternalStorageDirectory()} and {@link |
| 967 | android.content.Context#getPackageName()} methods to construct the path to your files.</p> |
| 968 | |
| 969 | <p>Here's a method you can use in your application to get an array containing the complete path |
| 970 | to both your expansion files:</p> |
| 971 | |
| 972 | <pre> |
| 973 | // The shared path to all app expansion files |
| 974 | private final static String EXP_PATH = "/Android/obb/"; |
| 975 | |
| 976 | static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) { |
| 977 | String packageName = ctx.getPackageName(); |
| 978 | Vector<String> ret = new Vector<String>(); |
| 979 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { |
| 980 | // Build the full path to the app's expansion files |
| 981 | File root = Environment.getExternalStorageDirectory(); |
| 982 | File expPath = new File(root.toString() + EXP_PATH + packageName); |
| 983 | |
| 984 | // Check that expansion file path exists |
| 985 | if (expPath.exists()) { |
| 986 | if ( mainVersion > 0 ) { |
| 987 | String strMainPath = expPath + File.separator + "main." + |
| 988 | mainVersion + "." + packageName + ".obb"; |
| 989 | File main = new File(strMainPath); |
| 990 | if ( main.isFile() ) { |
| 991 | ret.add(strMainPath); |
| 992 | } |
| 993 | } |
| 994 | if ( patchVersion > 0 ) { |
| 995 | String strPatchPath = expPath + File.separator + "patch." + |
| 996 | mainVersion + "." + packageName + ".obb"; |
| 997 | File main = new File(strPatchPath); |
| 998 | if ( main.isFile() ) { |
| 999 | ret.add(strPatchPath); |
| 1000 | } |
| 1001 | } |
| 1002 | } |
| 1003 | } |
| 1004 | String[] retArray = new String[ret.size()]; |
| 1005 | ret.toArray(retArray); |
| 1006 | return retArray; |
| 1007 | } |
| 1008 | </pre> |
| 1009 | |
| 1010 | <p>You can call this method by passing it your application {@link android.content.Context} |
| 1011 | and the desired expansion file's version.</p> |
| 1012 | |
| 1013 | <p>There are many ways you could determine the expansion file version number. One simple way is to |
| 1014 | save the version in a {@link android.content.SharedPreferences} file when the download begins, by |
| 1015 | querying the expansion file name with the {@code APKExpansionPolicy} class's {@code |
| 1016 | getExpansionFileName(int index)} method. You can then get the version code by reading the {@link |
| 1017 | android.content.SharedPreferences} file when you want to access the expansion |
| 1018 | file.</p> |
| 1019 | |
| 1020 | <p>For more information about reading from the shared storage, see the <a |
| 1021 | href="{@docRoot}guide/topics/data/data-storage.html#filesExternal">Data Storage</a> |
| 1022 | documentation.</p> |
| 1023 | |
| 1024 | |
| 1025 | |
| 1026 | <h3 id="ZipLib">Using the APK Expansion Zip Library</h3> |
| 1027 | |
| 1028 | <div class="sidebox-wrapper"> |
| 1029 | <div class="sidebox"> |
| 1030 | <h3>Reading media files from a ZIP</h3> |
| 1031 | <p>If you're using your expansion files to store media files, a ZIP file still allows you to |
| 1032 | use Android media playback calls that provide offset and length controls (such as {@link |
| 1033 | android.media.MediaPlayer#setDataSource(FileDescriptor,long,long) MediaPlayer.setDataSource()} and |
| 1034 | {@link android.media.SoundPool#load(FileDescriptor,long,long,int) SoundPool.load()}). In order for |
| 1035 | this to work, you must not perform additional compression on the media files when creating the ZIP |
| 1036 | packages. For example, when using the <code>zip</code> tool, you should use the <code>-n</code> |
| 1037 | option to specify the file suffixes that should not be compressed:</p> |
| 1038 | <p><code>zip -n .mp4;.ogg main_expansion media_files</code></p> |
| 1039 | </div> |
| 1040 | </div> |
| 1041 | |
| 1042 | <p>The Google Market Apk Expansion package includes a library called the APK |
| 1043 | Expansion Zip Library (located in {@code |
| 1044 | <sdk>/extras/google/google_market_apk_expansion/zip_file/}). This is an optional library that |
| 1045 | helps you read your expansion |
| 1046 | files when they're saved as ZIP files. Using this library allows you to easily read resources from |
| 1047 | your ZIP expansion files as a virtual file system.</p> |
| 1048 | |
| 1049 | <p>The APK Expansion Zip Library includes the following classes and APIs:</p> |
| 1050 | |
| 1051 | <dl> |
| 1052 | <dt>{@code APKExpansionSupport}</dt> |
| 1053 | <dd>Provides some methods to access expansion file names and ZIP files: |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 1054 | |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 1055 | <dl style="margin-top:1em"> |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 1056 | <dt>{@code getAPKExpansionFiles()}</dt> |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 1057 | <dd>The same method shown above that returns the complete file path to both expansion |
| 1058 | files.</dd> |
| 1059 | <dt>{@code getAPKExpansionZipFile(Context ctx, int mainVersion, int |
| 1060 | patchVersion)}</dt> |
| 1061 | <dd>Returns a {@code ZipResourceFile} representing the sum of both the main file and |
| 1062 | patch file. That is, if you specify both the <code>mainVersion</code> and the |
| 1063 | <code>patchVersion</code>, this returns a {@code ZipResourceFile} that provides read access to |
| 1064 | all the data, with the patch file's data merged on top of the main file.</dd> |
| 1065 | </dl> |
| 1066 | </dd> |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 1067 | |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 1068 | <dt>{@code ZipResourceFile}</dt> |
| 1069 | <dd>Represents a ZIP file on the shared storage and performs all the work to provide a virtual |
| 1070 | file system based on your ZIP files. You can get an instance using {@code |
| 1071 | APKExpansionSupport.getAPKExpansionZipFile()} or with the {@code ZipResourceFile} by passing it the |
| 1072 | path to your expansion file. This class includes a variety of useful methods, but you generally |
| 1073 | don't need to access most of them. A couple of important methods are: |
| 1074 | |
| 1075 | <dl style="margin-top:1em"> |
| 1076 | <dt>{@code getInputStream(String assetPath)}</dt> |
| 1077 | <dd>Provides an {@link java.io.InputStream} to read a file within the ZIP file. The |
| 1078 | <code>assetPath</code> must be the path to the desired file, relative to |
| 1079 | the root of the ZIP file contents.</dd> |
| 1080 | <dt>{@code getAssetFileDescriptor(String assetPath)}</dt> |
| 1081 | <dd>Provides an {@link android.content.res.AssetFileDescriptor} for a file within the |
| 1082 | ZIP file. The <code>assetPath</code> must be the path to the desired file, relative to |
| 1083 | the root of the ZIP file contents. This is useful for certain Android APIs that require an {@link |
| 1084 | android.content.res.AssetFileDescriptor}, such as some {@link android.media.MediaPlayer} APIs.</dd> |
| 1085 | </dl> |
| 1086 | </dd> |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 1087 | |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 1088 | <dt>{@code APEZProvider}</dt> |
| 1089 | <dd>Most applications don't need to use this class. This class defines a {@link |
| 1090 | android.content.ContentProvider} that marshals the data from the ZIP files through a content |
| 1091 | provider {@link android.net.Uri} in order to provide file access for certain Android APIs that |
| 1092 | expect {@link android.net.Uri} access to media files. For example, this is useful if you want to |
| 1093 | play a video with {@link android.widget.VideoView#setVideoURI VideoView.setVideoURI()}.</p></dd> |
| 1094 | </dl> |
| 1095 | |
| 1096 | <h4>Reading from a ZIP file</h4> |
| 1097 | |
| 1098 | <p>When using the APK Expansion Zip Library, reading a file from your ZIP usually requires the |
| 1099 | following:</p> |
| 1100 | |
| 1101 | <pre> |
| 1102 | // Get a ZipResourceFile representing a merger of both the main and patch files |
| 1103 | ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext, |
| 1104 | mainVersion, patchVersion); |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 1105 | |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 1106 | // Get an input stream for a known file inside the expansion file ZIPs |
| 1107 | InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip); |
| 1108 | </pre> |
| 1109 | |
| 1110 | <p>The above code provides access to any file that exists in either your main expansion file or |
| 1111 | patch expansion file, by reading from a merged map of all the files from both files. All you |
| 1112 | need to provide the {@code getAPKExpansionFile()} method is your application {@code |
| 1113 | android.content.Context} and the version number for both the main expansion file and patch |
| 1114 | expansion file.</p> |
| 1115 | |
| 1116 | <p>If you'd rather read from a specific expansion file, you can use the {@code |
| 1117 | ZipResourceFile} constructor with the path to the desired expansion file:</p> |
| 1118 | |
| 1119 | <pre> |
| 1120 | // Get a ZipResourceFile representing a specific expansion file |
| 1121 | ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip); |
| 1122 | |
| 1123 | // Get an input stream for a known file inside the expansion file ZIPs |
| 1124 | InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip); |
| 1125 | </pre> |
| 1126 | |
| 1127 | <p>For more information about using this library for your expansion files, look at |
| 1128 | the sample application's {@code SampleDownloaderActivity} class, which includes additional code to |
| 1129 | verify the downloaded files using CRC. Beware that if you use this sample as the basis for |
| 1130 | your own implementation, it requires that you <strong>declare the byte size of your expansion |
| 1131 | files</strong> in the {@code xAPKS} array.</p> |
| 1132 | |
| 1133 | |
| 1134 | |
| 1135 | |
| 1136 | <h2 id="Testing">Testing Your Expansion Files</h2> |
| 1137 | |
| 1138 | <p>Before publishing your application, there are two things you should test: Reading the |
| 1139 | expansion files and downloading the files.</p> |
| 1140 | |
| 1141 | |
| 1142 | <h3 id="TestingReading">Testing file reads</h3> |
| 1143 | |
| 1144 | <p>Before you upload your application to Google Play, you |
| 1145 | should test your application's ability to read the files from the shared storage. All you need to do |
| 1146 | is add the files to the appropriate location on the device shared storage and launch your |
| 1147 | application:</p> |
| 1148 | |
| 1149 | <ol> |
| 1150 | <li>On your device, create the appropriate directory on the shared storage where Google |
| 1151 | Play will save your files. |
| 1152 | <p>For example, if your package name is {@code com.example.android}, you need to create |
| 1153 | the directory {@code Android/obb/com.example.android/} on the shared storage space. (Plug in |
| 1154 | your test device to your computer to mount the shared storage and manually create this |
| 1155 | directory.)</p> |
| 1156 | </li> |
| 1157 | <li>Manually add the expansion files to that directory. Be sure that you rename your files to |
| 1158 | match the <a href="#Filename">file name format</a> that Google Play will use. |
| 1159 | <p>For example, regardless of the file type, the main expansion file for the {@code |
| 1160 | com.example.android} application should be {@code main.0300110.com.example.android.obb}. |
| 1161 | The version code can be whatever value you want. Just remember:</p> |
| 1162 | <ul> |
| 1163 | <li>The main expansion file always starts with {@code main} and the patch file starts with |
| 1164 | {@code patch}.</li> |
| 1165 | <li>The package name always matches that of the APK to which the file is attached on |
| 1166 | Google Play. |
| 1167 | </ul> |
| 1168 | </li> |
| 1169 | <li>Now that the expansion file(s) are on the device, you can install and run your application to |
| 1170 | test your expansion file(s).</li> |
| 1171 | </ol> |
| 1172 | |
| 1173 | <p>Here are some reminders about handling the expansion files:</p> |
| 1174 | <ul> |
| 1175 | <li><strong>Do not delete or rename</strong> the {@code .obb} expansion files (even if you unpack |
| 1176 | the data to a different location). Doing so will cause Google Play (or your app itself) to |
| 1177 | repeatedly download the expansion file.</li> |
| 1178 | <li><strong>Do not save other data into your <code>obb/</code> |
| 1179 | directory</strong>. If you must unpack some data, save it into the location specified by {@link |
| 1180 | android.content.Context#getExternalFilesDir getExternalFilesDir()}.</li> |
| 1181 | </ul> |
| 1182 | |
| 1183 | |
| 1184 | |
| 1185 | <h3 id="TestingReading">Testing file downloads</h3> |
| 1186 | |
| 1187 | <p>Because your application must sometimes manually download the expansion files when it first |
| 1188 | opens, it's important that you test this process to be sure your application can successfully query |
| 1189 | for the URLs, download the files, and save them to the device.</p> |
| 1190 | |
| 1191 | <p>To test your application's implementation of the manual download procedure, you must upload |
| 1192 | your application to Google Play as a "draft" to make your expansion files available for |
| 1193 | download:</p> |
| 1194 | |
| 1195 | <ol> |
| 1196 | <li>Upload your APK and corresponding expansion files using the Google Play Developer |
| 1197 | Console.</li> |
| 1198 | <li>Fill in the necessary application details (title, screenshots, etc.). You can come back and |
| 1199 | finalize these details before publishing your application. |
| 1200 | <p>Click the <strong>Save</strong> button. <em>Do not click Publish.</em> This saves |
| 1201 | the application as a draft, such that your application is not published for Google Play users, |
| 1202 | but the expansion files are available for you to test the download process.</p></li> |
| 1203 | <li>Install the application on your test device using the Eclipse tools or <a |
| 1204 | href="{@docRoot}tools/help/adb.html">{@code adb}</a>.</li> |
| 1205 | <li>Launch the app.</li> |
| 1206 | </ol> |
| 1207 | |
| 1208 | <p>If everything works as expected, your application should begin downloading the expansion |
| 1209 | files as soon as the main activity starts.</p> |
| 1210 | |
| 1211 | |
| 1212 | |
| 1213 | |
| 1214 | <h2 id="Updating">Updating Your Application</h2> |
| 1215 | |
| 1216 | <p>One of the great benefits to using expansion files on Google Play is the ability to |
| 1217 | update your application without re-downloading all of the original assets. Because Google Play |
| 1218 | allows you to provide two expansion files with each APK, you can use the second file as a "patch" |
| 1219 | that provides updates and new assets. Doing so avoids the |
| 1220 | need to re-download the main expansion file which could be large and expensive for users.</p> |
| 1221 | |
| 1222 | <p>The patch expansion file is technically the same as the main expansion file and neither |
| 1223 | the Android system nor Google Play perform actual patching between your main and patch expansion |
| 1224 | files. Your application code must perform any necessary patches itself.</p> |
| 1225 | |
| 1226 | <p>If you use ZIP files as your expansion files, the <a href="#ZipLib">APK Expansion Zip |
| 1227 | Library</a> that's included with the Apk Expansion package includes the ability to merge |
| 1228 | your |
| 1229 | patch file with the main expansion file.</p> |
| 1230 | |
| 1231 | <p class="note"><strong>Note:</strong> Even if you only need to make changes to the patch |
| 1232 | expansion file, you must still update the APK in order for Google Play to perform an update. |
| 1233 | If you don't require code changes in the application, you should simply update the <a |
| 1234 | href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code versionCode}</a> in the |
| 1235 | manifest.</p> |
| 1236 | |
| 1237 | <p>As long as you don't change the main expansion file that's associated with the APK |
| 1238 | in the Developer Console, users who previously installed your application will not |
| 1239 | download the main expansion file. Existing users receive only the updated APK and the new patch |
| 1240 | expansion file (retaining the previous main expansion file).</p> |
| 1241 | |
| 1242 | <p>Here are a few issues to keep in mind regarding updates to expansion files:</p> |
| 1243 | |
| 1244 | <ul> |
| 1245 | <li>There can be only two expansion files for your application at a time. One main expansion |
| 1246 | file and one patch expansion file. During an update to a file, Google Play deletes the |
| 1247 | previous version (and so must your application when performing manual updates).</li> |
| 1248 | <li>When adding a patch expansion file, the Android system does not actually patch your |
| 1249 | application or main expansion file. You must design your application to support the patch data. |
| 1250 | However, the Apk Expansion package includes a library for using ZIP files |
| 1251 | as expansion files, which merges the data from the patch file into the main expansion file so |
| 1252 | you can easily read all the expansion file data.</li> |
| 1253 | </ul> |
| 1254 | |
| 1255 | |
| 1256 | |
| 1257 | <!-- Tools are not ready. |
Joe Fernandez | e4ba91d | 2012-10-24 15:36:10 -0700 | [diff] [blame] | 1258 | |
Scott Main | 50e990c | 2012-06-21 17:14:39 -0700 | [diff] [blame] | 1259 | <h3>Using OBB tool and APIs</h3> |
| 1260 | |
| 1261 | <pre> |
| 1262 | $ mkobb.sh -d /data/myfiles -k my_secret_key -o /data/data.obb |
| 1263 | $ obbtool a -n com.example.myapp -v 1 -s seed_from_mkobb /data/data.obb |
| 1264 | </pre> |
| 1265 | |
| 1266 | <pre> |
| 1267 | storage = (StorageManager) getSystemService( STORAGE_SERVICE ); |
| 1268 | storage.mountObb( obbFilepath, "my_secret_key", myListener ); |
| 1269 | obbContentPath = storage.getMountedObbPath( obbFilepath ); |
| 1270 | </pre> |
| 1271 | --> |