Repository: TestPlanB/SillyBoy
Branch: master
Commit: 08f4d5773130
Files: 68
Total size: 99.6 KB
Directory structure:
gitextract_mqv2qagf/
├── .gitignore
├── .idea/
│ ├── .gitignore
│ ├── compiler.xml
│ ├── gradle.xml
│ ├── misc.xml
│ └── vcs.xml
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── example/
│ │ └── nativecpp/
│ │ └── ExampleInstrumentedTest.java
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── cpp/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── nativeso1.c
│ │ │ ├── nativeso2.c
│ │ │ └── nativeso3.c
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── nativecpp/
│ │ │ ├── CustomApplication.java
│ │ │ └── MainActivity.java
│ │ └── res/
│ │ ├── drawable/
│ │ │ └── ic_launcher_background.xml
│ │ ├── drawable-v24/
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── layout/
│ │ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26/
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── values/
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── themes.xml
│ │ └── values-night/
│ │ └── themes.xml
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── nativecpp/
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── lib_sillyboy/
│ ├── .gitignore
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── example/
│ │ └── lib_sillyboy/
│ │ └── ExampleInstrumentedTest.java
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── com/
│ │ └── pika/
│ │ └── sillyboy/
│ │ ├── DynamicLoad.kt
│ │ ├── DynamicSo.java
│ │ ├── DynamicSoLauncher.kt
│ │ ├── elf/
│ │ │ ├── Dynamic32Structure.java
│ │ │ ├── Dynamic64Structure.java
│ │ │ ├── Elf.java
│ │ │ ├── Elf32Header.java
│ │ │ ├── Elf64Header.java
│ │ │ ├── ElfParser.java
│ │ │ ├── Program32Header.java
│ │ │ ├── Program64Header.java
│ │ │ ├── Section32Header.java
│ │ │ └── Section64Header.java
│ │ └── pathinsert/
│ │ ├── LoadLibraryUtils.java
│ │ └── ShareReflectUtil.java
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── lib_sillyboy/
│ └── ExampleUnitTest.java
├── lib_sillyplugin/
│ ├── .gitignore
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── example/
│ │ └── lib_sillyplugin/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── groovy/
│ │ │ └── com.plugin.core/
│ │ │ ├── DynamicPlugin.groovy
│ │ │ └── DynamicTransform.groovy
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── plugin/
│ │ │ └── helper/
│ │ │ └── SystemLoadHelper.java
│ │ └── resources/
│ │ └── META-INF/
│ │ └── gradle-plugins/
│ │ └── com.plugins.core.properties
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── lib_sillyplugin/
│ └── ExampleUnitTest.kt
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
================================================
FILE: .idea/.gitignore
================================================
# Default ignored files
/shelf/
/workspace.xml
================================================
FILE: .idea/compiler.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>
================================================
FILE: .idea/gradle.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/lib_sillyboy" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ASMIdeaPluginConfiguration">
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
<groovy codeStyle="LEGACY" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
================================================
FILE: README.md
================================================
# dyso
so dynamically loading (dy so)
android 动态加载so库实现 你所不知道的“船新”版本
## 关于demo
本项目是动态so加载的核心流程,所以并不直接演示so的下载过程,需要demo使用者将test_so_package的三个demo so库放到指定的演示位置(模拟so已经下载好了,可对接自己使用的下载方式),本例子是放在目录
```
data/data/com.example.sillyboy/files/dynamic_so
```

## 设计原理
文档请看这里:https://juejin.cn/post/7107958280097366030
## 使用说明
### 声明
注意:由于这是一个基础库,如果需要在正式项目中使用,还是需要继续封装或者优化的,这里只是动态加载so的实现,在实战中应当配合so版本管理,下载器等实际使用
### 使用步骤
1. 如何删除本地的so库【可选】
```
方式1
packagingOptions下增加
exclude 'lib/arm64-v8a/xxx.so'
exclude 'lib/armeabi-v7a/xxx.so'
```
```
方式2
复制以下task到app的build.gradle
如果需要删除so的过程中进行一些列的定制化操作,可参考如下task,见app目录例子
ext {
deleteSoName = ["libnativecpptwo.so","libnativecpp.so"]
}
// 这个是初始化 -配置 -执行阶段中,配置阶段执行的任务之一,完成afterEvaluate就可以得到所有的tasks,从而可以在里面插入我们定制化的数据
task(dynamicSo) {
}.doLast {
println("dynamicSo insert!!!! ")
//projectDir 在哪个project下面,projectDir就是哪个路径
print(getRootProject().findAll())
def file = new File("${projectDir}/build/intermediates/merged_native_libs/debug/out/lib")
//默认删除所有的so库
if (file.exists()) {
file.listFiles().each {
if (it.isDirectory()) {
it.listFiles().each {
target ->
print("file ${target.name}")
def compareName = target.name
deleteSoName.each {
if (compareName.contains(it)) {
target.delete()
}
}
}
}
}
} else {
print("nil")
}
}
afterEvaluate {
print("dynamicSo task start")
def customer = tasks.findByName("dynamicSo")
def merge = tasks.findByName("mergeDebugNativeLibs")
def strip = tasks.findByName("stripDebugDebugSymbols")
if (merge != null || strip != null) {
customer.mustRunAfter(merge)
strip.dependsOn(customer)
}
}
```
2. 初始化
通过initDynamicSoConfig 方法初始化,第一个参数是context,第二个参数是path,即下载完的so的path,第三个参数是一个回调,在调用动态so加载的时候会先回调,如果返回值为true,才会真正进入到so的加载逻辑,提供给使用者版本校验等一些列活动
```
// 在合适的时候将自定义路径插入so检索路径 需要使用者自己负责在这个路径上有写入权限
DynamicSoLauncher.INSTANCE.initDynamicSoConfig(this, path, s -> {
// 处理一些自定义逻辑
return true;
});
```
3. 调用so加载【有两种使用姿势】
3.1 手动加载
在使用到某个so方法前,需要调用loadSoDynamically(代替System.loadLibrary方法),参数是so的名称或者一个File,该File位于初始化时传入的path之内即可
```
DynamicSoLauncher.INSTANCE.loadSoDynamically(file)
```
3.2 注解加载
在需要采取动态so加载的类上,添加@DynamicLoad注解即可,内部会采用字节码插桩的方式,会把类中所有的System.loadLibrary 替换为DynamicSoLauncher内部的so加载
```
//@DynamicLoad
public class MainActivity extends AppCompatActivity {
```
注意,执行这个步骤需要发布当前的lib_sillyplugin插件

之后在需要的工程build.gradle引入即可,跟一般的插件引入方式一样
```
apply plugin: 'com.plugins.core'
```
ps:这里其实已经可以实现了无痕替代,只是考虑到大多数可能只动态加载少数的so,才提供出来注解的方式去限定范围。
## 项目层级介绍
* **app下是使用例子**
* **lib_sillyboy 是dyso的封装实现**
* **lib_sillyplugin 是dyso的插件,用于替换代码的System.loadLibrary,默认关闭了,可看代码注释打开**
## 环境准备
建议直接用最新的稳定版本Android Studio打开工程。目前项目已适配`Android Studio Arctic Fox | 2020.3.1`
###
## 后续todo
* **maven发布**
* **贡献者名单**
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
// 替换System.loadLibrary的插件
//apply plugin: 'com.plugins.core'
android {
compileSdkVersion 31
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'migrateToAndroidx/migration.xml'
exclude 'META-INF/common.kotlin_module'
}
defaultConfig {
applicationId "com.example.sillyboy"
minSdkVersion 23
targetSdkVersion 29
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
viewBinding {
enabled = true
}
}
ext {
deleteSoName = ["libnative1.so","libnative2.so","libnative3.so"]
}
// 这个是初始化 -配置 -执行阶段中,配置阶段执行的任务之一,完成afterEvaluate就可以得到所有的tasks,从而可以在里面插入我们定制化的数据
task(dynamicSo) {
}.doLast {
println("dynamicSo insert!!!! ")
//projectDir 在哪个project下面,projectDir就是哪个路径
print(getRootProject().findAll())
def file = new File("${projectDir}/build/intermediates/merged_native_libs/debug/out/lib")
//默认删除所有的so库
if (file.exists()) {
file.listFiles().each {
if (it.isDirectory()) {
it.listFiles().each {
target ->
def compareName = target.name
deleteSoName.each {
if (compareName.contains(it)) {
target.delete()
print("delete file ${target.name}")
}
}
}
}
}
} else {
print("nil")
}
}
afterEvaluate {
print("dynamicSo task start")
def customer = tasks.findByName("dynamicSo")
def merge = tasks.findByName("mergeDebugNativeLibs")
def strip = tasks.findByName("stripDebugDebugSymbols")
if (merge != null || strip != null) {
customer.mustRunAfter(merge)
strip.dependsOn(customer)
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.core:core-ktx:1.5.0'
implementation project(path: ':lib_sillyboy')
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/src/androidTest/java/com/example/nativecpp/ExampleInstrumentedTest.java
================================================
package com.example.nativecpp;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.example.nativecpp", appContext.getPackageName());
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.nativecpp">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:name=".CustomApplication"
android:theme="@style/Theme.NativeCpp">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
================================================
FILE: app/src/main/cpp/CMakeLists.txt
================================================
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.18.1)
# Declares and names the project.
project("nativecpp")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library(
native1
SHARED
nativeso1.c
)
add_library(
native2
SHARED
nativeso2.c
)
add_library(
native3
SHARED
nativeso3.c
)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
# 构建多依赖场景
target_link_libraries( # Specifies the target library.
native1
# Links the target library to the log library
# included in the NDK.
${log-lib})
target_link_libraries( # Specifies the target library.
native2
# Links the target library to the log library
# included in the NDK.
${log-lib}
native1)
target_link_libraries( # Specifies the target library.
native3
# Links the target library to the log library
# included in the NDK.
${log-lib}
native2)
================================================
FILE: app/src/main/cpp/nativeso1.c
================================================
#include <jni.h>
#include <android/log.h>
JNIEXPORT void JNICALL
Java_com_example_nativecpp_MainActivity_clickNative1(JNIEnv *env, jobject thiz) {
__android_log_print(ANDROID_LOG_ERROR, "hello", "%s", "native1");
}
================================================
FILE: app/src/main/cpp/nativeso2.c
================================================
#include <jni.h>
#include <android/log.h>
JNIEXPORT void JNICALL
Java_com_example_nativecpp_MainActivity_clickNative2(JNIEnv *env, jobject thiz) {
__android_log_print(ANDROID_LOG_ERROR, "hello", "%s", "native2");
}
================================================
FILE: app/src/main/cpp/nativeso3.c
================================================
#include <jni.h>
#include <android/log.h>
//
// Created by chenhailiang on 2023/4/27.
//
JNIEXPORT void JNICALL
Java_com_example_nativecpp_MainActivity_clickNative3(JNIEnv *env, jobject thiz) {
__android_log_print(ANDROID_LOG_ERROR, "hello", "%s", "native3");
}
================================================
FILE: app/src/main/java/com/example/nativecpp/CustomApplication.java
================================================
package com.example.nativecpp;
import android.app.Application;
import android.util.Log;
import com.pika.sillyboy.DynamicSoLauncher;
import java.io.File;
import kotlin.jvm.functions.Function1;
public class CustomApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// data/data/com.example.sillyboy/files/dynamic_so
String path = getFilesDir().getAbsolutePath() + "/dynamic_so/";
File file = new File(path);
if (!file.exists()) {
file.mkdir();
}
// 在合适的时候将自定义路径插入so检索路径 需要使用者自己负责在这个路径上有写入权限
DynamicSoLauncher.INSTANCE.initDynamicSoConfig(this, path, s -> {
// 处理一些自定义逻辑
return true;
});
}
}
================================================
FILE: app/src/main/java/com/example/nativecpp/MainActivity.java
================================================
package com.example.nativecpp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.example.nativecpp.databinding.ActivityMainBinding;
import com.pika.sillyboy.DynamicLoad;
import com.pika.sillyboy.DynamicSoLauncher;
import java.io.File;
// 把这个注解删掉,System.loadLibrary就会走正常的流程,否则就会走插桩流程
//@DynamicLoad
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String path = getFilesDir().getAbsolutePath() + "/dynamic_so/";
Log.i("hello", "path:" + path);
File file = new File(path + "libnative3.so");
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// 正常的流程System.loadLibrary
binding.loadNormal.setOnClickListener(v -> System.loadLibrary("native3"));
// 调用动态so库
binding.loadDynamic.setOnClickListener(v -> DynamicSoLauncher.INSTANCE.loadSoDynamically(file));
binding.native1.setOnClickListener(v -> clickNative1());
binding.native2.setOnClickListener(v -> clickNative2());
binding.native3.setOnClickListener(v -> clickNative3());
}
public native void clickNative1();
public native void clickNative2();
public native void clickNative3();
}
================================================
FILE: app/src/main/res/drawable/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
================================================
FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:text="pika:see log !!!!!"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/native1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="click native 1!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/native2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="click native 2!"
app:layout_constraintTop_toBottomOf="@id/native1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
<Button
android:id="@+id/native3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="click native 3!"
app:layout_constraintTop_toBottomOf="@id/native2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
<Button
android:id="@+id/load_normal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="system load "
app:layout_constraintTop_toBottomOf="@id/native3"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/load_dynamic" />
<Button
android:id="@+id/load_dynamic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="dynamic load"
app:layout_constraintTop_toBottomOf="@id/native3"
app:layout_constraintLeft_toRightOf="@+id/load_normal"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name">sillyboy</string>
</resources>
================================================
FILE: app/src/main/res/values/themes.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.NativeCpp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
================================================
FILE: app/src/main/res/values-night/themes.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.NativeCpp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
================================================
FILE: app/src/test/java/com/example/nativecpp/ExampleUnitTest.java
================================================
package com.example.nativecpp;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
task clean(type: Delete) {
delete rootProject.buildDir
}
buildscript {
repositories {
maven {url uri('./repositories')}
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.4'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10'
//classpath 'com.pika.plugin:sillyboy:1.0.2'
}
}
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
allprojects {
repositories {
google()
mavenCentral()
}
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Sat Jan 08 09:59:34 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: lib_sillyboy/.gitignore
================================================
/build
================================================
FILE: lib_sillyboy/build.gradle
================================================
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdkVersion 32
defaultConfig {
minSdkVersion 21
targetSdkVersion 32
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
}
================================================
FILE: lib_sillyboy/consumer-rules.pro
================================================
================================================
FILE: lib_sillyboy/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: lib_sillyboy/src/androidTest/java/com/example/lib_sillyboy/ExampleInstrumentedTest.java
================================================
package com.example.lib_sillyboy;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.example.lib_sillyboy.test", appContext.getPackageName());
}
}
================================================
FILE: lib_sillyboy/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.lib_sillyboy">
</manifest>
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/DynamicLoad.kt
================================================
package com.pika.sillyboy
import androidx.annotation.Keep
@Keep
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class DynamicLoad()
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/DynamicSo.java
================================================
package com.pika.sillyboy;
import android.content.Context;
import com.pika.sillyboy.elf.ElfParser;
import com.pika.sillyboy.pathinsert.LoadLibraryUtils;
import java.io.File;
import java.io.IOException;
import java.util.List;
class DynamicSo {
public static void loadSoDynamically(File soFIle, String path) {
try {
ElfParser parser = null;
final List<String> dependencies;
try {
parser = new ElfParser(soFIle);
dependencies = parser.parseNeededDependencies();
} finally {
if (parser != null) {
parser.close();
}
}
//如果nativecpp3->nativecpptwo->nativecpp 则先加载 DynamicSo.loadStaticSo(nativecpptwo),此时nativecpp作为nativecpptwo的直接依赖被加载了
//不能直接加载nativecpp3,导致加载直接依赖nativetwo的时候nativecpp没加载导致错误。 这个可以优化,比如递归
for (final String dependency : dependencies) {
try {
File file = new File(path + dependency);
if (file.exists()) {
//递归查找
loadSoDynamically(file, path);
} else {
// so文件不存在这个文件夹,代表是ndk中的so,如liblog.so,则直接加载
// 把本来lib前缀和.so后缀去掉即可
String dependencySo = dependency.substring(3, dependency.length() - 3);
//在application已经注入了路径DynamicSo.insertPathToNativeSystem(this,file) 所以采用系统的加载就行
System.loadLibrary(dependencySo);
}
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException ignored) {
}
// 先把依赖项加载完,再加载本身
System.loadLibrary(soFIle.getName().substring(3, soFIle.getName().length() - 3));
}
public static void insertPathToNativeSystem(Context context,File file){
try {
LoadLibraryUtils.installNativeLibraryPath(context.getClassLoader(), file);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/DynamicSoLauncher.kt
================================================
package com.pika.sillyboy
import android.content.Context
import android.util.Log
import androidx.annotation.Keep
import java.io.File
@Keep
object DynamicSoLauncher {
private var soPath = ""
private var beforeSoLoadListener: ((soName: String) -> Boolean)? = null
fun initDynamicSoConfig(context: Context, soPath: String,beforeLoadListener: ((soName: String) -> Boolean)?=null) {
this.soPath = soPath
this.beforeSoLoadListener = beforeLoadListener
DynamicSo.insertPathToNativeSystem(context, File(soPath))
}
fun loadSoDynamically(file: File) {
if (soPath.isEmpty()) {
throw RuntimeException("you must call initDynamicSoConfig first. The soPath is empty")
}
if(beforeSoLoadListener?.invoke(file.name) == false){
return
}
DynamicSo.loadSoDynamically(file, soPath)
}
fun loadSoDynamically(fileName: String) {
if (soPath.isEmpty()) {
throw RuntimeException("you must call initDynamicSoConfig first. The soPath is empty")
}
if(beforeSoLoadListener?.invoke(fileName) == false){
return
}
DynamicSo.loadSoDynamically(File(soPath + fileName), soPath)
}
// 插件调用
@JvmStatic
fun loadLibrary(soName: String) {
Log.e("hello", soName)
val wrapSoName = "lib${soName}.so"
loadSoDynamically(wrapSoName)
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Dynamic32Structure.java
================================================
/**
* Copyright 2015 - 2016 KeepSafe Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pika.sillyboy.elf;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class Dynamic32Structure extends Elf.DynamicStructure {
public Dynamic32Structure(final ElfParser parser, final Elf.Header header,
long baseOffset, final int index) throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
baseOffset = baseOffset + (index * 8);
tag = parser.readWord(buffer, baseOffset);
val = parser.readWord(buffer, baseOffset + 0x4);
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Dynamic64Structure.java
================================================
/**
* Copyright 2015 - 2016 KeepSafe Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pika.sillyboy.elf;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class Dynamic64Structure extends Elf.DynamicStructure {
public Dynamic64Structure(final ElfParser parser, final Elf.Header header,
long baseOffset, final int index) throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
baseOffset = baseOffset + (index * 16);
tag = parser.readLong(buffer, baseOffset);
val = parser.readLong(buffer, baseOffset + 0x8);
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf.java
================================================
/**
* Copyright 2015 - 2016 KeepSafe Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pika.sillyboy.elf;
import java.io.IOException;
public interface Elf {
abstract class Header {
public static final int ELFCLASS32 = 1; // 32 Bit ELF
public static final int ELFCLASS64 = 2; // 64 Bit ELF
public static final int ELFDATA2MSB = 2; // Big Endian, 2s complement
public boolean bigEndian;
public int type;
public long phoff;
public long shoff;
public int phentsize;
public int phnum;
public int shentsize;
public int shnum;
public int shstrndx;
abstract public SectionHeader getSectionHeader(int index) throws IOException;
abstract public ProgramHeader getProgramHeader(long index) throws IOException;
abstract public DynamicStructure getDynamicStructure(long baseOffset, int index)
throws IOException;
}
abstract class ProgramHeader {
public static final int PT_LOAD = 1; // Loadable segment
public static final int PT_DYNAMIC = 2; // Dynamic linking information
public long type;
public long offset;
public long vaddr;
public long memsz;
}
abstract class SectionHeader {
public long info;
}
abstract class DynamicStructure {
public static final int DT_NULL = 0; // Marks end of structure list
public static final int DT_NEEDED = 1; // Needed library
public static final int DT_STRTAB = 5; // String table
public long tag;
public long val; // Union with d_ptr
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf32Header.java
================================================
/**
* Copyright 2015 - 2016 KeepSafe Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pika.sillyboy.elf;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class Elf32Header extends Elf.Header {
private final ElfParser parser;
public Elf32Header(final boolean bigEndian, final ElfParser parser) throws IOException {
this.bigEndian = bigEndian;
this.parser = parser;
final ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
type = parser.readHalf(buffer, 0x10);
phoff = parser.readWord(buffer, 0x1C);
shoff = parser.readWord(buffer, 0x20);
phentsize = parser.readHalf(buffer, 0x2A);
phnum = parser.readHalf(buffer, 0x2C);
shentsize = parser.readHalf(buffer, 0x2E);
shnum = parser.readHalf(buffer, 0x30);
shstrndx = parser.readHalf(buffer, 0x32);
}
@Override
public Elf.SectionHeader getSectionHeader(final int index) throws IOException {
return new Section32Header(parser, this, index);
}
@Override
public Elf.ProgramHeader getProgramHeader(final long index) throws IOException {
return new Program32Header(parser, this, index);
}
@Override
public Elf.DynamicStructure getDynamicStructure(final long baseOffset, final int index)
throws IOException {
return new Dynamic32Structure(parser, this, baseOffset, index);
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf64Header.java
================================================
/**
* Copyright 2015 - 2016 KeepSafe Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pika.sillyboy.elf;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class Elf64Header extends Elf.Header {
private final ElfParser parser;
public Elf64Header(final boolean bigEndian, final ElfParser parser) throws IOException {
this.bigEndian = bigEndian;
this.parser = parser;
final ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
type = parser.readHalf(buffer, 0x10);
phoff = parser.readLong(buffer, 0x20);
shoff = parser.readLong(buffer, 0x28);
phentsize = parser.readHalf(buffer, 0x36);
phnum = parser.readHalf(buffer, 0x38);
shentsize = parser.readHalf(buffer, 0x3A);
shnum = parser.readHalf(buffer, 0x3C);
shstrndx = parser.readHalf(buffer, 0x3E);
}
@Override
public Elf.SectionHeader getSectionHeader(final int index) throws IOException {
return new Section64Header(parser, this, index);
}
@Override
public Elf.ProgramHeader getProgramHeader(final long index) throws IOException {
return new Program64Header(parser, this, index);
}
@Override
public Elf.DynamicStructure getDynamicStructure(final long baseOffset, final int index)
throws IOException {
return new Dynamic64Structure(parser, this, baseOffset, index);
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/ElfParser.java
================================================
/**
* Copyright 2015 - 2016 KeepSafe Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pika.sillyboy.elf;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ElfParser implements Closeable, Elf {
private final int MAGIC = 0x464C457F;
private final FileChannel channel;
public ElfParser(File file) throws FileNotFoundException {
if (file == null || !file.exists()) {
throw new IllegalArgumentException("File is null or does not exist");
}
final FileInputStream inputStream = new FileInputStream(file);
this.channel = inputStream.getChannel();
}
public Header parseHeader() throws IOException {
channel.position(0L);
// Read in ELF identification to determine file class and endianness
final ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.order(ByteOrder.LITTLE_ENDIAN);
if (readWord(buffer, 0) != MAGIC) {
throw new IllegalArgumentException("Invalid ELF Magic!");
}
final short fileClass = readByte(buffer, 0x4);
final boolean bigEndian = (readByte(buffer, 0x5) == Header.ELFDATA2MSB);
if (fileClass == Header.ELFCLASS32) {
return new Elf32Header(bigEndian, this);
} else if (fileClass == Header.ELFCLASS64) {
return new Elf64Header(bigEndian, this);
}
throw new IllegalStateException("Invalid class type!");
}
public List<String> parseNeededDependencies() throws IOException {
channel.position(0);
final List<String> dependencies = new ArrayList<String>();
final Header header = parseHeader();
final ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
long numProgramHeaderEntries = header.phnum;
if (numProgramHeaderEntries == 0xFFFF) {
/**
* Extended Numbering
*
* If the real number of program header table entries is larger than
* or equal to PN_XNUM(0xffff), it is set to sh_info field of the
* section header at index 0, and PN_XNUM is set to e_phnum
* field. Otherwise, the section header at index 0 is zero
* initialized, if it exists.
**/
final SectionHeader sectionHeader = header.getSectionHeader(0);
numProgramHeaderEntries = sectionHeader.info;
}
long dynamicSectionOff = 0;
for (long i = 0; i < numProgramHeaderEntries; ++i) {
final ProgramHeader programHeader = header.getProgramHeader(i);
if (programHeader.type == ProgramHeader.PT_DYNAMIC) {
dynamicSectionOff = programHeader.offset;
break;
}
}
if (dynamicSectionOff == 0) {
// No dynamic linking info, nothing to load
return Collections.unmodifiableList(dependencies);
}
int i = 0;
final List<Long> neededOffsets = new ArrayList<Long>();
long vStringTableOff = 0;
DynamicStructure dynStructure;
do {
dynStructure = header.getDynamicStructure(dynamicSectionOff, i);
if (dynStructure.tag == DynamicStructure.DT_NEEDED) {
neededOffsets.add(dynStructure.val);
} else if (dynStructure.tag == DynamicStructure.DT_STRTAB) {
vStringTableOff = dynStructure.val; // d_ptr union
}
++i;
} while (dynStructure.tag != DynamicStructure.DT_NULL);
if (vStringTableOff == 0) {
throw new IllegalStateException("String table offset not found!");
}
// Map to file offset
final long stringTableOff = offsetFromVma(header, numProgramHeaderEntries, vStringTableOff);
for (final Long strOff : neededOffsets) {
dependencies.add(readString(buffer, stringTableOff + strOff));
}
return dependencies;
}
private long offsetFromVma(final Header header, final long numEntries, final long vma)
throws IOException {
for (long i = 0; i < numEntries; ++i) {
final ProgramHeader programHeader = header.getProgramHeader(i);
if (programHeader.type == ProgramHeader.PT_LOAD) {
// Within memsz instead of filesz to be more tolerant
if (programHeader.vaddr <= vma
&& vma <= programHeader.vaddr + programHeader.memsz) {
return vma - programHeader.vaddr + programHeader.offset;
}
}
}
throw new IllegalStateException("Could not map vma to file offset!");
}
@Override
public void close() throws IOException {
this.channel.close();
}
protected String readString(final ByteBuffer buffer, long offset) throws IOException {
final StringBuilder builder = new StringBuilder();
short c;
while ((c = readByte(buffer, offset++)) != 0) {
builder.append((char) c);
}
return builder.toString();
}
protected long readLong(final ByteBuffer buffer, final long offset) throws IOException {
read(buffer, offset, 8);
return buffer.getLong();
}
protected long readWord(final ByteBuffer buffer, final long offset) throws IOException {
read(buffer, offset, 4);
return buffer.getInt() & 0xFFFFFFFFL;
}
protected int readHalf(final ByteBuffer buffer, final long offset) throws IOException {
read(buffer, offset, 2);
return buffer.getShort() & 0xFFFF;
}
protected short readByte(final ByteBuffer buffer, final long offset) throws IOException {
read(buffer, offset, 1);
return (short) (buffer.get() & 0xFF);
}
protected void read(final ByteBuffer buffer, long offset, final int length) throws IOException {
buffer.position(0);
buffer.limit(length);
long bytesRead = 0;
while (bytesRead < length) {
final int read = channel.read(buffer, offset + bytesRead);
if (read == -1) {
throw new EOFException();
}
bytesRead += read;
}
buffer.position(0);
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Program32Header.java
================================================
/**
* Copyright 2015 - 2016 KeepSafe Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pika.sillyboy.elf;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class Program32Header extends Elf.ProgramHeader {
public Program32Header(final ElfParser parser, final Elf.Header header, final long index)
throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
final long baseOffset = header.phoff + (index * header.phentsize);
type = parser.readWord(buffer, baseOffset);
offset = parser.readWord(buffer, baseOffset + 0x4);
vaddr = parser.readWord(buffer, baseOffset + 0x8);
memsz = parser.readWord(buffer, baseOffset + 0x14);
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Program64Header.java
================================================
/**
* Copyright 2015 - 2016 KeepSafe Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pika.sillyboy.elf;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class Program64Header extends Elf.ProgramHeader {
public Program64Header(final ElfParser parser, final Elf.Header header, final long index)
throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
final long baseOffset = header.phoff + (index * header.phentsize);
type = parser.readWord(buffer, baseOffset);
offset = parser.readLong(buffer, baseOffset + 0x8);
vaddr = parser.readLong(buffer, baseOffset + 0x10);
memsz = parser.readLong(buffer, baseOffset + 0x28);
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Section32Header.java
================================================
/**
* Copyright 2015 - 2016 KeepSafe Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pika.sillyboy.elf;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class Section32Header extends Elf.SectionHeader {
public Section32Header(final ElfParser parser, final Elf.Header header, final int index)
throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
info = parser.readWord(buffer, header.shoff + (index * header.shentsize) + 0x1C);
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Section64Header.java
================================================
/**
* Copyright 2015 - 2016 KeepSafe Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pika.sillyboy.elf;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class Section64Header extends Elf.SectionHeader {
public Section64Header(final ElfParser parser, final Elf.Header header, final int index)
throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
info = parser.readWord(buffer, header.shoff + (index * header.shentsize) + 0x2C);
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/pathinsert/LoadLibraryUtils.java
================================================
/*
* Tencent is pleased to support the open source community by making Tinker available.
*
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pika.sillyboy.pathinsert;
import android.os.Build;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class LoadLibraryUtils {
private static final String TAG = "LoadLibrary";
public static void installNativeLibraryPath(ClassLoader classLoader, File folder)
throws Throwable {
if (folder == null || !folder.exists()) {
Log.e(TAG, "installNativeLibraryPath, folder is illegal");
return;
}
// android o sdk_int 26
// for android o preview sdk_int 25
if ((Build.VERSION.SDK_INT == 25 && Build.VERSION.PREVIEW_SDK_INT != 0)
|| Build.VERSION.SDK_INT > 25) {
try {
V25.install(classLoader, folder);
} catch (Throwable throwable) {
// install fail, try to treat it as v23
// some preview N version may go here
Log.e(TAG, String.format("installNativeLibraryPath, v25 fail, sdk: %d, error: %s, try to fallback to V23",
Build.VERSION.SDK_INT, throwable.getMessage()));
V23.install(classLoader, folder);
}
} else if (Build.VERSION.SDK_INT >= 23) {
try {
V23.install(classLoader, folder);
} catch (Throwable throwable) {
// install fail, try to treat it as v14
Log.e(TAG, String.format("installNativeLibraryPath, v23 fail, sdk: %d, error: %s, try to fallback to V14",
Build.VERSION.SDK_INT, throwable.getMessage()));
V14.install(classLoader, folder);
}
} else if (Build.VERSION.SDK_INT >= 14) {
V14.install(classLoader, folder);
} else {
V4.install(classLoader, folder);
}
}
private static final class V4 {
private static void install(ClassLoader classLoader, File folder) throws Throwable {
String addPath = folder.getPath();
Field pathField = ShareReflectUtil.findField(classLoader, "libPath");
final String origLibPaths = (String) pathField.get(classLoader);
final String[] origLibPathSplit = origLibPaths.split(":");
final StringBuilder newLibPaths = new StringBuilder(addPath);
for (String origLibPath : origLibPathSplit) {
if (origLibPath == null || addPath.equals(origLibPath)) {
continue;
}
newLibPaths.append(':').append(origLibPath);
}
pathField.set(classLoader, newLibPaths.toString());
final Field libraryPathElementsFiled = ShareReflectUtil.findField(classLoader, "libraryPathElements");
final List<String> libraryPathElements = (List<String>) libraryPathElementsFiled.get(classLoader);
final Iterator<String> libPathElementIt = libraryPathElements.iterator();
while (libPathElementIt.hasNext()) {
final String libPath = libPathElementIt.next();
if (addPath.equals(libPath)) {
libPathElementIt.remove();
break;
}
}
libraryPathElements.add(0, addPath);
libraryPathElementsFiled.set(classLoader, libraryPathElements);
}
}
private static final class V14 {
private static void install(ClassLoader classLoader, File folder) throws Throwable {
final Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
final Object dexPathList = pathListField.get(classLoader);
final Field nativeLibDirField = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories");
final File[] origNativeLibDirs = (File[]) nativeLibDirField.get(dexPathList);
final List<File> newNativeLibDirList = new ArrayList<>(origNativeLibDirs.length + 1);
newNativeLibDirList.add(folder);
for (File origNativeLibDir : origNativeLibDirs) {
if (!folder.equals(origNativeLibDir)) {
newNativeLibDirList.add(origNativeLibDir);
}
}
nativeLibDirField.set(dexPathList, newNativeLibDirList.toArray(new File[0]));
}
}
private static final class V23 {
private static void install(ClassLoader classLoader, File folder) throws Throwable {
final Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
final Object dexPathList = pathListField.get(classLoader);
final Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories");
List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);
if (origLibDirs == null) {
origLibDirs = new ArrayList<>(2);
}
final Iterator<File> libDirIt = origLibDirs.iterator();
while (libDirIt.hasNext()) {
final File libDir = libDirIt.next();
if (folder.equals(libDir)) {
libDirIt.remove();
break;
}
}
origLibDirs.add(0, folder);
final Field systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories");
List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);
if (origSystemLibDirs == null) {
origSystemLibDirs = new ArrayList<>(2);
}
final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);
newLibDirs.addAll(origLibDirs);
newLibDirs.addAll(origSystemLibDirs);
final Method makeElements = ShareReflectUtil.findMethod(dexPathList,
"makePathElements", List.class, File.class, List.class);
final ArrayList<IOException> suppressedExceptions = new ArrayList<>();
final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs, null, suppressedExceptions);
final Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements");
nativeLibraryPathElements.set(dexPathList, elements);
}
}
private static final class V25 {
private static void install(ClassLoader classLoader, File folder) throws Throwable {
final Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
final Object dexPathList = pathListField.get(classLoader);
final Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories");
List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);
if (origLibDirs == null) {
origLibDirs = new ArrayList<>(2);
}
final Iterator<File> libDirIt = origLibDirs.iterator();
while (libDirIt.hasNext()) {
final File libDir = libDirIt.next();
if (folder.equals(libDir)) {
libDirIt.remove();
break;
}
}
origLibDirs.add(0, folder);
final Field systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories");
List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);
if (origSystemLibDirs == null) {
origSystemLibDirs = new ArrayList<>(2);
}
final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);
newLibDirs.addAll(origLibDirs);
newLibDirs.addAll(origSystemLibDirs);
final Method makeElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class);
final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs);
final Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements");
nativeLibraryPathElements.set(dexPathList, elements);
}
}
}
================================================
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/pathinsert/ShareReflectUtil.java
================================================
package com.pika.sillyboy.pathinsert;
import android.content.Context;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
public class ShareReflectUtil {
/**
* Locates a given field anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the field into.
* @param name field name
* @return a field object
* @throws NoSuchFieldException if the field cannot be located
*/
public static Field findField(Object instance, String name) throws NoSuchFieldException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Field field = clazz.getDeclaredField(name);
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field;
} catch (NoSuchFieldException e) {
// ignore and search next
}
}
throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
}
public static Field findField(Class<?> originClazz, String name) throws NoSuchFieldException {
for (Class<?> clazz = originClazz; clazz != null; clazz = clazz.getSuperclass()) {
try {
Field field = clazz.getDeclaredField(name);
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field;
} catch (NoSuchFieldException e) {
// ignore and search next
}
}
throw new NoSuchFieldException("Field " + name + " not found in " + originClazz);
}
/**
* Locates a given method anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the method into.
* @param name method name
* @param parameterTypes method parameter types
* @return a method object
* @throws NoSuchMethodException if the method cannot be located
*/
public static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Method method = clazz.getDeclaredMethod(name, parameterTypes);
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}
throw new NoSuchMethodException("Method "
+ name
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + instance.getClass());
}
/**
* Locates a given method anywhere in the class inheritance hierarchy.
*
* @param clazz a class to search the method into.
* @param name method name
* @param parameterTypes method parameter types
* @return a method object
* @throws NoSuchMethodException if the method cannot be located
*/
public static Method findMethod(Class<?> clazz, String name, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (; clazz != null; clazz = clazz.getSuperclass()) {
try {
Method method = clazz.getDeclaredMethod(name, parameterTypes);
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}
throw new NoSuchMethodException("Method "
+ name
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + clazz);
}
/**
* Locates a given constructor anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the constructor into.
* @param parameterTypes constructor parameter types
* @return a constructor object
* @throws NoSuchMethodException if the constructor cannot be located
*/
public static Constructor<?> findConstructor(Object instance, Class<?>... parameterTypes)
throws NoSuchMethodException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Constructor<?> ctor = clazz.getDeclaredConstructor(parameterTypes);
if (!ctor.isAccessible()) {
ctor.setAccessible(true);
}
return ctor;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}
throw new NoSuchMethodException("Constructor"
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + instance.getClass());
}
/**
* Replace the value of a field containing a non null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
*
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
* @param extraElements elements to append at the end of the array.
*/
public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);
// NOTE: changed to copy extraElements first, for patch load first
System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
System.arraycopy(original, 0, combined, extraElements.length, original.length);
jlrField.set(instance, combined);
}
/**
* Replace the value of a field containing a non null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
*
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
*/
public static void reduceFieldArray(Object instance, String fieldName, int reduceSize)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
if (reduceSize <= 0) {
return;
}
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
int finalLength = original.length - reduceSize;
if (finalLength <= 0) {
return;
}
Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), finalLength);
System.arraycopy(original, reduceSize, combined, 0, finalLength);
jlrField.set(instance, combined);
}
public static Object getActivityThread(Context context,
Class<?> activityThread) {
try {
if (activityThread == null) {
activityThread = Class.forName("android.app.ActivityThread");
}
Method m = activityThread.getMethod("currentActivityThread");
m.setAccessible(true);
Object currentActivityThread = m.invoke(null);
if (currentActivityThread == null && context != null) {
// In older versions of Android (prior to frameworks/base 66a017b63461a22842)
// the currentActivityThread was built on thread locals, so we'll need to try
// even harder
Field mLoadedApk = context.getClass().getField("mLoadedApk");
mLoadedApk.setAccessible(true);
Object apk = mLoadedApk.get(context);
Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread");
mActivityThreadField.setAccessible(true);
currentActivityThread = mActivityThreadField.get(apk);
}
return currentActivityThread;
} catch (Throwable ignore) {
return null;
}
}
/**
* Handy method for fetching hidden integer constant value in system classes.
*
* @param clazz
* @param fieldName
* @return
*/
public static int getValueOfStaticIntField(Class<?> clazz, String fieldName, int defVal) {
try {
final Field field = findField(clazz, fieldName);
return field.getInt(null);
} catch (Throwable thr) {
return defVal;
}
}
}
================================================
FILE: lib_sillyboy/src/test/java/com/example/lib_sillyboy/ExampleUnitTest.java
================================================
package com.example.lib_sillyboy;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
================================================
FILE: lib_sillyplugin/.gitignore
================================================
/build
================================================
FILE: lib_sillyplugin/build.gradle
================================================
apply plugin: 'groovy'
apply plugin: 'java'
// gradle 7 以上的删除了maven plugin,改为了maven-publish 可以先改为gradle 6
apply plugin: 'maven'
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation 'org.ow2.asm:asm:7.2'
implementation 'org.ow2.asm:asm-commons:7.2'
implementation 'com.android.tools.build:gradle:3.4.1'
implementation 'com.google.guava:guava:27.0.1-android'
}
repositories {
mavenCentral()
}
uploadArchives {
repositories {
mavenDeployer {
//本地Maven
repository(url: uri('../repositories'))
pom.groupId = 'com.pika.plugin'
pom.artifactId = 'sillyboy'
pom.version = '1.0.2'
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
================================================
FILE: lib_sillyplugin/consumer-rules.pro
================================================
================================================
FILE: lib_sillyplugin/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: lib_sillyplugin/src/androidTest/java/com/example/lib_sillyplugin/ExampleInstrumentedTest.kt
================================================
package com.example.lib_sillyplugin
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.lib_sillyplugin.test", appContext.packageName)
}
}
================================================
FILE: lib_sillyplugin/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.lib_sillyplugin">
</manifest>
================================================
FILE: lib_sillyplugin/src/main/groovy/com.plugin.core/DynamicPlugin.groovy
================================================
package com.plugin.core
import com.android.build.gradle.AppExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
class DynamicPlugin implements Plugin<Project> {
Project project
@Override
void apply(Project target) {
project = target
def android = project.extensions.getByType(AppExtension)
android.registerTransform(new DynamicTransform(project), Collections.EMPTY_LIST)
}
}
================================================
FILE: lib_sillyplugin/src/main/groovy/com.plugin.core/DynamicTransform.groovy
================================================
package com.plugin.core
import com.android.annotations.NonNull
import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.ide.common.internal.WaitableExecutor
import com.plugin.helper.SystemLoadHelper
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
import java.nio.file.Files
import java.nio.file.attribute.FileTime
import java.util.concurrent.Callable
import java.util.function.Consumer
import java.util.zip.CRC32
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
class DynamicTransform extends Transform {
private static final FileTime ZERO = FileTime.fromMillis(0)
private static final String FILE_SEP = File.separator
private WaitableExecutor waitableExecutor = WaitableExecutor.useGlobalSharedThreadPool()
private Project project
DynamicTransform() {
}
DynamicTransform(Project outProject) {
this.project = outProject
}
@Override
String getName() {
return "DynamicTransform"
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return false
}
@Override
void transform(@NonNull TransformInvocation transformInvocation) {
println '--------------- DynamicTransform start --------------- ' + transformInvocation.incremental
def startTime = System.currentTimeMillis()
Collection<TransformInput> inputs = transformInvocation.inputs
TransformOutputProvider outputProvider = transformInvocation.outputProvider
if (!transformInvocation.incremental) {
if (outputProvider != null) {
outputProvider.deleteAll()
}
}
boolean flagForCleanDexBuilderFolder = false
inputs.each { TransformInput input ->
input.directoryInputs.each { DirectoryInput directoryInput ->
handleDirectoryInput(directoryInput, outputProvider, transformInvocation.incremental)
}
input.jarInputs.each { JarInput jarInput ->
Status status = jarInput.getStatus()
File dest = outputProvider.getContentLocation(
jarInput.getFile().getAbsolutePath(),
jarInput.getContentTypes(),
jarInput.getScopes(),
Format.JAR)
if (transformInvocation.incremental) {
switch (status) {
case Status.NOTCHANGED:
break
case Status.ADDED:
case Status.CHANGED:
waitableExecutor.execute(new Callable<Object>() {
@Override
Object call() throws Exception {
transformJar(jarInput.file, dest, jarInput)
return null
}
})
break
case Status.REMOVED:
if (dest.exists()) {
FileUtils.forceDelete(dest)
}
break
}
} else {
if (!transformInvocation.incremental && !flagForCleanDexBuilderFolder) {
cleanDexBuilderFolder(dest)
flagForCleanDexBuilderFolder = true
}
transformJar(jarInput.file, dest, jarInput)
}
}
}
def costTime = (System.currentTimeMillis() - startTime) / 1000
waitableExecutor.waitForTasksWithQuickFail(true)
println "DynamicTransform cost:$costTime s"
}
byte[] handleTransform(byte[] bytes) {
def classNode = new ClassNode()
new ClassReader(bytes).accept(classNode, 0)
classNode = handleTransform(classNode)
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)
classNode.accept(cw)
return cw.toByteArray()
}
ClassNode handleTransform(ClassNode klass) {
def targetClass = false
if (klass.visibleAnnotations != null){
klass.visibleAnnotations.forEach(new Consumer<AnnotationNode>() {
@Override
void accept(AnnotationNode annotationNode) {
println annotationNode.desc
if (annotationNode.desc == "Lcom/pika/sillyboy/DynamicLoad;"){
targetClass = true
}
}
})
}
if (targetClass){
SystemLoadHelper.transClass(klass)
}
return klass
}
void handleDirectoryInput(DirectoryInput directoryInput, TransformOutputProvider outputProvider, boolean incremental) {
File dest = outputProvider.getContentLocation(directoryInput.getName(),
directoryInput.getContentTypes(), directoryInput.getScopes(),
Format.DIRECTORY)
FileUtils.forceMkdir(dest)
if (incremental) {
String srcDirPath = directoryInput.getFile().getAbsolutePath()
String destDirPath = dest.getAbsolutePath()
Map<File, Status> fileStatusMap = directoryInput.getChangedFiles()
for (Map.Entry<File, Status> changedFile : fileStatusMap.entrySet()) {
Status status = changedFile.getValue()
File inputFile = changedFile.getKey()
String destFilePath = inputFile.getAbsolutePath().replace(srcDirPath, destDirPath)
File destFile = new File(destFilePath)
switch (status) {
case Status.NOTCHANGED:
break
case Status.REMOVED:
if (destFile.exists()) {
destFile.delete()
}
break
case Status.ADDED:
case Status.CHANGED:
try {
FileUtils.touch(destFile)
} catch (IOException e) {
e.printStackTrace()
com.google.common.io.Files.createParentDirs(destFile)
}
transformSingleFile(inputFile, destFile, srcDirPath)
break
}
}
} else {
if (directoryInput.file.isDirectory()) {
directoryInput.file.eachFileRecurse { File file ->
def name = file.name
if (checkClassFile(name)) {
try {
byte[] code = handleTransform(file.bytes)
FileOutputStream fos = new FileOutputStream(
file.parentFile.absolutePath + File.separator + name)
fos.write(code)
fos.close()
} catch (Exception e) {
println e.message
}
}
}
FileUtils.copyDirectory(directoryInput.file, dest)
}
}
}
private void transformSingleFile(final File inputFile, final File outputFile, final String srcBaseDir) {
weaveSingleClassToFile(inputFile, outputFile, srcBaseDir)
}
final void weaveSingleClassToFile(File inputFile, File outputFile, String inputBaseDir) throws IOException {
if (!inputBaseDir.endsWith(FILE_SEP)) {
inputBaseDir = inputBaseDir + FILE_SEP
}
if (isInjectClass(inputFile.getAbsolutePath().replace(inputBaseDir, "").replace(FILE_SEP, "."))) {
FileUtils.touch(outputFile)
byte[] bytes = handleTransform(inputFile.bytes)
FileOutputStream fos = new FileOutputStream(outputFile)
fos.write(bytes)
fos.close()
inputStream.close()
} else {
if (inputFile.isFile()) {
FileUtils.touch(outputFile)
FileUtils.copyFile(inputFile, outputFile)
}
}
}
static boolean isInjectClass(String fullQualifiedClassName) {
return fullQualifiedClassName.endsWith(".class") && !fullQualifiedClassName.contains("R\$") && !fullQualifiedClassName.contains("R.class") && !fullQualifiedClassName.contains("BuildConfig.class")
}
void transformJar(File inputJar, File outputJar, JarInput jarInput) throws IOException {
ZipFile inputZip = new ZipFile(inputJar)
ZipOutputStream outputZip = new ZipOutputStream(new BufferedOutputStream(
Files.newOutputStream(outputJar.toPath())))
Enumeration<? extends ZipEntry> inEntries = inputZip.entries()
while (inEntries.hasMoreElements()) {
ZipEntry entry = inEntries.nextElement()
InputStream originalFile =
new BufferedInputStream(inputZip.getInputStream(entry))
ZipEntry outEntry = new ZipEntry(entry.getName())
byte[] newEntryContent
if (!checkClassFile(outEntry.getName().replace("/", "."))) {
newEntryContent = IOUtils.toByteArray(originalFile)
} else {
newEntryContent = handleTransform(IOUtils.toByteArray(originalFile))
}
CRC32 crc32 = new CRC32()
crc32.update(newEntryContent)
outEntry.setCrc(crc32.getValue())
outEntry.setMethod(ZipEntry.STORED)
outEntry.setSize(newEntryContent.length)
outEntry.setCompressedSize(newEntryContent.length)
outEntry.setLastAccessTime(ZERO)
outEntry.setLastModifiedTime(ZERO)
outEntry.setCreationTime(ZERO)
outputZip.putNextEntry(outEntry)
outputZip.write(newEntryContent)
outputZip.closeEntry()
}
outputZip.flush()
outputZip.close()
}
private void cleanDexBuilderFolder(File dest) {
try {
String dexBuilderDir = replaceLastPart(dest.getAbsolutePath(), getName(), "dexBuilder")
File file = new File(dexBuilderDir).getParentFile()
if (file.exists() && file.isDirectory()) {
com.android.utils.FileUtils.deleteDirectoryContents(file)
}
} catch (Exception e) {
e.printStackTrace()
}
}
private static String replaceLastPart(String originString, String replacement, String toreplace) {
int start = originString.lastIndexOf(replacement)
StringBuilder builder = new StringBuilder()
builder.append(originString.substring(0, start))
builder.append(toreplace)
builder.append(originString.substring(start + replacement.length()))
return builder.toString()
}
static boolean checkClassFile(String name) {
return (name.endsWith(".class") && !name.startsWith("R\$")
&& name != "R.class" && !name.startsWith("BR\$")
&& name != "BR.class" && name != "BuildConfig.class" && !name.startsWith("kotlinx"))
}
}
================================================
FILE: lib_sillyplugin/src/main/java/com/plugin/helper/SystemLoadHelper.java
================================================
package com.plugin.helper;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodInsnNode;
// power by pika
/*
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
*/
public class SystemLoadHelper {
private static String PACKAGE_PATH = "com/pika/sillyboy";
private static String OWNER = "java/lang/System";
private static String METHOD_NAME = "loadLibrary";
private static String METHOD_DESC = "(Ljava/lang/String;)V";
private static String DYNAMIC_OWNER = "com/pika/sillyboy/DynamicSoLauncher";
public static void transClass(ClassNode classNode) {
if (classNode.name.startsWith(PACKAGE_PATH)){
return;
}
classNode.methods.forEach(methodNode -> methodNode.instructions.forEach(abstractInsnNode -> {
// 如果是InvokeStatic才继续进行
if (abstractInsnNode.getOpcode() == Opcodes.INVOKESTATIC) {
transformInvokeStatic((MethodInsnNode) abstractInsnNode);
}
}));
}
static void transformInvokeStatic(MethodInsnNode methodInsnNode) {
// (Ljava/lang/String;)V loadLibrary java/lang/System
if (OWNER.equals(methodInsnNode.owner) && METHOD_NAME.equals(methodInsnNode.name) && METHOD_DESC.equals(methodInsnNode.desc)) {
methodInsnNode.owner = DYNAMIC_OWNER;
}
}
}
================================================
FILE: lib_sillyplugin/src/main/resources/META-INF/gradle-plugins/com.plugins.core.properties
================================================
implementation-class=com.plugin.core.DynamicPlugin
================================================
FILE: lib_sillyplugin/src/test/java/com/example/lib_sillyplugin/ExampleUnitTest.kt
================================================
package com.example.lib_sillyplugin
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
================================================
FILE: settings.gradle
================================================
rootProject.name = "sillyboy"
include ':app'
include ':lib_sillyboy'
include ':lib_sillyplugin'
gitextract_mqv2qagf/ ├── .gitignore ├── .idea/ │ ├── .gitignore │ ├── compiler.xml │ ├── gradle.xml │ ├── misc.xml │ └── vcs.xml ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ └── nativecpp/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── cpp/ │ │ │ ├── CMakeLists.txt │ │ │ ├── nativeso1.c │ │ │ ├── nativeso2.c │ │ │ └── nativeso3.c │ │ ├── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── nativecpp/ │ │ │ ├── CustomApplication.java │ │ │ └── MainActivity.java │ │ └── res/ │ │ ├── drawable/ │ │ │ └── ic_launcher_background.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ └── values-night/ │ │ └── themes.xml │ └── test/ │ └── java/ │ └── com/ │ └── example/ │ └── nativecpp/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── lib_sillyboy/ │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ └── lib_sillyboy/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── pika/ │ │ └── sillyboy/ │ │ ├── DynamicLoad.kt │ │ ├── DynamicSo.java │ │ ├── DynamicSoLauncher.kt │ │ ├── elf/ │ │ │ ├── Dynamic32Structure.java │ │ │ ├── Dynamic64Structure.java │ │ │ ├── Elf.java │ │ │ ├── Elf32Header.java │ │ │ ├── Elf64Header.java │ │ │ ├── ElfParser.java │ │ │ ├── Program32Header.java │ │ │ ├── Program64Header.java │ │ │ ├── Section32Header.java │ │ │ └── Section64Header.java │ │ └── pathinsert/ │ │ ├── LoadLibraryUtils.java │ │ └── ShareReflectUtil.java │ └── test/ │ └── java/ │ └── com/ │ └── example/ │ └── lib_sillyboy/ │ └── ExampleUnitTest.java ├── lib_sillyplugin/ │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ └── lib_sillyplugin/ │ │ └── ExampleInstrumentedTest.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── groovy/ │ │ │ └── com.plugin.core/ │ │ │ ├── DynamicPlugin.groovy │ │ │ └── DynamicTransform.groovy │ │ ├── java/ │ │ │ └── com/ │ │ │ └── plugin/ │ │ │ └── helper/ │ │ │ └── SystemLoadHelper.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── gradle-plugins/ │ │ └── com.plugins.core.properties │ └── test/ │ └── java/ │ └── com/ │ └── example/ │ └── lib_sillyplugin/ │ └── ExampleUnitTest.kt └── settings.gradle
SYMBOL INDEX (86 symbols across 23 files)
FILE: app/src/androidTest/java/com/example/nativecpp/ExampleInstrumentedTest.java
class ExampleInstrumentedTest (line 18) | @RunWith(AndroidJUnit4.class)
method useAppContext (line 20) | @Test
FILE: app/src/main/cpp/nativeso1.c
function JNICALL (line 4) | JNICALL
FILE: app/src/main/cpp/nativeso2.c
function JNICALL (line 6) | JNICALL
FILE: app/src/main/cpp/nativeso3.c
function JNICALL (line 9) | JNICALL
FILE: app/src/main/java/com/example/nativecpp/CustomApplication.java
class CustomApplication (line 13) | public class CustomApplication extends Application {
method onCreate (line 14) | @Override
FILE: app/src/main/java/com/example/nativecpp/MainActivity.java
class MainActivity (line 17) | public class MainActivity extends AppCompatActivity {
method onCreate (line 22) | @Override
method clickNative1 (line 47) | public native void clickNative1();
method clickNative2 (line 48) | public native void clickNative2();
method clickNative3 (line 49) | public native void clickNative3();
FILE: app/src/test/java/com/example/nativecpp/ExampleUnitTest.java
class ExampleUnitTest (line 12) | public class ExampleUnitTest {
method addition_isCorrect (line 13) | @Test
FILE: lib_sillyboy/src/androidTest/java/com/example/lib_sillyboy/ExampleInstrumentedTest.java
class ExampleInstrumentedTest (line 18) | @RunWith(AndroidJUnit4.class)
method useAppContext (line 20) | @Test
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/DynamicSo.java
class DynamicSo (line 13) | class DynamicSo {
method loadSoDynamically (line 14) | public static void loadSoDynamically(File soFIle, String path) {
method insertPathToNativeSystem (line 55) | public static void insertPathToNativeSystem(Context context,File file){
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Dynamic32Structure.java
class Dynamic32Structure (line 22) | public class Dynamic32Structure extends Elf.DynamicStructure {
method Dynamic32Structure (line 23) | public Dynamic32Structure(final ElfParser parser, final Elf.Header hea...
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Dynamic64Structure.java
class Dynamic64Structure (line 23) | public class Dynamic64Structure extends Elf.DynamicStructure {
method Dynamic64Structure (line 24) | public Dynamic64Structure(final ElfParser parser, final Elf.Header hea...
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf.java
type Elf (line 20) | public interface Elf {
class Header (line 21) | abstract class Header {
method getSectionHeader (line 36) | abstract public SectionHeader getSectionHeader(int index) throws IOE...
method getProgramHeader (line 37) | abstract public ProgramHeader getProgramHeader(long index) throws IO...
method getDynamicStructure (line 38) | abstract public DynamicStructure getDynamicStructure(long baseOffset...
class ProgramHeader (line 42) | abstract class ProgramHeader {
class SectionHeader (line 52) | abstract class SectionHeader {
class DynamicStructure (line 56) | abstract class DynamicStructure {
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf32Header.java
class Elf32Header (line 24) | public class Elf32Header extends Elf.Header {
method Elf32Header (line 27) | public Elf32Header(final boolean bigEndian, final ElfParser parser) th...
method getSectionHeader (line 44) | @Override
method getProgramHeader (line 49) | @Override
method getDynamicStructure (line 54) | @Override
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf64Header.java
class Elf64Header (line 23) | public class Elf64Header extends Elf.Header {
method Elf64Header (line 26) | public Elf64Header(final boolean bigEndian, final ElfParser parser) th...
method getSectionHeader (line 43) | @Override
method getProgramHeader (line 48) | @Override
method getDynamicStructure (line 53) | @Override
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/ElfParser.java
class ElfParser (line 33) | public class ElfParser implements Closeable, Elf {
method ElfParser (line 37) | public ElfParser(File file) throws FileNotFoundException {
method parseHeader (line 46) | public Header parseHeader() throws IOException {
method parseNeededDependencies (line 67) | public List<String> parseNeededDependencies() throws IOException {
method offsetFromVma (line 130) | private long offsetFromVma(final Header header, final long numEntries,...
method close (line 146) | @Override
method readString (line 151) | protected String readString(final ByteBuffer buffer, long offset) thro...
method readLong (line 161) | protected long readLong(final ByteBuffer buffer, final long offset) th...
method readWord (line 166) | protected long readWord(final ByteBuffer buffer, final long offset) th...
method readHalf (line 171) | protected int readHalf(final ByteBuffer buffer, final long offset) thr...
method readByte (line 176) | protected short readByte(final ByteBuffer buffer, final long offset) t...
method read (line 181) | protected void read(final ByteBuffer buffer, long offset, final int le...
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Program32Header.java
class Program32Header (line 23) | public class Program32Header extends Elf.ProgramHeader {
method Program32Header (line 24) | public Program32Header(final ElfParser parser, final Elf.Header header...
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Program64Header.java
class Program64Header (line 23) | public class Program64Header extends Elf.ProgramHeader {
method Program64Header (line 24) | public Program64Header(final ElfParser parser, final Elf.Header header...
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Section32Header.java
class Section32Header (line 23) | public class Section32Header extends Elf.SectionHeader {
method Section32Header (line 24) | public Section32Header(final ElfParser parser, final Elf.Header header...
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Section64Header.java
class Section64Header (line 23) | public class Section64Header extends Elf.SectionHeader {
method Section64Header (line 24) | public Section64Header(final ElfParser parser, final Elf.Header header...
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/pathinsert/LoadLibraryUtils.java
class LoadLibraryUtils (line 32) | public class LoadLibraryUtils {
method installNativeLibraryPath (line 35) | public static void installNativeLibraryPath(ClassLoader classLoader, F...
class V4 (line 71) | private static final class V4 {
method install (line 72) | private static void install(ClassLoader classLoader, File folder) t...
class V14 (line 102) | private static final class V14 {
method install (line 103) | private static void install(ClassLoader classLoader, File folder) t...
class V23 (line 121) | private static final class V23 {
method install (line 122) | private static void install(ClassLoader classLoader, File folder) t...
class V25 (line 163) | private static final class V25 {
method install (line 164) | private static void install(ClassLoader classLoader, File folder) t...
FILE: lib_sillyboy/src/main/java/com/pika/sillyboy/pathinsert/ShareReflectUtil.java
class ShareReflectUtil (line 11) | public class ShareReflectUtil {
method findField (line 21) | public static Field findField(Object instance, String name) throws NoS...
method findField (line 39) | public static Field findField(Class<?> originClazz, String name) throw...
method findMethod (line 66) | public static Method findMethod(Object instance, String name, Class<?>...
method findMethod (line 98) | public static Method findMethod(Class<?> clazz, String name, Class<?>....
method findConstructor (line 129) | public static Constructor<?> findConstructor(Object instance, Class<?>...
method expandFieldArray (line 159) | public static void expandFieldArray(Object instance, String fieldName,...
method reduceFieldArray (line 181) | public static void reduceFieldArray(Object instance, String fieldName,...
method getActivityThread (line 203) | public static Object getActivityThread(Context context,
method getValueOfStaticIntField (line 236) | public static int getValueOfStaticIntField(Class<?> clazz, String fiel...
FILE: lib_sillyboy/src/test/java/com/example/lib_sillyboy/ExampleUnitTest.java
class ExampleUnitTest (line 12) | public class ExampleUnitTest {
method addition_isCorrect (line 13) | @Test
FILE: lib_sillyplugin/src/main/java/com/plugin/helper/SystemLoadHelper.java
class SystemLoadHelper (line 14) | public class SystemLoadHelper {
method transClass (line 20) | public static void transClass(ClassNode classNode) {
method transformInvokeStatic (line 32) | static void transformInvokeStatic(MethodInsnNode methodInsnNode) {
Condensed preview — 68 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (114K chars).
[
{
"path": ".gitignore",
"chars": 225,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor."
},
{
"path": ".idea/.gitignore",
"chars": 47,
"preview": "# Default ignored files\n/shelf/\n/workspace.xml\n"
},
{
"path": ".idea/compiler.xml",
"chars": 169,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"CompilerConfiguration\">\n <bytecodeTar"
},
{
"path": ".idea/gradle.xml",
"chars": 738,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"GradleMigrationSettings\" migrationVersio"
},
{
"path": ".idea/misc.xml",
"chars": 578,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ASMIdeaPluginConfiguration\">\n <asm sk"
},
{
"path": ".idea/vcs.xml",
"chars": 180,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"VcsDirectoryMappings\">\n <mapping dire"
},
{
"path": "README.md",
"chars": 3283,
"preview": "# dyso\nso dynamically loading (dy so)\nandroid 动态加载so库实现 你所不知道的“船新”版本\n## 关于demo\n本项目是动态so加载的核心流程,所以并不直接演示so的下载过程,需要demo使用者"
},
{
"path": "app/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "app/build.gradle",
"chars": 2578,
"preview": "\napply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\n// 替换System.loadLibrary的插件\n//apply plugin: 'com."
},
{
"path": "app/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "app/src/androidTest/java/com/example/nativecpp/ExampleInstrumentedTest.java",
"chars": 756,
"preview": "package com.example.nativecpp;\n\nimport android.content.Context;\n\nimport androidx.test.platform.app.InstrumentationRegist"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 971,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "app/src/main/cpp/CMakeLists.txt",
"chars": 2032,
"preview": "# For more information about using CMake with Android Studio, read the\n# documentation: https://d.android.com/studio/pro"
},
{
"path": "app/src/main/cpp/nativeso1.c",
"chars": 219,
"preview": "#include <jni.h>\n#include <android/log.h>\n\nJNIEXPORT void JNICALL\nJava_com_example_nativecpp_MainActivity_clickNative1(J"
},
{
"path": "app/src/main/cpp/nativeso2.c",
"chars": 221,
"preview": "#include <jni.h>\n#include <android/log.h>\n\n\n\nJNIEXPORT void JNICALL\nJava_com_example_nativecpp_MainActivity_clickNative2"
},
{
"path": "app/src/main/cpp/nativeso3.c",
"chars": 268,
"preview": "#include <jni.h>\n#include <android/log.h>\n\n//\n// Created by chenhailiang on 2023/4/27.\n//\n\n\nJNIEXPORT void JNICALL\nJava_"
},
{
"path": "app/src/main/java/com/example/nativecpp/CustomApplication.java",
"chars": 754,
"preview": "package com.example.nativecpp;\n\nimport android.app.Application;\nimport android.util.Log;\n\n\nimport com.pika.sillyboy.Dyna"
},
{
"path": "app/src/main/java/com/example/nativecpp/MainActivity.java",
"chars": 1427,
"preview": "package com.example.nativecpp;\n\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport android.os.Bundle;\nimport andro"
},
{
"path": "app/src/main/res/drawable/ic_launcher_background.xml",
"chars": 5606,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
"chars": 1702,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:aapt=\"http://schemas.android.com/aapt\"\n "
},
{
"path": "app/src/main/res/layout/activity_main.xml",
"chars": 2562,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
},
{
"path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
"chars": 272,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
"chars": 272,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 378,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"purple_200\">#FFBB86FC</color>\n <color name=\"purpl"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 70,
"preview": "<resources>\n <string name=\"app_name\">sillyboy</string>\n</resources>"
},
{
"path": "app/src/main/res/values/themes.xml",
"chars": 831,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <!-- Base application theme. -->\n <style name=\"Theme.N"
},
{
"path": "app/src/main/res/values-night/themes.xml",
"chars": 831,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <!-- Base application theme. -->\n <style name=\"Theme.N"
},
{
"path": "app/src/test/java/com/example/nativecpp/ExampleUnitTest.java",
"chars": 382,
"preview": "package com.example.nativecpp;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit tes"
},
{
"path": "build.gradle",
"chars": 705,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\ntask clean(type: De"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 230,
"preview": "#Sat Jan 08 09:59:34 CST 2022\ndistributionBase=GRADLE_USER_HOME\ndistributionUrl=https\\://services.gradle.org/distributio"
},
{
"path": "gradle.properties",
"chars": 1358,
"preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
},
{
"path": "gradlew",
"chars": 5766,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 2763,
"preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
},
{
"path": "lib_sillyboy/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "lib_sillyboy/build.gradle",
"chars": 762,
"preview": "plugins {\n id 'com.android.library'\n id 'org.jetbrains.kotlin.android'\n}\n\nandroid {\n compileSdkVersion 32\n\n "
},
{
"path": "lib_sillyboy/consumer-rules.pro",
"chars": 0,
"preview": ""
},
{
"path": "lib_sillyboy/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "lib_sillyboy/src/androidTest/java/com/example/lib_sillyboy/ExampleInstrumentedTest.java",
"chars": 767,
"preview": "package com.example.lib_sillyboy;\n\nimport android.content.Context;\n\nimport androidx.test.platform.app.InstrumentationReg"
},
{
"path": "lib_sillyboy/src/main/AndroidManifest.xml",
"chars": 156,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"com"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/DynamicLoad.kt",
"chars": 170,
"preview": "package com.pika.sillyboy\n\nimport androidx.annotation.Keep\n\n\n@Keep\n@Target(AnnotationTarget.CLASS)\n@Retention(Annotation"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/DynamicSo.java",
"chars": 2125,
"preview": "package com.pika.sillyboy;\n\nimport android.content.Context;\n\nimport com.pika.sillyboy.elf.ElfParser;\nimport com.pika.sil"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/DynamicSoLauncher.kt",
"chars": 1416,
"preview": "package com.pika.sillyboy\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.annotation.Keep\nimport"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Dynamic32Structure.java",
"chars": 1297,
"preview": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Dynamic64Structure.java",
"chars": 1300,
"preview": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf.java",
"chars": 2234,
"preview": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf32Header.java",
"chars": 2109,
"preview": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf64Header.java",
"chars": 2107,
"preview": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/ElfParser.java",
"chars": 7322,
"preview": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Program32Header.java",
"chars": 1409,
"preview": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Program64Header.java",
"chars": 1410,
"preview": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Section32Header.java",
"chars": 1188,
"preview": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Section64Header.java",
"chars": 1188,
"preview": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/pathinsert/LoadLibraryUtils.java",
"chars": 9207,
"preview": "/*\n * Tencent is pleased to support the open source community by making Tinker available.\n *\n * Copyright (C) 2016 THL A"
},
{
"path": "lib_sillyboy/src/main/java/com/pika/sillyboy/pathinsert/ShareReflectUtil.java",
"chars": 9104,
"preview": "package com.pika.sillyboy.pathinsert;\n\nimport android.content.Context;\n\nimport java.lang.reflect.Array;\nimport java.lang"
},
{
"path": "lib_sillyboy/src/test/java/com/example/lib_sillyboy/ExampleUnitTest.java",
"chars": 385,
"preview": "package com.example.lib_sillyboy;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit "
},
{
"path": "lib_sillyplugin/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "lib_sillyplugin/build.gradle",
"chars": 846,
"preview": "apply plugin: 'groovy'\napply plugin: 'java'\n// gradle 7 以上的删除了maven plugin,改为了maven-publish 可以先改为gradle 6\napply plugin: "
},
{
"path": "lib_sillyplugin/consumer-rules.pro",
"chars": 0,
"preview": ""
},
{
"path": "lib_sillyplugin/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "lib_sillyplugin/src/androidTest/java/com/example/lib_sillyplugin/ExampleInstrumentedTest.kt",
"chars": 686,
"preview": "package com.example.lib_sillyplugin\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext."
},
{
"path": "lib_sillyplugin/src/main/AndroidManifest.xml",
"chars": 163,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "lib_sillyplugin/src/main/groovy/com.plugin.core/DynamicPlugin.groovy",
"chars": 436,
"preview": "package com.plugin.core\n\n\nimport com.android.build.gradle.AppExtension\nimport org.gradle.api.Plugin\nimport org.gradle.ap"
},
{
"path": "lib_sillyplugin/src/main/groovy/com.plugin.core/DynamicTransform.groovy",
"chars": 11756,
"preview": "package com.plugin.core\n\n\nimport com.android.annotations.NonNull\nimport com.android.build.api.transform.*\nimport com.and"
},
{
"path": "lib_sillyplugin/src/main/java/com/plugin/helper/SystemLoadHelper.java",
"chars": 1464,
"preview": "package com.plugin.helper;\n\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.object"
},
{
"path": "lib_sillyplugin/src/main/resources/META-INF/gradle-plugins/com.plugins.core.properties",
"chars": 50,
"preview": "implementation-class=com.plugin.core.DynamicPlugin"
},
{
"path": "lib_sillyplugin/src/test/java/com/example/lib_sillyplugin/ExampleUnitTest.kt",
"chars": 351,
"preview": "package com.example.lib_sillyplugin\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, w"
},
{
"path": "settings.gradle",
"chars": 97,
"preview": "\nrootProject.name = \"sillyboy\"\ninclude ':app'\ninclude ':lib_sillyboy'\ninclude ':lib_sillyplugin'\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the TestPlanB/SillyBoy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 68 files (99.6 KB), approximately 26.5k tokens, and a symbol index with 86 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.