I have a library in my Android app like UnityAds. I want to get the version name and the version code of it in runtime. I wrote the below code, but always exception occurred. What is the best practice to get this information programmatically?
implementation 'com.unity3d.ads:unity-ads:3.7.1'
try {
Log.d("Version", getPackageManager().getPackageInfo("com.unity3d.ads", 0).versionName);
} catch (PackageManager.NameNotFoundException e) {
Log.e("error", e.getMessage());
}
That won’t work, PackageManager APIs help you check for any valid packages/apps installed using a package name lookup.
Since the library is in the class path for your own package the only way to reference these ids at runtime would be to have a workflow to record these during compilation and expose it to your application at runtime.
Following approaches are viable:
Expose library metadata in build config/resources (https://developer.android.com/studio/build/gradle-tips#simplify-app-development)
Custom gradle plugin to capture all libraries in classpath into assets which can be read at runtime, something similar to https://github.com/google/play-services-plugins
With (1) you can’t scale it for multiple libraries conveniently so it’s only useful for very specific needs.
(2) might require some customisations for your usecase (adding version info) but is definitely scalable.
Related
Background
Suppose I make an Android library called "MySdk", and I publish it on Jitpack/Maven.
The user of the SDK would use it by adding just the dependency of :
implementation 'com.github.my-sdk:MySdk:1.0.1'
What I'd like to get is the "1.0.1" part from it, whether I do it from within the Android library itself (can be useful to send to the SDK-server which version is used), or from the app that uses it (can be useful to report about specific issues, including via Crashlytics).
The problem
I can't find any reflection or gradle task to reach it.
What I've tried
Searching about it, if I indeed work on the Android library (that is used as a dependency), all I've found is that I can manage the version myself, via code.
Some said I could use BuildConfig of the package name of the library, but then it means that if I forget to update the code a moment before I publish the dependency, it will use the wrong value. Example of using this method:
plugins {
...
}
final def sdkVersion = "1.0.22"
android {
...
buildTypes {
release {
...
buildConfigField "String", "SDK_VERSION", "\"" + sdkVersion + "\""
}
debug {
buildConfigField "String", "SDK_VERSION", "\"" + sdkVersion + "-unreleased\""
}
}
Usage is just checking the value of BuildConfig.SDK_VERSION (after building).
Another possible solution is perhaps from gradle task inside the Android-library, that would be forced to be launched whenever you build the app that uses this library. However, I've failed to find how do it (found something here)
The question
Is it possible to query the dependency version from within the Android library of the dependency (and from the app that uses it, of course), so that I could use it during runtime?
Something automatic, that won't require me to update it before publishing ?
Maybe using Gradle task that is defined in the library, and forced to be used when building the app that uses the library?
You can use a Gradle task to capture the version of the library as presented in the build.gradle dependencies and store the version information in BuildConfig.java for each build type.
The task below captures the version of the "appcompat" dependency as an example.
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.0'
}
task CaptureLibraryVersion {
def libDef = project.configurations.getByName('implementation').allDependencies.matching {
it.group.equals("androidx.appcompat") && it.name.equals("appcompat")
}
if (libDef.size() > 0) {
android.buildTypes.each {
it.buildConfigField 'String', 'LIB_VERSION', "\"${libDef[0].version}\""
}
}
}
For my example, the "appcompat" version was 1.4.0. After the task is run, BuildConfig.java contains
// Field from build type: debug
public static final String LIB_VERSION = "1.4.0";
You can reference this field in code with BuildConfig.LIB_VERSION. The task can be automatically run during each build cycle.
The simple answer to your question is 'yes' - you can do it. But if you want a simple solution to do it so the answer transforms to 'no' - there is no simple solution.
The libraries are in the classpath of your package, thus the only way to access their info at the runtime would be to record needed information during the compilation time and expose it to your application at the runtime.
There are two major 'correct' ways and you kinda have described them in your question but I will elaborate a bit.
The most correct way and relatively easy way is to expose all those variables as BuildConfig or String res values via gradle pretty much as described here. You can try to generify the approach for this using local-prefs(or helper gradle file) to store versions and use them everywhere it is needed. More info here, here, and here
The second correct, but much more complicated way is to write a gradle plugin or at least some set of tasks for collecting needed values during compile-time and providing an interface(usually via your app assets or res) for your app to access them during runtime. A pretty similar thing is already implemented for google libraries in Google Play services Plugins so it would be a good place to start.
All the other possible implementations are variations of the described two or their combination.
You can create buildSrc folder and manage dependencies in there.
after that, you can import & use Versions class in anywhere of your app.
I am trying to debug an existing Android app that uses tensorflow-lite to detect objects. The app implements the tensorflow library like below :
implementation('org.tensorflow:tensorflow-lite:0.0.0-nightly') { changing = true }
implementation('org.tensorflow:tensorflow-lite-gpu:0.0.0-nightly') { changing = true }
implementation('org.tensorflow:tensorflow-lite-support:0.0.0-nightly') { changing = true }
But examples I have found online for object detection, have implemented tensorflow-lite in the following way :
implementation 'org.tensorflow:tensorflow-lite-task-vision:0.2.0'
My questions are:
What is the difference between using the nightly snapshot and the "normal" library. From what I can gather online, the nightly build is an experimental branch and may contain bugs? I'm just confused about this because the existing app does not contain a reference to sonatype maven repository, which I understand was required to get the nightly builds to work in the app.
allprojects {
mavenCentral
maven {
name 'ossrh-snapshot'
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
}
My second question is what does this do line do : { changing = true } ?
PS: We are using our own custom trained model/tflite.
Changing, or snapshot versions are used when you need Gradle to get a new version of the dependency with the same name from time to time (once in 24 hours, unless specified explicitly otherwise).
I believe that whoever chose the nightly version of tensorflow, was wrong. As you say, this version may have bugs, and worse, these bugs will change overnight. Find some fixed version that you are comfortable with, study its changelog, and reset your implementation to refer to this version.
The following was done with Android Studio 3.4, Android Gradle Plugin 3.3.2 and Gradle 4.10.3.
In the build.gradle file, I have configured some unit test options like this:
android {
testOptions {
unitTests.all {
systemProperty "debug","true"
}
}
}
I do have a test function that tries to read this property:
package com.demo;
public class SysPropTestDemo {
#Test
public static void dumpSysProps() {
System.out.println("sysprop(debug)=" + System.getProperty("debug"));
}
}
When run via command line gradlew test --test com.demo.SysPropTestDemo I will get the property debug set correctly to true. If I run the same test via Android Studio without setting any options, the value shown will be null.
In order to get the same result from Android Studio, I explicitly have to enter some values in the "Run/Debug Configurations" panel, i.e something like -Ddebug=true in the VM options.
Now this is a trivial example, but what I really want to do, is to add some path to the java.library.path property in order to be able to load a JNI library compiled within the project. (I do need to write some tests that make use a modified SQLite lib, so not using JNI is not an option here)
It does work when setting additional options, but I think this is very inconvenient, since I can't enter a variable based value in the configuration options (or at least, I don't know how to). To sum it up: when setting or changing values, I do have to go through a bunch of config screens where I would really prefer to have one place in a config file.
Shouldn't Android Studio somehow make use of the values specified in the build.gradle file? If not, the docs don't make it clear that the testOptions.unitTests.all settings can only be used via gradlew invocation.
Skybow,
I feel you have two questions
1. How to load jni lib for androidTest(not for 'test[non instrumented unit tests])
- copy your jni library in corresponding folder [JNI libraries: [app/src/androidTestFLAVORNAMEDebug/jniLibs]
- load your jni library
static {
try {
System.loadLibrary("xyzjni");
} catch (Exception e) {
Logger.error("Exception on loading the jni library : " + e.getMessage());
}
}
2. How to make android studio use your config variables defined for unitTests.
- It would have great if some text file is there which has all configs.
- Or it is part of build.gradle
- I don't have any detail on this.
I would like to use the Scala (2.11) reflection package's runtime mirror in a Scala application compiled for android which is being build using Scala on android.
I was able to fiddle with ProGuard options in order to make it include the required Scala classes. However when I try to get a mirror instance:
universe.runtimeMirror(this.getClass.getClassLoader)
(Indeed it fails during the lazy computation of universe)
The application crashes in run time:
java.lang.NoClassDefFoundError: Failed resolution of: Ljava/rmi/Remote;
at scala.reflect.internal.Definitions$DefinitionsClass.RemoteInterfaceClass$lzycompute(Definitions.scala:370)
at scala.reflect.internal.Definitions$DefinitionsClass.RemoteInterfaceClass(D efinitions.scala:370)
at scala.reflect.runtime.JavaUniverseForce$class.force(JavaUniverseForce.scal a:255)
at scala.reflect.runtime.JavaUniverse.force(JavaUniverse.scala:16)
at scala.reflect.runtime.JavaUniverse.init(JavaUniverse.scala:147)
at scala.reflect.runtime.JavaUniverse.<init>(JavaUniverse.scala:78)
at scala.reflect.runtime.package$.universe$lzycompute(package.scala:17)
at scala.reflect.runtime.package$.universe(package.scala:17)
This crash is for me as expected as it isn't:
It is expected as java.rmi is not part of the Android API and I should expect any code trying to load its classes to crash.
It is unexpected as I didn't know that Scala's reflect package used java.rmi
I have traced the code to were rmi is required, that is to JavaUniverse (a trait mixed in JavaUniverse class) force method:
...
definitions.RemoteInterfaceClass
...
Which leads to DefinitionsClass:
lazy val RemoteInterfaceClass = requiredClass[java.rmi.Remote]
Am I wrong to think that this is a no-go for Scala reflection in Android?
If I am, what could be a workaround to this problem?
To summarize your solution and a related solution, it is sufficient to add two files, and modify build.sbt to include:
dexAdditionalParams in Android += "--core-library"
Add java/rmi/Remote.java to your project with the content:
package java.rmi;
public interface Remote {}
Add java/rmi/RemoteException.java to your project with the content:
package java.rmi;
public interface RemoteException {}
In the project I'm working we've recently added some level of security, now i don't want to have to rewrite the entire nework logic if it can be done much more easily with AOP.
So, I'm trying to intercept the "onRequestSuccess" method of the requestListeners that are used throughout the application.
For this I have made a simple aspect:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class NetworkResponseAspect {
#Around("execution(public void *.onRequestSuccess(..))")
public void intercept(ProceedingJoinPoint joinPoint){
System.out.println("call intercepted " + joinPoint);
try {
joinPoint.proceed();
} catch (Throwable e) {
System.out.println("wut");
}
}
}
I've added the aspectj weaver dependency: compile 'org.aspectj:aspectjweaver:1.8.6'
And it seems to work, at least the annotations are recognized by android studio.
I've placed a breakpoint on the "joinpoint.proceed()" call and started the application in debug mode.
But when I log in (an action that triggers one such listener) nothing happens. Am I missing something?
In Maven you would use AspectJ Maven Plugin (current version is 1.7), in Gradle something similar. This is what you need for compilation if you want to use CTW (compile-time weaving). Those plugins should already contain a dependency on aspectjtools.jar which contains the AspectJ compiler and other stuff. If you use CTW, you need aspectjrt.jar (AspectJ runtime) as a default-scoped (compile) or dependency because it is needed during runtime as well.
If you want to use load-time weaving (LTW), though, you need aspectjweaver.jar on your JVM command line via -javaagent:... because the weaving agent needs to hook into class-loading before your first application class is loaded. P.S.: The weaving agent also contains the AspectJ runtime classes, so you do not need an additional dependency on the runtime in this case.