Search code examples
androidandroid-studiosdkobfuscationr.java-file

How to prepare a closed sourced SDK module on Android Studio?


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) :

  1. 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.

  2. 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 :

  1. 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.

  2. 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

Solution

  • 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.