0

I wrote two simple java programs for Android 4.1.2.

1) Console program:

public class console_hello_world
{
    console_hello_world() {
        System.out.println("Init!");
    }
    protected static int method() {
        System.out.println("Method!");
        return 0;
    }
    public static void main(String[] args)
    {   console_hello_world variable=new console_hello_world();
        variable.method();
        System.out.println("Hello World!");
    }
}

It was built in a such way (As you can see this script also launches this program):

#!/bin/bash

DX="~/Programs/android-sdk-linux/build-tools/18.0.1/dx"
REMOTE_PATH=/data/local/tmp

CLASS_NAME="console_hello_world"
javac "${CLASS_NAME}.java"
${DX} --dex --output="classes.dex" "${CLASS_NAME}.class"
zip "${CLASS_NAME}.zip" "classes.dex"

ADB="~/Programs/android-sdk-linux/platform-tools/adb"
"${ADB}" push "${CLASS_NAME}.zip" $REMOTE_PATH/
"${ADB}" shell mkdir $REMOTE_PATH/dalvik-cache

"${ADB}" shell "logcat -c"
"${ADB}" shell ANDROID_DATA=$REMOTE_PATH dalvikvm -cp "$REMOTE_PATH/${CLASS_NAME}.zip" ${CLASS_NAME}
"${ADB}" shell "logcat -d" > out.log

2) GUI application:

package com.example.my_app;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

public class my_app_activity extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    /** Called when the user clicks the button */
    public void onButtonClickMethod(View view)
    {
        // do smth
        EditText editText = (EditText) findViewById(R.id.edit_message);
        String message = editText.getText().toString();
        message += " + something!\n";
        editText.setText(message);
    }
}

It was built in a standard way.

Each of application launches OK. The way I launch console program is in the bash script inserted before.

Then I removed ODEX files of each program:

/data/local/tmp/dalvik-cache/data@local@tmp@console_hello_world.zip@classes.dex

/data/dalvik-cache/data@app@com.example.my_app-1.apk@classes.dex

(It's OK - my GUI application name is "my_app-1.apk")

For console program I also removed "/data/local/tmp/dalvik-cache/" directory and created new one from root, access flags were changed to 771 (the same access flags "/data/dalvik-cache/" directory has). So Dalvik VM is not able to write to "/data/local/tmp/dalvik-cache/" if it wasn't launched from root.

So after all these steps:

1) GUI application still works OK. Dalvik VM process has no access to "/data/dalvik-cache/" and new ODEX file isn't been created.

2) Console application throws an exception:

Dalvik VM unable to locate class 'console_hello_world'
java.lang.NoClassDefFoundError: console_hello_world
    at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.ClassNotFoundException: console_hello_world
    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:61)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:501)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
    ... 1 more

It is surprisingly for me.

Because I thought that ODEX file is just verified and optimized version of classes.dex file, that is in archive, and applications can work without it.

My GUI application proofs this theory. But console application doesn't.

Can somebody explain me, why doesn't console android java application work without ODEX file, at the same time when GUI application feels good?

UPDATE:

I tested my GUI application more thoroughly.

If I delete odex file "/data/dalvik-cache/data@app@com.example.my_app-1.apk@classes.dex" before I launch "my_app-1.apk" first time, "my_app-1.apk" doesn't work after that. Here is Android log (I modified dalvik little bit):

W/ActivityThread( 4133): Application com.example.my_app can be debugged on port 8100...
I/dalvikvm( 4133): dvmJarFileOpen. fileName = /data/app/com.example.my_app-1.apk; odexOutputName = (null)
I/dalvikvm( 4133): dvmOpenCachedDexFile. fileName = /data/app/com.example.my_app-1.apk; cacheFileName = /data/dalvik-cache/data@app@com.example.my_app-1.apk@classes.dex
E/dalvikvm( 4133): Dex cache directory isn't writable: /data/dalvik-cache
I/dalvikvm( 4133): Unable to open or create cache for /data/app/com.example.my_app-1.apk (/data/dalvik-cache/data@app@com.example.my_app-1.apk@classes.dex)
D/AndroidRuntime( 4133): Shutting down VM

But if I remove ODEX file after I launched my_app-1.apk several times before, everything is OK with my_app-1.apk. Also there is no "/data/app/com.example.my_app-1.apk" file reading in Android log. I guess, Android puts ODEX somewhere in RAM for optimization and then just sets a pointer to it when it's necessary.

So, Mr. fadden was right, and everyone that was agree with him was also right! :)

It would be great, if someone could answer am I right saying that Android leaves the application in RAM for optimization, and explain me what Service/application/etc... is responsible for this Android cheat.

Lucky Man
  • 1,488
  • 3
  • 19
  • 41

1 Answers1

2

The odex file is always required. The classes.dex must be extracted from the zip file, byte-swapped if necessary, and have some basic structural verification performed. For a GUI app, the package manager will recreate it automatically if necessary; the PM has the necessary permissions to update /data/dalvik-cache.

A full explanation of the process can be found in the Dalvik sources (dalvik/docs/dexopt.html). (Some of it is a bit out of date, but it's mostly correct.)

BTW, in your creation step, if you tell dx to create foo.zip or foo.jar instead of foo.dex it will automatically zip the output file for you.

fadden
  • 51,356
  • 5
  • 116
  • 166
  • "For a GUI app, the package manager will recreate it automatically if necessary". I checked "/data/dalvik-cache" directory during GUI application work and after I closed it. There wasn't data@app@com.example.my_app-1.apk@classes.dex file in it. Is there another directory, where this file is writed? – Lucky Man Oct 30 '13 at 16:28
  • I have read this article earlier. According to it - there are 3 ways to create ODEX file in Android: "1. The VM does it "just in time". The output goes into a special dalvik-cache directory. This works on the desktop and engineering-only device builds where the permissions on the dalvik-cache directory are not restricted. On production devices, this is not allowed. ... And other". But It means that there no way to create ODEX file on unrooted device if it was removed. I is only way to reboot or reinstall app. Nevertheless, GUI application works fine without ODEX file. – Lucky Man Oct 30 '13 at 16:36
  • The app can't run without an odex file. Did you explicitly kill the app process after removing the /data/dalvik-cache entry, or just switch away and then back in? If the app is still alive, removing the file won't affect it. – fadden Oct 30 '13 at 17:03
  • No, I just deleted odex file and after that successfully launched GUI application. Nothing created new ODEX file. Dalvik process of launched GUI app has no rights to write to "/data/dalvik-cache" (It is in the article you mentioned). During GUI app launch there isn't call to Package Manager. Nothing writes to "/data/dalvik-cache/". But app works fine :) . I guess, that Dalvik extracts calsses.dex directly in memory and performs everything it need there? – Lucky Man Oct 30 '13 at 17:22
  • In another case: "The app can't run without an odex file." I have no idea what is happening! ) – Lucky Man Oct 30 '13 at 17:34