Android 动态加载so库

57 阅读10分钟

一、背景

随着第三方sdk的增多,sdk集成的so库越来越多,包体也越来越大,继续开发一个动态加载so库的框架代码:DynamicSoLoader

二、具体使用

提供DynamicSoLoader用于支持此功能,用法如下;

1、打包时,将对应的so库在打包时移除:

packagingOptions {
 exclude 'lib/armeabi-v7a/libagora-core.so'
 exclude 'lib/armeabi-v7a/libagora-ffmpeg.so'
 exclude 'lib/armeabi-v7a/libagora-fdkaac.so'
 exclude 'lib/armeabi-v7a/libagora-mpg123.so'
 exclude 'lib/armeabi-v7a/libagora-soundtouch.so'
 exclude 'lib/armeabi-v7a/libagora-rtc-sdk.so'
 exclude 'lib/armeabi-v7a/libagora_ai_denoise_extension.so'
 exclude 'lib/armeabi-v7a/libagora_dav1d_extension.so'
 exclude 'lib/armeabi-v7a/libagora_jnd_extension.so'
 exclude 'lib/armeabi-v7a/libagora-rtc-sdk.so'

}

将对应的so库打包成zip,上传到服务端,配置到后台(这个未做);

2、启动app时调用:

DynamicSoLoader.getInstance().init(getApplicationContext());

3、启动后调用下载接口进行下载;

DynamicSoLoader.getInstance().start(getApplicationContext(), url, matchSoFileName, new CommomCallBack() {
    @Override
 public void onResult(Object o) {
        boolean flag = (boolean)o;
 if(flag){
            LogUtils.i(TAG,"Dynamic执行成功,开始测试加载SDK");

 }else{
            LogUtils.i(TAG,"Dynamic执行失败");
 }
    }
});

参数说明

url:打包后的so库zip 地址;
matchSoFileName:比如:libagora-core.so;此文件 用于精准校验是否已经真正下载并解压完成,取zip包里的任意一个so库即可。

4、注意事项:

由于是动态下载,所有下载失败是有可能的,需要针对下载失败的情况进行处理,比如hook住对应调用的入口,如果没下载完,进行提示(让产品出优化需求),然后重新下载,直到下载成功为止;

5、RN yoga so库 实践:

1、打包配置如下,移除对应的so库:

packagingOptions {

 exclude 'lib/armeabi-v7a/libagora-core.so'
 exclude 'lib/armeabi-v7a/libagora-ffmpeg.so'
 exclude 'lib/armeabi-v7a/libagora-fdkaac.so'
 exclude 'lib/armeabi-v7a/libagora-mpg123.so'
 exclude 'lib/armeabi-v7a/libagora-soundtouch.so'
 exclude 'lib/armeabi-v7a/libagora-rtc-sdk.so'
 exclude 'lib/armeabi-v7a/libagora_ai_denoise_extension.so'
 exclude 'lib/armeabi-v7a/libagora_dav1d_extension.so'
 exclude 'lib/armeabi-v7a/libagora_jnd_extension.so'
 exclude 'lib/armeabi-v7a/libagora-rtc-sdk.so'
}

2、application初始化:


DynamicSoLoader.getInstance().init(getApplicationContext());

3、在启动页面后立刻执行:

  
if(!DynamicSoLoader.getInstance().isSoLoaded(getApplicationContext(),"[libagora-core.so]")){  
    String url = "https://fe-meiyou.oss-cn-hangzhou.aliyuncs.com/FE/thirdsolibs.zip";  
    DynamicSoLoader.getInstance().start(getApplicationContext(), url,

new CommomCallBack() {  
@Override  
public void onResult(Object o) {  
boolean flag = (boolean)o;  
if(flag) {  
LogUtils.i(TAG, "Dynamic执行成功,开始进行预加载逻辑");  
ReactFetchManager.getInstance().prefetch();  
}else{  
LogUtils.e(TAG, "Dynamic执行失败");  
}  
}  
}

);  
}  
ReactLoader.getInstance().setReactResourcePreparedListener(new ReactLoader.OnReactResourcePreparedListener(){  
  @Override  
  public boolean isReactResourcePrepared() {  
     return DynamicSoLoader.getInstance().isSoLoaded(getApplicationContext(),"[libagora-core.so](http://libagora-core.so)");  
}

  @Override  
  public void loadReactResource(Activity activity,CommomCallBack commomCallBack) {  
    
     PhoneProgressDialog.showRoundDialog(activity, "正在加载RN资源", new DialogInterface.OnCancelListener() {  
     @Override  
     public void onCancel(DialogInterface dialog) {

    }  
    });  
String url = "<https://fe-meiyou.oss-cn-hangzhou.aliyuncs.com/FE/thirdsolibs.zip>";  
String matchSoFileName = "[libagora-core.so](http://libagora-core.so)";  
DynamicSoLoader.getInstance().start(getApplicationContext(), url, matchSoFileName, new CommomCallBack() {  
       @Override  
       public void onResult(Object o) {  
          PhoneProgressDialog.dismissDialog(activity);  
          boolean flag = (boolean)o;  
          if(flag){  
          LogUtils.i(TAG,"Dynamic执行成功,开始测试加载SDK");  
          if(commomCallBack!=null){  
               commomCallBack.onResult(true);  
          }  
//重新开启预加载


ReactFetchManager.getInstance().prefetch();


  
          }else{  
               LogUtils.i(TAG,"Dynamic执行失败");  
               if(commomCallBack!=null){  
                    commomCallBack.onResult(false);  
               }  
          }  
     }  
});  
}  
});

6、DynamicSoLoader详细实现:



public class DynamicSoLoader {


    /**
     * 存储第三方so库的文件夹名称
     * DON'T MODIFY ,IMPORTANT!!
     */
    private static final String THIRD_SO_FOLDER_NAME = "dynamicsolibs";
    private static final String TAG = "DynamicSoLoader";
    private static DynamicSoLoader instance;
    public synchronized static DynamicSoLoader getInstance(){
        if(instance==null){
            instance = new DynamicSoLoader();
        }
        return instance;
    }
    private List<String> listUrl = Collections.synchronizedList(new ArrayList<>());
    private Map<String,List<CommomCallBack>> listMapCallBack = Collections.synchronizedMap(new HashMap<>());
    public DynamicSoLoader(){

    }

    public String getSoLoaderPath(Context context){
        String soloaderPath =  context.getApplicationInfo().dataDir + "/lib-main";
        LogUtils.i(TAG,"getSoLoaderPath:"+soloaderPath);
        return soloaderPath;
    }

    /**
     *
     * 是否命中64适配规则
     *
     * @param context
     * @return
     */
    public boolean isHit64(Context context){
        try {
            if(CPUUtils.isSupport64Bit()){
                if(isAdapter64()){
                    /*if(ApkBitsUtils.hasApkBits(context)){
                        if(ApkBitsUtils.isApk64(context) || ApkBitsUtils.isApk3264(context)){
                            //64设备+64位包;
                            LogUtils.e(TAG,"isHit64:64设备+64位包,return true");
                            return true;
                        }else if(ApkBitsUtils.isApk32(context)){
                            //64设备+32位包;
                            LogUtils.e(TAG,"isHit64:64设备+32位包,return false");
                            return false;
                        }else{
                            LogUtils.e(TAG,"isHit64:未知位数,return true");
                            return true;
                        }
                    }else{
                        //64设备+未知包;
                        LogUtils.e(TAG,"isHit64:64设备+未知包,return true");
                        return true;
                    }*/
                    LogUtils.e(TAG,"isHit64:64设备+isAdapter64=true,return true");
                    return true;
                }else{
                    //64位设备+isAdapter64=false;
                    LogUtils.e(TAG,"isHit64:64设备+isAdapter64=false,return false");
                    return false;
                }

            }else if(CPUUtils.isSupport32Bit()){
                LogUtils.i(TAG,"此设备CPU类型是:armeabi-v7a");
                // 因为32位设备装不了64位包,所以肯定是32位的包;
                LogUtils.e(TAG,"isHit64:32设备 return false");
                return false;
            }else{
                //LogUtils.i(TAG,"此设备CPU类型是未知:"+Build.SUPPORTED_ABIS.toString());
                LogUtils.e(TAG,"isHit64:未知设备 return true:"+Build.SUPPORTED_ABIS.toString());
                return true;
            }
        }catch (Exception exception){
            exception.printStackTrace();
            LogUtils.e(TAG,"isHit64:发生异常!!!!"+exception.getMessage());
        }
        return true;
    }

    public String getDynamicSoPath(Context context){
        try {
            if(isHit64(context)){
                return getDynamicV8aSoPath(context);
            }else{
                return getDynamicV7aSoPath(context);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return getDynamicV7aSoPath(context);
    }

    private String getDynamicV7aSoPath(Context context){
        return context.getFilesDir().getAbsolutePath()+"/"+THIRD_SO_FOLDER_NAME+"/armeabi-v7a";
    }
    private String getDynamicV8aSoPath(Context context){
        return context.getFilesDir().getAbsolutePath()+"/"+THIRD_SO_FOLDER_NAME+"/arm64-v8a";
    }
    //是否适配64位;
    private boolean isAdapter64 = false;

    public boolean isAdapter64() {
        return isAdapter64;
    }

    public void setAdapter64(boolean adapter64) {
        isAdapter64 = adapter64;
    }

    private boolean isInited = false;
    /**
     * 初始化:往系统中设置 第三方 so 文件夹
     * @param context
     */
    public void init(Context context){
        try {
            if(isInited){
                return;
            }
            //创建data目录下文件夹;业务启动下载后,必须将so拷贝到此文件夹下;
            File thirdsoFile = new File(getDynamicSoPath(context));
            if(!thirdsoFile.exists()){
                thirdsoFile.mkdirs();
            }
            try {
                installNativeLibraryPath(context.getClassLoader(),thirdsoFile);
            } catch (Throwable throwable) {
                LogUtils.e(TAG,throwable.getMessage());
                HashMap<String,String> map = new HashMap<>();
                map.put("error",throwable.getMessage()+"");
                GaController.getInstance(MeetyouFramework.getContext()).onEvent("/dynamicsofail",map);
            }
            LogUtils.i(TAG,"DynamicSoLoader 初始化完成");
        }catch (Exception ex){
            ex.printStackTrace();
            HashMap<String,String> map = new HashMap<>();
            map.put("error1",ex.getMessage()+"");
            GaController.getInstance(MeetyouFramework.getContext()).onEvent("/dynamicsofail",map);
            LogUtils.e(TAG,"DynamicSoLoader 初始化失败!!");
        }finally {
            isInited = true;
        }
    }

    @Deprecated
    public boolean isSoLoaded(Context context,String soList,boolean isCheckSoLoaderDirectory){
        try {
            String dynamicSoPath  = getDynamicSoPath(context);
            String soloaderPath = getSoLoaderPath(context);
            boolean isSoloaded1 = isSoLoadedImp(dynamicSoPath,soList,null);
            if(isSoloaded1){
                if(isCheckSoLoaderDirectory) {
                    isSoloaded1 = isSoLoadedImp(soloaderPath, soList, null);
                }
            }
            return isSoloaded1;

            /*String dynamicSoPath  = getDynamicSoPath(context);
            String soloaderPath = getSoLoaderPath(context);
            if(matchSoFileName.contains(",")){
                String []matchSoFileNameArray  = matchSoFileName.split(",");
                for(String fileSoName:matchSoFileNameArray){
                    File file = new File(dynamicSoPath+"/"+fileSoName);
                    if(!file.exists() || (file.exists() && file.length()==0)){
                        LogUtils.e(TAG,dynamicSoPath+"文件夹下不存在此so库:"+fileSoName);
                        return false;
                    }
                }
                if(isCheckSoLoaderDirectory){
                    for(String fileSoName:matchSoFileNameArray){
                        File file = new File(soloaderPath+"/"+fileSoName);
                        if(!file.exists() || (file.exists() && file.length()==0)){
                            LogUtils.e(TAG,"Soloader lib-main文件夹下不存在此so库:"+fileSoName);
                            return false;
                        }
                    }
                }

                return true;
            }else{
                File file = new File(dynamicSoPath+"/"+matchSoFileName);
                if(file.exists()){
                    if(isCheckSoLoaderDirectory){
                        file = new File(soloaderPath+"/"+matchSoFileName);
                        if(file.exists() && file.length()>0){
                            return true;
                        }else{
                            LogUtils.e(TAG,"1 Soloader lib-main文件夹下不存在此so库:"+matchSoFileName);
                        }
                    }else{
                        return true;
                    }
                }else{
                    LogUtils.e(TAG,"1" +dynamicSoPath+"文件夹下不存在此so库:"+matchSoFileName);
                }
            }*/
        }catch (Exception ex){
            ex.printStackTrace();
        }
        return false;
    }

    /**
     * 检测so是否完整加载,校验Md5会耗时
     * @param context
     * @param soList so列表,以逗号隔开
     * @param soMd5List so对应的md5列表,以逗号隔开
     * @param isCheckSoLoaderDirectory
     * @return
     */
    public boolean isSoLoadedComplete(Context context,String soList,String soMd5List,boolean isCheckSoLoaderDirectory){
        try {
            String dynamicSoPath  = getDynamicSoPath(context);
            String soloaderPath = getSoLoaderPath(context);
            boolean isSoloaded1 = isSoLoadedImp(dynamicSoPath,soList,soMd5List);
            if(isSoloaded1){
                if(isCheckSoLoaderDirectory) {
                    isSoloaded1 = isSoLoadedImp(soloaderPath, soList, soMd5List);
                }
            }
            return isSoloaded1;
        }catch (Exception ex){
            ex.printStackTrace();
        }
        return false;
    }

    private boolean isSoLoadedImp(String dynamicSoPath,String soList,String soMd5List){
        // so文件列表
        String []matchSoFileNameArray  = soList.split(",");
        // md5列表
        String []md5Array  = null;
        if(soMd5List!=null){
            md5Array =  soMd5List.split(",");
            if(md5Array.length!=md5Array.length){
                LogUtils.e(TAG,"md5列表和文件列表数量不匹配");
                md5Array = null;
            }
        }
        int length = matchSoFileNameArray.length;
        for(int i=0;i<length;i++){
            String fileSoName = matchSoFileNameArray[i];
            //文件大小校验
            File file = new File(dynamicSoPath+"/"+fileSoName);
            if(!file.exists() || (file.exists() && file.length()==0)){
                LogUtils.e(TAG,dynamicSoPath+"文件夹下不存在此so库:"+fileSoName);
                return false;
            }
            //md5校验
            if(md5Array!=null){
                String fileMd5 = MD5Utils.getFileMD5(file);
                String fileMd5Match = md5Array[i];
                if(!StringUtils.isNull(fileMd5) && !StringUtils.isNull(fileMd5Match) && fileMd5.equalsIgnoreCase(fileMd5Match)){
                    LogUtils.i(TAG,"md5校验通过:"+fileSoName);
                }else{
                    LogUtils.e(TAG,"md5校验失败:"+fileSoName+" fileMd5:"+fileMd5+" fileMd5Match:"+fileMd5Match);
                    return false;
                }
            }
        }
        return true;
    }

    @Deprecated
    public boolean isSoLoaded(Context context,String matchSoFileName){
       return isSoLoaded(context,matchSoFileName,true);
    }

    /*public boolean deleteSoLoaded(Context context,String matchSoFileName){
        try {
            String dynamicSoPath  = getDynamicSoPath(context);
            if(matchSoFileName.contains(",")){
                String []matchSoFileNameArray  = matchSoFileName.split(",");
                for(String fileSoName:matchSoFileNameArray){
                    File file = new File(dynamicSoPath+"/"+fileSoName);
                    if(file.exists()){
                        file.delete();
                    }
                }
                return true;
            }else{
                File file = new File(dynamicSoPath+"/"+matchSoFileName);
                if(file.exists()){
                    file.delete();
                }
                return true;
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }
        return false;
    }*/


    private boolean isSoDownLoading(String url){
        try {
            if(StringUtils.isNull(url)){
                return false;
            }
            if(listUrl.contains(url)){
                return true;
            }
            return false;

        }catch (Exception ex){
            ex.printStackTrace();
        }
        return false;
    }

    private void setSoDownLoading(String url,boolean flag){
        try {
            LogUtils.e(TAG,"setSoDownLoading flag :"+flag+" url:"+url);
            if(StringUtils.isNull(url)){
                return;
            }
            if(flag){
                if(!listUrl.contains(url)){
                    listUrl.add(url);
                    LogUtils.e(TAG,"setSoDownLoading add url:"+url);
                }
            }else{
                if(listUrl.contains(url)){
                    listUrl.remove(url);
                    LogUtils.e(TAG,"setSoDownLoading remove url:"+url);
                }
            }

        }catch (Exception ex){
            ex.printStackTrace();
        }
    }

    private void addSoDownLoadCallBack(String url,CommomCallBack callBack){
        try {
            if(StringUtils.isNull(url) || callBack==null){
                return;
            }
            if(listMapCallBack.containsKey(url)){
                List<CommomCallBack> list = listMapCallBack.get(url);
                if(!list.contains(callBack)){
                    list.add(callBack);
                }
                LogUtils.i(TAG,"addSoDownLoadCallBack contains, size is:"+list.size()+" url:"+url+" listMapCallBack.size:"+listMapCallBack.size());
            }else{
                List<CommomCallBack> list = new ArrayList<>();
                list.add(callBack);
                listMapCallBack.put(url,list);
                LogUtils.i(TAG,"addSoDownLoadCallBack no contains, size is:"+list.size()+" url:"+url+" listMapCallBack.size:"+listMapCallBack.size());
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
    private void notifySoDownloadCallBackAndRemove(String url,boolean flag){
        try {
            if(StringUtils.isNull(url)){
                return;
            }
            if(!listMapCallBack.containsKey(url)){
                return;
            }
            List<CommomCallBack> list = listMapCallBack.get(url);
            if(list!=null){
                for(CommomCallBack commomCallBack:list){
                    try {
                        LogUtils.i(TAG,"notifySoDownloadCallBackAndRemove url:"+url);
                        commomCallBack.onResult(flag);
                    }catch (Exception ex){
                        ex.printStackTrace();
                    }
                }
            }
            //最后移除
            listMapCallBack.remove(url);
            LogUtils.i(TAG,"notifySoDownloadCallBackAndRemove remove url:"+url+" listMapCallBack size:"+listMapCallBack.size());
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
    private void removeDownloadCallBackAndRemove(String url){
        try {
            if(StringUtils.isNull(url)){
                return;
            }
            if(!listMapCallBack.containsKey(url)){
                return;
            }

            //最后移除
            listMapCallBack.remove(url);
            LogUtils.i(TAG,"removeDownloadCallBackAndRemove remove url:"+url+" listMapCallBack size:"+listMapCallBack.size());
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }

    /**
     * 启动下载第三方SO,并拷贝进入 so loader
     * @param context
     */
    public void start(Context context,DynamicSoLoaderParams params, CommomCallBack commomCallBack){
        start(context,params,false,commomCallBack);
    }
    private void start(Context context,DynamicSoLoaderParams params, boolean useBackupUrl,CommomCallBack commomCallBack){
        try {
            if(context==null || params==null){
                return;
            }
            String matchSoFileName = params.getMatchSoFileName();
            String url = useBackupUrl?params.getBackupUrl():params.getUrl();
            boolean copyToSoLoaderDirectory = params.isCopyToSoLoaderDiretory();
            if(StringUtils.isNull(url)){
                LogUtils.e(TAG,"url为空,useBackupUrl:"+useBackupUrl);
                if(commomCallBack!=null){
                    commomCallBack.onResult(false);
                }
                return;
            }
            if(!params.isIgnoreCache()){
                if(isSoLoaded(context,matchSoFileName)){
                    LogUtils.i(TAG,"文件之前已拷贝完毕,无需进行下载和拷贝:"+matchSoFileName);
                    setSoDownLoading(url,false);
                    if(commomCallBack!=null){
                        commomCallBack.onResult(true);
                    }
                    return;
                }
            }

            if(isSoDownLoading(url)){
                LogUtils.e(TAG,"已经在下载中,无需重复执行下载,等待即可");
                addSoDownLoadCallBack(url,commomCallBack);
                return;
            }
            //回调监听
            addSoDownLoadCallBack(url,commomCallBack);
            //设置加载中
            setSoDownLoading(url,true);
            //开始下载
            DynamicSoDiskProducer abstractProducer = new DynamicSoDiskProducer(context,url,matchSoFileName,new AbstractProducer.ProducerListener() {
                @Override
                public void onException(String cache, Exception e) {
                    try {
                        LogUtils.e(TAG,"onException url:"+url +" useBackupUrl:"+useBackupUrl);
                        if(!useBackupUrl){
                            String backupUrl = params.getBackupUrl();
                            if(StringUtils.isNull(backupUrl)){
                                setSoDownLoading(url,false);
                                notifySoDownloadCallBackAndRemove(url,false);
                                return;
                            }
                            if(!url.equals(backupUrl)){
                                LogUtils.e(TAG,"原地址下载失败,现在采用备用地址下载:"+backupUrl);
                                setSoDownLoading(url,false);
                                removeDownloadCallBackAndRemove(url);
                                start(context,params,true,commomCallBack);
                            }else{
                                setSoDownLoading(url,false);
                                notifySoDownloadCallBackAndRemove(url,false);
                                return;
                            }
                        }else{
                            setSoDownLoading(url,false);
                            notifySoDownloadCallBackAndRemove(url,false);
                            return;
                        }

                    }catch (Exception ex){
                        ex.printStackTrace();
                    }

                }

                @Override
                public void onFinish(String cache) {
                    ThreadUtil.addTask(context, new ThreadUtil.ITasker() {
                        @Override
                        public Object onExcute() {
                            try {
                                String sourcePath = cache;
                                String dynamicSoPath  = getDynamicSoPath(context);
                                LogUtils.i(TAG,"回调成功,开始拷贝 source:"+sourcePath+" targetPath:"+dynamicSoPath+" copyToSoLoaderDirectory:"+copyToSoLoaderDirectory);
                                copyFolder(sourcePath,dynamicSoPath);
                                if(copyToSoLoaderDirectory){
                                    String soLoaderDirectory = getSoLoaderPath(context);
                                    LogUtils.i(TAG, "开始拷贝到SoLoader加载路径 source:" + dynamicSoPath + " targetPath:" + soLoaderDirectory, new Object[0]);
                                    copyFolder(sourcePath,soLoaderDirectory);
                                }
                                return true;
                            }catch (Exception ex){
                                ex.printStackTrace();
                                LogUtils.e(TAG,"拷贝异常:"+ex.getMessage());
                            }
                            return false;
                        }

                        @Override
                        public void onFinish(Object o) {
                            setSoDownLoading(url,false);
                            boolean success = (boolean)o;
                            if(success){
                                LogUtils.i(TAG,"拷贝成功");
                            }else{
                                LogUtils.e(TAG,"拷贝失败!!!");
                            }
                            notifySoDownloadCallBackAndRemove(url,success);
                        }
                    });

                }
            });
            //开始预加载
            abstractProducer.produce(null);
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }

    /**
     * 启动下载第三方SO,并拷贝进入 so loader
     * @param context
     * @param url
     */
    @Deprecated
    public void start(Context context, String url, String matchSoFileName, CommomCallBack commomCallBack){
        try {
            DynamicSoLoaderParams params = new DynamicSoLoaderParams();
            params.setUrl(url);
            params.setMatchSoFileName(matchSoFileName);
            start(context,params,commomCallBack);
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }


    private  void installNativeLibraryPath(ClassLoader classLoader, File folder)
            throws Throwable {
        if (folder == null || !folder.exists()) {
            LogUtils.e(TAG, "installNativeLibraryPath, folder %s is illegal", folder);
            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
                LogUtils.e(TAG, "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
                LogUtils.e(TAG, "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);
        }
    }


    /**
            * 复制文件夹(使用缓冲字节流)
     * @param sourcePath 源文件夹路径
     * @param targetPath 目标文件夹路径
     */
    private  void copyFolder(String sourcePath,String targetPath) throws Exception{
        //源文件夹路径
        File sourceFile = new File(sourcePath);
        //目标文件夹路径
        File targetFile = new File(targetPath);

        if(!sourceFile.exists()){
            throw new Exception("文件夹不存在");
        }
        if(!sourceFile.isDirectory()){
            throw new Exception("源文件夹不是目录");
        }
        if(!targetFile.exists()){
            targetFile.mkdirs();
        }
        if(!targetFile.isDirectory()){
            throw new Exception("目标文件夹不是目录");
        }

        File[] files = sourceFile.listFiles();
        if(files == null || files.length == 0){
            throw new Exception("文件夹为空");
        }

        for(File file : files){
            if(file.getName().endsWith(".zip")){
                LogUtils.e(TAG,"zip文件不拷贝:"+file.getAbsolutePath());
                continue;
            }
            //文件要移动的路径
            String movePath = targetFile+File.separator+file.getName();
            if(file.isDirectory()){
                //如果是目录则递归调用
                copyFolder(file.getAbsolutePath(),targetPath);
            }else {
                File fileMove = new File(movePath);
                if(fileMove.exists()){
                    LogUtils.i(TAG,"文件已存在,删除,重新拷贝:"+movePath);
                    fileMove.delete();
                }
                //如果是文件则复制文件
                FileInputStream fileInputStream = null;
                BufferedInputStream in = null;
                FileOutputStream fileOutputStream = null;
                BufferedOutputStream out = null;
                try {
                    fileInputStream = new FileInputStream(file);
                    in = new BufferedInputStream(fileInputStream);
                    fileOutputStream = new FileOutputStream(movePath);
                    out = new BufferedOutputStream(fileOutputStream);
                    byte[] b = new byte[1024];
                    int temp = 0;
                    while((temp = in.read(b)) != -1){
                        out.write(b,0,temp);
                    }

                }catch (Exception ex){
                    ex.printStackTrace();
                }finally {
                    try {
                        if(out!=null){
                            out.close();
                        }
                        if(in!=null){
                            in.close();
                        }
                        if(fileOutputStream!=null){
                            fileOutputStream.close();
                        }
                        if(fileInputStream!=null){
                            fileInputStream.close();
                        }
                    }catch (Exception ex){
                        ex.printStackTrace();
                    }
                }

            }
        }
    }

}