I am currently considering porting a app that I start developping with react-native to codenameone. For this, I am still checking the feasability and the amount of work it would requiere (as I would have to port or developp some native library binding from react-native to codenameone because codenameone miss some of my needs, like socket.io support for example). The free codenameone build cloud service beeing limited to app of 1Mb, I have to make my test builds locally (with only a few test classes and the use of the google maps cn1lib, my test app is already above the 1Mb limit)
Sadly, there is no free documentation on codenameone on how to perform local builds and actually I couldn't find any instructions on internet on how to do it (I only found, on a blog post, some basic and deprecated instructions on how to perform a local iOS build but nothing for Android). So I had to figure it out myself...
After some time spent digging into gradle configuration parametters, I finally succeed into building a basic codenameone app localy that works on my android test device. But the problem is that, when I add an external cn1lib (the google maps native cn1lib https://github.com/codenameone/codenameone-google-maps ), my app bug when oppening a screen that depends from this lib.
In the android error log, I could find this message:
D/MyApplication( 551): [EDT] 0:0:0,99 - Exception: java.lang.ClassCastException - com.codename1.googlemaps.InternalNativeMapsImpl cannot be cast to com.codename1.system.NativeInterface
W/System.err( 551): java.lang.ClassCastException: com.codename1.googlemaps.InternalNativeMapsImpl cannot be cast to com.codename1.system.NativeInterface
W/System.err( 551): at com.codename1.system.NativeLookup.create(Unknown Source)
W/System.err( 551): at com.codename1.googlemaps.MapContainer.<init>(MapContainer.java:171)
W/System.err( 551): at com.codename1.googlemaps.MapContainer.<init>(MapContainer.java:151)
W/System.err( 551): at com.tbdlab.testapp.MyApplication.start(MyApplication.java:207)
W/System.err( 551): at com.tbdlab.testapp.MyApplicationStub.run(MyApplicationStub.java:183)
W/System.err( 551): at com.codename1.ui.Display.processSerialCalls(Unknown Source)
W/System.err( 551): at com.codename1.ui.Display.mainEDTLoop(Unknown Source)
W/System.err( 551): at com.codename1.ui.RunnableWrapper.run(Unknown Source)
W/System.err( 551): at com.codename1.impl.CodenameOneThread$1.run(Unknown Source)
W/System.err( 551): at java.lang.Thread.run(Thread.java:818)
I don't really understand why InternalNativeMapsImpl could not be cast into NativeInterface as I looked into the dex file of my compiled apk and all the necessary classes (for android) from the google maps cn1lib are correctly included (So I have com.codenameone.googlemaps.InternalNativeMaps, com.codenameone.googlemaps.InternalNativeMapsImpl and com.codenameone.googlemaps.MapContainer) and so are the codenameone native interface classes they depend on (com.codename1.system.NativeInterface, com.codename1.impl.android.LifecycleListener...). And I decompilled them and the code is correct (I do not use any obfuscation method anyway so there is no real reason why the compiled code would have differ from the source code). There is probably something that I am missing here to make a local codenameone build with the usage of a cn1lib.
So has anyone already succeed into making a local build with the usage of a cn1lib that perform native bindings? If yes, what is the exact procedure?
I really hope someone would be able to help, because, at this point, I am seriously considering to stick with react-native (which I am quite pleased with, exept the fact that it is not completely native) or to jump into flutter (or kotlin native) even if I still think codenameone offers many advantages over these other solutions (but not beeing able to perform local builds during the development phase is just a complete no-go for me)
As said, in some of my tests (where I use the full set of cn1libs I would need + some custom libs), I am already above the 1Mb limitation (the server rejected my test builds for this reason). So using the free build cloud server during the development phase is not an option for me (Anyway I won't use a solution if I am not sure I can be completely independent if necessary. To make my release builds I would certainly take a subscription and use the cloud buid server as it is far more convenient than tweeking a local server, furthermore that I don't own a Mac computer (I only have a test iphone) and need to borrow one when I want to make some iOS build ;) . But I want to be sure that if, for any reason, your service dissapear, I will still be able to make my builds. Furthermore, I don't see the point of paying a subscription during the developpment phase of my app (that could take me months) especially as I am not certain I would use codenameone as a final solution (I still have to check the amount of work it would requiere to adapt some of the libs I already have for react-native to codenameone)). That is the reason why I try to make a local build.
Concerning the socket.io library, I already started to create a cn1lib that will use native solutions (https://github.com/socketio/socket.io-client-java for android, https://github.com/socketio/socket.io-client-swift for iOS, and the original socket.io lib for javascript). This is not really an issue and it was just to give an example of libraries I would have to create in codenameone if I want to switch from react-native.
In what concerns how cn1lib works, I already figured that out, I included into my android project all the necessary class of the cn1 google-maps lib (so I included the content from main.zip, nativeand.zip and stubs.zip in my project) and checked in the .dex files of my generated apk that they are actually correctly packaged in them, as already said. So my problem doesn't seems to be that I forgot to include some class of the cn1lib in my project but something else. The error message is: Exception: java.lang.ClassCastException - com.codename1.googlemaps.InternalNativeMapsImpl cannot be cast to com.codename1.system.NativeInterface so it doesn't refer to a Class not found but to a cast exception... I don't really know what can cause this issue. I took the codenameone core classes from here https://github.com/codenameone/CodenameOne/tree/master/CodenameOne/src/
https://github.com/codenameone/CodenameOne/tree/master/Ports/Android http://github.com/codenameone/codenameone-skins
to include them in my project, so I think I didn't miss one. And when building a project that doesn't use a cn1lib (like a simple "hello word" app), it compiles and run just fine on my android test device.
The problem, is really just when my app try to create a googlemap view, where it returns the cast exception (and then default to try to create an html browser mapview and fails here as it is missing some html file).
So it is probably a configuration problem ( may it be a problem with the java version used by the compiler as native class files where already compiled in the cn1lib main.zip file?)
Here is the gradle build file I use:
buildscript {
repositories {
jcenter()
maven { url "https://plugins.gradle.org/m2/" }
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'me.tatarka:gradle-retrolambda:3.2.0'
}
}
allprojects {
repositories {
jcenter()
google()
}
}
apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.retrolambda'
android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
dexOptions {
// Prevent OutOfMemory with MultiDex during the build phase
javaMaxHeapSize "4g"
}
lintOptions {
checkReleaseBuilds false
}
defaultConfig {
applicationId "com.tbdlab.testapp"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' //my proguard files are actually empty so no obfuscation is performed. I checked it in the generated apk
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7//.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_7//.VERSION_1_8
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.google.android.gms:play-services:9.4.0' //compile 'com.google.android.gms:play-services-maps:11.8.0'
compile 'com.android.support:multidex:1.0.1'
}
and here is my AndroidManifest.xml file where I included all the permissions defined in the cn1lib:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--- Permissions requiered by the google maps cn1lib -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
<application android:allowBackup="true" android:icon="#drawable/icon" android:label="MyApplication" android:name="android.support.multidex.MultiDexApplication">
<meta-data android:name="com.google.android.gms.version" android:value="#integer/google_play_services_version"/>
<meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="...masked_it_but_put_my_correct_key_here..."/>
<activity android:label="MyApplication" android:launchMode="singleTop" android:name="com.tbdlab.testapp.MyApplicationStub" >
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver android:name="com.codename1.impl.android.LocalNotificationPublisher"/>
<service android:exported="false" android:name="com.codename1.impl.android.BackgroundFetchHandler"/>
<activity android:name="com.codename1.impl.android.CodenameOneBackgroundFetchActivity" android:theme="#android:style/Theme.NoDisplay"/>
<activity android:name="com.codename1.location.CodenameOneBackgroundLocationActivity" android:theme="#android:style/Theme.NoDisplay"/>
<service android:exported="false" android:name="com.codename1.location.BackgroundLocationHandler"/>
<service android:exported="false" android:name="com.codename1.location.GeofenceHandler"/>
<service android:exported="false" android:name="com.codename1.media.AudioService"/>
<activity android:excludeFromRecents="true" android:exported="false" android:name="com.google.android.gms.auth.api.signin.internal.SignInHubActivity" android:theme="#android:style/Theme.Translucent.NoTitleBar"/>
<provider android:authorities="com.tbdlab.testapp.google_measurement_service" android:exported="false" android:name="com.google.android.gms.measurement.AppMeasurementContentProvider"/>
<receiver android:enabled="true" android:name="com.google.android.gms.measurement.AppMeasurementReceiver">
<intent-filter>
<action android:name="com.google.android.gms.measurement.UPLOAD"/>
</intent-filter>
</receiver>
<service android:enabled="true" android:exported="false" android:name="com.google.android.gms.measurement.AppMeasurementService"/>
<activity android:name="com.google.android.gms.ads.AdActivity" android:theme="#android:style/Theme.Translucent"/>
I really don't see what can cause the cast exception, but in the lack of a basic tutorial on how to create local builds, I may have miss something without even knowing it...
For this test I made a really simple app than only display a native google map and it runs correctly in the simulator and compiles on the build cloud server and runs fine in my android test device. So the issue is either in my gradle build configuration (or maybe AndroidManifest.xml file even if I don't think it has any effect on the JVM) or in the codenameone core and cn1lib I included in my android project for the local build.
1mb is huge as it can fit the full google maps app and a lot more. It maps to the compiled size of the jar which starts off at 6kb. The whole cn1lib (only a portion of it is packaged) is 40kb. So I would suggest using the build servers for your tests.
Steve built some support for working with native interfaces a few years back here. He stopped maintaining it a bit after we hired him mostly due to lack of time and demand (not because we told him or anything like that). I'm not sure about the status of this but you can use it as a reference to how native interfaces work.
There is also this plugin (direct link here) which I personally didn't try.
Generally a native interface generates an intermediate class that invokes the native implementation directly. The native implementation for all platforms other than Java SE doesn't implement the native interface and shouldn't. I think I explained it somewhere in the docs but explaining it again in the case of Google Maps is super easy.
This is a method from the native interface:
public PeerComponent createNativeMap(int mapId);
This is the same method from the Android implementation class:
public android.view.View createNativeMap(int mapId);
As you can see the return value differs and we need to wrap it in a peer component to abstract that behavior. By avoiding inheritance and casting we get the flexibility of making a more sensible native API.
Here is the class our build server generates for maps, as you can see it's just "glue code":
package com.codename1.googlemaps;
import com.codename1.ui.PeerComponent;
public class InternalNativeMapsStub implements InternalNativeMaps{
private InternalNativeMapsImpl impl = new InternalNativeMapsImpl();
public void setShowMyLocation(boolean param0) {
impl.setShowMyLocation(param0);
}
public void setRotateGestureEnabled(boolean param0) {
impl.setRotateGestureEnabled(param0);
}
public void setMapType(int param0) {
impl.setMapType(param0);
}
public int getMapType() {
return impl.getMapType();
}
public int getMaxZoom() {
return impl.getMaxZoom();
}
public int getMinZoom() {
return impl.getMinZoom();
}
public long addMarker(byte[] param0, double param1, double param2, String param3, String param4, boolean param5) {
return impl.addMarker(param0, param1, param2, param3, param4, param5);
}
public void addToPath(long param0, double param1, double param2) {
impl.addToPath(param0, param1, param2);
}
public long finishPath(long param0) {
return impl.finishPath(param0);
}
public void removeMapElement(long param0) {
impl.removeMapElement(param0);
}
public void removeAllMarkers() {
impl.removeAllMarkers();
}
public PeerComponent createNativeMap(int param0) {
return PeerComponent.create(impl.createNativeMap(param0));
}
public void setPosition(double param0, double param1) {
impl.setPosition(param0, param1);
}
public void calcScreenPosition(double param0, double param1) {
impl.calcScreenPosition(param0, param1);
}
public int getScreenX() {
return impl.getScreenX();
}
public int getScreenY() {
return impl.getScreenY();
}
public void calcLatLongPosition(int param0, int param1) {
impl.calcLatLongPosition(param0, param1);
}
public double getScreenLat() {
return impl.getScreenLat();
}
public double getScreenLon() {
return impl.getScreenLon();
}
public void deinitialize() {
impl.deinitialize();
}
public float getZoom() {
return impl.getZoom();
}
public void setZoom(double param0, double param1, float param2) {
impl.setZoom(param0, param1, param2);
}
public double getLatitude() {
return impl.getLatitude();
}
public double getLongitude() {
return impl.getLongitude();
}
public long beginPath() {
return impl.beginPath();
}
public void initialize() {
impl.initialize();
}
public boolean isSupported() {
return impl.isSupported();
}
}
About socket.io you can probably just wrap the JavaScript version with a call to the BrowserComponent to get the native JS code working as a start. A full on native port can come later.
It seems you have cn1libs figured out otherwise but just for completeness this is how they are supposed to work:
The cn1lib is just a zip file containing other zip files for each platform. Refresh libs unzips the this and arranges the files in the appropriate directories under lib/impl. So you need to package the lib/impl directory matching the platform you are trying to compile with your distribution.
cn1libs also include two additional property files codenameone_library_appended.properties & codenameone_library_required.properties. Refresh libs will handle that automatically for you by setting these values into the build hints. The former values are appended to the existing build hint and the latter override an existing build hint.
Build hints effectively tell the build servers how to compile some things e.g. if we want to inject stuff into the plist, manifest etc. How this maps to a local build will vary a lot. In some cases like plistInject it would be trivial to understand but other cases might be odd. If you have a question about how a specific build hint maps to local build then you can ask that.
Related
I'm trying to run an Apache MINA SSHD Server (v. 2.2.0) on an Android Pie (API 28.0) device. I found various post about it running on Android 4 devices, but none of them seems to work on my Android 9 device. So I tried to implement it myself but got stuck at the initialization of the Server with a ReflectionException/NoClassDefFoundError.
When I try to set up the server with SshServer.setUpDefaultServer(); I get this exception:
java.lang.NoClassDefFoundError: Failed resolution of: Ljavax/management/ReflectionException;
at org.apache.sshd.common.util.GenericUtils.peelException(GenericUtils.java:730)
at org.apache.sshd.common.util.GenericUtils.peelException(GenericUtils.java:728)
at org.apache.sshd.common.util.security.SecurityEntityFactory$2.getInstance(SecurityEntityFactory.java:134)
at org.apache.sshd.common.util.security.SecurityUtils.getMessageDigest(SecurityUtils.java:726)
at org.apache.sshd.common.digest.DigestUtils.checkSupported(DigestUtils.java:53)
at org.apache.sshd.common.digest.BuiltinDigests.<init>(BuiltinDigests.java:61)
at org.apache.sshd.common.digest.BuiltinDigests.<clinit>(BuiltinDigests.java:36)
at org.apache.sshd.common.cipher.ECCurves.<clinit>(ECCurves.java:61)
at org.apache.sshd.common.keyprovider.KeyPairProvider.<clinit>(KeyPairProvider.java:63)
at org.apache.sshd.common.signature.BuiltinSignatures.<clinit>(BuiltinSignatures.java:62)
at org.apache.sshd.common.BaseBuilder.<clinit>(BaseBuilder.java:133)
at org.apache.sshd.server.ServerBuilder.builder(ServerBuilder.java:165)
at org.apache.sshd.server.SshServer.setUpDefaultServer(SshServer.java:429)
at ch.zhaw.init.sshshell.SshShellService.<init>(SshShellService.java:20)
From some older posts for Android 4 it seems that this is was a known problem and the solution there was to add $useLibrary 'org.apache.http.legacy' to gradle and add to the manifest.xml. I did that but the exception still occurs.
build.gradle file:
android {
compileSdkVersion 28
useLibrary 'org.apache.http.legacy'
...
}
dependencies {
...
implementation 'org.apache.sshd:sshd-core:2.2.0'
implementation 'org.slf4j:slf4j-simple:1.6.2'
implementation 'org.apache.mina:mina-core:2.1.2'
implementation "org.bouncycastle:bcprov-jdk16:1.46"
}
manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ch.zhaw.init.sshshell">
<application
android:networkSecurityConfig="#xml/network_security_config">
<uses-library android:name="org.apache.http.legacy"
android:required="false" />
<service android:name=".SshShellService">
<intent-filter>
<action android:name="StartSshServer"/>
</intent-filter>
</service>
</application>
</manifest>
My Java-Code:
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
private static final int PORT = 8022;
private final SshServer sshd = SshServer.setUpDefaultServer(); // Here I get the error
private final SimplePasswordAuthenticator passwordAuth = new SimplePasswordAuthenticator();
private final SimplePublicKeyAuthenticator publicKeyAuth = new SimplePublicKeyAuthenticator();
private final SimpleForwardingFilter forwardingFilter = new SimpleForwardingFilter();
It seems that older versions worked, but I search for days and did not find any working example for the newest version. Does anyone has a working example which runs the latest version of Apache Mina SSHD (2.2.0) on Android 9 (without rooting the device)?
or if not is there an alternative SSH-Server (not client - there are many of them) for Android Pie?
Thank you for any help.
For this specific failure, it suffices to create a dummy ReflectionException class (example). Given that the class doesn't even exist, nothing outside of your own code would ever instantiate it, so SSHD's instanceof ReflectionException check would never match, and you don't need to program any of its members.
You'll also want to create a dummy MBeanException (example).
the error because of BouncyCastle lib has been deprecated as android9(api level 28).
you can get detail tips from aosp: http://androidxref.com/9.0.0_r3/xref/libcore/ojluni/src/main/java/sun/security/jca/Providers.java#548
https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html
to solve this problem ,you can follow steps:
uninstall BouncyCastleProvider from system default
Security.removeProvider("BC");
install new "BC" custom provider
Security.addProvider(new BouncyCastleProvider());
init your mina sshd Service
SshServer sshd = SshServer.setUpDefaultServer();
sshd.setPort(ssdServerPort);
....
aosp just compare object to decide if bc provider object is system default provider,so just replace with new bc provider object
:http://androidxref.com/9.0.0_r3/xref/libcore/ojluni/src/main/java/sun/security/jca/Providers.java#341
notes: you must uninstall provider with :Security.removeProvider("BC"),before install provider
UPDATE: To those who asked about which error codes the users are receiving: there are no error codes. It just opens a blank, post-installation page that says "The app was not installed" with a big 'X' next to it. It's possible different versions of Android could have different messages. There is no indication for what went wrong during the installation.
UPDATE 2: Some users reported that they receive error code "-504" when they try to install/update from the Play Store, and the "app not installed" message when manually trying to install the .apk. I don't know what correlation this error has with users being unable to install, but the solutions from the only 2 questions on SO on this topic did not fix anything for me. I've included the updated manifests and build files.
UPDATE 3: It appears as users report this issue in versions after IAB has been successfully installed, which further de-legitimatizes the concept that this issue is caused by introducing IAB.
UPDATE 4: It looks like the problem is with old users trying to update to a new version, and not with new users. With that in mind, there is a high likelihood that this issue is result of INSTALL_FAILED_UID_CHANGED. Looking through the version history, the significant change I made in the problematic version that users cannot update from is removing drawables that I no longer intended of using.
Asking users to go through the procedure to fix this isn't plausible. If there is a solution that I can enforce which would fix it for faulty users, wonderful... if not, the least I can do at this point is damage control and ensure this doesn't happen in the future.
NOTE: Below is the original post speculating that the problem is the result of introducing IAB into the app. Since then, it has become more and more unlikely for that to be the cause. Regardless, the post still has relevant information.
------------------------------------------------------------------------------------------
Original Title: Android App Users Get "App not installed" After Introducing IAB
I recently introduced IAB in my app that was published on Google Play. After a while, I've started to get reports from some users that they get an "installation unsuccessful" error when they try to install or update it.
What makes me think it's caused by introducing IAB is that one particular long-time user e-mailed me that when he's attempting to update to the version with IAB, the installer mentions that new permissions were introduced and requires the user's permission. Once granted, it says that the app failed to install.
I've done some Googling and it appears to be a problem from their end, one user even tried to manually install an .apk with said permissions removed without any success. I wan't to make sure that it's not something I've done wrong, but an inevitability that I have to accept with some users.
Note that the vast majority has no problem of installing the app, and I haven't received any reports of this until after IAB was introduced. It wouldn't bother me so much were it a small amount of lost users, but the problem is, those users hurt my app's rating. Users have also mentioned that they can install apps, excluding my own, perfectly well.
I don't rule out the possibility that users may have been getting these errors even before IAB was introduced, and the linkage could be a mistaken one.
Here is my manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest package = "com.jjsoftware.fullscientificcalculator"
xmlns:android = "http://schemas.android.com/apk/res/android">
<uses-permission android:name = "android.permission.VIBRATE"/>
<uses-permission android:name = "com.android.vending.BILLING"/>
<application
android:allowBackup = "true"
android:fullBackupContent = "true"
android:icon = "#drawable/logo"
android:label = "#string/app_name">
<activity
android:name = ".MainActivity"
android:hardwareAccelerated = "false"
android:label = "#string/app_name"
android:screenOrientation = "portrait"
android:theme="#style/AppTheme">
<intent-filter>
<action android:name = "android.intent.action.MAIN"/>
<category android:name = "android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name = ".SettingsActivity"
android:theme = "#style/PreferencesTheme">
<intent-filter>
<action android:name = ".SettingsActivity"/>
<category android:name = "android.intent.category.PREFERENCE"/>
</intent-filter>
</activity>
</application>
Here is the Gradle file:
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.jjsoftware.fullscientificcalculator"
minSdkVersion 14
targetSdkVersion 23
versionCode 102
versionName "1.679"
}
sourceSets { main { assets.srcDirs = ['src/main/assets', 'src/main/assets/'] } }
}
dependencies {
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.google.android.gms:play-services-ads:8.4.0'
compile 'com.android.support:gridlayout-v7:23.2.1'
compile files('libs/exp4j-0.4.5.jar')
compile files('libs/EJML-core-0.28.jar')
compile files('libs/EJML-dense64-0.28.jar')
compile files('libs/Jama-1.0.3.jar')
compile files('libs/EJML-simple-0.28.jar')
}
And, if need be, the top-level build:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
There is a typo in the manifest file on line android:largeHeap="true">>. xml line ends with >>. This may be causing the error.
it could be the phones have a lower version of the Google play services than the minimum you defined in the APK.
There is nothing much you can do if that is the case, other than for the users to upgrade their google services (https://play.google.com/store/apps/details?id=com.google.android.gms&hl=en) or you reduce your version.
also you may need to add the following to your manifest. (i think this is used to compare the versions installed in the phones with the versions required by the apk)
<application ...>
<!-- This meta-data tag is required to use Google Play Services. -->
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
</application>
I got this error while installing on my Android device a new APK for the app I am developing.
I checked the debug log on the device (using "adb -d logcat > log.txt") and found an entry saying "PackageManager: Downgrade detected: Update version code 2 is older than current 10000". Obviously something went wrong with the versioning somehow.
I uninstalled the package and then was able to install the APK with no problems.
Remember that the latest installment of Android (Marshmallow version) has changed the permissions to give more access to permissions to the user. Hence it is not enough to define the permissions in the manifest anymore.
Through code you need to make sure that the billing permission is enabled by the users when they use the app. The simplest way to go around this is to set the target SDK to v-22. This should temporarily solve your issue.
The real solution however is to handle the new permissions introduced in Marshmallow. Here is how to do it:
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch(requestCode) {
case BILLING_REQUEST:
if (canAccessBilling()) {
callBillingPerms();
}
break;
}
}
private void callBillingPerms() {
Toast.makeText(this, "We need your permission to access Billing", Toast.LENGTH_SHORT).show();
}
private boolean canAccessSMS() {
return(hasPermission(Manifest.vending.BILLING));
}
#TargetApi(Build.VERSION_CODES.M)
private boolean hasPermission(String perm) {
return(PackageManager.PERMISSION_GRANTED==this.checkSelfPermission(perm));
}
Hope this helps :)
Perhaps the error is on the user's side.
This article states the following:
Google Play - Error 504
Problem
App could not be downloaded due to an error.
First solution
The usual, please: go to Settings > Apps > All > Google Play Store and Clear cache and Clear data. Also Clear cache and Clear data for Google Services Framework.
Second solution
Try removing your GMail account
This Stack Exchange answer discussed similar ideas.
According to the list of status codes at Wikipedia, a 504 error means the following:
504 Gateway Timeout
The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.
Ask your users if doing the above solves their issue.
I want to know about Android Annotations, is it better way to use in all android projects?.
If correct, how to implement it. Is there any good tutorial for it?
If it is a wrong way. what are the drawbacks of Android Annotations?
Thanks in advance for the help.
Android Annotations is an annotation-driven framework that allows you to simplify the code in your applications and reduces the boilerplate of common patterns, such as setting click listeners, enforcing ui/background thread executions, etc.
You could go from having something like this:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView descriptionTextView = (TextView) findViewById(R.id.tv_description);
final Button hideButton = (Button) findViewById(R.id.btn_hide);
hideButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
descriptionTextView.setVisibility(View.INVISIBLE);
}
});
}
}
To something like this:
#EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
#ViewById(R.id.tv_description)
TextView mDescriptionTextView;
#Click(R.id.btn_hide)
protected void onHideButtonClick() {
mDescriptionTextView.setVisibility(View.INVISIBLE);
}
}
How it works
You annotate your activities and components, the annotations processor then generates classes (at compilation time) that extend your activities and components (i.e. your activities cannot be final) with an underscore suffix by default, so if you have MainActivity, now you will also have a MainActivity_ class.
This new class contains a well-written boilerplate code that does whatever the annotation specifies.
How to implement
I wrote this tutorial about how to integrate Android Annotations and even include an example on how integration tests get updated, check here.
That tutorial is valid as of today, using Android Studio ~1.5.1, and it tries to explain a little bit on how the internal works.
Should you use it?
I would say that if you have a small-medium project its fine. It will make your code easier to read. But if your application is bigger and contains a lot of navigation flows with complex activity/component life cycles, it can get a little bit hard to implement or difficult to debug and understand errors if something is not appropriately annotated.
Because of how Android Annotations operate, they embed themselves in the life cycle and doing so, you are now dependent of their lifecycle (e.g. if you annotate your views with #ViewById, then you cannot reference them in onCreate(), you need to make a method and annotate it with #AfterViews and when this method gets executed then your views are ready to be used). This is not necessarily a problem, you just need to have a good understanding of Android's behaviors and well, Android Annotations behaviors as well.
In summary, as in any library, if you depend on it, well you depend on it, so you might as well understand very thoroughly how it works. Your project now depends on someone else's.
I don't use Android Annotations, not anymore. When I used this library, it was buggy and made debugging a nightmare. Another downside is that it lowers the portability of your code. If you're working alone on the project, then it's okay, you don't have this issue, but when you work in a team, you have to give this a second thought.
If you want to use it, there are plenty of tutorials right on their site.
An alternative:
If you want to decrease the amount of code while making it really easy to use and understand, I suggest you the Butter Knife library. I use is a lot and didn't encounter any bugs so far. Very easy to use and read.
Android Annotations is a library that "autogenerates" code for us by using some attributes or anotations like #EActivity, #ViewById, #OnClick. It's intended to facilitate and decrease coding time.
"AndroidAnnotations is an Open Source framework that speeds up Android development. It takes care of the plumbing, and lets you concentrate on what's really important. By simplifying your code, it facilitates its maintenance."
(Documentation here: https://github.com/excilys/androidannotations/wiki)
But... We don't use it, I completely agree with DDsix answer. Use SOLID principles and code what should be coded when and where it should be...
Android Annotations is 3rd party library that was made to be all in one tool for Android. Its capable of dependency injection, thread handling, and more. I don't recommend using it; it's buggy, and unstable. In my current job I am working on a project and my task is to remove Android Annotations. I would suggest using Dagger 2, Butterknife, and RxJava
Here is the example with Android Studio.
Create an Android project with the "Empty Activity" template.
Edit the project_root/build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
==> classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
allprojects {
repositories {
jcenter()
==> maven {
==> url = 'https://oss.sonatype.org/content/repositories/snapshots'
==> }
}
}
Edit the app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'android-apt' <============
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "com.just.myapplication"
minSdkVersion 19
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
==> apt "org.androidannotations:androidannotations:4.0-SNAPSHOT"
==> compile 'org.androidannotations:androidannotations-api:4.0-SNAPSHOT'
}
Add a TextView and a Button in the main layout, activity_main.xml
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Not Click Yet"
android:id="#+id/textView"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:id="#+id/button"
android:text="Click"
android:layout_below="#+id/textView" />
Change the activity name to "MainActivity_" in AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
==> <activity android:name=".MainActivity_">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
Now, your MainActivity.java could be simplified as below
package com.just.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.ViewById;
#EActivity (R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
#ViewById(R.id.textView)
TextView mText;
#Click
void button() {
mText.setText("Button Clicked!");
}
}
Try to run it with a device or a emulator to see how simple it works.
I'm trying to get rid of unused classes from Google Play Services library. I've created brand new android project with single empty activity. The project does not use anything from Google Play Services library. So I would expect, that when I build release (which includes running proguard in my configuration) I will see no difference in binary size comparing building with/without play-services dependency. But actually, I see ~700 KB difference in apk size.
I've found relatively complex solution, using gradle script, which involves repacking play-services.jar file. Also, this solution requires to specify explicitly each package which is not going to be used. But I don't understand why doesn't proguard do this work in my case?
build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 21
buildToolsVersion "21.1.1"
defaultConfig {
minSdkVersion 10
targetSdkVersion 21
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
// !!! when I comment the line below, release APK is 700 KB smaller !!! //
compile 'com.google.android.gms:play-services:6.5.87'
}
proguard-rules.pro:
-assumenosideeffects class android.util.Log {
public static *** d(...);
}
AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.noplayservices">
<application android:allowBackup="true"
android:label="#string/app_name"
android:icon="#drawable/ic_launcher"
android:theme="#style/AppTheme">
<activity android:name=".ui.activities.MainActivity" android:icon="#drawable/ic_launcher">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
MainActivity.java:
package com.test.noplayservices.ui.activities;
import android.app.Activity;
import android.os.Bundle;
import com.test.noplayservices.R;
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.main_activity);
}
}
From Google Play Services version 6.5 and beyond you can select which individual APIs you want to use, and import just those ones. Maybe that will help you decrease the APK size a little bit. Here's a list:
Google+ com.google.android.gms:play-services-plus:6.5.+
Google Account Login com.google.android.gms:play-services-identity:6.5.+
Google Activity Recognition com.google.android.gms:play-services-location:6.5.+
Google App Indexing com.google.android.gms:play-services-appindexing:6.5.+
Google Cast com.google.android.gms:play-services-cast:6.5.+
Google Drive com.google.android.gms:play-services-drive:6.5.+
Google Fit com.google.android.gms:play-services-fitness:6.5.+
Google Maps com.google.android.gms:play-services-maps:6.5.+
Google Mobile Ads com.google.android.gms:play-services-ads:6.5.+
Google Panorama Viewer com.google.android.gms:play-services-panorama:6.5.+
Google Play Game services com.google.android.gms:play-services-games:6.5.+
Google Wallet com.google.android.gms:play-services-wallet:6.5.+
Android Wear com.google.android.gms:play-services-wearable:6.5.+
Google Actions
Google Analytics
Google Cloud Messaging com.google.android.gms:play-services-base:6.5.+
You can see more about this here.
I have seen various versions of the dex erros before, but this one is new. clean/restart etc won't help. Library projects seems intact and dependency seems to be linked correctly.
Unable to execute dex: method ID not in [0, 0xffff]: 65536
Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
or
Cannot merge new index 65950 into a non-jumbo instruction
or
java.util.concurrent.ExecutionException: com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536
tl;dr: Official solution from Google is finally here!
http://developer.android.com/tools/building/multidex.html
Only one small tip, you will likely need to do this to prevent out of memory when doing dex-ing.
dexOptions {
javaMaxHeapSize "4g"
}
There's also a jumbo mode that can fix this in a less reliable way:
dexOptions {
jumboMode true
}
Update: If your app is fat and you have too many methods inside your main app, you may need to re-org your app as per
http://blog.osom.info/2014/12/too-many-methods-in-main-dex.html
Update 3 (11/3/2014)
Google finally released official description.
Update 2 (10/31/2014)
Gradle plugin v0.14.0 for Android adds support for multi-dex. To enable, you just have to declare it in build.gradle:
android {
defaultConfig {
...
multiDexEnabled true
}
}
If your application supports Android prior to 5.0 (that is, if your minSdkVersion is 20 or below) you also have to dynamically patch the application ClassLoader, so it will be able to load classes from secondary dexes. Fortunately, there's a library that does that for you. Add it to your app's dependencies:
dependencies {
...
compile 'com.android.support:multidex:1.0.0'
}
You need to call the ClassLoader patch code as soon as possible. MultiDexApplication class's documentation suggests three ways to do that (pick one of them, one that's most convenient for you):
1 - Declare MultiDexApplication class as the application in your AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.multidex.myapplication">
<application
...
android:name="android.support.multidex.MultiDexApplication">
...
</application>
</manifest>
2 - Have your Application class extend MultiDexApplication class:
public class MyApplication extends MultiDexApplication { .. }
3 - Call MultiDex#install from your Application#attachBaseContext method:
public class MyApplication {
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
....
}
....
}
Update 1 (10/17/2014):
As anticipated, multidex support is shipped in revision 21 of Android Support Library. You can find the android-support-multidex.jar in /sdk/extras/android/support/multidex/library/libs folder.
Multi-dex support solves this problem. dx 1.8 already allows generating several dex files.
Android L will support multi-dex natively, and next revision of support library is going to cover older releases back to API 4.
It was stated in this Android Developers Backstage podcast episode by Anwar Ghuloum. I've posted a transcript (and general multi-dex explanation) of the relevant part.
As already stated, you have too many methods (more than 65k) in your project and libs.
Prevent the Problem: Reduce the number of methods with Play Services 6.5+ and support-v4 24.2+
Since often the Google Play services is one of the main suspects in "wasting" methods with its 20k+ methods. Google Play services version 6.5 or later, it is possible for you to include Google Play services in your application using a number of smaller client libraries. For example, if you only need GCM and maps you can choose to use these dependencies only:
dependencies {
compile 'com.google.android.gms:play-services-base:6.5.+'
compile 'com.google.android.gms:play-services-maps:6.5.+'
}
The full list of sub libraries and it's responsibilities can be found in the official google doc.
Update: Since Support Library v4 v24.2.0 it was split up into the following modules:
support-compat, support-core-utils, support-core-ui, support-media-compat and support-fragment
dependencies {
compile 'com.android.support:support-fragment:24.2.+'
}
Do note however, if you use support-fragment, it will have dependencies to all the other modules (ie. if you use android.support.v4.app.Fragment there is no benefit)
See here the official release notes for support-v4 lib
Enable MultiDexing
Since Lollipop (aka build tools 21+) it is very easy to handle. The approach is to work around the 65k methods per dex file problem to create multiple dex files for your app. Add the following to your gradle build file (this is taken from the official google doc on applications with more than 65k methods):
android {
compileSdkVersion 21
buildToolsVersion "21.1.0"
defaultConfig {
...
// Enabling multidex support.
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
The second step is to either prepare your Application class or if you don't extend Application use the MultiDexApplication in your Android Manifest:
Either add this to your Application.java
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
or use the provided application from the mutlidex lib
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.myapplication">
<application
...
android:name="android.support.multidex.MultiDexApplication">
...
</application>
</manifest>
Prevent OutOfMemory with MultiDex
As further tip, if you run into OutOfMemory exceptions during the build phase you could enlarge the heap with
android {
...
dexOptions {
javaMaxHeapSize "4g"
}
}
which would set the heap to 4 gigabytes.
See this question for more detail on the dex heap memory issue.
Analyze the source of the Problem
To analyze the source of the methods the gradle plugin https://github.com/KeepSafe/dexcount-gradle-plugin can help in combination with the dependency tree provided by gradle with e.g.
.\gradlew app:dependencies
See this answer and question for more information on method count in android
Your project is too large. You have too many methods. There can only be 65536 methods per application. see here https://code.google.com/p/android/issues/detail?id=7147#c6
The below code helps, if you use Gradle. Allows you to easily remove unneeded Google services (presuming you're using them) to get back below the 65k threshold. All credit to this post: https://gist.github.com/dmarcato/d7c91b94214acd936e42
Edit 2014-10-22: There's been a lot of interesting discussion on the gist referenced above. TLDR? look at this one: https://gist.github.com/Takhion/10a37046b9e6d259bb31
Paste this code at the bottom of your build.gradle file and adjust the list of google services you do not need:
def toCamelCase(String string) {
String result = ""
string.findAll("[^\\W]+") { String word ->
result += word.capitalize()
}
return result
}
afterEvaluate { project ->
Configuration runtimeConfiguration = project.configurations.getByName('compile')
ResolutionResult resolution = runtimeConfiguration.incoming.resolutionResult
// Forces resolve of configuration
ModuleVersionIdentifier module = resolution.getAllComponents().find { it.moduleVersion.name.equals("play-services") }.moduleVersion
String prepareTaskName = "prepare${toCamelCase("${module.group} ${module.name} ${module.version}")}Library"
File playServiceRootFolder = project.tasks.find { it.name.equals(prepareTaskName) }.explodedDir
Task stripPlayServices = project.tasks.create(name: 'stripPlayServices', group: "Strip") {
inputs.files new File(playServiceRootFolder, "classes.jar")
outputs.dir playServiceRootFolder
description 'Strip useless packages from Google Play Services library to avoid reaching dex limit'
doLast {
copy {
from(file(new File(playServiceRootFolder, "classes.jar")))
into(file(playServiceRootFolder))
rename { fileName ->
fileName = "classes_orig.jar"
}
}
tasks.create(name: "stripPlayServices" + module.version, type: Jar) {
destinationDir = playServiceRootFolder
archiveName = "classes.jar"
from(zipTree(new File(playServiceRootFolder, "classes_orig.jar"))) {
exclude "com/google/ads/**"
exclude "com/google/android/gms/analytics/**"
exclude "com/google/android/gms/games/**"
exclude "com/google/android/gms/plus/**"
exclude "com/google/android/gms/drive/**"
exclude "com/google/android/gms/ads/**"
}
}.execute()
delete file(new File(playServiceRootFolder, "classes_orig.jar"))
}
}
project.tasks.findAll { it.name.startsWith('prepare') && it.name.endsWith('Dependencies') }.each { Task task ->
task.dependsOn stripPlayServices
}
}
I've shared a sample project which solve this problem using custom_rules.xml build script and a few lines of code.
I used it on my own project and it is runs flawless on 1M+ devices (from android-8 to the latest android-19). Hope it helps.
https://github.com/mmin18/Dex65536
Faced the same problem and solved it by editing my build.gradle file on the dependencies section, removing:
compile 'com.google.android.gms:play-services:7.8.0'
And replacing it with:
compile 'com.google.android.gms:play-services-location:7.8.0'
compile 'com.google.android.gms:play-services-analytics:7.8.0'
Try adding below code in build.gradle, it worked for me -
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
multiDexEnabled true
}
The perfect solution for this would be to work with Proguard. as aleb mentioned in the comment.
It will decrease the size of the dex file by half.
You can analyse problem (dex file references) using Android Studio:
Build -> Analyse APK ..
On the result panel click on classes.dex file
And you'll see:
gradle + proguard solution:
afterEvaluate {
tasks.each {
if (it.name.startsWith('proguard')) {
it.getInJarFilters().each { filter ->
if (filter && filter['filter']) {
filter['filter'] = filter['filter'] +
',!.readme' +
',!META-INF/LICENSE' +
',!META-INF/LICENSE.txt' +
',!META-INF/NOTICE' +
',!META-INF/NOTICE.txt' +
',!com/google/android/gms/ads/**' +
',!com/google/android/gms/cast/**' +
',!com/google/android/gms/games/**' +
',!com/google/android/gms/drive/**' +
',!com/google/android/gms/wallet/**' +
',!com/google/android/gms/wearable/**' +
',!com/google/android/gms/plus/**' +
',!com/google/android/gms/topmanager/**'
}
}
}
}
}
Remove some jar file from Libs folder and copy to some other folder, And Go to _Project Properties > Select Java Build Path, Select Libraries, Select Add External Jar, Select the Removed jar to your project, Click save, this will be added under Referenced Library instead of Libs folder. Now clean and Run your project. You dont need to add Any code for MultDex. Its simply worked for me.
I was facing the same issue today what worked for is below down
For ANDROID STUDIO... Enable Instant Run
In File->Preferences->Build, Execution, Deployment->Instant Run-> Check Enable Instant run for hot swap...
Hope it helps