I put a .jar file containing .dex file to directory "/sdcard", then I try to load the class in the .jar file using DexClassLoader and PathClassLoader respectively. Both of them can load the class successfully. What are differences between them?
Here is my code:
String dexPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "test.jar";
PathClassLoader classLoader1 = new PathClassLoader(dexPath, getClassLoader());
DexClassLoader classLoader2 = new DexClassLoader(dexPath, getDir("dex", 0).getAbsolutePath(), null, getClassLoader());
try {
Class clazz1 = classLoader1.loadClass("com.focans.loader.Peter");
Class clazz2 = classLoader2.loadClass("com.focans.loader.Peter");
} catch (Exception e) {
e.printStackTrace();
}
You should read official Guideline about
DexClassLoader
A class loader that loads classes from .jar and .apk files containing
a classes.dex entry. This can be used to execute code not installed as
part of an application.
PathClassLoader
Provides a simple ClassLoader implementation that operates on a list
of files and directories in the local file system, but does not
attempt to load classes from the network. Android uses this class for
its system class loader and for its application class loader(s).
DexClassLoader is instantiated to load the library from the extracted secondary dex file.
PathClassLoader Used to load classes within ant with a different classpath from that used to start ant. Note that it is possible to force a class into this loader even when that class is on the system classpath by using the forceLoadClass method. Any subsequent classes loaded by that class will then use this loader rather than the system class loader.
For Android 8.1 (API 27) and up, DexClassLoader and PathClassLoader are essentially identical. They both extend BaseDexClassLoader, and immediately call super() when constructed. There are no implementation differences or side effects (at least in the AOSP versions I've referenced here).
In prior versions (8.0 and earlier) DexClassLoader accepted an argument for String optimizedDirectory, which allowed the caller to specify the directory to store optimized Dex code (ODEX files), for the Dex that was loaded by the class loader. This argument still exists in newer versions of Android, but it has no effect.
Related
I had created one project using native c++ support. In this project I am passing int value from activity to c++ code and native code returns whether this is prime number or not. This works perfectly, Now I want to create .so file to use in another project. I had google many post but not got answer how to get different .so file for all devices. So I had rename .apk file to .zip and extract it. After that I got one .so file.
Now I want to use this .so file in another project. therefore I had created new project with different name but package name is same. I had created one directory inside src/main and named it as jniLib in this lib I had copied my .so file directory. In my MainActivity I load so file as static {
System.loadLibrary("native-lib");
}
and call my native method private native String isPrimeNumber(int number);. Here everything is perfect. Now I can get result without having actual c++ code.
Now Again I created new project and follow above steps which was followed by creating second project, but difference is that now I had changed package name of my application. when I run application my application got crashed with error as
FATAL EXCEPTION: main
Process: com.app.androidkt.differentpackage, PID: 16970
java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.app.androidkt.differentpackage.MainActivity.isPrimeNumber(int) (tried Java_com_app_androidkt_differentpackage_MainActivity_isPrimeNumber and Java_com_app_androidkt_differentpackage_MainActivity_isPrimeNumber__I)
at com.app.androidkt.differentpackage.MainActivity.isPrimeNumber(Native Method)
at com.app.androidkt.differentpackage.MainActivity.access$000(MainActivity.java:10)
at com.app.androidkt.differentpackage.MainActivity$1.onClick(MainActivity.java:38)
at android.view.View.performClick(View.java:5268)
at android.view.View$PerformClick.run(View.java:21550)
at android.os.Handler.handleCallback(Handler.java:822)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5811)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:681)
So my question are - 1) Is it necessary to use same package name to use .so file in our application as that of .so file.
2) How I can get different .so file directory - currently for time being I had extracted it from apk.
3) To use of .so file is to hide the native code only or there is any other purpose also there?
Thanks in advance.
Your application package name may be anything, but the Java class that consumes native methods implemented in libnative-lib.so must be exactly the same as intended by the authors of this libnative-lib.so file.
The easiest workaround for your setup is to move your com.app.androidkt.differentpackage.MainActivity class to the com.app.androidkt.samplendk package. Android Studio will help you with this refactoring. Note that now you must declare the full path for MainActivity in your AndroidManifest.xml.
Alternatively, you can create a small com.app.androidkt.samplendk.MainActivity
class:
package com.app.androidkt.oldpackage;
public class MainActivity {
static {
System.loadLibrary("native-lib");
}
public native String isPrimeNumber(int number);
}
and add few lines to your MainActivity.java:
package com.app.androidkt.differentpackage;
public class MainActivity extends AppCompatActivity {
private com.app.androidkt.oldpackage.MainActivity pmSolver;
private String isPrimeNumber(int number) {
return pmSolver.isPrimeNumber(number);
}
…
}
If you don't know the exact package name used for this libnative-lib.so, you can find it by parsing its ELF headers: you will see an exported function named similar to Java_com_app_androidkt_ samplendk_MainActivity_isPrimeNumber.
Nitpicker's corner: it is possible to build a JNI library that will hide its designated class name(s), but it is hard to reliably prevent reverse engineering these names; it is also possible to build a JNI library that will seamlessly connect to different packages.
1) Is it necessary to use same package name to use .so file in our
application as that of .so file
No, you can use any package name you desire
2) How I can get different .so file directory - currently for time
being I had extracted it from apk
Copy all .so files to your new project folder: src/main/jniLibs/armeabi
3) To use of .so file is to hide the native code only or there is any
other purpose also there?
.so file is a library. Hence the purpose is to be convenient in reusing implemented features in multiple projects.
I'm trying to load dynamically external classes using DexClassLoader, like #Shlublu proposes here
When I execute my application and the DexClassLoader object try to find the class, it throws a ClassNotFound exception. I have added read and write permissions in the manifest.xml, so it is not the mistake.
I think the problem is the method that I use to make the .jar that I want to load on my application. So I have some questions...
What is the correct method to convert a .java file to .jar using dx
tool?
It is necessary that the package where the external class is loaded be the same that the package of my .jar file? (I think no)
I'm using an Android emulator API 19 (kit-kat)
Since APK is the standard Android package, my suggestion is that you use an APK instead of a JAR. Build an application APK linking the needed JAR (let the Android build tools "dex" the JAR) but without any activity, then install the APK as if it was a normal app. You can then access the APK file itself using the PackageManager to get its path and load it using DexClassLoader.
public static ClassLoader loadAPK(final Context context, final String appName) throws PackageManager.NameNotFoundException {
final String apkPath = context.getPackageManager().getApplicationInfo(appName, 0).sourceDir;
final File tmpDir = context.getDir("tmp", 0);
return new DexClassLoader(apkPath, tmpDir.getAbsolutePath(), null, context.getClassLoader());
}
I am using PowerMockRunner to run my unit tests. I want to load some canned network response json files from my assets folder.
I am using this method to try to get the file.
private static File getFileFromPath(Object obj, String fileName) {
ClassLoader classLoader = obj.getClass().getClassLoader();
URL resource = classLoader.getResource(fileName);
return new File(resource.getPath());
}
I call the method like this from my class which has these annotations at the top.
#RunWith(PowerMockRunner.class)
#PrepareForTest(Network.class)
File file = getFileFromPath(this, "mock_response.json");
However, when I evaluate this expression.
classLoader.getResource(".");
It shows that I am currently in this the directory below while running this test.
/Users/tylerpfaff/Library/Android/sdk/platforms/android-23/data/res/
Seeing as I'm in the platforms resource directory, there is no hope of me successfully loading my resources from my project's resource directory. What do I need to do to access my resource directory of my project?
You have two options:
try to get system classloader via ClassLoader.getSystemClassLoader()
Get classloader from the class that has been ignored by PowerMock. By default:
"org.hamcrest.", "java.",
"javax.accessibility.", "sun.", "org.junit.", "org.testng.",
"junit.", "org.pitest.", "org.powermock.modules.junit4.common.internal.",
"org.powermock.modules.junit3.internal.PowerMockJUnit3RunnerDelegate",
"org.powermock.core*", "org.jacoco.agent.rt.*"
Or you may use #PowerMockIgnore.
Are there any restrictions to ClassLoaders under android ICS?
I´m not getting a single example working at all, and I think I´m doing everything right.
For example, this code
DexFile df = new DexFile("/sdcard/test.apk");
ClassLoader cl = context.getClassLoader();
Class clazz = df.loadClass("com/test/LibraryClass", cl);
Produces:
E/dalvikvm﹕ Dex cache directory isn't writable: /data/dalvik-cache
I/dalvikvm﹕ Unable to open or create cache for /sdcard/test.apk (/data/dalvik-cache/sdcard#test.apk#classes.dex)
W/System.err﹕ java.io.IOException: unable to open DEX file
The location is correct, the dex file exists.
/data/dalivk-cache has permission of 775. It is not writable directory for others. It is done so for security purpose, so that applications don't modify other applications. It is meant for system installer which unpacks and unzip the dex file contained in apk.
For applications to load external classes, use DexClassLoader
DexClassLoader loader = new DexClassLoader("/sdcard/com.example.test.apk", getApplicationInfo().dataDir, null, getClassLoader());
try {
loader.loadClass("com.example.test.MainActivity");
} catch (ClassNotFoundException e) {
Log.e(TAG, "Could not load class");
e.printStackTrace();
}
If you are reading from the internal storage area, then for API 18 and earlier, you'll need this permission in your AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
You don't need the above permission starting from API 19 onwards. See here for more info.
Also, instead of hardcoding the path (i.e. "/sdcard/") in your class, I would use this instead:
String path = Environment.getExternalStorageDirectory().getAbsolutePath();
I'm developing an android library project that should read from an xml file in its raw resources (let's call it xml_file_name.myextension).
What I do is basically creating a jar file of the library project including these folders:
src
gen
lib
res/raw
and referencing it as a library in a test app. This is the code that I use (inside the library project) in order to get the xml file:
int xml_res_id = -1;
for (Field f : R.raw.class.getFields()) {
System.out.println("Raw resource found: " + f.getName());
if (f.getName().equalsIgnoreCase("xml_file_name"))
xml_res_id = f.getInt(null);
}
if(xml_res_id != -1){
System.out.println("xml_file_id: " + xml_res_id);
InputStream is = context.getResources().openRawResource(xml_res_id);
// Decode xml file with SAXParser..
}
(I have the app context because the app explicitly passes it to the library project.)
What happens is that when I launch the test app (and call the method that reads the xml file) I get this error:
It seems that the xml file is actually in the right folder, because:
1) The for loop actually prints "Raw resource found: xml_file_name.myextension" and "xml_file_id: 2130968576"
2) If I put a file named "xml_file_name.myextension" in the res/raw folder of the app, it does not compile, and the error is: "Error generating final archive: Found duplicate file for APK: res/raw/xml_file_name.myextension". This basically gives me the proof that the
file is correctly "imported" from the library project.
Please Note:
I also tried in this other way
InputStream is = context.getResources().openRawResource(R.raw.xml_file_name);
getting the same error.
I honestly don't understand what could be the problem.. what am I doing wrong?
Edit:
for anyone interested in this issue:
I finally realized that this is not possible, basically because when I try to get a resource through context.anymethod I refer to the R file of the app, so I can't give the resource ID got from the R file of my library project.
It will compile, because the library project jar file contains the resource (R.raw.xml_file), but the call to context.something will always give null as a result because it refers to the app R file, that does not have that particular resource in it.
I finally had to put my xml file in the res/raw folder of the app, and access the xml_file raw resource in this way:
int xml_id = context.getResources().getIdentifier("xml_file_name", "raw", context.getPackageName());
// Getting input stream from xml file
InputStream is = context.getResources().openRawResource(xml_id);
I have actually done this with success - the library object should be within the app context. However, it only works with Activity and no other type that I have found. Using the same library with a FragmentActivity fails with NoClassDefFoundError.
EDIT****
It may work with a FragmentActivity within the same root namespace as the library. I was accessing from a different root namespace.
END EDIT****
I have a library project that references an xml file:
InputStream is = context.getResources().openRawResource(R.raw.application_table_defs);
when I call the library method that executes the previous line I have to pass in a context:
Context context = this.getContext();
The key is fully qualifying the getResource to context.getResources()... that was injected.