I need to upload a .apk but it exceeds the 50 MB limit.
I read about this on documentation and some questions but I'm having difficulty making this work.
Does anyone have some tutorials that explain how to do this (using Android Studio) and if there is some way to use Gradle to do this?
I've done this on a few projects, but I never figured out a simple way. The instructions at
http://developer.android.com/google/play/expansion-files.html
helped me get my head around the basics, but I found parts of the process (like "android update project") didn't work properly with Gradle.
The instructions below will help you set up your project with the required libraries. After that you can go back to the official docs and figure out what to do with all the stuff you just included.
Add these permissions to AndroidManifest.xml
<uses-permission android:name="com.android.vending.CHECK_LICENSE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
Add Play Services dependency to build.gradle
dependencies {
compile 'com.google.android.gms:play-services:4.0.30'
}
Open SDK Manager and install Google Play APK Expansion Library and Google Play Licensing Library.
Copy java source files from these folders into your project's source/main/java folder:
YOUR-ANDROID-SDK-FOLDER\extras\google\play_apk_expansion\zip_file\src
YOUR-ANDROID-SDK-FOLDER\extras\google\play_apk_expansion\downloader_library\src
YOUR-ANDROID-SDK-FOLDER\extras\google\play_licensing\library\src
Open
YOUR-ANDROID-SDK-FOLDER\extras\google\play_apk_expansion\downloader_library\res
Copy drawable-hdpi, drawable-mdpi and layout into your project's source/main/res folder.
For all files in the values folders, merge content from the file into the matching file in your project.
Create a class which extends android.content.BroadcastReceiver
Add something like this to your manifest:
<receiver android:name="mypackage.MyReceiver"/>
Create a class which extends com.google.android.vending.expansion.downloader.impl.DownloaderService
Add something like this to your manifest:
<service android:name="mypackage.MyDownloaderService"/>
Compile the project and look for errors relating to
import com.android.vending.expansion.downloader.R;
Import your own project resources here instead.
Here is some helfull information for people that end up here in this post since there are some things that changed in the way apk expansions work and also if you are using Android Studio to make the libraries work. So you will need the play services library and the downloader library. (and also the zip tools if you want to use a zip as expansion file and read files and movies directly from the zip without unpacking).
With these libraries it's pretty easy to implement the apk expansion download just make sure:
your activity (the one where you want to implement the downloading
of the expansion pack when the downloading has not been done
automatically) implements IDownloaderClient.
you set up the service & receiver and set them up in your manifest.
The BASE64_PUBLIC_KEY in the service class is correct. Upload the
first apk => look in Services and API's in the developer console
under your app => License code for this app.
This code is used to see if the expansion file can be found on the device:
boolean expansionFilesDelivered() {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsMain, xf.mFileVersion);
Log.i(TAG, "Expansion filename " +fileName);
if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false))
return false;
}
return true;
}
It uses the class XAPKS wich represents an expansion file, be it either a main or patch file, having a certain filesize(bytes) and associated with a apk version (the one it was first added in).
private static class XAPKFile {
public final boolean mIsMain; // true
public final int mFileVersion; //example 4
public final long mFileSize; //example 126515695L
// example => main expansion that was first introduced in apk version 4 and is 126515695 bytes in size
XAPKFile(boolean isMain, int fileVersion, long fileSize) {
mIsMain = isMain;
mFileVersion = fileVersion;
mFileSize = fileSize;
}
}
Its also quite easy to read movie files and other stuff directly from the expansion file using the zip tools that google has provided (com.android.vending.zipfile).
First get the expansionfile using the methods provided in the library, the paremeters are integers that represent your main expansion apk version (the apk version where the expansion pack you need was first added) and the patch apk version.
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(context, APKX_MAIN_APK, APKX_PATCH_APK);
Video
For playing video directly from this zipresourcefile:
AssetFileDescriptor a = expansionFile.getAssetFileDescriptor(pathToFileInsideZip);
Now from this assetFileDescriptor you can get a FileDescriptor and use this in your mediaplayer, the correct syntax to get your mediaplayer to play the video also needs the second and third parameter.. Be it the startoffset and length you can get from the AssetFileDescriptor.
player.setDataSource(a.getFileDescriptor(), a.getStartOffset(), a.getLength());
Other
For all the other stuff (like images) you can just get an inputstream of the zipresourcefile:
expansionFile.getInputStream(pathToFileInsideZip);`
ALSO make sure you don't compress the videos in the zip for this to work!
for example not to compress .mp4 files:
zip -n .mp4 -r zipfile.zip . -x ".*" -x "*/.*"
NOTE 1
You can't use draft anymore as the link to get the expansion file won't be active yet. You have to upload a version to Alpha or Beta first with expansion file. (adding an expansion file is only possible from the second apk you upload and up) So make sure you see the apk expansion file listed when you click the details in the developer publish section under APK.
NOTE 2
If you are using android studio and want to make use of the downloader library don't just copy the package name and java files into your own app src directory. Import the downloader library in eclipse and choose export => gradle build files. Afterwards you can import the library as a module in android studio.
NOTE 3
Not sure of this but I also think it's neccesary to download the app atleast once through the play store and have access to it with the account on your test device. So if you are working with alpha create a google+ test group and add yourself or other test devices to it.
I have just succeeded in making an app that uses the expansion files with Android Studio. Doing this is a monumental challenge. My first advice is that if you just want a big app, make it for an Apple product. Apple accommodates large apps very nicely. On the other hand, Android has made it nearly impossible to make an app larger than 100Meg.
If you do wish to make a big Android app, first get the app working. But do not release any version of the app with a compileSkdVersion larger than 22. Later, I explain that the expansion libraries don't work with a later version so your final app must be version 22 or less. If you do release a later version the Google Store will not let you go back to version 22. In that case, abandon your app identifier and make a new app using version 22.
The libraries and code that you need can be obtained thru tools->android->SDK Manager". On the SDK Tools tab select and load Google Play APK Expansion library and Google Play Licensing Library. At the top of SDK Manager window note the Android SDK Location: field. After the download you can find these files in your SDK directory under extras/google. At this point the directories you are interested in are called market_apk_expansion and market_licensing.
Android documentation on the expansion files give you instructions for linking these libraries into your project. I have spent hours attempting make this work. Generally, Android Studio does bad things. First, it never seems to give error messages when what you enter is not right. Both of the times I got the expansion files to work I incorporated the libraries into my app and did not use the library features of Android Studio. However, putting the libraries into your app is not easy.
First, I should explain that these libraries are old and do not work with the latest SDK versions. I was able to get the libraries to work with a compileSdkVersion of 22. (See build.gradle for the module:app.) The targetSdkVersion should be 22. And under dependencies you should have compile 'com.android.support:appcompat-v7:22.2.1'. This will allow you to use the DisplayWebpageActivity.class. I think this was added in version 23 but it seems to work in this version of 22. I also have added compile 'com.google.android.gms:play-services-appindexing:8.1.0' but I do not know if this is necessary.
In my opinion there is no hope for ordinary people to write the code necessary to download an app. I used the code from the sample that is included in the "extras". You find this sample at /extras/google/market_apk_expansion/downloader_sample. What you need to do is to make this sample app (which does nothing but load the .obb files) be the opening page of your app. Add an Intent to the sample program so that it calls the real first page of your app when the .obb files are present.
The sample program consists of a "res" directory and three .java files. You need to put the three .java files into your main java directory. Change the name of the "res" directory (to "res2") and put this into the same directory that contains your normal "res" directory. To get Android Studio to recognize both "res" directories you add the following to your "build.gradle (Module:app)" file:
sourceSets {
main {
res.srcDirs = ['src/main/res']
res.srcDirs += [ 'src/main/res2']
res.srcDirs += [ 'src/main/res3']
}
}
The above is in the android { } section. The "res3" will come from the libraries.
All of the package names and some of the includes in the three .java sample files must be fixed to match your package name. In "res2" there will be an app name that conflicts with your app name, so delete it. You must fix your manifest so that it starts to execute SampleDownloaderActivity. And fix SampleDownloaderActivity so that it call the start of your app when the .obb files are there.
The Android documentation suggests that you make your .obb file a zip archive. I did this with my first expansion file app. For my application this did not work well. I had lots of small images. These images were already compressed (.jpg). Accessing any image was slow so I ended up making a poorly performing app.
In the second expansion app I used the "patch" file for a directory and put the photos into the "main" file. To access a photo the patch file is read and put into a dictionary that contains photo names, locations and lengths. This lets the app find where an image is in the main file, which is treated as a random access file. The photos that I need are copied into the storage for the app and given their proper names (xxx.jpg). The photos are then accessed inside of HTML code.
The two libraries are also integrated into the app. There is a "res" directory that is renamed to "res3" and put into the same directory as the regular "res" directory. In my app/src/main/java/com/developer/app_name directory I added a "downloader" and "licensing" directories. I wrote a program to move the libraries into their directories while fixing the package names and other occurrences of the original package name. The program only got some of the names that needed to be changed. Keep working on getting all these names fixed while ignoring some other kinds of errors. Many things that reference the "res3" directory did not work until I added an import statement like this:
import com.developer.app_name.R;
In the licensing library the LicenseChecker.java file contains code that will crash in SDK version 22. You must fix this code or use an older SDK version. Go to around line 150. Comment out the code that starts with boolean bindResult = mContext to Context.BIND_AUTO_CREATE);. Replace it with the following:
Intent serviceIntent = new Intent(
new String(Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")));
serviceIntent.setPackage("com.android.vending");
There are a couple other errors in the library where Android Studio gives good suggestions on ways to fix them.
During debug you can manually put the .obb files into your device. On my Android this is done in Nexus 5/Internal storage/Android/obb where I make a folder called com.developer.app_name. I have discovered that what I see on my computer is not always what is in the device. Sometimes I must power the device off and on in order to see what is actually there. I spent lots of time trying to understand why the app could not find the .obb files when I could see them thru the computer. In fact, the .obb files were not there.
After you app works in debug with and without the .obb files, and without the .obb files it tells you that they can't be obtained from the store, it is time to take the app to production in order to finish testing it. If this is the first upload to the store the upload software fails to ask for the .obb files. So upload your app. Then, before attempting to release it, change the load number and version number and upload it a second time. This time it will ask for the .obb files.
Add them as a modules , Or import them in eclipse as a libraries ,go to Android studio and import non android studio project .. Go to Project Directory
Related
Background info
When uploading an app to the play store that uses a native library its necessary to also upload the native debug symbols to get useful crash/ANR info.
If you upload without symbols you receive the following warning: "This App Bundle contains native code, and you've not uploaded debug symbols. We recommend you upload a symbol file to make your crashes and ANRs easier to analyze and debug."
In the past when uploading apps as .apk files it was necessary to manually upload such debug info. Now using .aab if the native library is built via android studio its possible to set android.defaultConfig.ndk.debugSymbolLevel = 'FULL' at which point when you build a the .aab it will include the debug info automatically, you upload this single file and everything is done/working.
pre-built libraries
However its not always possible/ideal/necessary to build a library inside android studio. Sometimes there are reasons for libraries to be externally pre-built and just used by android studio not built by it; Android studio supports this via a directory structure which is described here https://developer.android.com/studio/projects/gradle-external-native-builds#jniLibs
In short you just copy the libraries into the correct src/main/jniLibs/{ABI} path(s) and it is picked up and made part of the bundle.
Problem
Android studio can build a .aab that contains debug info that play store can understand so that you don't need to upload it manually.
Android studio can use pre built native libraries if you place them in the right path structure
I am unable to find any documentation or way to do both of these things together, use native pre-built libraries but include their debug info in the .aab. Even though logically it should be possible to do this.
I have searched everywhere I think but can't find anyone even talking about this really, how/where do you place the corresponding debug information so that that also can be included as part of the .aab? Is there a separate path for this, do they just need a specific file extension, does gradle need to be told what to do with them somehow?
Is it really just not possible?
Things I have tried:
Don't split the debug info just leave them in the .so files - play store does not strip them then so you deliver giant debug versions of your builds to your users
Split the debug info into files with .so.dbg extension and place them alongside the .so files - they aren't included in the .aab
Following the instructions (here https://support.google.com/googleplay/android-developer/answer/9848633 and elsewhere) to manually zip and upload the symbols after uploading the .aab - this appears to work but isn't the same convenience wise as having them in the .aab
I've tried building a sample app with android studio building a lib instead of using a pre-built lib just to verify that it does then include the debug info and what file extension it uses.
After some more digging I found the task responsible for this is "ExtractNativeDebugMetadataTask" with which some effort can likely be tailored/altered to do custom things here.
However this ended up being unnecessary as while digging into this I discovered that it actually already works.
At least as of now in latest gradle versions (not sure about the past).
It was only failing due to some NDK path confusion which doesn't fail the build/creation of the bundle building but just logs an easy to miss informational message.
Ultimately all you need to do to make this work is:
Build your external .so files with -g -g3 or similar
Do not strip them in any way
Place them in jniLibs directory structure un-stripped
Configure your build.gradle appropriately android{ ndk { debugSymbolLevel 'FULL' } ndkPath "$projectDir/path/to/NDK" }
Resulting .aab will contain the stripped .so files and the split-debug .so.dbg files all in the right location.
Background
I'm working on an app that has become very popular, so much that a part of it is supposed to become an SDK (which would be available for developers), and the app will split to 2 apps (both use the SDK).
According to what I know, there are multiple ways to create an SDK module (previously called "project" on Eclipse) :
Completely open sourced (Android library) - all sources and resources are open sourced and can be modified. An example might be Facebook's SDK and a lot of Github repos.
a single Jar file, which can be closed sourced.
The problem
Sadly, I can't make the SDK open sourced, and it should relatively be protected vs prying eyes (obfuscated etc...).
The issue here is, the SDK needs to use some resources of its own (drawables, strings,...), and so far (because I didn't have a lot of experience with creating SDKs) I've found 2 ways to handle resources for SDKs :
use reflection and/or "context.getResources().getIdentifier" . This is quite messy, as I lose the whole "R" usage of the code. Also, it has issues with "styleable" , as I've written here. It also makes it hard to find unused resources.
even worse ways: put resources in assets folder, put files in a wacky way inside the jar file, ...
Note that a part of the SDK includes custom views (for example, classes that extend from TextView), so even if I do split the SDk into 2 modules- resources and java files, both might have issues of dependencies (each uses the other one).
The question
Is it possible to somehow solve this issue?
Is it possible for the code part of the SDK to remain closed sourced, reach the "R" file as usual, and make it easy for both me and whoever use the SDK ?
How would I then generate the jar file as being obfuscated via Android Studio? and is it possible to prepare it to to be used via gradle afterwards?
Can I maybe make the Android-library of the SDK into an obfuscated jar file and not worry about the "R" file ? I ask this because this way I could enjoy both worlds: for our apps, it would remain open sourced, and for third party apps it would be closed sourced.
EDIT: seeing that this is supposed to be easy, I've tried it myself. I've created a totally new POC project which has an Android library module called "sdkmodule", and added this class to it:
public class SdkClass
{
public String doIt(Context context)
{
return context.getResources().getString(R.string.app_name);
}
}
Then, I've made the app's module to use this one, and I wrote this code in it:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SdkClass sdkClass=new SdkClass();
Log.d("AppLog","string from SDK:"+sdkClass.doIt(this));
Log.d("AppLog","string with same ID name from app:"+getResources().getString(R.string.app_name));
}
What I expected is that the first log would print the string that's in the SDK module, and the second to show the string of the current project, but instead I got an exception:
java.lang.NoClassDefFoundError: Failed resolution of: Lcom/example/user/sdkmodule/R$string;
On another try, I've got the same string that's used on the app itself (the one that uses the SDK module). And, on another run, the SDK produced the needed string as I've wanted.
How could it be? What should I do ?
In addition, I've tried to make a second activity in the SDK itself, and I've created a resource there that has the same resource name (used for a textView in its layout) as of the app itself, yet with a different value, yet when I've reached this activity, I've seen the one used by the app.
Here's the project, zipped (ignore the name of the folder, I wanted to try flavors too) :
https://drive.google.com/file/d/0B-PZZGk2vPohX25WUDNKTmotUTg/view?usp=sharing
The answer to your problem is to package and distribute your library as an AAR bundle
This format allows you to provide an obfuscated SDK jar and with its resources and the R mapping file.
This format is a standard and fully supported by maven-android-plugin (actually it's the replacement of the old APKLib format which supports only the distribution of source files).
Of course it's also supported by Gradle and Android Studio.
The Android Archive (AAR) format does what you want. It's like an Android-specific JAR file and contains compiled code, but includes its own resources and manifest. You can also include obfuscation as part of the build process. By default, the current version of Android Studio (1.2) and Gradle automatically build .AAR files for all library modules you create in your project.
You can change an app module into a library project that will publish an AAR file just by changing apply plugin: 'com.android.application' into apply plugin: 'com.android.library' in your module's Gradle file. The .AAR file will be placed in your MODULENAME/build/outputs/aar folder after each build. Some more information is available here.
Edit 1, after question updated:
The resources in your AAR get consolidated with the app module when the final APK gets compiled. The app resources will override the library's. This is probably by design, to allow people using a 3rd party library to customize its resources when creating their app, without having to rebuild the library. I think the easiest way to solve your resource conflict issue would just be to name your sdkmodule resources something more unique. Why not make the string key R.string.com_example_sdk_name or something?
No, the AAR libraries don't get obfuscated by default, but you can set up ProGuard in the Gradle build file for your AAR library to take care of this. Other tools are also available.
I need to be able to change out the package name of an existing .apk in order to run simultaneous instances of an Android app I created in Visual Studio using Xamarin. However, I will not reliably have access to the computer with the Xamarin license when I need to change this package name, so I can't simply edit the manifest pre-build. I tried using ApkTool to unpack the apk, edit the manifest, then re-package and re-sign the apk. However, while I am able to install this new apk on a device side-by-side with the original apk, the new app instance fails to start. I receive the following error:
"monodroid" "No assemblies found in '(null)' or '/storage/emulated/0/Android/data/com.newpackagename/files/.override'. Assuming this is part of Fast Deployment. Exiting..."
I assume this means that somehow changing the package name post-build has lost some link to the assemblies. However, I can find no remaining reference to my original package name even after searching the entire unpacked apk (both folders and within files). So I'd like to figure out how to regain these assemblies to allow my app to run. I understand this is very unusual and it may not even be the right way to go about it, but I need the ability to change my package name without having access to Xamarin. This seems like the most promising solution.
Xamarin relies on storing files, unknown to aapt (Android Asset Packaging Tool), within the final built apk. These files are inserted usually in the assemblies folder within the root of the apk with the STORED compression type so that Xamarin can access the files without decompression.
Apktool used to ignore all files that aapt would ignore. IE. If the file isn't a folder/file of this array
private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] {
"classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "lib", "libs", "assets", "META-INF" };
than Apktool would ignore it. However, an attempt was made during development of 2.0.x to include these unknown files.
A bug with the Java 7 NIO library caused all "unknown" files to be stored as DEFLATED. This changed the storage format of the DLLs from STORED to DEFLATED thus this error.
This error was fixed on March 25 - https://github.com/iBotPeaches/Apktool/commit/628286c022e3a872d6ab6bfb3431579f98743c25
As of April 8, 2015 - There are no official releases with this fix in it. You may build Apktool yourself though until 2.0.0 Gold is released.
I added the audio android-ffmeg library project in my application.
I added a file called libsox.so in the libs folder of the application.
The file is called via a ShellCallback , looking in the file system.
The problem is that it return a "nullPointerException", depending on the mobile phone. On my Samsung it works without problem, but this problem appear on other device.
The problem looks to be in the first line:
fileBinDir = new File(_context.getFilesDir().getParentFile(),"lib");
soxBin = new File(fileBinDir,"libsox.so").getCanonicalPath();
Somebody know why it can't be found on some device ?
Native libraries are handled different to assets. Usually you include one library for each platform (armebi, armebi-v7a and x86). When you install the app the matching library in installed and the other libraries are just ignored.
If you want to load a library use System.loadlibrary("sox") instead. The "lib" and ".so" parts will be automatically added and Android knows where it installed the library.
Alternatively you can use System.mapLibraryName(..) to get the library file name and then use findLibrary(..) of your current classloader for getting the path to the library.
when building .ane files, you should be careful about naming your resources so no two extensions would conflict each other when used in one app. I know that and it's ok.
I have another kind of conflict problem! you see, in one of the extensions I've been building, I used the external .jar file, Google play services and the extension works like charm. everything was fine until the day that I created another extension which was happened to use the external jar file, Google Play Services again!
when building .ane files, I actually merge any needed external .jar files into the one that I have exported myself from eclipse.
now that I have two different extensions both happen to be using Google Play services external .jar file, when I use them in one app, it won't allow me to package my .apk
the error message says:
java.lang.IllegalArgumentException: already added:
com/google/android/gms/appstate/OnStateListLoadedListener;
at
com.android.dx.dex.file.ClassDefsSection.add(ClassDefsSection.java:123)
how can we bypass this kind of confliction?!
Extension's will not allow same method name's even they are in different extensions. i think that is the mistake you did in making extension's. please look at them and changes method names it will work.