How do I make the resources (string, dimen, color, etc.) in my Android library module private?
I've tried both documented ways of doing this and neither work...
Following the official Android doc of creating a
res/values/public.xml does not work; all of the resources remain
public in the app that uses this library.
Following Chris Banes's instruction (and reiterated in this StackOverflow answer) of creating a
res-public/values/public.xml folder does not work either; all of
the resources are made private, but those listed in the public.xml
file are not made public as they should be.
Does anyone have the definitive instructions for making library resources private? Are there any open-source libraries out there that have properly made their resources private?
I'm using...
Android Studio v2.2.3
buildToolsVersion "24.0.2"
com.android.tools.build:gradle:2.2.3
Your resources are assumed to be public in any module (library, etc) unless you start, explicitly, declaring any resource in the module to be public. At that point you basically opt-in to every resource (within the module) being private except what you mark as public. This preserves backward compatibility and let's you incrementally tighten down access in large projects with many modules.
To make all resources private simply add <public /> to any of your existing resource files.
The above answer talks about adding a specific resource directory just to manage the public/private modifiers. While that works, I might suggest you manage the visibility and declaration in the main resource files next to where they are declared. Here's a sample strings.xml resource file I used with a brand new library module. The public/private prefix in the string names is for illustrative purposes only.
res/values/strings.xml
<resources>
<string name="app_name">My Library2</string>
<public type="string" name="public_string_in_lib2" />
<string name="public_string_in_lib2">public string in lib2</string>
<string name="private_string_in_lib2">private string in lib2</string>
</resources>
Fundamentally, these qualifications are being used to create a public.txt file that is embedded in the AAR that will be depended upon by another module. Various pieces of tooling like Android Studio will use this information to flag/warn, but technically, a consumer of the library isn't prevented from using the resource unless their tooling is being really strict.
Option #2 actually works. I had not properly defined my sourceSets in my build.gradle...
sourceSets {
main.res.srcDirs = [
'src/main/res',
'src/main/res-public'
]
}
Related
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.
When a project with one of more apk libs is compiled, then more then one R.java is generated by the aapt tool. One for each library and one for the application itself.
Each of those R files would define the same IDs. This worked without problems for quite a while now. Years in fact. But suddenly not any more. Now the same resource has two different IDs.
target/generated-sources/r/com/viewpagerindicator/R.java:
public static int default_line_indicator_selected_color=0x7f04000b;
target/generated-sources/r/net/sourceforge/uiq3/fx602p/R.java:
public static final int default_line_indicator_selected_color=0x7f07000b;
Has anybody got an idea what might have gone wrong?
Update:
I double checked with other projects. There I noted that apart from the final the R.java files should be 100% identical. Especially: each R.java file should define all IDs — Even the IDs which are not part of a library.
This too is not the case in my troublesome project. Each library R.java only defines the IDs used by the library.
Quick and Dirty Fix
Until i find out what the real problem I use the following ant task as a quick and dirty fix:
<copy
encoding='${project.build.sourceEncoding}'
file='target/generated-sources/r/net/sourceforge/uiq3/fx602p/R.java'
overwrite='true'
toFile='target/generated-sources/r/com/viewpagerindicator/R.java'
>
<filterchain>
<tokenfilter>
<replacestring
from='net.sourceforge.uiq3.fx602p'
to='com.viewpagerindicator'
></replacestring>
</tokenfilter>
</filterchain>
</copy>
I wonder why aapt is called several times when a copy with search and replace can do the trick as well. And note that i don't remove the final as well.
When you assemble the project, the library ressource are overwritten by the main project.
I think this explain why the R ids in library aren't final.
All your references to default_line_indicator_selected_color will use the new value 0x7f07000b.
In what situation do you have problems with this?
When you build an application that depends on a library project, the SDK tools compile the library into a temporary JAR file and uses it in the main project, then uses the result to generate the .apk. In cases where a resource ID is defined in both the application and the library, the tools ensure that the resource declared in the application gets priority and that the resource in the library project is not compiled into the application .apk. This gives your application the flexibility to either use or redefine any resource behaviors or values that are defined in any library.
http://developer.android.com/tools/projects/index.html
I have a project A that referenced by Library B, A and B have the same name and type, but their value are different. I think aapt should deal with this issue that make sure project and library access the correct value. besides renaming all the resource in project or library, what else should I do to solve this problem?
The build system intentionally makes all project resources overlay on top of the library resources. This is done on purpose to be able to customize a library resource differently depending on the app using it.
If you want to prevent this happening without your knowledge we have always recommended users to use prefix in the library resources.
Changing the behavior at this point would break many, many people's projects. We've looked at making it an option, but it won't happen before the new build system is finished though.
As per the Android Building process, all projects and libraries (and all of the resources in all of them) are combined as part of the apkbuilder process. If there is a conflict between your project and library (or between two libraries), the final build will not know which to reference as they share the same name. Of course, this has benefits in that you can reference library resources in your project by name, even though the underlying build process is de-conflicting the underlying ids.
Import the appropriate R.java file to resolve the resource conflicts.
Make sure that your package name and the library's package names are different.
If you need your libraries resources then refer to them by library.packagename.R.drawable.resourceId and not by R.drawable.resourceId
Check your gen files if it has 2 R.java files.
I am trying to create a android project with two libraries,however, I have been able to link the two libraries I created with my android project.
I have same xml name in two libbaries,when I am compiling my project, the R.java file is getting compiled in my new project and I am getting only one xml file from the two libraries. how could I link the two libraries in my android project with the same xml names? I know it can be done with diff names but I want the same name.
Are you saying you have say strings.xml in /res/values in both library projects and expect to get more than one strings.xml in your consuming project?
From what I experience, if I have strings.xml in library projects and also in my consuming project, the resulting R.java will have the values from all 3 merged together, with any duplicate entries having the value as defined in the consuming project.
Not possible. That's the way library projects are designed to work. Resources are merged into the parent project. If you want values from both libraries to make it in, they need to have different resource IDs, and even then you'll have one resource file (unless you have different file names to start with).
The only way you could get this to "work" is to do something with your build process renaming the files, which is doable, but not trivial, especially if there are possible resource ID conflicts and even harder if your main project needs to use any of those resources directly.
I'm pretty new to Android development, but I have some experience with Java and Eclipse. I'm looking for ways to create re-usable libraries (controls, helpers, "standard" activities, etc.) that I could use in my own projects, but that could also be distributed to other developers without disclosing the source code.
Normally, I'd package the class files into a JAR file and any other developer could add it to the classpath and use the packaged classes.
How can I do that with Android projects? I've read about Android Library Projects, but as the documentation states they can not be packaged into a JAR, but will be compiled along with the project that references the library project. This means I also have to distribute the source code.
I've also read this post, which asks about the same question but didn't provide a satisfying answer.
So: Is there a way of compiling and packaging a set of classes and other files (including XML layouts, resources and stuff) and distribute only that package without any source codes so that it can be "referenced" and used like a JAR file by any other developer?
I've read about Android Library Projects, but as the documentation states they can not be
packaged into a JAR, but will be compiled along with the project that references the library
project. This means I also have to distribute the source code.
Not true. It does require a bit of extra packaging work, but you can compile your code to a JAR and distribute the JAR in the library project's libs/ directory.
So: Is there a way of compiling and packaging a set of classes and other files (including
XML layouts, resources and stuff) and distribute only that package without any source
codes so that it can be "referenced" and used like a JAR file by any other developer?
Use an Android library project. I have some stuff written up here that describes a bit more of the packaging options, plus pointers to some "parcels" that follow the conventions described therein.
Thanx for your solution. From what I understand, you still can not access the resources private to the library from within the libary code. So assume your library has a string resource named "my_lib_resource" under res/values in the library. You bundle this in the jar along with the source code. Can you access this resource from the library source code using something like:
int id = res.getIdentifier("com.example.mylib:string/my_lib_resource",null,null)
assuming your library package name is com.example.mylib.
For me this does not work.
Brave new world of dependency management:
http://tools.android.com/recent/dealingwithdependenciesinandroidprojects