I followed the exact steps given in the post below:
Is it possible to dynamically load a library at runtime from an Android application?
I am using and android 2.2 phone for testing and getting an error which is driving me crazy :(
07-27 01:24:55.692: W/System.err(14319): java.lang.ClassNotFoundException: com.shoaib.AndroidCCL.MyClass in loader dalvik.system.DexClassLoader#43abbc20
Can someone help me what to do now..i have tried various suggested solutions on different posts
I was wondering whether this was feasible so I wrote the following class:
package org.shoaib.androidccl;
import android.util.Log;
public class MyClass {
public MyClass() {
Log.d(MyClass.class.getName(), "MyClass: constructor called.");
}
public void doSomething() {
Log.d(MyClass.class.getName(), "MyClass: doSomething() called.");
}
}
And I packaged it in a DEX file that I saved on my device's SD card as /sdcard/testdex.jar.
Then I wrote the program below, after having removed MyClass from my Eclipse project and cleaned it:
public class Main extends Activity {
#SuppressWarnings("unchecked")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
try {
final String libPath = Environment.getExternalStorageDirectory() + "/testdex.jar";
final File tmpDir = getDir("dex", 0);
final DexClassLoader classloader = new DexClassLoader(libPath, tmpDir.getAbsolutePath(), null, this.getClass().getClassLoader());
final Class<Object> classToLoad = (Class<Object>) classloader.loadClass("org.shoaib.androidccl.MyClass");
final Object myInstance = classToLoad.newInstance();
final Method doSomething = classToLoad.getMethod("doSomething");
doSomething.invoke(myInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
I guess your problem lies on the way you package MyClass in a DEX. How do you do that?
I followed Shlublu's thread and tried myself and succeeded. I don't know how to "package MyClass in a DEX", so I simply build an android apk and put it in the sdcard. And the code worked.
Are you wraping MyClass in a classes.dex? DexClassLoader will only look for classes.dex in your jar/dex. Maybe you can first try what I did (build an signed apk instead of jar, but you don't need to install the apk), and see if it's working. Hope this helps!
Related
I'm running some tests with Roboletric, but I came across a issue that I can't solve.
When I run the test, the following error appears with the "AndroidManifest":
WARNING: No manifest file found at .\AndroidManifest.xml.
Falling back to the Android OS resources only. To remove this warning, annotate
your test class with #Config(manifest=Config.NONE).
No such manifest file: .\AndroidManifest.xml
I've tried these solutions that failed:
#Config (manifest = Config.DEFAULT_MANIFEST_NAME)
#Config(manifest = Config.NONE, constants = BuildConfig.class, sdk = 26)
#Config( constants = BuildConfig.class, manifest="src/main/AndroidManifest.xml", sdk = 26 )
And the other error during execution is:
android.content.res.Resources$NotFoundException: Unable to find
resource ID #0x7f09001b in packages [android, org.robolectric.default]
...
at
com.example.robertoassad.alltestsmerge.MainActivity.onCreate(MainActivity.java:52)
This line that have the error is the following code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Specifically in: setContentView(R.layout.activity_main);
For me I didn't see sense in this issue ...
DETAILS:
The test class is on the folder: app\src\test\java\com\example\robertoassad
The test is:
#RunWith( RobolectricTestRunner.class)
public class Roboletric {
#Test
public void clickingLogin_shouldStartLoginActivity() {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
activity.findViewById(R.id.button2).performClick();
Intent expectedIntent = new Intent(activity, SecondActivity.class);
Intent actual = ShadowApplication.getInstance().getNextStartedActivity();
assertEquals(expectedIntent.getComponent(), actual.getComponent());
}
}
I had a similar problem to the one you face. The post by jongerrish on the Robolectric GitHub Issue about this resolved the problem for me.
The aspect of the answer that worked for me was to add a testOptions block in my module's build.gradle file:
testOptions {
unitTests {
includeAndroidResources = true
}
}
After adding this block my tests were able to run and access String resources.
This problem bug me for some time, and here is my note in my test code.
About manifest location
With Gradle build system, Robolectric looks for AndroidManifest.xml in the following order.
Java resource folder
build/intermediates/manifests/[full or fast-start]/[build-type]
So it is a common mistake to specify the location of AndroidManifest.xml according to source code folder organization (e.g. src/main/AndroidManifest.xml) . The specified AndroidManifest.xml location affect how Robolectric look for merged resources as well. So if some resource is not found in test, it is probably due to incorrect setting of AndroidManifest.xml location.
That said, the Android Gradle plugin merge the AndroidManifest.xml and put the result under the above mentioned intermediates directory. So the content of src/main/AndroidManifest.xml affect the test result.
So if you want to specify manifest option in #Config, just use #Config(manifest=“AndroidManifest.xml”) should probably be fine. If you want to use an alternate AndroidManifest.xml, put it in Java resources folder, and specify #Config according to the relative path in resources folder.
I was also facing same problem while testing my library module from app. Now my Receievers and Service are in my library, so to test those , i had to implement custom Test Class, so Roboelectric can point to my library manifest and not the app manifest :
import android.os.Build;
import org.junit.runners.model.InitializationError;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.res.Fs;
import java.io.File;
import java.io.IOException;
public class RobolectricGradleTestRunner extends RobolectricTestRunner {
private static final String PROJECT_DIR =
"C:/MyProject/";
private static final int MAX_SDK_SUPPORTED_BY_ROBOLECTRIC =
Build.VERSION_CODES.JELLY_BEAN_MR2;
public RobolectricGradleTestRunner(final Class<?> testClass) throws Exception {
super(testClass);
}
private static AndroidManifest getAndroidManifest() {
String manifestPath = PROJECT_DIR+"/src/main/AndroidManifest.xml";
String resPath = PROJECT_DIR+"/src/main/res";
String assetPath = PROJECT_DIR+"/src/main/assets";
System.out.print("manifest path: "+manifestPath);
System.out.print("resPath path: "+resPath);
System.out.print("assetPath path: "+assetPath);
return new AndroidManifest(
Fs.fileFromPath(manifestPath), Fs.fileFromPath(resPath), Fs.fileFromPath(assetPath)) {
#Override public int getTargetSdkVersion() {
return MAX_SDK_SUPPORTED_BY_ROBOLECTRIC;
}
};
}
private static String getProjectDirectory() {
String path = "";
try {
File file = new File("..");
path = file.getCanonicalPath();
path = path + "/app/";
} catch (IOException ex) {}
return path;
}
#Override public AndroidManifest getAppManifest(Config config) {
return getAndroidManifest();
}
}
and use it in your test class like :
#RunWith(RobolectricGradleTestRunner.class)
public class MyClassChangeTest {
}
I am trying to build a library. I have an Android library project and some resources under the res directory that I want to access in the library project's code. The Android docs says:
source code in the library module can access its own resources through its R class
But I just can't figure out how to do this. Because it's a library and intended to be used from other applications, not run itself, I have no Activity, so I can't get a Context to use getResources(). How can I access these resources explicitly without a context?
Without an Activity, it doesn't seem possible to use the R class. If you have a test application within your library, the test application will be able to access R, but not from the lib itself.
Still, you can access the resources by name.
For instance, I have a class like this inside my library,
public class MyContext extends ContextWrapper {
public MyContext(Context base) {
super(base);
}
public int getResourceId(String resourceName) {
try{
// I only access resources inside the "raw" folder
int resId = getResources().getIdentifier(resourceName, "raw", getPackageName());
return resId;
} catch(Exception e){
Log.e("MyContext","getResourceId: " + resourceName);
e.printStackTrace();
}
return 0;
}
}
(See https://stackoverflow.com/a/24972256/1765629 for more information about ContextWrappers)
And the constructor of an object in the library takes that context wrapper,
public class MyLibClass {
public MyLibClass(MyContext context) {
int resId = context.getResourceId("a_file_inside_my_lib_res");
}
}
Then, from the app that uses the lib, I have to pass the context,
public class MyActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
MyLibClass a = new MyLibClass(new MyContext(this));
}
}
MyContext, MyLibClass, and a_file_inside_my_lib_res, they all live inside the library project.
I hope it helps.
I have looked everywhere for an answer and every time I see someone else use the method:
getFilesDir();
But when I try and use that method in any way, especially:
File myFile = new File (getFilesDir();, filename );
Eclipse just says, "Cannot make static reference to non-static method getFilesDir from tye ContextWrapper"
I am trying to use it to get the internal directory to write a file for my application.
Thanks!
That is because in the static method you have not got the object of the class and getFilesDir is not a static method that means it will only be accessible via the object of class Context.
So what you can do is store the reference to the object in a static variable of your class and then use that in your static method.
for example:
static YourContextClass obj;
static void method(){
File myFile = new File (obj.getFilesDir(), filename );
}
also you will have to store the reference to the object in your onCreateMethod()
obj = this;
The best way to achieve this is
static void method(YourContextClass obj){
File myFile = new File (obj.getFilesDir(), filename );
}
I'm gonna talk about what happened to me. I'm developing a log system files, so i created a new class and i wanted to was for all my application and having many instances of this class doing different logs. So i thougth to create protected or public objects of my class on the application class that is similar to a singleton class.
So i had something like that:
public class MyApp extends Application {
protected LogApp logApp = new LogApp(getFilesDir());
When i called it from my main class to get the list files for example:
public class LogApp {
public File dirFiles;
//file parameter can't be null, the app will crash
public LogApp(File file){
dirFiles = file;
}
public File[] getListFiles(){
return dirFiles.listFiles()
}
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
MyApp myApp = (MyApp)getApplicationContext();
File file[] = myApp.logApp.getListFiles();
}
This was getting me an nullPointException error.
The solution in this case was so easy that i felt stuoid and proud at the same time.
I couldn't call to getFilesDir in MyApp's declaration space because at that moment there isn'r a context to get that Dir. The order of execute in an Android App is: Application --> Activity. Like it's said in Manifest file.
Solution? Create my object in onCreate event of my MyApp Class, it looks like this:
public class MyApp extends Application {
protected LogApp logApp;
void onCreate(){
logApp = new LogApp(getFilesDir());
So now i can use it in my main class in the same way i did it because exist an instance of my MainActivity that extends in last instance from Context Class.
Maybe i'm wrong with may explanation and this is not what is really happening in meanings of terminology and how works android. If someone understand better than me why this works i invite you to clear up our doubts.
I hope this will help you.
I am using android-json-rpc library. I added the android-json-rpc-0.3.4.jar library to my build path. Just following some basic tutorial.
Here is my code:
public class MainActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.text_view);
tv.setText(testMethod());
}
private String testMethod() {
// Crashes here on this line
JSONRPCClient client = JSONRPCClient.create("10.1.2.3/json", JSONRPCParams.Versions.VERSION_2);
String string = "";
try {
string = client.callString("cf.test");
} catch (JSONRPCException e) {
Log.i("JSON-RPC Client", e.toString());
}
return string;
}
}
Error:
*AndroidRuntime(1528): java.lang.NoClassDefFoundError: org.alexd.jsonrpc.JSONRPCParams$Versions*
While going around this error, I found out that some guy has a blogpost about the same issue on a Mac Lion (same as mine) but worked fine on Ubuntu. http://www.1771.in/android-jsonrpc-not-working-on-mac.html
Could anyone help me with a workaround for this issue?
Thanks,
Dexter
There are 2 options I have in mind. 1. Have "libs" folder in root of the project, copy lib there, no need to add to the build path. This is a new requirement from android team. 2. Add import of used class.
Just got it compiled and working.
I am trying to do something similar to this stackoverflow posting. What I want to do is to read the definition of an activity or service from the SD card. To avoid manifest permission issues, I create a shell version of this activity in the .apk, but try to replace it with an activity of the same name residing on the SD card at run time. Unfortunately, I am able to load the activity class definition from the SD card using DexClassLoader, but the original class definition is the one that is executed. Is there a way to specify that the new class definition replaces the old one, or any suggestions on avoiding the manifest permission issues without actually providing the needed activity in the package? The code sample:
ClassLoader cl = new DexClassLoader("/sdcard/mypath/My.apk",
getFilesDir().getAbsolutePath(),
null,
MainActivity.class.getClassLoader());
try {
Class<?> c = cl.loadClass("com.android.my.path.to.a.loaded.activity");
Intent i = new Intent(getBaseContext(), c);
startActivity(i);
}
catch (Exception e) {
Intead of launching the com.android.my.path.to.a.loaded.activity specified in /sdcard/mypath/My.apk, it launches the activity statically loaded into the project.
Starting an Activity through DexClassLoader will be very tricky, because you didn't install the APK, and so there is nothing to handle your Intent.
You should have the same activity class in your platform APK and declare it in AndroidManifest.xml. Then, you should change the current ClassLoader to your desired DexClassLoader. As a result, it will start your plugin APK. (Note: "platform APK" refers to the app that is already installed in the phone, whereas "plugin APK" refers to the apk file saved in your SD card.)
The platform's application should look something like this:
public static ClassLoader ORIGINAL_LOADER;
public static ClassLoader CUSTOM_LOADER = null;
#Override
public void onCreate() {
super.onCreate();
try {
Context mBase = new Smith<Context>(this, "mBase").get();
Object mPackageInfo = new Smith<Object>(mBase, "mPackageInfo")
.get();
//get Application classLoader
Smith<ClassLoader> sClassLoader = new Smith<ClassLoader>(
mPackageInfo, "mClassLoader");
ClassLoader mClassLoader = sClassLoader.get();
ORIGINAL_LOADER = mClassLoader;
MyClassLoader cl = new MyClassLoader(mClassLoader);
//chage current classLoader to MyClassLoader
sClassLoader.set(cl);
} catch (Exception e) {
e.printStackTrace();
}
}
class MyClassLoader extends ClassLoader {
public MyClassLoader(ClassLoader parent) {
super(parent);
}
#Override
public Class<?> loadClass(String className)
throws ClassNotFoundException {
if (CUSTOM_LOADER != null) {
try {
Class<?> c = CUSTOM_LOADER.loadClass(className);
if (c != null)
return c;
} catch (ClassNotFoundException e) {
}
}
return super.loadClass(className);
}
}
For more code, you can visit https://github.com/Rookery/AndroidDynamicLoader
I'm reading the Android source code in order to find a more elegant method to implement this feature. If you have any idea, feel free to contact me.