diff --git a/app/build.gradle b/app/build.gradle index 5be59f995..bb4204913 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -75,6 +75,99 @@ android { exclude 'lib/arm64-v8a/libfuai.so' } + //声网 rtc + exclude 'lib/arm64-v8a/libagora_ai_echo_cancellation_extension.so' + exclude 'lib/arm64-v8a/libagora_ai_noise_suppression_extension.so' + exclude 'lib/arm64-v8a/libagora_audio_beauty_extension.so' + exclude 'lib/arm64-v8a/libagora_clear_vision_extension.so' + exclude 'lib/arm64-v8a/libagora_content_inspect_extension.so' + exclude 'lib/arm64-v8a/libagora_face_capture_extension.so' + exclude 'lib/arm64-v8a/libagora_face_detection_extension.so' + exclude 'lib/arm64-v8a/libagora_lip_sync_extension.so' + exclude 'lib/arm64-v8a/libagora_screen_capture_extension.so' + exclude 'lib/arm64-v8a/libagora_segmentation_extension.so' + exclude 'lib/arm64-v8a/libagora_spatial_audio_extension.so' + exclude 'lib/arm64-v8a/libagora_video_av1_decoder_extension.so' + exclude 'lib/arm64-v8a/libagora_video_decoder_extension.so' + exclude 'lib/arm64-v8a/libagora_video_encoder_extension.so' + exclude 'lib/arm64-v8a/libagora_video_quality_analyzer_extension.so' + exclude 'lib/arm64-v8a/libagora-core.so' + exclude 'lib/arm64-v8a/libagora-fdkaac.so' + exclude 'lib/arm64-v8a/libagora-ffmpeg.so' + exclude 'lib/arm64-v8a/libagora-rtc-sdk.so' + exclude 'lib/arm64-v8a/libagora-soundtouch.so' + exclude 'lib/arm64-v8a/libvideo_dec.so' + exclude 'lib/arm64-v8a/libvideo_enc.so' + + exclude 'lib/armeabi-v7a/libagora_ai_echo_cancellation_extension.so' + exclude 'lib/armeabi-v7a/libagora_ai_noise_suppression_extension.so' + exclude 'lib/armeabi-v7a/libagora_audio_beauty_extension.so' + exclude 'lib/armeabi-v7a/libagora_clear_vision_extension.so' + exclude 'lib/armeabi-v7a/libagora_content_inspect_extension.so' + exclude 'lib/armeabi-v7a/libagora_face_capture_extension.so' + exclude 'lib/armeabi-v7a/libagora_face_detection_extension.so' + exclude 'lib/armeabi-v7a/libagora_lip_sync_extension.so' + exclude 'lib/armeabi-v7a/libagora_screen_capture_extension.so' + exclude 'lib/armeabi-v7a/libagora_segmentation_extension.so' + exclude 'lib/armeabi-v7a/libagora_spatial_audio_extension.so' + exclude 'lib/armeabi-v7a/libagora_video_av1_decoder_extension.so' + exclude 'lib/armeabi-v7a/libagora_video_decoder_extension.so' + exclude 'lib/armeabi-v7a/libagora_video_encoder_extension.so' + exclude 'lib/armeabi-v7a/libagora_video_quality_analyzer_extension.so' + exclude 'lib/armeabi-v7a/libagora-core.so' + exclude 'lib/armeabi-v7a/libagora-fdkaac.so' + exclude 'lib/armeabi-v7a/libagora-ffmpeg.so' + exclude 'lib/armeabi-v7a/libagora-rtc-sdk.so' + exclude 'lib/armeabi-v7a/libagora-soundtouch.so' + exclude 'lib/armeabi-v7a/libvideo_dec.so' + exclude 'lib/armeabi-v7a/libvideo_enc.so' + + exclude 'lib/x86/libagora_ai_echo_cancellation_extension.so' + exclude 'lib/x86/libagora_ai_noise_suppression_extension.so' + exclude 'lib/x86/libagora_audio_beauty_extension.so' + exclude 'lib/x86/libagora_clear_vision_extension.so' + exclude 'lib/x86/libagora_content_inspect_extension.so' + exclude 'lib/x86/libagora_face_capture_extension.so' + exclude 'lib/x86/libagora_face_detection_extension.so' + exclude 'lib/x86/libagora_lip_sync_extension.so' + exclude 'lib/x86/libagora_screen_capture_extension.so' + exclude 'lib/x86/libagora_segmentation_extension.so' + exclude 'lib/x86/libagora_spatial_audio_extension.so' + exclude 'lib/x86/libagora_video_av1_decoder_extension.so' + exclude 'lib/x86/libagora_video_decoder_extension.so' + exclude 'lib/x86/libagora_video_encoder_extension.so' + exclude 'lib/x86/libagora_video_quality_analyzer_extension.so' + exclude 'lib/x86/libagora-core.so' + exclude 'lib/x86/libagora-fdkaac.so' + exclude 'lib/x86/libagora-ffmpeg.so' + exclude 'lib/x86/libagora-rtc-sdk.so' + exclude 'lib/x86/libagora-soundtouch.so' + exclude 'lib/x86/libvideo_dec.so' + exclude 'lib/x86/libvideo_enc.so' + + exclude 'lib/x86_64/libagora_ai_echo_cancellation_extension.so' + exclude 'lib/x86_64/libagora_ai_noise_suppression_extension.so' + exclude 'lib/x86_64/libagora_audio_beauty_extension.so' + exclude 'lib/x86_64/libagora_clear_vision_extension.so' + exclude 'lib/x86_64/libagora_content_inspect_extension.so' + exclude 'lib/x86_64/libagora_face_capture_extension.so' + exclude 'lib/x86_64/libagora_face_detection_extension.so' + exclude 'lib/x86_64/libagora_lip_sync_extension.so' + exclude 'lib/x86_64/libagora_screen_capture_extension.so' + exclude 'lib/x86_64/libagora_segmentation_extension.so' + exclude 'lib/x86_64/libagora_spatial_audio_extension.so' + exclude 'lib/x86_64/libagora_video_av1_decoder_extension.so' + exclude 'lib/x86_64/libagora_video_decoder_extension.so' + exclude 'lib/x86_64/libagora_video_encoder_extension.so' + exclude 'lib/x86_64/libagora_video_quality_analyzer_extension.so' + exclude 'lib/x86_64/libagora-core.so' + exclude 'lib/x86_64/libagora-fdkaac.so' + exclude 'lib/x86_64/libagora-ffmpeg.so' + exclude 'lib/x86_64/libagora-rtc-sdk.so' + exclude 'lib/x86_64/libagora-soundtouch.so' + exclude 'lib/x86_64/libvideo_dec.so' + exclude 'lib/x86_64/libvideo_enc.so' + } compileOptions { sourceCompatibility JavaVersion.VERSION_18 @@ -288,7 +381,6 @@ android { } } - repositories { flatDir { dirs 'libs', '../libs' @@ -306,6 +398,7 @@ dependencies { api project(':main') //短视频 api project(':video') + implementation project(path: ':lib_so') annotationProcessor rootProject.ext.dependencies["arouter-compiler"] diff --git a/app/src/main/java/com/shayu/phonelive/AppContext.java b/app/src/main/java/com/shayu/phonelive/AppContext.java index b08d81f4a..4ebddbfce 100644 --- a/app/src/main/java/com/shayu/phonelive/AppContext.java +++ b/app/src/main/java/com/shayu/phonelive/AppContext.java @@ -25,6 +25,7 @@ import com.blankj.utilcode.util.Utils; import com.facebook.appevents.AppEventsLogger; import com.fm.openinstall.OpenInstall; import com.google.gson.Gson; +import com.pdlive.lib_so.DynamicSoLauncher; import com.yunbao.common.utils.LogUtils; import com.tencent.imsdk.v2.V2TIMGroupMemberInfo; import com.tencent.imsdk.v2.V2TIMManager; @@ -144,6 +145,17 @@ public class AppContext extends CommonAppContext { if (!isMainProcess()) { return; } + String path = getFilesDir().getAbsolutePath() + "/dynamic_so/"; + File file = new File(path); + if (!file.exists()) { + boolean b= file.mkdir(); + Log.i("mLog","创建文件 "+b); + } + // 在合适的时候将自定义路径插入so检索路径 需要使用者自己负责在这个路径上有写入权限 + DynamicSoLauncher.INSTANCE.initDynamicSoConfig(this, path, s -> { + // 处理一些自定义逻辑 + return true; + }); CrashSaveBean.getInstance().setStartTime(System.currentTimeMillis()); //注册全局异常捕获 registerError(); diff --git a/app/src/main/java/com/shayu/phonelive/activity/LauncherActivity.java b/app/src/main/java/com/shayu/phonelive/activity/LauncherActivity.java index f74ecb999..ca82e90ba 100644 --- a/app/src/main/java/com/shayu/phonelive/activity/LauncherActivity.java +++ b/app/src/main/java/com/shayu/phonelive/activity/LauncherActivity.java @@ -1,7 +1,6 @@ package com.shayu.phonelive.activity; -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -22,7 +21,6 @@ import android.widget.ImageView; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityOptionsCompat; import androidx.core.app.NotificationManagerCompat; @@ -50,14 +48,12 @@ import com.yunbao.common.interfaces.CommonCallback; import com.yunbao.common.manager.IMLoginManager; import com.yunbao.common.manager.imrongcloud.RongcloudIMManager; import com.yunbao.common.utils.DownloadUtil; -import com.yunbao.common.utils.DpUtil; import com.yunbao.common.utils.L; import com.yunbao.common.utils.LogUtil; import com.yunbao.common.utils.MD5Util; import com.yunbao.common.utils.RouteUtil; import com.yunbao.common.utils.SpUtil; import com.yunbao.common.utils.ToastUtil; -import com.yunbao.common.utils.WordUtil; import com.yunbao.live.views.LauncherAdViewHolder; import com.yunbao.main.activity.EntryActivity; import com.yunbao.main.activity.MainActivity; @@ -592,4 +588,5 @@ public class LauncherActivity extends AppCompatActivity implements View.OnClickL this.finish(); } } + } diff --git a/build.gradle b/build.gradle index 0c4e14aba..dbba0fd39 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,7 @@ allprojects { ext { IS_PUBLISH_LOCAL=true LIB_VERSION="1.0.6" - AGORA_RTC_SDK= 'io.agora.rtc:agora-special-full:4.2.6.12' + AGORA_RTC_SDK= 'io.agora.rtc:agora-special-full:4.2.6.14' // AGORA_RTC_SDK= "${rootProject.rootDir.absolutePath}/sdk" // AGORA_RTC_SDK="io.agora.rtc:full-sdk:4.2.6" } diff --git a/common/src/main/java/com/yunbao/common/utils/LoadSoUtil.java b/common/src/main/java/com/yunbao/common/utils/LoadSoUtil.java new file mode 100644 index 000000000..470bd8a60 --- /dev/null +++ b/common/src/main/java/com/yunbao/common/utils/LoadSoUtil.java @@ -0,0 +1,240 @@ +package com.yunbao.common.utils; + +import android.content.Context; +import android.content.res.AssetManager; +import android.util.Log; + +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Cookie; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class LoadSoUtil { + + public static void loadSo(Context context, onLoadSoListener listener) { + try { + String path = context.getFilesDir().getAbsolutePath() + "/dynamic_so"; + File file = new File(path); + AssetManager assetManager = context.getAssets(); + String[] files = assetManager.list("libso"); + if (!file.exists()) { + boolean b = file.mkdir(); + Log.i("mLog", "创建文件 " + b); + } + if (file.listFiles().length != files.length) {//说明之前已经保存了 + file.delete(); + file.mkdir(); + Log.i("mLog", "需要拷贝so文件 "); + // 获取AssetManager对象 + for (int i = 0; i < files.length; i++) { + loadLibrary(context, "libso/" + files[i], files[i]); + } + } + listener.ok(); + Log.i("mLog", "拷贝so成功------------------------"); + } catch (IOException e) { + Log.e("mLog", "拷贝so错误++++++++++++++++++++"); + e.printStackTrace(); + listener.error(); + } + } + + public static void loadSo2(Context context, onLoadSoListener listener) { + String path = context.getFilesDir().getAbsolutePath() + "/so_zip"; + File file = new File(path); + if (!file.exists()) { + file.mkdir(); + } else if (isLoadSo(context)) { + listener.ok(); + return; + } + OkHttpClient.Builder builder = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10 * 5, TimeUnit.SECONDS) + .cookieJar(new CookieJar() { + private final HashMap> cookieStore = new HashMap<>(); + + @Override + public void saveFromResponse(@NotNull HttpUrl httpUrl, @NotNull List list) { + cookieStore.put(httpUrl.host(), list); + } + + @NotNull + @Override + public List loadForRequest(@NotNull HttpUrl httpUrl) { + List cookies = cookieStore.get(httpUrl.host()); + return cookies != null ? cookies : new ArrayList<>(); + } + }); + builder.hostnameVerifier((hostname, session) -> true); + //开始下载文件 + downloadFile(builder.build(), "https://downs.yaoulive.com/x86.zip", new File(path + "/" + "libSo.zip"), new onLoadSoListener() { + @Override + public void ok() { + //下载完成开始解压 解压完成之后 区初始化声网数据 + zip(context, path + "/" + "libSo.zip", listener); + } + + @Override + public void error() { + listener.error(); + } + }); + } + + private static void zip(Context context, String path, onLoadSoListener listener) { + ZipInputStream zis = null; + FileOutputStream fos = null; + try { + zis = new ZipInputStream(new FileInputStream(path)); + ZipEntry ze; + String soPath = context.getFilesDir().getAbsolutePath() + "/dynamic_so"; + File file = new File(soPath); + if (!file.exists()) { + file.mkdir(); + } + while ((ze = zis.getNextEntry()) != null) { + String fileName = ze.getName(); + File newFile = new File(soPath + File.separator + fileName); + fos = new FileOutputStream(newFile); + byte[] buffer = new byte[1024]; + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + fos.close(); + zis.closeEntry(); + } + listener.ok(); + } catch (IOException e) { + e.printStackTrace(); + listener.error(); + } finally { + try { + if (zis != null) zis.close(); + if (fos != null) fos.close(); + } catch (Exception e) { + } + } + } + + private static void downloadFile(OkHttpClient mOkHttpClient, String url, File file, onLoadSoListener listener) { + Request request = new Request.Builder() + .get() + .url(url) + .build(); + mOkHttpClient.newCall(request) + .enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + if (listener != null) listener.error(); + } + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + InputStream is = null; + BufferedOutputStream bos = null; + try { + is = response.body().byteStream(); + bos = new BufferedOutputStream(new FileOutputStream(file)); + byte[] bytes = new byte[10240]; + int len; + while ((len = is.read(bytes)) != -1) { + bos.write(bytes, 0, len); + } + bos.flush(); + if (listener != null) listener.ok(); + } catch (Exception e) { + e.printStackTrace(); + if (listener != null) listener.error(); + } finally { + try { + if (bos != null) bos.close(); + if (is != null) is.close(); + } catch (Exception e) { + + } + } + } + } + }); + } + + public static boolean isLoadSo(Context context) { + String pathSo = context.getFilesDir().getAbsolutePath() + "/dynamic_so"; + File fileSo = new File(pathSo); + if (fileSo.listFiles().length == 22) {//说明之前已经保存了 + Log.i("mLog", "已经下载了so 所有直接加载就行 "); + return true; + } + return false; + } + + public static boolean loadLibrary(Context context, String oldFileName, String libName) throws IOException { + // 获取应用的私有模式的libs目录(真实的目录是app-libs) + String path = context.getFilesDir().getAbsolutePath() + "/dynamic_so/"; + // 可以获取到assets资源文件的数据流 + InputStream open = context.getAssets().open(oldFileName); + String new_file_name = path + libName; + // 把资源文件的数据流写入到app-libs目录下 + if (!copyLibrary(open, new_file_name)) { + return false; + } + return true; + } + + //从assets资源目录下复制到app-libs目录下 + public static boolean copyLibrary(InputStream fileInputStream, String new_file) { + FileOutputStream fos = null; + try { + File file = new File(new_file); + fos = new FileOutputStream(file); + int dataSize; + byte[] dataBuffer = new byte[2048]; + + while ((dataSize = fileInputStream.read(dataBuffer)) != -1) { + fos.write(dataBuffer, 0, dataSize); + } + fos.flush(); + return true; + } catch (Exception e) { + e.printStackTrace(); + Log.e("mLog", e.getMessage()); + } finally { + try { + if (fos != null) fos.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return false; + } + + public interface onLoadSoListener { + void ok(); + + void error(); + } + +} diff --git a/lib_faceunity/src/main/java/io/agora/beautyapi/faceunity/agora/SWAuManager.java b/lib_faceunity/src/main/java/io/agora/beautyapi/faceunity/agora/SWAuManager.java index 1d89d03ad..82e8721a4 100644 --- a/lib_faceunity/src/main/java/io/agora/beautyapi/faceunity/agora/SWAuManager.java +++ b/lib_faceunity/src/main/java/io/agora/beautyapi/faceunity/agora/SWAuManager.java @@ -30,6 +30,7 @@ import com.yunbao.common.utils.L; import com.yunbao.common.utils.StringUtil; import com.yunbao.common.utils.ToastUtil; +import java.util.ArrayList; import java.util.List; import io.agora.rtc2.ChannelMediaOptions; @@ -62,6 +63,7 @@ public class SWAuManager extends BaseCacheManager { private FrameLayout pkContainer2; //pk主播视图2 private FrameLayout pkContainer3; //pk主播视图3 private FrameLayout linkUserContainer;//连麦用户视图 + private List mDatas=new ArrayList<>();//保存数据 如果还没初始化的时候就把这个数据保存起来,等加载完so再来添加 private int liveMicUid; @@ -113,6 +115,7 @@ public class SWAuManager extends BaseCacheManager { config.mContext = mContext; config.mAppId = CommonAppConfig.getSwAppId(); config.mEventHandler = mRtcEventHandler; + config.mNativeLibPath=mContext.getFilesDir().getAbsolutePath() + "/dynamic_so/"; // 创建并初始化 RtcEngine mRtcEngine = (RtcEngineEx) RtcEngineEx.create(config); } catch (Exception e) { @@ -441,9 +444,21 @@ public class SWAuManager extends BaseCacheManager { } public void preloadChannel(List uids) { - for (int i = 0; i < uids.size(); i++) { - int code = mRtcEngine.preloadChannel(CommonAppConfig.SWToken, getChannelName(uids.get(i).getUid()), Integer.parseInt(CommonAppConfig.getInstance().getUid())); - L.eSw("设置秒开数据 uid" + uids.get(i).getUid() + " --- userName:" + uids.get(i).getUserNiceName() + " code " + code); + if (mRtcEngine==null){ + mDatas.addAll(uids); + }else { + for (int i = 0; i < uids.size(); i++) { + int code = mRtcEngine.preloadChannel(CommonAppConfig.SWToken, getChannelName(uids.get(i).getUid()), Integer.parseInt(CommonAppConfig.getInstance().getUid())); + L.eSw("设置秒开数据 uid" + uids.get(i).getUid() + " --- userName:" + uids.get(i).getUserNiceName() + " code " + code); + } + } + } + public void preloadChannel() { + if (mRtcEngine!=null && mDatas.size()>0){ + for (int i = 0; i < mDatas.size(); i++) { + int code = mRtcEngine.preloadChannel(CommonAppConfig.SWToken, getChannelName(mDatas.get(i).getUid()), Integer.parseInt(CommonAppConfig.getInstance().getUid())); + L.eSw("设置秒开数据 uid" + mDatas.get(i).getUid() + " --- userName:" + mDatas.get(i).getUserNiceName() + " code " + code); + } } } } diff --git a/lib_so/.gitignore b/lib_so/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/lib_so/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib_so/build.gradle b/lib_so/build.gradle new file mode 100644 index 000000000..1985ef7fc --- /dev/null +++ b/lib_so/build.gradle @@ -0,0 +1,31 @@ +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} + +android { + namespace 'com.pdlive.lib_so' + compileSdk rootProject.ext.android.compileSdkVersion + + defaultConfig { + minSdk rootProject.ext.android.minSdkVersion + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_18 + targetCompatibility JavaVersion.VERSION_18 + } +} + +dependencies { + api rootProject.ext.dependencies["appcompat-androidx"] +} \ No newline at end of file diff --git a/lib_so/consumer-rules.pro b/lib_so/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/lib_so/proguard-rules.pro b/lib_so/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/lib_so/proguard-rules.pro @@ -0,0 +1,21 @@ +# 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 \ No newline at end of file diff --git a/lib_so/src/main/AndroidManifest.xml b/lib_so/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a5918e68a --- /dev/null +++ b/lib_so/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lib_so/src/main/java/com/pdlive/lib_so/DynamicLoad.kt b/lib_so/src/main/java/com/pdlive/lib_so/DynamicLoad.kt new file mode 100644 index 000000000..314153991 --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/DynamicLoad.kt @@ -0,0 +1,9 @@ +package com.pdlive.lib_so + +import androidx.annotation.Keep + + +@Keep +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class DynamicLoad() diff --git a/lib_so/src/main/java/com/pdlive/lib_so/DynamicSo.java b/lib_so/src/main/java/com/pdlive/lib_so/DynamicSo.java new file mode 100644 index 000000000..f1995e217 --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/DynamicSo.java @@ -0,0 +1,65 @@ +package com.pdlive.lib_so; + +import android.content.Context; +import android.util.Log; + +import com.pdlive.lib_so.elf.ElfParser; +import com.pdlive.lib_so.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 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)); + Log.i("mLog","通过库加载so "+soFIle.getAbsolutePath()); + System.load(soFIle.getAbsolutePath()); + } + + public static void insertPathToNativeSystem(Context context, File file) { + try { + LoadLibraryUtils.installNativeLibraryPath(context.getClassLoader(), file); + } catch (Throwable e) { + e.printStackTrace(); + } + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/DynamicSoLauncher.kt b/lib_so/src/main/java/com/pdlive/lib_so/DynamicSoLauncher.kt new file mode 100644 index 000000000..eb45cfad7 --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/DynamicSoLauncher.kt @@ -0,0 +1,46 @@ +package com.pdlive.lib_so + +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) { + DynamicSoLauncher.soPath = soPath + 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) + } +} \ No newline at end of file diff --git a/lib_so/src/main/java/com/pdlive/lib_so/elf/Dynamic32Structure.java b/lib_so/src/main/java/com/pdlive/lib_so/elf/Dynamic32Structure.java new file mode 100644 index 000000000..5352bc0dc --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/elf/Dynamic32Structure.java @@ -0,0 +1,32 @@ +/** + * 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.pdlive.lib_so.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); + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/elf/Dynamic64Structure.java b/lib_so/src/main/java/com/pdlive/lib_so/elf/Dynamic64Structure.java new file mode 100644 index 000000000..74ae897fb --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/elf/Dynamic64Structure.java @@ -0,0 +1,33 @@ +/** + * 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.pdlive.lib_so.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); + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/elf/Elf.java b/lib_so/src/main/java/com/pdlive/lib_so/elf/Elf.java new file mode 100644 index 000000000..2006bd97f --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/elf/Elf.java @@ -0,0 +1,64 @@ +/** + * 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.pdlive.lib_so.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 + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/elf/Elf32Header.java b/lib_so/src/main/java/com/pdlive/lib_so/elf/Elf32Header.java new file mode 100644 index 000000000..697d39873 --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/elf/Elf32Header.java @@ -0,0 +1,59 @@ +/** + * 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.pdlive.lib_so.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); + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/elf/Elf64Header.java b/lib_so/src/main/java/com/pdlive/lib_so/elf/Elf64Header.java new file mode 100644 index 000000000..db912b996 --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/elf/Elf64Header.java @@ -0,0 +1,58 @@ +/** + * 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.pdlive.lib_so.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); + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/elf/ElfParser.java b/lib_so/src/main/java/com/pdlive/lib_so/elf/ElfParser.java new file mode 100644 index 000000000..48065da4a --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/elf/ElfParser.java @@ -0,0 +1,195 @@ +/** + * 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.pdlive.lib_so.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 "+file.getAbsolutePath()); + } + + 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 parseNeededDependencies() throws IOException { + channel.position(0); + final List dependencies = new ArrayList(); + 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 neededOffsets = new ArrayList(); + 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); + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/elf/Program32Header.java b/lib_so/src/main/java/com/pdlive/lib_so/elf/Program32Header.java new file mode 100644 index 000000000..69fe17d74 --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/elf/Program32Header.java @@ -0,0 +1,35 @@ +/** + * 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.pdlive.lib_so.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); + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/elf/Program64Header.java b/lib_so/src/main/java/com/pdlive/lib_so/elf/Program64Header.java new file mode 100644 index 000000000..1182f8d75 --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/elf/Program64Header.java @@ -0,0 +1,35 @@ +/** + * 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.pdlive.lib_so.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); + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/elf/Section32Header.java b/lib_so/src/main/java/com/pdlive/lib_so/elf/Section32Header.java new file mode 100644 index 000000000..1851cb68b --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/elf/Section32Header.java @@ -0,0 +1,31 @@ +/** + * 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.pdlive.lib_so.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); + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/elf/Section64Header.java b/lib_so/src/main/java/com/pdlive/lib_so/elf/Section64Header.java new file mode 100644 index 000000000..2bd10a8dd --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/elf/Section64Header.java @@ -0,0 +1,31 @@ +/** + * 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.pdlive.lib_so.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); + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/pathinsert/LoadLibraryUtils.java b/lib_so/src/main/java/com/pdlive/lib_so/pathinsert/LoadLibraryUtils.java new file mode 100644 index 000000000..359fdc98b --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/pathinsert/LoadLibraryUtils.java @@ -0,0 +1,202 @@ +/* + * 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.pdlive.lib_so.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 libraryPathElements = (List) libraryPathElementsFiled.get(classLoader); + final Iterator 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 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 origLibDirs = (List) nativeLibraryDirectories.get(dexPathList); + if (origLibDirs == null) { + origLibDirs = new ArrayList<>(2); + } + final Iterator 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 origSystemLibDirs = (List) systemNativeLibraryDirectories.get(dexPathList); + if (origSystemLibDirs == null) { + origSystemLibDirs = new ArrayList<>(2); + } + + final List 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 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 origLibDirs = (List) nativeLibraryDirectories.get(dexPathList); + if (origLibDirs == null) { + origLibDirs = new ArrayList<>(2); + } + final Iterator 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 origSystemLibDirs = (List) systemNativeLibraryDirectories.get(dexPathList); + if (origSystemLibDirs == null) { + origSystemLibDirs = new ArrayList<>(2); + } + + final List 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); + } + } +} diff --git a/lib_so/src/main/java/com/pdlive/lib_so/pathinsert/ShareReflectUtil.java b/lib_so/src/main/java/com/pdlive/lib_so/pathinsert/ShareReflectUtil.java new file mode 100644 index 000000000..ed2a0a2ad --- /dev/null +++ b/lib_so/src/main/java/com/pdlive/lib_so/pathinsert/ShareReflectUtil.java @@ -0,0 +1,244 @@ +package com.pdlive.lib_so.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; + } + } +} diff --git a/log.txt b/log.txt new file mode 100644 index 000000000..8a3c38006 Binary files /dev/null and b/log.txt differ diff --git a/main/src/main/java/com/yunbao/main/activity/MainActivity.java b/main/src/main/java/com/yunbao/main/activity/MainActivity.java index e1655c875..daa322b30 100644 --- a/main/src/main/java/com/yunbao/main/activity/MainActivity.java +++ b/main/src/main/java/com/yunbao/main/activity/MainActivity.java @@ -225,10 +225,18 @@ public class MainActivity extends AbsActivity implements MainAppBarLayoutListene OpenAdManager.getInstance().dismiss(); } + public static boolean isLoadSo=false; + @Override protected void main() { //初始化声网 - SWAuManager.get().initRtcEngine(this); + String path = getFilesDir().getAbsolutePath() + "/dynamic_so"; + File file = new File(path); + if (file.listFiles().length==22){ + //说明已经有了可以直接初始化 + isLoadSo=true; + SWAuManager.get().initRtcEngine(this); + } ActivityCompat.postponeEnterTransition(this); ConversationIMListManager.get(this); //在请求一下这个接口给我后台版本号 diff --git a/main/src/main/java/com/yunbao/main/views/MainHomeLiveViewHolder.java b/main/src/main/java/com/yunbao/main/views/MainHomeLiveViewHolder.java index def2d4e70..17bab88e9 100644 --- a/main/src/main/java/com/yunbao/main/views/MainHomeLiveViewHolder.java +++ b/main/src/main/java/com/yunbao/main/views/MainHomeLiveViewHolder.java @@ -12,6 +12,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.widget.ImageView; +import android.widget.Toast; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; @@ -29,7 +30,6 @@ import com.bumptech.glide.request.target.DrawableImageViewTarget; import com.bumptech.glide.request.target.Target; import com.ms.banner.Banner; import com.ms.banner.listener.OnBannerClickListener; -import com.yunbao.common.utils.MobclickAgent; import com.yunbao.common.CommonAppConfig; import com.yunbao.common.Constants; import com.yunbao.common.activity.WebViewActivity; @@ -46,14 +46,14 @@ import com.yunbao.common.http.HttpCallback; import com.yunbao.common.http.LiveHttpUtil; import com.yunbao.common.interfaces.OnItemClickListener; import com.yunbao.common.manager.LiveClassManager; -import com.yunbao.common.manager.OpenAdManager; import com.yunbao.common.utils.DialogUitl; import com.yunbao.common.utils.LiveRoomCheckLivePresenter; +import com.yunbao.common.utils.LoadSoUtil; import com.yunbao.common.utils.MicStatusManager; +import com.yunbao.common.utils.MobclickAgent; import com.yunbao.common.utils.RouteUtil; import com.yunbao.common.utils.StringUtil; import com.yunbao.common.utils.ToastUtil; -import com.yunbao.common.utils.WordUtil; import com.yunbao.common.views.CustomViewHolder; import com.yunbao.live.utils.LiveStorge; import com.yunbao.live.views.LiveRoomViewHolder; @@ -211,7 +211,7 @@ public class MainHomeLiveViewHolder extends AbsMainHomeChildViewHolder implement }); } else { pp = 0; - if(select==0){ + if (select == 0) { select = list.get(0).getId(); } MainHttpUtil.getClassLive(select, p, callback); @@ -409,8 +409,8 @@ public class MainHomeLiveViewHolder extends AbsMainHomeChildViewHolder implement liveBean.setParams(gotoRoomKey); new LiveRoomCheckLivePresenter(mContext, liveBean.getUid(), liveBean.getStream(), new LiveRoomCheckLivePresenter.NewActionListener() { @Override - public void onLiveRoomChanged(String liveUid, String stream, int liveType, String liveTypeVal, String liveSdk,boolean isSw) { - RouteUtil.forwardLiveAudienceActivity(liveBean, liveType, Integer.parseInt(liveSdk), Integer.parseInt(liveTypeVal),isSw); + public void onLiveRoomChanged(String liveUid, String stream, int liveType, String liveTypeVal, String liveSdk, boolean isSw) { + RouteUtil.forwardLiveAudienceActivity(liveBean, liveType, Integer.parseInt(liveSdk), Integer.parseInt(liveTypeVal), isSw); } @Override @@ -488,6 +488,35 @@ public class MainHomeLiveViewHolder extends AbsMainHomeChildViewHolder implement @Override public void onItemClick(LiveBean bean, int position) { + if (MainActivity.isLoadSo) { + onItemClick2(bean, position); + } else { + Toast.makeText(mContext, "正在下载so文件,请稍后。。。", Toast.LENGTH_LONG).show(); + LoadSoUtil.loadSo2(mContext, new LoadSoUtil.onLoadSoListener() { + @Override + public void ok() { + MainActivity.isLoadSo = true; + SWAuManager.get().initRtcEngine((MainActivity) mContext); + SWAuManager.get().preloadChannel(); + onItemClick2(bean, position); + } + + @Override + public void error() { + mRefreshView.post(new Runnable() { + @Override + public void run() { + Toast.makeText(mContext, "下载so文件失败了", Toast.LENGTH_SHORT).show(); + } + }); + Log.e("mLog", "加载so文件的时候错误了 ****************** "); + } + }); + } + } + + public void onItemClick2(LiveBean bean, int position) { + //到这里判断是否需要加载so if ("1".equals(bean.getIslive())) { intoIndex = 1; watchLive(bean, Constants.LIVE_HOME, position); @@ -504,7 +533,7 @@ public class MainHomeLiveViewHolder extends AbsMainHomeChildViewHolder implement } new LiveRoomCheckLivePresenter(mContext, liveBean.getUid(), liveBean.getStream(), new LiveRoomCheckLivePresenter.NewActionListener() { @Override - public void onLiveRoomChanged(String liveUid, String stream, int liveType, String liveTypeVal, String liveSdk,boolean isSw) { + public void onLiveRoomChanged(String liveUid, String stream, int liveType, String liveTypeVal, String liveSdk, boolean isSw) { if (LiveRoomViewHolder.mHandler != null) { LiveRoomViewHolder.mHandler.removeCallbacksAndMessages(null); @@ -516,7 +545,7 @@ public class MainHomeLiveViewHolder extends AbsMainHomeChildViewHolder implement } EventBus.getDefault().post(new LiveRoomChangeEvent(liveBean, liveType, Integer.parseInt(liveTypeVal))); } else { - RouteUtil.forwardLiveAudienceActivity(liveBean, liveType, Integer.parseInt(liveTypeVal), Integer.parseInt(liveSdk),isSw); + RouteUtil.forwardLiveAudienceActivity(liveBean, liveType, Integer.parseInt(liveTypeVal), Integer.parseInt(liveSdk), isSw); } } @@ -537,7 +566,6 @@ public class MainHomeLiveViewHolder extends AbsMainHomeChildViewHolder implement } } - @Override public void loadData() { if (mAdapter != null) { diff --git a/package_config.gradle b/package_config.gradle index 5397b1b5f..282bfc31a 100644 --- a/package_config.gradle +++ b/package_config.gradle @@ -82,4 +82,6 @@ android{ } } + // 设置默认的flavor + defaultPublishConfig "link_test" } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 9620d2b17..34a8bd36b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,3 +8,5 @@ include ':lib_huawei' include ':lib_google' include ':IAP6Helper' include ':lib_faceunity' +include ':lib_so' +include ':lib_so'