I've been facing a problem that seem very common in Android.
My Webview with a custom Javascript interface is not working when the application is in release mode.
I am unable to call JS code from Java.
I feel that the problem is linked to proguard, as the Webview works normally in release mode as soon as I set the "minifyEnabled" to "false".
My proguard mappings are looking like this, I'm not sure if this is alright:
com.company.app.jsBridge.BaseJavascriptInterface ->
com.company.app.jsBridge.BaseJavascriptInterface:
java.util.Map mCallbacks -> a
java.util.Map mHandlers -> b
com.company.app.jsBridge.BridgeWebView mWebView -> c
29:29:void send(java.lang.String,java.lang.String,java.lang.String):0:0 -> send
...
I have already browsed through Stackoverflow related questions such as these, and a few others:
Proguard stops Javascript in WebView from working
How to configure proguard for javascript interface?
Proguard mess Javascript Interface functions when targeting SDK in Android Manifest above 17
I tried applying the answers of these questions and my proguard file now looks like this:
-keepattributes JavascriptInterface
-keepattributes *Annotation*
-keepclassmembers class * {
#android.webkit.JavascriptInterface <methods>;
}
-keepclassmembers class com.company.app.jsBridge.BaseJavascriptInterface {
public *;
}
-keep public class com.company.app.jsBridge.BaseJavascriptInterface
And my interface:
package com.company.app.jsBridge;
public abstract class BaseJavascriptInterface {
// Callbacks to execute after successful JS calls
private final Map<String, IOnBridgeCallback> mCallbacks;
// Handler methods that will be available to the JS
private final Map<String, IBridgeHandler> mHandlers;
private BridgeWebView mWebView;
public BaseJavascriptInterface(Map<String, IOnBridgeCallback> callbacks, Map<String, IBridgeHandler> handlers, BridgeWebView webView) {
mCallbacks = callbacks;
mHandlers = handlers;
mWebView = webView;
}
#JavascriptInterface
public String send(String handlerName, String data) {
//
}
}
Other things that I have tried:
Running on both an emulator and a physical device, as I read that emulators could have a different behaviour with proguard
Using the #Keep annotation on the Javascript interface
Does anybody know what is wrong with my config or if I'm not looking at the right place ? Thanks
I ended up finding the solution by myself with the help of this post : Proguard issue while using GSON
I needed to preserve both the Javascript Interface in Proguard like I did before, as well as the classes that I used for JSON communication between Java and JS. Gson was unable to properly serialize the data because the models were not preserved.
Related
I have an interaction between UI Fragment and background Service.
I use EventBus.
Of course, if Activity is stopped/killed, then there are no subscribers.
Here's the code so you understand:
public class LocationService extends Service {
//...
EventBus.getDefault().post(new MessageEventLocationClient(data));
}
public class ClientFragment extends Fragment {
//...
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEventLocationClient event) {
// update UI
textViewLastSentData.setText(event.trackData.lastLatLon());
}
}
And all OK.
However, here is the report sent to me from Google Play Developer Console:
Devices with errors 14:
Google Nexus 7 (flo) - Android 5.0
Google Nexus 9 (flounder) - Android 5.0
Google Pixel (sailfish) - Android 7.1
Motorola XT1096 (victara) - Android 4.4
...
Exceptions:
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.tim4dev.imokhere/com.tim4dev.imokhere.MainActivity}: org.a.a.e:
Subscriber class com.tim4dev.imokhere.c and its super
classes have no public methods with the #Subscribe annotation
...
What does it mean?
Is this really a problem?
Then what to do?
A similar problem is described here.
UPD. RTM. Thanks to all.
as pointed out, you need to instruct proguard not to remove method annotated with #Subscribe. Proguard will remove them if they are unused and since EventBus will look for them with reflection they will very likely be unused. you can add some directives to your proguard configuration file, from here:
## New rules for EventBus 3.0.x ##
# http://greenrobot.org/eventbus/documentation/proguard/
-keepattributes *Annotation*
-keepclassmembers class ** {
#org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
I am very new to DexGuard and Proguard. I was going through their documentation and sample examples. They have dexguard_util which helps you detect if the application is tampered with and also helps in detecting if it is running in the environment it is supposed to run. The document suggests that this tamper and environment detection be encrypted using the following code is dexgaurd-project.txt.
-encryptclasses A$D
-encryptstrings A$D
follwing is the activity
public class A extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
new D().c();
}
private class D
{
public void c()
{
//some code to which detects the tampering and environment and takes action accordingly
}
}
}
What if a hacker inject this line of code.
public class A extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//code commented by hacker
//new D().c();
}
private class D
{
public void c()
{
//some code to which detects the tampering and environment and takes action accordingly
}
}
}
Then my application will run without running those tests which I think is a big problem. Is my understanding of how reverse engineering works wrong or there are better ways of doing this. Please share better methods of doing this if they exist. Thanks in advance. Note that public class A cannot be encrypted as it is an entry point and is kept using this command in progaurd-project.txt
-keep class somepackage.A
When it comes to anti-tampering, it is important to keep in mind that their goal is not to stop any and all potential tampering efforts, but, rather, it's just a matter of raising the security bar of the target high enough to dissuade most attackers.
With that said, the
A bit of a tangent:
The document suggests that this tamper and environment detection be encrypted using the following code is dexgaurd-project.txt.
Class encryption does prevent basic static analysis of the application package, e.g. simply unzipping the package and loading it in jd-gui. However, as this answer shows, it's trivial to circumvent: one only has to hook into the static method that decrypts the apk on load, and dump it. But this allows the security bar to be raised.
Now back to your original question:
What if a hacker inject this line of code.
As an attacker, that would be the next step. However, that would require repackaging the app, and signing it with the hacker's signing key. Therefore, it is necessary to combine Dexguard's anti-tampering measures like checking the apk signature.
Is DexGuard tamper and Environment detection helpful?
In summary, yes, it is helpful in as far as it raises the bar above the vast majority of apps out there. But it's no silver bullet.
I've been testing my app on Sony and Nexus devices as well as multiple emulators (API 10 - 19) for several weeks and things seem fine, however, two of my testers (who live remotely) are experiencing crashes during startup and ACRA isn't offering the option to send a crash log email, which leads me to believe it's a crash before ACRA is even instantiated. ACRA works as expected on my devices and emulators and offers to send an email (via default app).
Are Samsung phones doing some special initialization? Am I doing anything in the following code which could cause a crash?
#ReportsCrashes(
formKey = "", // This is required for backward compatibility but not used
mailTo = "crash#mydomain.com",
customReportContent = {
ReportField.APP_VERSION_CODE,
ReportField.APP_VERSION_NAME,
ReportField.ANDROID_VERSION,
ReportField.PHONE_MODEL,
ReportField.CUSTOM_DATA,
ReportField.STACK_TRACE,
ReportField.LOGCAT }
)
public class AppState extends Application {
private static final String DIRECTORY_ROOT = Environment.getExternalStorageDirectory();
private static final String DIRECTORY_PICTURES_APP = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + File.separator + "MyApp";
private static final LogConfigurator logConfigurator = new LogConfigurator();
private static final Logger logger = Logger.getLogger(AppState.class);
#Override
public void onCreate() {
super.onCreate();
ACRA.init(this);
// try to create log file and directory; if successful, configure ACRA to include last lines with crash report
if (createLogFile()) {
ACRAConfiguration config = ACRA.getConfig();
config.setApplicationLogFile(LOGGER_FILENAME);
ACRA.setConfig(config);
}
...log4j, database init, etc...
The source for the project is located here on SourceForge.
EDIT #1:
Finally got one of the Samsung Remote Test Lab devices working! Screen still isn't visible, but I can at least see logcat. Here is the reason for the crash:
ERROR AndroidRuntime at org.dumpsterdiver.sync.AppState.onCreate(Unknown Source)
So that means the system is unable to find my Application class? After a bit of Googling similar problems, I think it might be a proguard error (sigh, again), so I've been playing with various suggestions from other answers. None of these have helped so far (still experimenting with various values):
-keep public class org.dumpsterdiver.sync.AppState
-keep public class * extends android.app.Application
-keep public class * extends com.actionbarsherlock.app.SherlockActivity
EDIT #2:
Adding this line to my proguard config file seems to fix the problem:
-keep class * { *; }
But my APK is 500 KB larger (3.7 -> 4.2 MB) and I suspect this probably defeats half the point of using proguard in the first place? Is there a better solution?
After a lot of experimentation, this line was the problem:
-keep public class org.dumpsterdiver.sync.AppState
Changing it to this seems to have done the trick:
-keep public class * extends android.app.Application
I have a library project and different sub-projects with images/teksts etc, that use this library. I want every app (sub-project) to have it's own crashreport formkey, but I can only set it once, statically in the library's Application class, using "#ReportsCrashes(formKey=..."
Is there another way to set it up, so the formkey can differ for every app I create using this library?
Found the solution. You'll need the very latest version of Acra, and do exactly this:
#ReportsCrashes(formKey = "")
public class RootApplication extends Application {
#Override
public void onCreate() {
ACRA.getConfig().setFormKey(
getResources().getString(R.string.acra_form_key));
ACRA.init(this);
ACRA.getErrorReporter().setReportSender(new HockeySender());
super.onCreate();
}
}
Of course you only use a hockeysender when you use hockeyapp.
I would to know whether logging can decrease application performance?
Also, please give me some tips to increase android application performance.
Yes. Excessive logging affects the performance of any application not just Android.
The developer guide suggests you to deactivate and disabled logging before release:
Turn off logging and debugging Make sure you deactivate logging and
disable the debugging option before you build your application for
release. You can deactivate logging by removing calls to Log methods
in your source files. You can disable debugging by removing the
android:debuggable attribute from the tag in your
manifest file, or by setting the android:debuggable attribute to false
in your manifest file. Also, remove any log files or static test files
that were created in your project.
Also, you should remove all Debug tracing calls that you added to your
code, such as startMethodTracing() and stopMethodTracing() method
calls.
So, you should suppress the logs in "release" or "production" build of your App.
Turn off it in Android Manifest by setting debuggable:
<application android:icon="#drawable/icon"
android:label="#string/app_name"
android:debuggable="false">
Another way
Create your own logger class and check for debugging mode before executing log. It allows single point modification between debugging mode and deployed application and allows extra things to do such as writing to log file.
import android.util.Log;
public class MyLog {
private static final boolean isDebug = false;;
public static void i(String tag, String msg) {
if (isDebug) {
Log.i(tag, msg);
}
}
public static void e(String tag, String msg) {
if (isDebug) {
Log.e(tag, msg);
}
}
}
For further info read http://rxwen.blogspot.com/2009/11/logging-in-android.html
and The SO QAs :
Log.d and impact on performance
Android Logging - How to clear for better performance
use 'if' statement before log writing.
you can disable log when application release.
example :
public class LogTest extends Activity {
private static final String TAG = "YOUR_LOG_TAG_NAME";
private static final boolean mDebug = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//--[Start Log example]-----------------------------------------
if(mDebug) Log.d(TAG, "Log Write Test");
//--[End Log example]-----------------------------------------
}
}
Any text output in excess will slow down your application, even with a desktop application. Excessive logging does slow things down, but you would basically need an insane amount of data being sent through, which by the way, isn't that hard to do with a for or while loop. Things that happen in the background while logging sometime include string manipulation, text rendering, cache management, data filtering, log length limiting, and the list goes on.
There's several ways to remove logcat from your final app, especially those involving ProGuard. ProGuard didn't work for me, but there's plenty of brilliant ideas out there on how to remove them including scripting programs like sed.