Use VS2019 to develop and debug Android dynamic library

1. Environment preparation

1.1 Install JDK: jdk1.8.0_112

1.2 Install the Android SDK

The SDK can install the specified platforms and ndk-bundle. For compatibility considerations, the older version of android-ndk-r10b is installed separately

1.3 Install VS2019

Install VS2019 and select: Mobile development using C++.

1.4 Connect to the development phone

Connect the development phone with a USB data cable and turn on the USB debugging option of the development phone.

2. Create Android dynamic library project

2.1  Create a new project

Start VS2019, choose to create a new project

2.2 Select project type

Select C++, Android from the drop-down box, select Dynamic Shared Library (Android) from the list, and click Next

2.3 Fill in the project properties

Fill in the project name: MyTestSo, select the creation location, and click Create

2.4  The project is created

3.  Create a dynamic library test project

3.1 Create a new Android project

Right-click on the solution and select the Add -> New Project menu item from the popup menu.

3.2  Select project type

Select C++, Android from the drop-down box, select Native-Activity application (Android) from the list, and click Next

3.3  Fill in the project properties

Fill in the project name: Test, click Create

 

3.4 The project is created

 3.5 ​​​​​​​​Adding references

Right-click on the reference under the Test.NativeActivity project on the left, and select the pop-up menu: Add Reference (R)...

Select MyTestSo, click OK to complete the setup.

  1. 4. Set IDE properties

 Select the VS2019 menu item: Tools -> Options to open the option settings page.

Select Cross-platform –> C++ -> Android, and fill in the development tool directory on the right.

  1. 5.  Set project properties

 The project created by direct compilation will have some errors as shown below:

It is necessary to set the compilation properties of the MyTestSo project and the Test.NativeActivity project. The following uses the MyTestSo project as an example.

Open the MytestSo project properties, select General on the left, modify the platform toolset on the right to: GCC 4.9, and the target API level is android-L.

  1. 6.  Compile and run

    6.1  Modify dynamic library project code

Add the test function test in the MyTestSo.cpp source code file, the code is as follows:

   

void test()
{
	LOGI("Hello World From MyTestSo");
}

6.2 ​​​​​​​​Modify the test project code

Call the test function test in the main.cpp source code file, the code is as follows:

extern "C" void test();

test();

 ​​​​​​6.3  Compile and run the project

Set the Test.Packaging project as the startup project, choose to compile and run the Test.Packaging project, and view the Logcat output log.

The project may crash when running on some low-version Android platforms. For the solution, refer to Section 7.

6.4 Debugging code

Set a breakpoint in main.cpp or MyTestSo.cpp, debug and start the Test.Packaging project, it will be interrupted at the set breakpoint, and the effect is shown in the following figure:

7. Crash problem solved

The project may crash when running on some low-version Android platforms, with the following errors:

The reason is that the libTest.so library depends on the libMyTestSo.so library, and the project does not load the libMyTestSo.so library before loading the libTest.so library at runtime, resulting in a crash. The solution is to customize the project startup class and load the so library in the startup class.

​​​​​​​​7.1  Custom project startup class

Add the directory structure in the project Test.Packaging: src/com/mytest, create the java source file MyTestActivity.java in the mytest directory, the content is as follows:

package com.mytest;

import android.app.NativeActivity;

public class MyTestActivity extends NativeActivity
{
    static
    {
	    System.loadLibrary("MyTestSo"); 
        System.loadLibrary("Test"); 
	}
}

 

7.2  Modify AndroidManifest.xml

Open AndroidManifest.xml to edit the content,

  • The android:hasCode attribute is modified to: true
  • The android:name attribute is changed to: com.mytest.MyTestActivity

7.3 Recompile and run the project

Appendix: Using jar packages

Original link: https://retroscience.net/visual-studio-android-ndk-jar-files.html

Android NDK, JAR files, JNI, and Visual Studio

For those of you who don’t know, I have been a Visual Studio user for a long time now, amoung other forms of IDEs I’ve used Visual Studio the most. Something else I also love to use is the C programming language (I wish VS was more up to date for C but it’s good enough). One of the things you can do is develop for Android using NDK and Visual Studio which works fairly well, even though it is using Ant instead of Gradle, I find that it has suited all of my needs so far. That being said, I’m going to drop some tips here on how to make the development process a bit more friendly to be able to interact via JNI and native code.

Note: I am assuming you’ve setup Visual Studio and installed native android development

Update Ant

If you’ve installed Android native development through Visual Studio, you should have everything you need (NDK & SDK) inside of the C:/Microsoft folder. Something we need to do is tell the Ant build system to use a more modern version of JDK (OpenJDK) for building java code. To do this, open the C:/Microsoft/AndroidSDK/25/tools/ant/build.xml file in a text editor and locate the line that starts with <property name="java.target". Change the value of this to 1.7. Do the same thing for <property name="java.source". At this point you should see something like the following:

<!-- compilation options -->
<property name="java.encoding" value="UTF-8" />
<property name="java.target" value="1.7" />
<property name="java.source" value="1.7" />
<property name="java.compilerargs" value="" />
<property name="java.compiler.classpath" value="" />

project.properties

My project properties file in the .sln looks like the following:

# Project target
target=$(androidapilevel)
# Provide path to the directory where prebuilt external jar files are by setting jar.libs.dir=
jar.libs.dir=libs

AndroidManifest.xlm

Since we are going to be writing .jar files and possibly loading in external libraries at runtime, we will need to setup our project to have our own custom native activity code. Inside the AndroidManifest.xlm file you will need to find the android:hasCode="" value in the <application> tag and set it’s value to true. It should look similar to the following:

<application android:label="@string/app_name" android:hasCode="true">
	<!-- ... -->
</application>

Next we will want to set the <activity android:name="" value to our package and activity name that we will be creating. So if your activity class name is going to be FancyActivity then you should have something similar to the following:

<activity android:name="com.PackageName.FancyActivity" android:label="@string/app_name">
	<!-- ... -->
</activity>

Creating our custom activity

Since our full class path will be com.PackageName.FancyActivity we will need to create a few folders inside of our *.Packaging project in Visual studio. Create a folder path named src/com/PackageName/. Next create a file inside of the PackageName folder named FancyActivity.java. Below is the code you should have inside of FancyActivity.java:

package com.PackageName;
import android.app.NativeActivity;
public class FancyActivity extends NativeActivity
{
	static
	{
		//System.loadLibrary("other_lib");
	}
}

Notice the commented out line System.loadLibrary. You can call this as many times as needed, but all you need to do is replace "other_lib" with the name of your library, like System.loadLibrary("fmod"); or something similar. At this point you should be able to build without any issues.

Pro tip: You should always add System.loadLibrary("ProjectName"); where ProjectName is the name of the .so file that is generated for your NDK project build. This will allow you to call native functions from within your Java code (great for callbacks and the like).

Custom JAR files

Now that we’ve setup our activity to better interact with JNI and load other libraries, we are going to look at how to add our own .jar files and access the types within them from native code.

Building .jar files

Make sure and compile your code with -source 1.7 -target 1.7 so that it is matching Ant’s versions we setup earlier. After you’ve built your .class files, ensure your folder structure is correct as it relates to the package path. If your package path for your class(es) is package com.PackageName; then you should have the .class file within a folder structure com/PackageName/*.class. When you build your .jar file it should be for the whole folder structure.

Including .jar files in project

Now that you have your .jar file, you should create a folder named libs in your *.Packaging project. Place your .jar file into this folder and make sure to right click it and select Include In Project.

Accessing your code inside the .jar file

Lets assume for this part you’ve created a class named Dummy with a function that has the signature void SayHi(string name) which will print out “Hello, %s!” (%s = name input string of function). We will use JNI to access your code and invoke your method. Below is the code we will use to call our function. You can place it directly inside of your void android_main(struct android_app* state) function:

JNIEnv* env = NULL;
const ANativeActivity* activity = state->activity;
(*activity->vm)->AttachCurrentThread(activity->vm, &env, 0);

jobject jobj = activity->clazz;
jclass clazz = (*env)->GetObjectClass(env, jobj);
jmethodID getClassLoader = (*env)->GetMethodID(env, clazz, "getClassLoader", "()Ljava/lang/ClassLoader;");
jobject cls = (*env)->CallObjectMethod(env, jobj, getClassLoader);
jclass classLoader = (*env)->FindClass(env, "java/lang/ClassLoader");
jmethodID findClass = (*env)->GetMethodID(env, classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
jstring strClassName = (*env)->NewStringUTF(env, "com.PackageName.Dummy");
jclass fancyActivityClass = (jclass)((*env)->CallObjectMethod(env, cls, findClass, strClassName));
(*env)->DeleteLocalRef(env, strClassName);
jmethodID sayHi = (*env)->GetStaticMethodID(env, fancyActivityClass, "SayHi", "(Ljava/lang/String;)V");
jstring words = (*env)->NewStringUTF(env, "Brent");
(*env)->CallStaticVoidMethod(env, fancyActivityClass, sayHi, words);
(*env)->DeleteLocalRef(env, words);

​​​​​​​Now those who have had a little exposure with JNI might say “Can’t we just use the (*env)->FindClass method? While this may be true for normal Android built in classes, it is not true for our own custom class. The reasoning is that JNI can only look through what is currently on the stack, and believe it or not, even though our FancyActivity is running our code, it isn’t on the stack so we can’t even find it. So what we need to do is get the current activity, then find a method on it called getClassLoader. Once we have this function, we are free to load any class from anywhere that is loaded, even inside our .jar code.

Hope this helps people who are having trouble. It tooke me a full day to figure out all of this stuff because there isn’t anything straight forward on the internet, I had to dig really deep to find all the pieces to put this together!

Guess you like

Origin blog.csdn.net/ayang1986/article/details/128269064