zoukankan      html  css  js  c++  java
  • Android动态加载jar、apk的实现

            前段时间到阿里巴巴参加支付宝技术分享沙龙,看到支付宝在Android使用插件化的技术,挺好奇的。正好这几天看到了农民伯伯的相关文章,因此简单整理了下,有什么错误希望大神指正。

            

           核心类

          1.1      DexClassLoader类
    可以加载jar/apk/dex,可以从SD卡中加载为安装的apk。
     1.2      PathClassLoader类
    只能加载已经安装到Android系统中的apk文件。


        一、正文

           1.1 动态加载jar

       类似于eclipse的插件化实现, 首先定义好接口, 用户实现接口功能后即可通过动态加载的方式载入jar文件, 以实现具体功能。注意, 这里的jar包需要经过android dx工具的处理 , 否则不能使用。

    首先我们定义如下接口 :

    package com.example.interf;  
    /** 
     * @Title: ILoader.java 
     * @Package com.example.loadjardemo 
     * @Description:  通用接口, 需要用户实现
     * @version V1.0 
     */
    public interface ILoader {
    	 public String sayHi();
    }
      

    用户需实现,该接口, 并且将工程导出为jar包的形式。

    示例如下 :

    public class JarLoader implements ILoader {
    
    	public JarLoader() {
    	}
    
    	@Override
    	public String sayHi() {
    		return "I am jar loader.";
    	}
    
    }


    最后, 实现功能的代码打包成jar包 :

    首先选中工程, 右键后选择“导出”, 然后选择“java”-----“jar文件”, 然后将你的具体功能实现类导出为jar,文件名为loader.jar,如下图所示 : 


    将打包好的jar拷贝到SDK安装目录android-sdk-windowsplatform-tools下,DOS进入这个目录,执行如下命令:

    dx --dex --output=loader_dex.jar loader.jar

    然后将loader_dex.jar放到android手机中, 这里我们放到SD卡根目录下。


    动态加载代码 : 

    	/**
    	 * 
    	 * @Title: loadJar 
    	 * @Description: 项目工程中必须定义接口, 而被引入的第三方jar包实现这些接口,然后进行动态加载 。
    	 *          相当于第三方按照接口协议来开发, 使得第三方应用可以以插件的形式动态加载到应用平台中。
    	 * @return void    
    	 * @throws
    	 */
    	private void loadJar(){
            final File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().toString()
                    + File.separator + "loader_dex.jar");
                
                BaseDexClassLoader cl = new BaseDexClassLoader(Environment.getExternalStorageDirectory().toString(),
                		optimizedDexOutputPath, optimizedDexOutputPath.getAbsolutePath(), getClassLoader());
                Class libProviderClazz = null;
                
                try {
                    // 载入JarLoader类, 并且通过反射构建JarLoader对象, 然后调用sayHi方法
                    libProviderClazz = cl.loadClass("com.example.interf.JarLoader");
                    ILoader loader = (ILoader)libProviderClazz.newInstance();
                    Toast.makeText(MainActivity.this, loader.sayHi() , Toast.LENGTH_SHORT).show();
                } catch (Exception exception) {
                    // Handle exception gracefully here.
                    exception.printStackTrace();
                }
    	}


    效果如下图所示 : 
    请等待...



    1.2 加载未安装的apk

         首先新建一个Android项目, 定义如下接口 : 

        

    public interface ISayHello {
    	public String sayHello()  ;
    }

        

    定义一个Activity实现该接口, 如下:

    package com.example.loaduninstallapkdemo;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Toast;
    
    /**
     * 
     * @ClassName: UninstallApkActivity 
     * @Description: 这是被动态加载的Activity类
     *
     */
    public class UninstallApkActivity extends Activity implements ISayHello{
    
    	private View mShowView = null ;
    	
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		
    		mShowView = findViewById(R.id.show) ;
    		mShowView.setOnClickListener(new OnClickListener() {
    			
    			@Override
    			public void onClick(View v) {
    				Toast.makeText(UninstallApkActivity.this, "这是已安装的apk被动态加载了", Toast.LENGTH_SHORT).show();
    			}
    		}) ;
    	}
    
    	@Override
    	public boolean onCreateOptionsMenu(Menu menu) {
    		// Inflate the menu; this adds items to the action bar if it is present.
    		getMenuInflater().inflate(R.menu.activity_main, menu);
    		return true;
    	}
    
    	@Override
    	public String sayHello(){
    		return "Hello, this apk is not installed";
    	}
    }
    

    然后将该编译生apk, 并且将该apk拷贝到SD卡根目录下。

    动态加载未安装的apk

    	/**
    	 * 
    	 * @Title: loadUninstallApk 
    	 * @Description: 动态加载未安装的apk
    	 * @return void    
    	 * @throws
    	 */
    	private void loadUninstallApk(){
            String path = Environment.getExternalStorageDirectory() + File.separator;
            String filename = "LoadUninstallApkDemo.apk";
    
            // 4.1以后不能够将optimizedDirectory设置到sd卡目录, 否则抛出异常.
            File optimizedDirectoryFile = getDir("dex", 0) ;
            DexClassLoader classLoader = new DexClassLoader(path + filename, optimizedDirectoryFile.getAbsolutePath(),
                    											null, getClassLoader());
    
            try {
            	// 通过反射机制调用, 包名为com.example.loaduninstallapkdemo, 类名为UninstallApkActivity
                Class mLoadClass = classLoader.loadClass("com.example.loadunstallapkdemo.UninstallApkActivity");
                Constructor constructor = mLoadClass.getConstructor(new Class[] {});
                Object testActivity = constructor.newInstance(new Object[] {});
                
                // 获取sayHello方法
                Method helloMethod = mLoadClass.getMethod("sayHello", null);
                helloMethod.setAccessible(true);
                Object content = helloMethod.invoke(testActivity, null);
                Toast.makeText(MainActivity.this, content.toString(), Toast.LENGTH_LONG).show();
                
            } catch (Exception e) {
                e.printStackTrace();
            }
    	}


    DexClassLoader 注意点 :

    A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.

    This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) to create such a directory:

       File dexOutputDir = context.getDir("dex",0);
     

    Do not cache optimized classes on external storage. External storage does not provide access controls necessary to protect your application from code injection attacks.


    效果如图 : 



    1.3 加载已安装的apk

    将1.2中的apk安装到手机中,我的例子中,该apk的包名为“com.example.loaduninstallapkdemo”,Activity名为"UninstallApkActivity". 加载代码如下 :

    	/**
    	 * 
    	 * @Title: loadInstalledApk 
    	 * @Description: 动态加载已安装的apk    
    	 * @return void    
    	 * @throws
    	 */
    	private void loadInstalledApk() {
    		try {
    			String pkgName = "com.example.loaduninstallapkdemo";
    			Context context = createPackageContext(pkgName,
    			        Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE) ;
    			
    			// 获取动态加载得到的资源
    			Resources resources = context.getResources() ;
    			// 过去该apk中的字符串资源"tips", 并且toast出来,apk换肤的实现就是这种原理
    			String toast = resources.getString(resources.getIdentifier("tips", "string", pkgName) ) ;
    			Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show() ;
    			
    			Class cls = context.getClassLoader().loadClass(pkgName + ".UninstallApkActivity") ;
    			// 跳转到该Activity
    			startActivity(new Intent(context, cls)) ;
    		} catch (NameNotFoundException e) {
    			e.printStackTrace();
    		}catch (ClassNotFoundException e) {
    			Log.d("", e.toString()) ;
    		}
    	}

    效果如图:

    消息被Toast出来, 并且跳转到了目标Activity. 

  • 相关阅读:
    MySql数据库水平扩展过程
    (转)MySQL数据库水平切分的实现原理解析
    SVN安装使用
    servlet基础
    数据库读写分离的性能分析
    java的可变长参数
    java消息服务
    static/final成员与多态
    商业软件与开源软件
    托管堆
  • 原文地址:https://www.cnblogs.com/riskyer/p/3323000.html
Copyright © 2011-2022 走看看