Android插件化开发之用DexClassLoader加载未安装的APK资源文件来实现app切换背景皮肤(2)

简介: Android插件化开发之用DexClassLoader加载未安装的APK资源文件来实现app切换背景皮肤(2)

第五步、爆出所有代码(为了详细点)

package com.chenyu.dexclassloaderapk;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.example.dexclassloaderapk.R;
import dalvik.system.DexClassLoader;
public class MainActivity extends ActionBarActivity {
    public static final String TAG = "DexClassLoaderApk";
    public static final String PKG_NAME = "pkgName";
    public static final String APK_PATH = "testClassLoader.apk";
    public static final String ADDSSETPATH = "addAssetPath";
    public static final String DEX = "dex";
    //这个IMAGE_ID是只我放入手机里面APK 在代码里面这个图片的ID,这里我们拿到之后,然后去替换北京图片
    public static final String IMAGE_ID = "about_log";
    public static final String DRAWABLE = ".R$drawable";
    public TextView mTextView;
    //背景的布局
    public RelativeLayout mLayout;
    public Map<String, String> apkInfo;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final String apkPath = Environment.getExternalStorageDirectory().toString() + File.separator + APK_PATH;
    mTextView = (TextView)findViewById(R.id.text);
    mLayout = (RelativeLayout)findViewById(R.id.re_Layout);
    mTextView.setOnClickListener(new OnClickListener(){
      @Override
      public void onClick(View v) {
        //一定要记得加上android.permission.READ_EXTERNAL_STORAGE权限,不然死活都拿不到数据
        //我就换了一个这个错误,如果发现代码没问题,网上找也没问题,这个时候应该思考是不是没有加权限
        apkInfo = getUninstallApkInfo(MainActivity.this, apkPath);
        String packageName = apkInfo.get(PKG_NAME);
        if (null != packageName) {
          try {
            dynamicLoadApk(apkPath, packageName);
          } catch (Exception e) {
            e.printStackTrace();
            Log.i(TAG, "change image fail");
          }
        } else {
          Log.i(TAG, "package is null");
        }
      }
    });
  }
    /** 
     * 获取未安装apk的信息 
     * @param context 
     * @param apkPath apk文件的path 
     * @return 
     */  
    private Map<String,String> getUninstallApkInfo(Context context, String apkPath) {  
      Map hashMap = new HashMap<String,String>();
        PackageManager pm = context.getPackageManager();  
        PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);  
        if (null != pkgInfo) {  
            ApplicationInfo appInfo = pkgInfo.applicationInfo;  
            String pkgName = appInfo.packageName;//包名  
            hashMap.put(PKG_NAME, pkgName);
        } else {
          Log.d(TAG, "program don't get apk package information");
        }  
        return hashMap;  
    }  
    /** 
     * @param apkPath  
     * @return 得到对应插件的Resource对象 
     */  
    private Resources getPluginResources(String apkPath) {  
      try {  
            AssetManager assetManager = AssetManager.class.newInstance();  
            //反射调用方法addAssetPath(String path)
            Method addAssetPath = assetManager.getClass().getMethod(ADDSSETPATH, String.class);  
            //将未安装的Apk文件的添加进AssetManager中,第二个参数是apk的路径  
            addAssetPath.invoke(assetManager, apkPath);
            Resources superRes = this.getResources();  
            Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());  
            return mResources;  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
    /** 
     * 加载apk获得内部资源,并且替换背景
     * @param apkDir apk目录 
     * @param apkName apk名字,带.apk 
     * @throws Exception 
     */  
    private void dynamicLoadApk(String apkPath, String apkPackageName) throws Exception {
      //在应用安装目录下创建一个名为app_dex文件夹目录,如果已经存在则不创建,这个目录主要是最优化目录,用于缓存dex文件
        File optimizedDirectoryFile = getDir(DEX, Context.MODE_PRIVATE); 
        //打印路径 理论上是/data/data/package/app_dex
        Log.v(TAG, optimizedDirectoryFile.getPath().toString());  
        //构建DexClassLoader
        DexClassLoader dexClassLoader = new DexClassLoader(apkPath, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader());  
        //通过使用apk自己的类加载器,反射出R类中相应的内部类进而获取我们需要的资源id
        Class<?> clazz = dexClassLoader.loadClass(apkPackageName + DRAWABLE);  
        //得到名为about_log的这张图片字段,这个图片是为安装apk里面的图片
        Field field = clazz.getDeclaredField(IMAGE_ID); 
        //得到图片id  
        int resId = field.getInt(R.id.class);
        //得到插件apk中的Resource  
        Resources mResources = getPluginResources(apkPath);
        if (mResources != null) {  
            //通过插件apk中的Resource得到resId对应的资源  
            Drawable btnDrawable = mResources.getDrawable(resId);
            mLayout.setBackgroundDrawable(btnDrawable);   
        } else {
          Log.d(TAG, "mResources is null");
        }
    }  
}
  dynamicLoadApk(apkPath, packageName);
          } catch (Exception e) {
            e.printStackTrace();
            Log.i(TAG, "change image fail");
          }
        } else {
          Log.i(TAG, "package is null");
        }
      }
    });
  }
    /** 
     * 获取未安装apk的信息 
     * @param context 
     * @param apkPath apk文件的path 
     * @return 
     */  
    private Map<String,String> getUninstallApkInfo(Context context, String apkPath) {  
      Map hashMap = new HashMap<String,String>();
        PackageManager pm = context.getPackageManager();  
        PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);  
        if (null != pkgInfo) {  
            ApplicationInfo appInfo = pkgInfo.applicationInfo;  
            String pkgName = appInfo.packageName;//包名  
            hashMap.put(PKG_NAME, pkgName);
        } else {
          Log.d(TAG, "program don't get apk package information");
        }  
        return hashMap;  
    }  
    /** 
     * @param apkPath  
     * @return 得到对应插件的Resource对象 
     */  
    private Resources getPluginResources(String apkPath) {  
      try {  
            AssetManager assetManager = AssetManager.class.newInstance();  
            //反射调用方法addAssetPath(String path)
            Method addAssetPath = assetManager.getClass().getMethod(ADDSSETPATH, String.class);  
            //将未安装的Apk文件的添加进AssetManager中,第二个参数是apk的路径  
            addAssetPath.invoke(assetManager, apkPath);
            Resources superRes = this.getResources();  
            Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());  
            return mResources;  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
    /** 
     * 加载apk获得内部资源,并且替换背景
     * @param apkDir apk目录 
     * @param apkName apk名字,带.apk 
     * @throws Exception 
     */  
    private void dynamicLoadApk(String apkPath, String apkPackageName) throws Exception {
      //在应用安装目录下创建一个名为app_dex文件夹目录,如果已经存在则不创建,这个目录主要是最优化目录,用于缓存dex文件
        File optimizedDirectoryFile = getDir(DEX, Context.MODE_PRIVATE); 
        //打印路径 理论上是/data/data/package/app_dex
        Log.v(TAG, optimizedDirectoryFile.getPath().toString());  
        //构建DexClassLoader
        DexClassLoader dexClassLoader = new DexClassLoader(apkPath, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader());  
        //通过使用apk自己的类加载器,反射出R类中相应的内部类进而获取我们需要的资源id
        Class<?> clazz = dexClassLoader.loadClass(apkPackageName + DRAWABLE);  
        //得到名为about_log的这张图片字段,这个图片是为安装apk里面的图片
        Field field = clazz.getDeclaredField(IMAGE_ID); 
        //得到图片id  
        int resId = field.getInt(R.id.class);
        //得到插件apk中的Resource  
        Resources mResources = getPluginResources(apkPath);
        if (mResources != null) {  
            //通过插件apk中的Resource得到resId对应的资源  
            Drawable btnDrawable = mResources.getDrawable(resId);
            mLayout.setBackgroundDrawable(btnDrawable);   
        } else {
          Log.d(TAG, "mResources is null");
        }
    }  
}

dynamicLoadApk(apkPath, packageName);

    } catch (Exception e) {

     e.printStackTrace();

     Log.i(TAG, "change image fail");

    }

   } else {

    Log.i(TAG, "package is null");

   }

  }

 });

}

 

   /**

    * 获取未安装apk的信息

    * @param context

    * @param apkPath apk文件的path

    * @return

    */  

   private Map<String,String> getUninstallApkInfo(Context context, String apkPath) {  

    Map hashMap = new HashMap<String,String>();

       PackageManager pm = context.getPackageManager();  

       PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);  

       if (null != pkgInfo) {  

           ApplicationInfo appInfo = pkgInfo.applicationInfo;  

           String pkgName = appInfo.packageName;//包名  

           hashMap.put(PKG_NAME, pkgName);

       } else {

        Log.d(TAG, "program don't get apk package information");

       }  

       return hashMap;  

   }  

 

   /**

    * @param apkPath  

    * @return 得到对应插件的Resource对象

    */  

   private Resources getPluginResources(String apkPath) {  

    try {  

           AssetManager assetManager = AssetManager.class.newInstance();  

           //反射调用方法addAssetPath(String path)

           Method addAssetPath = assetManager.getClass().getMethod(ADDSSETPATH, String.class);  

           //将未安装的Apk文件的添加进AssetManager中,第二个参数是apk的路径  

           addAssetPath.invoke(assetManager, apkPath);

           Resources superRes = this.getResources();  

           Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());  

           return mResources;  

       } catch (Exception e) {  

           e.printStackTrace();  

       }  

       return null;  

   }  

 

   /**

    * 加载apk获得内部资源,并且替换背景

    * @param apkDir apk目录

    * @param apkName apk名字,带.apk

    * @throws Exception

    */  

   private void dynamicLoadApk(String apkPath, String apkPackageName) throws Exception {

    //在应用安装目录下创建一个名为app_dex文件夹目录,如果已经存在则不创建,这个目录主要是最优化目录,用于缓存dex文件

       File optimizedDirectoryFile = getDir(DEX, Context.MODE_PRIVATE);

       //打印路径 理论上是/data/data/package/app_dex

       Log.v(TAG, optimizedDirectoryFile.getPath().toString());  

       //构建DexClassLoader

       DexClassLoader dexClassLoader = new DexClassLoader(apkPath, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader());  

       //通过使用apk自己的类加载器,反射出R类中相应的内部类进而获取我们需要的资源id

       Class<?> clazz = dexClassLoader.loadClass(apkPackageName + DRAWABLE);  

       //得到名为about_log的这张图片字段,这个图片是为安装apk里面的图片

       Field field = clazz.getDeclaredField(IMAGE_ID);

       //得到图片id  

       int resId = field.getInt(R.id.class);

       //得到插件apk中的Resource  

       Resources mResources = getPluginResources(apkPath);

       if (mResources != null) {  

           //通过插件apk中的Resource得到resId对应的资源  

           Drawable btnDrawable = mResources.getDrawable(resId);

点击TextView内容“换皮肤”来触发的,当初背景是设置的一个机器人。

第六步:运行项目爆结果照片

点击换皮护之前背景图片如下

1.png

1.png


ok,说明获取到了这种图片资源,换皮肤成功,这里只是代表换皮肤意思,效果比较丑,不要喷哈。

第七步、总结

这样做资源和宿主分离了,减轻了apk负担,同时也有解耦和作用,我们手机一些浏览器换模式(日和夜)、QQ换皮肤、表情包、线上下载线下维护、是项目更加灵活,可扩展性更好,同时也复习了DexClassLoader和反射相关知识。


相关文章
|
23天前
|
JSON 小程序 JavaScript
uni-app开发微信小程序的报错[渲染层错误]排查及解决
uni-app开发微信小程序的报错[渲染层错误]排查及解决
353 7
|
23天前
|
小程序 JavaScript 前端开发
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
429 1
|
8天前
|
编解码 Java Android开发
通义灵码:在安卓开发中提升工作效率的真实应用案例
本文介绍了通义灵码在安卓开发中的应用。作为一名97年的聋人开发者,我在2024年Google Gemma竞赛中获得了冠军,拿下了很多项目竞赛奖励,通义灵码成为我的得力助手。文章详细展示了如何安装通义灵码插件,并通过多个实例说明其在适配国际语言、多种分辨率、业务逻辑开发和编程语言转换等方面的应用,显著提高了开发效率和准确性。
|
7天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
19 5
|
5天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
6天前
|
缓存 数据库 Android开发
安卓开发中的性能优化技巧
【10月更文挑战第29天】在移动应用的海洋中,性能是船只能否破浪前行的关键。本文将深入探讨安卓开发中的性能优化策略,从代码层面到系统层面,揭示如何让应用运行得更快、更流畅。我们将以实际案例和最佳实践为灯塔,引领开发者避开性能瓶颈的暗礁。
21 3
|
9天前
|
存储 IDE 开发工具
探索Android开发之旅:从新手到专家
【10月更文挑战第26天】在这篇文章中,我们将一起踏上一段激动人心的旅程,探索如何在Android平台上从零开始,最终成为一名熟练的开发者。通过简单易懂的语言和实际代码示例,本文将引导你了解Android开发的基础知识、关键概念以及如何实现一个基本的应用程序。无论你是编程新手还是希望扩展你的技术栈,这篇文章都将为你提供价值和启发。让我们开始吧!
|
9天前
|
小程序 数据挖掘 UED
开发1个上门家政小程序APP系统,都有哪些功能?
在快节奏的现代生活中,家政服务已成为许多家庭的必需品。针对传统家政服务存在的问题,如服务质量不稳定、价格不透明等,我们历时两年开发了一套全新的上门家政系统。该系统通过完善信用体系、提供奖励机制、优化复购体验、多渠道推广和多样化盈利模式,解决了私单、复购、推广和盈利四大痛点,全面提升了服务质量和用户体验,旨在成为家政行业的领导者。
|
14天前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
43 5
|
13天前
|
设计模式 IDE Java
探索安卓开发:从新手到专家的旅程
【10月更文挑战第22天】 在数字时代的浪潮中,移动应用开发如同一座金矿,吸引着无数探险者。本文将作为你的指南针,指引你进入安卓开发的广阔天地。我们将一起揭开安卓平台的神秘面纱,从搭建开发环境到掌握核心概念,再到深入理解安卓架构。无论你是初涉编程的新手,还是渴望进阶的开发者,这段旅程都将为你带来宝贵的知识和经验的财富。让我们开始吧!