Build a self updating Android Application

Valentin Ahrend
4 min readFeb 3, 2021

Imagine developing an application and on the release day some users get a bug or an error. There is of course the possibility to update your application. Setting up a new version and uploading you app again, so that the users can access it after a few days, isn’t that smart. That’s why you should build an auto-update app, that contains code, which updates itself and comes from your database, where you change it in a few seconds. So let’s get started…

At first you need to know what kind of source you want to have:

  • A dex-file source (.dex) is the result of pure java classes combined into a special, compressed file. A dex-file is ready to be integrated into your classloader, but you have to get this specific dex-file by yourself.
  • A directory of java classes (.class files). Java classes are compiled java code, but for loading them into runtime Class (java.lang.Class) objects you have to create a dex-file out of this class files. You also need to get the .class files by yourself, which is way easier than getting the dex-file.
  • Or a directory of pure java code (.java files). The java code must be compiled by a compiler, which you need to implement. These (offline) compilers can be very large and are not often used in applications, because often they do not support the android environment. The java compiler will return the classes as .class files. You need to do the same with them as I wrote above.

The pure java code option isn’t good for your project, because of space reasons and because you can create .class files by yourself. It will save the Android Phones a lot of energy and work.

A pure dex-file is the best option. You don’t have to implement any other libraries. But using this class file directory is also a nice way to update small classes or only a few lines of code.

Now I will show you, how you can create a .dex out of .class files:

  1. Step: open your .jar file, where the .class files are compressed in
File storage = new File(DIR_NAME);
java.util.jar.JarFile jar = new java.util.jar.JarFile(jar_file);
java.util.Enumeration enumEntries = jar.entries();
while (enumEntries.hasMoreElements()) {
java.util.jar.JarEntry file = (java.util.jar.JarEntry) enumEntries.nextElement();
java.io.File f = new java.io.File(storage.getAbsolutePath() + "/" + file.getName());
if (!f.getAbsolutePath().contains("__MACOSX")) {

if (file.isDirectory()) { // if its a directory, create it
//noinspection ResultOfMethodCallIgnored
f.mkdir();
continue;
}
java.io.InputStream is = jar.getInputStream(file); // get the input stream
java.io.FileOutputStream fos = new java.io.FileOutputStream(f);
while (is.available() > 0) { // write contents of 'is' to 'fos'
fos.write(is.read());
}
fos.close();
is.close();
}
}
jar.close();

2. Step: List all the unpacked .class files and generate the arguments for the command call

List<File> files2 = Arrays.asList(Objects.requireNonNull(storage.listFiles(pathname -> pathname.getAbsolutePath().endsWith(".class"))));

files2.forEach(file -> {
if (file.isDirectory()) {
List<File> add_files = Arrays.asList(Objects.requireNonNull(file.listFiles(pathname -> pathname.getAbsolutePath().endsWith(".class"))));
files2.addAll(add_files);
}
});

String[] args2 = new String[5 + files2.size()];
args2[0] = "--dex";
args2[1] = "--keep-classes";
args2[2] = "--output=" + DEX_FILE_NAME + ".dex";
args2[3] = "--min-sdk-version=26";
args2[4] = "--verbose";
int var0 = 5;
for (File f :
files2) {
args2[var0] = f.getAbsolutePath();
var0++;
}

3. Step: Pass the arguments into the command and receive a runnable. In this runnable open the DexClassLoader with your .dex file. Then load the class out of the ClassLoader.

com.android.dx.command.Main.main(args2, () -> {

//noinspection ResultOfMethodCallIgnored
jar_file.delete();

DexClassLoader cl = new DexClassLoader(storage.getAbsolutePath() + "/" + DEX_FILE_NAME + ".dex", storage.getAbsolutePath(), null, context.getClassLoader());
try {
pro.classLoaded(cl.loadClass("YOUR_PACKAGE.YOUR_CLASS"));
} catch (Exception e) {
e.printStackTrace();
pro.classLoaded(null);
}
});

Important: I am using a different version of the com.android.dx library. Normally there is no runnable (compare code above and below). I am changing the code of the lib for fixing bugs. You do not have to do this, so this is what it looks like without a special edit:

com.android.dx.command.Main.main(args2); 

//noinspection ResultOfMethodCallIgnored
jar_file.delete(); //optional

DexClassLoader cl = new DexClassLoader(storage.getAbsolutePath() + "/" + DEX_FILE_NAME + ".dex", storage.getAbsolutePath(), null, context.getClassLoader());
try {
pro.classLoaded(cl.loadClass("YOUR_PACKAGE.YOUR_CLASS"));
} catch (Exception e) {
e.printStackTrace();
pro.classLoaded(null);
}

Now we created a class instance of our downloaded class. You can use java reflection to handle with this new object. In Android for calling Activities you should use android.content.Intent. If you are trying to download executable activities, I must tell you that this won’t work. The IntentStarter.class is located in the internal api of Android, which is not included in your normal classloader. I made a way opening modified Activities by their superclass, I guess that this is different from a real Activity, which is registered in the final Manifest, but it is a good opportunity.

IntentStarter

com.android.dx

This articles code

I hoped you guys enjoyed my article a bit. I am very new to this platform and just wanted to share some stuff I worked on.

--

--

Valentin Ahrend

Self taught software developer. Sharing some ideas, experiences and projects.