Proguard causing NoSuchMethodException - android

Class<?> c = Class.forName("co.uk.MyApp.dir.TargetClass");
Method main = c.getDeclaredMethod("main", Report_Holder.class);
Throws a 'java.lang.NoSuchMethodException.main[class co.uk.MyApp.classes.Report_Holder]' error once I've prepared the app for release using Proguard.
I spent hours thinking the problem was in 'co.uk.MyApp.dir.TargetClass', commenting out things, re-releasing the app, and re-testing. But it turns out that the error is right at the root, at:
Method main = c.getDeclaredMethod("main", Report_Holder.class);
I then updated proguard-project.txt to include:
-dontobfuscate
-keeppackagenames
(I am using the Lint suggested method which suggested putting code into project.properties and putting the config in a text file), such as:
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
But adding those 2 lines didn't have any effect.
So now I am wondering if;
a) I should add anything on top of '-keeppackagenames' etc.
b) Is proguard.config set up correctly; should ${sdk.dir} actually be a proper uri to the sdk
The class that it is targeting is like this:
public static void main(Report_Holder args) {
....
}
Edit
Or is it because I have 2 instances of this type of thing, both called 'main' ? But called in different activities.
Method main = c.getDeclaredMethod("main", Report_Holder.class);
Method main = c.getDeclaredMethod("main", OtherReport_Holder.class);
And both targets being like this:
public static void main(Report_Holder args) {
....
}
public static void main(OtherReport_Holder args) {
....
}

Once you know how to use proguard, you should add the option -keepattributes Signature
. this is necesary when using generics (collections).
For all methods beeing called via reflection, you must explictly exclude them from obfsucation. use the option to output the obfuscation map file, to see if your rules had the desired effect.

Related

Storing (partial) Proguard obfuscation mapping in code

In an android Java code like this:
String methodName = "myMethod"
public void myMethod() {
}
Is it possible to figure out the obfuscated name of the method at runtime?
I know I can ask Proguard to not obfuscate that method, but I'm looking for a way to avoid that and still be able to use it through reflection.
I can't find a Proguard configuration for that, but I'm wondering if there could be a gradle task for merging the generated mapping?
If you need the method name from inside the method, you can use
String methodName;
public void myMethod() {
methodName = new Object() {}.getClass().getEnclosingMethod().getName();
}
Or, in newer Java versions
String methodName;
public void myMethod() {
methodName = StackWalker.getInstance(Set.of(), 1)
.walk(s -> s.map(StackWalker.StackFrame::getMethodName).findFirst())
.orElseThrow();
}
When you need the name from the outside, i.e. with calling it, you can mark the method with an annotation and search for it.
#Retention(RetentionPolicy.RUNTIME) #interface Marker {}
#Marker
public void myMethod() {
}
String name = Arrays.stream(ContainingClass.class.getDeclaredMethods())
.filter(m -> m.isAnnotationPresent(Marker.class))
.map(Method::getName)
.findFirst().orElseThrow();
As said by others, not to use Reflection might be the better option. E.g., referring to the method using a matching functional interface, is not a reflective access:
Runnable method = this::myMethod;
public void myMethod() {
}
This will keep working after obfuscation.
Code obfuscation usually just renames classes and variables to short and random text, keeping proper dependencies at the same time. There is no way to get the obfuscated name of the function as it is generated randomly and always different per each run.
Here are some options:
Instead, exclude the class from obfuscation and name your function with random text if you want to keep it unreadable.
-keep class <com.package.DontObfuscate>
After the first obfuscation, keep mapping.txt file and add a line to your proguard-rules.pro
-applymapping mapping.txt
That's will keep from generating new names for classes/variables.
-applymapping - Specifies to reuse the given name mapping that was printed out in a previous obfuscation run of ProGuard. Classes and class members that are listed in the mapping file receive the names specified along with them. Classes and class members that are not mentioned receive new names.
Look for a way to not use reflection in your code with class/interface extensions.

How to configure Android library from an app that imports it?

I'm kinda new to Android development so my question might be weird or not even possible. I wouldn't know!
Anyway, I'm building multiple apps that will have a lot of shared elements, so I decided to build a library with those components and use it in all of the apps, rather than stupid copying and pasting code.
For example, the library handles the welcome screen and login/signup flow activities, among other things. So here are the problems this approach might cause:
While the behavior is the same across the apps, but the logo that I show at the welcome screen is different. Right now I populate it with an image resource from the library resources (R class) which will be the same for all apps and is obviously not correct.
The login/signup process is based on Firebase, which will require the app to have a key to be able to use them. Right now I also populate it with a dummy string resource from the library resources.
So my question really boils down to 3 parts:
Is there anyway I could pass this info from the app to the library? can I somehow modify the R class of the library? Or can I use the app's R class from the library? I can also call this part of the library as a function passing the parameters I need. But the first solution looks maybe more clean to me?
Whatever the answer to Q1 is. Where would I do this and how? The library has the welcome activity itself which is supposed to be the first activity in the app. How and where do I do this once the app starts and before the first activity starts?
If what I'm doing is wrong or impossible, is there any other way to achieve it?
Is there anyway I could pass this info from the app to the library?
can I somehow modify the R class of the library? Or can I use the
app's R class from the library? I can also call this part of the
library as a function passing the parameters I need. But the first
solution looks maybe more clean to me?
You don't need to modify the R class because you can override the resource file by creating a file with the same name. But it's not a clean solution because you constantly need to ensure your project and library resources name are the same.
Whatever the answer to Q1 is. Where would I do this and how? The
library has the welcome activity itself which is supposed to be the
first activity in the app. How and where do I do this once the app
starts and before the first activity starts?
Instead of overriding the resources name, you're better to modify your library to receive a configuration as a contract to use the library. Here the sample:
First, create the class for holding the configuration:
public class Configuration {
private int welcomeImageDrawableId;
private int logoDrawableId;
// constructor
public Configuration(int welcomeImageDrawableId, int logoDrawableId) {
this.welcomeImageDrawableId = welcomeImageDrawableId;
this.logoDrawableId = logoDrawableId;
}
// setter and getter.
public int getLogoDrawableId() {
return logoDrawableId;
}
}
Second, use the configuration class for the library by creating a Singleton class which will be used internally by the library:
public class MyLibrary {
private static MyLibrary myLibrary;
private Configuration configuration;
private MyLibrary(){}
private MyLibrary(Configuration configuration) {
this.configuration = configuration;
}
public static MyLibrary getInstance() {
if(myLibrary == null) {
throw new RuntimeException("Need call createInstanceWith method first!!");
}
return myLibrary;
}
public static MyLibrary createInstanceWith(Configuration configuration) {
if(myLibrary == null) {
synchronized(MyLibrary.class) {
if (myLibrary == null) {
myLibrary = new MyLibrary(configuration);
}
}
}
return test;
}
public Configuration getConfiguration() {
return configuration;
}
}
Third, use the configuration class in your library via the singleton class. something like this:
// assume imvLogo is an existing ImageView
Configuration configuration = MyLibrary.getInstance().getConfiguration();
imvLogo.setImageResource(configuration.getLogoDrawableId());
Last, register the contract when the library is used with:
Configuration configuration = new Configuration(R.drawable.welcome, R.drawable.logo);
MyLibrary.createInstanceWith(configuration);
Note: all the code isn't tested yet, error is to be expected.
Apart from the solution above, I also found another way to achieve this whole thing without having to initialize libraries and whatnot.
I think the correct way to do this is to use productFlavors in the library. This allows the library to share the one main set of source code, one main set of resources, then an extra set of resource per app/flavors. This is very sufficient for my purposes.
For more info about build variants and flavors:
https://developer.android.com/studio/build/build-variants

Why some package-private classes are not obfuscated by Proguard?

Working with an Android project in Android Studio 3.2, having enabled Proguard and some specific rules, I'm not able to figure out the following:
a specific package (and its subpackages) in a library module, used by client code, is preserved through the rule:
-keep public class com.mylib.mypackage.** {
public protected *;
}
Now, within this package there are also a number of package-private classes, which should not be picked by that rule. Some of those classes are effectively obfuscated, both in their own names and their member names, which is fine.
Instead there are some classes, implementing public interfaces, whose class names are not obfuscated, while I'd expect they should. For completeness, their member names, when not part of interface, are effectively obfuscated.
Example:
/* package */ class InternalComponent implements ExternalInterface {
// ExternalInterface is kept: Ok
// InternalComponent is kept: don't like, I'd like it renamed
#Override
public void ExternalMethod() {
// this is kept: Ok
}
public void InternalMethod() {
// this is renamed: Ok
}
}
I'd like to highlight that InternalComponent is created within some other (kept) class and only returned to client code through the ExternalInterface.
How can I also obfuscate their class names as well, if possible?
Edit #1
After #emandt answer on Proguard output files, I double checked and com.mylib.mypackage.InternalComponent is listed in seeds.txt, which according to this blog post lists all items matched by keep rules. So, for some reason, the rule above also picks package-private classes, which still seems wrong to me.
Edit #2
In the meantime, I ended up doing exactly the same approach proposed by #shizhen. For completeness, in order to extend the exclusion to any package named internal, I modified my proguard rule as:
-keep public class !com.mylib.mypackage.**.internal.*, com.mylib.mypackage.** {
public protected *;
}
(note the first part before the comma, prefixed by !)
I'll mark #shizhen answer, though I'd like to be curious as to why the original rule is also picking package-private components.
Are you working on an Android Library project? Probably YES.
In order to achieve your purpose, I am afraid that you need to re-organise your packages into something like below:
Public interfaces
com.my.package.apiforusers
Private/Internal implementations
com.my.package.apiforusers.internal
Then for your obfuscation rules, you can have it like below:
-keep public class com.my.package.apiforusers.** { public *; }
So that only the public classes/interfaces are kept and all those ones inside com.my.package.apiforusers.internal will be obfuscated.
Please note the double-asterisk at the end so that public classes/interface are also kept for the sub-packages.
In "/build/outputs/mapping/release/" folder there are few files ("usage.txt", "seeds.txt", etc..) that contain the REASONS of why and which classes/variables/methods/etc.. are not-processed/not-shrinked/ot-obfuscated via ProGuard utilities.

Proguard not stripping unused generated variables after stripping out Log statements

I created a wrapper class MyLog to do logging for my Android app, which essentially just wraps android.util.Log. I want logging to be completely gone in my release app. I have the following rule defined in my proguard file:
-assumenosideeffects class com.myapp.logging.MyLog {
public static void d(...);
}
I am seeing that lines that have log statements as follows:
MyLog.d("Logging a boolean %b and a string %s parameter", isTrue, stringName);
are being shrunk to:
Object[] objArr = new Object[]{Boolean.valueOf(z), str};
and lines with log statements as follows:
MyLog.d("function call result: " + foo() + " end");
are shrunk to:
new Object[1][0] = foo();
In both cases the leftovers from obfuscation are pretty useless and might as well should've been removed.
Question:
Why would proguard leave unused variables in example #1 above?
Is there a better way to tell proguard - "Assume no side effects when you remove this method declaration and any calls to it, along with the parameters passed to it"?
I have read other answers related to the topic and the closest answer is here. I am not looking for solutions with BuildConfig.IS_LOGGING_ENABLED type solution where every log statement should be wrapped with this check.

Retrieve android:versionName from Library Project

I have created a Library Project which I import into another project.
In that Library Project at some point I retrieve it's android:versionName
To do that you need to supply the package name.
The problem arises when that code is executed when the Library Project is included within another project, then it seems that that code throws an exception :
10-04 10:15:36.987: WARN/System.err(1407): getSoftwareVersion(), Caught Exception : android.content.pm.PackageManager$NameNotFoundException: mobilaria.android.LandenPlayerCodeBase.baseplayer
Thats the package name of the package of the Project Library... it seems it cannot find it even though the same code that is executing that call is part of the Library itself...
Does anyone have experienced something like this or has an idea on how to solve this ?
As far as I know android library project manifest is ignored at the moment, manifest is not merged into end application when you reference a library. Hence you cant extract any data from the library's manifest.
I just tried something similar.
I tried to add a method getLibraryVersion() to my custom Application class. So I would be able to call
MyLibrary.getLibraryVersion()
from within the code that included that library. But it seems that you can not access the String resources via getText() or getString() like this:
public class MyLibrary extends Application {
#Override
public void onCreate() {
super.onCreate();
// provide an instance for our static accessors
MyLibrary.instance = this;
}
private static void checkInstance() {
if (MyLibrary.instance == null) {
throw new IllegalStateException("Application not created yet!");
}
}
/**
* #return the library name
*/
public String getLibraryName() {
MyLibrary.checkInstance();
return MyLibrary.instance.getString(R.string.app_project_name).toString();
}
...
}
Because the onCreate() method seems not to be called, the instance is always null!
As this way was not working out, and as you saw you cannot access the version the way you tried, I just hard coded the version and the library name, into my custom application class like this:
public class MyLibrary extends Application {
/**
* #return the library name
*/
public String getLibraryName() {
return "org.yourCompany.android.lib.YourLibName";
}
/**
* #return the library version
*/
public String getLibraryVersion() {
return "1.0.0";
}
}
I know that this is kind of a dirty solution, and I would prefer a cleaner version of coding, with these Strings stored as String resources in strings.xml but I don't know any better way. So you just have to change the library name and version in your manifest or better the strings.xml AND in the Application class.
But how often do you change the library name or version?
Hope this can help somebody and save time!
PS: some of the above code is based on this:
http://blog.tomgibara.com/post/126377651/global-application-state-in-android

Categories

Resources