精华内容
参与话题
问答
  • 初始化加载数据: <table id="table-large-columns" data-toggle="table... 添加了一个按钮,想改变URL,重新加载数据: ...button onclick="loadTable();...请大虾帮帮忙,如何实现改变URL,再动态加载数据。谢谢。
  • 应用开发人员通过标准接口动态加载终端上的jar包调用 接口的实现。 目前遇到的问题是当我在标准接口中写安卓动态加载的方法DexClassLoader()时, 最后一个参数写getClassLoader()报错,如图: ![图片说明]...
  • **官方说 这个方法要在页面加载的时候加载 也就是说 只能页面生成的时候 生成一个富文本 不能用点击事件生成 或者 其他动态加载 但是网上看到个帖子说可以在方法中调用 但是看不懂 哪个大神帮忙解决一下问题** ...
  • QT 动态加载dll

    万次阅读 2020-04-28 09:03:26
    最近在调用卡尔的库函数时候,发现他们仅仅提供了一个dll,于是百度调研QT动态加载dll的方式,在调用成功后分析给大家。 动态调用。在使用的时候才会加载dll进来,使用QT本身自己的加载机制,利用QLibrary进行动态...

    最近在调用卡尔的库函数时候,发现他们仅仅提供了一个dll,于是百度调研QT动态加载dll的方式,在调用成功后分析给大家。

    动态调用。在使用的时候才会加载dll进来,使用QT本身自己的加载机制,利用QLibrary进行动态加载。
     

    typedef void (*CB_AnswerData)(const char *pBuf,int iLen);
    typedef int (*FunCardcallback)(CB_AnswerData );

    void cardInfoCallback(const char *pBuf,int iLen)
    {
    }

    void MainWindow::on_pushButton_3_clicked()
    {
        if(true)
        {
            QLibrary lib("KeUsbHid.dll");

            if(lib.load())
            {

                qDebug() << "load DLL succeed";

                //usb回调
                try
                {
                    FunUSBcallback usbBack=(FunUSBcallback)lib.resolve("_KERECEIVER_SetUSBStatusCallBack@4");
                   
                }
                catch (...)
                {
                    qDebug() << "usb   catch---------------------catch";
                }
            }
            else
            {

            }
        }
    }

    注意事项:

    (1)dll中的函数我们在调用过程中,需要在调用类中重新写一个带有形参个数和类型都一样的函数,就像本文的FunCardcallback,它在这里是作为回调函数使用的。

    (2)包含头文件#include "QLibrary"。

    (3)把调用的dll放在exe所在路径中。

    (4)分享一个小工具用于查看dll的函数原型,Stud_PE.exe。这个可以查看dll的导出表。

    展开全文
  • 动态加载数据源关键一点:在系统运行时加载多一个数据源,加载此数据源的对象必须和系统启动时加载数据源的同一个,在我这里加载数据源的对象是类DynamicDataSource,可在这里添加一个加载数据源的方法: ...
  • Android apk动态加载机制的研究

    万次阅读 多人点赞 2014-03-30 21:53:00
    问题是这样的:我们知道,...我们的想法是这样的,首先要明白apk未安装是不能被直接调起来的,但是我们可以采用一个程序(称之为宿主程序)去动态加载apk文件并将其放在自己的进程中执行,本文要介绍的就是这么一种方法

    转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/22597587 (来自singwhatiwanna的csdn博客)

    背景

    问题是这样的:我们知道,apk必须安装才能运行,如果不安装要是也能运行该多好啊,事实上,这不是完全不可能的,尽管它比较难实现。在理论层面上,我们可以通过一个宿主程序来运行一些未安装的apk,当然,实践层面上也能实现,不过这对未安装的apk有要求。我们的想法是这样的,首先要明白apk未安装是不能被直接调起来的,但是我们可以采用一个程序(称之为宿主程序)去动态加载apk文件并将其放在自己的进程中执行,本文要介绍的就是这么一种方法,同时这种方法还有很多问题,尤其是资源的访问。因为将apk加载到宿主程序中去执行,就无法通过宿主程序的Context去取到apk中的资源,比如图片、文本等,这是很好理解的,因为apk已经不存在上下文了,它执行时所采用的上下文是宿主程序的上下文,用别人的Context是无法得到自己的资源的,不过这个问题貌似可以这么解决:将apk中的资源解压到某个目录,然后通过文件去操作资源,这只是理论上可行,实际上还是会有很多的难点的。除了资源存取的问题,还有一个问题是activity的生命周期,因为apk被宿主程序加载执行后,它的activity其实就是一个普通的类,正常情况下,activity的生命周期是由系统来管理的,现在被宿主程序接管了以后,如何替代系统对apk中的activity的生命周期进行管理是有难度的,不过这个问题比资源的访问好解决一些,比如我们可以在宿主程序中模拟activity的生命周期并合适地调用apk中activity的生命周期方法。本文暂时不对这两个问题进行解决,因为很难,本文仅仅对apk的动态执行机制进行介绍,尽管如此,听起来还是有点小激动,不是吗?

    工作原理

    如下图所示,首先宿主程序会到文件系统比如sd卡去加载apk,然后通过一个叫做proxy的activity去执行apk中的activity。

    关于动态加载apk,理论上可以用到的有DexClassLoader、PathClassLoader和URLClassLoader。

    DexClassLoader :可以加载文件系统上的jar、dex、apk

    PathClassLoader :可以加载/data/app目录下的apk,这也意味着,它只能加载已经安装的apk

    URLClassLoader :可以加载java中的jar,但是由于dalvik不能直接识别jar,所以此方法在android中无法使用,尽管还有这个类

    关于jar、dex和apk,dex和apk是可以直接加载的,因为它们都是或者内部有dex文件,而原始的jar是不行的,必须转换成dalvik所能识别的字节码文件,转换工具可以使用android sdk中platform-tools目录下的dx

    转换命令 :dx --dex --output=dest.jar src.jar

    示例

    宿主程序的实现

    1. 主界面很简单,放了一个button,点击就会调起apk,我把apk直接放在了sd卡中,至于先把apk从网上下载到本地再加载其实是一个道理。

        @Override
        public void onClick(View v) {
            if (v == mOpenClient) {
                Intent intent = new Intent(this, ProxyActivity.class);
                intent.putExtra(ProxyActivity.EXTRA_DEX_PATH, "/mnt/sdcard/DynamicLoadHost/plugin.apk");
                startActivity(intent);
            }
    
        }

    点击button以后,proxy会被调起,然后加载apk并调起的任务就交给它了

    2. 代理activity的实现(proxy)

    package com.ryg.dynamicloadhost;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    
    import dalvik.system.DexClassLoader;
    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.content.pm.PackageInfo;
    import android.os.Bundle;
    import android.util.Log;
    
    public class ProxyActivity extends Activity {
    
        private static final String TAG = "ProxyActivity";
    
        public static final String FROM = "extra.from";
        public static final int FROM_EXTERNAL = 0;
        public static final int FROM_INTERNAL = 1;
    
        public static final String EXTRA_DEX_PATH = "extra.dex.path";
        public static final String EXTRA_CLASS = "extra.class";
    
        private String mClass;
        private String mDexPath;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mDexPath = getIntent().getStringExtra(EXTRA_DEX_PATH);
            mClass = getIntent().getStringExtra(EXTRA_CLASS);
    
            Log.d(TAG, "mClass=" + mClass + " mDexPath=" + mDexPath);
            if (mClass == null) {
                launchTargetActivity();
            } else {
                launchTargetActivity(mClass);
            }
        }
    
        @SuppressLint("NewApi")
        protected void launchTargetActivity() {
            PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(
                    mDexPath, 1);
            if ((packageInfo.activities != null)
                    && (packageInfo.activities.length > 0)) {
                String activityName = packageInfo.activities[0].name;
                mClass = activityName;
                launchTargetActivity(mClass);
            }
        }
    
        @SuppressLint("NewApi")
        protected void launchTargetActivity(final String className) {
            Log.d(TAG, "start launchTargetActivity, className=" + className);
            File dexOutputDir = this.getDir("dex", 0);
            final String dexOutputPath = dexOutputDir.getAbsolutePath();
            ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
            DexClassLoader dexClassLoader = new DexClassLoader(mDexPath,
                    dexOutputPath, null, localClassLoader);
            try {
                Class<?> localClass = dexClassLoader.loadClass(className);
                Constructor<?> localConstructor = localClass
                        .getConstructor(new Class[] {});
                Object instance = localConstructor.newInstance(new Object[] {});
                Log.d(TAG, "instance = " + instance);
    
                Method setProxy = localClass.getMethod("setProxy",
                        new Class[] { Activity.class });
                setProxy.setAccessible(true);
                setProxy.invoke(instance, new Object[] { this });
    
                Method onCreate = localClass.getDeclaredMethod("onCreate",
                        new Class[] { Bundle.class });
                onCreate.setAccessible(true);
                Bundle bundle = new Bundle();
                bundle.putInt(FROM, FROM_EXTERNAL);
                onCreate.invoke(instance, new Object[] { bundle });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }

    说明:程序不难理解,思路是这样的:采用DexClassLoader去加载apk,然后如果没有指定class,就调起主activity,否则调起指定的class。activity被调起的过程是这样的:首先通过类加载器去加载apk中activity的类并创建一个新对象,然后通过反射去调用这个对象的setProxy方法和onCreate方法,setProxy方法的作用是将activity内部的执行全部交由宿主程序中的proxy(也是一个activity),onCreate方法是activity的入口,setProxy以后就调用onCreate方法,这个时候activity就被调起来了。

    待执行apk的实现

    1. 为了让proxy全面接管apk中所有activity的执行,需要为activity定义一个基类BaseActivity,在基类中处理代理相关的事情,同时BaseActivity还对是否使用代理进行了判断,如果不使用代理,那么activity的逻辑仍然按照正常的方式执行,也就是说,这个apk既可以按照执行,也可以由宿主程序来执行。

    package com.ryg.dynamicloadclient;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.view.ViewGroup.LayoutParams;
    
    public class BaseActivity extends Activity {
    
        private static final String TAG = "Client-BaseActivity";
    
        public static final String FROM = "extra.from";
        public static final int FROM_EXTERNAL = 0;
        public static final int FROM_INTERNAL = 1;
        public static final String EXTRA_DEX_PATH = "extra.dex.path";
        public static final String EXTRA_CLASS = "extra.class";
    
        public static final String PROXY_VIEW_ACTION = "com.ryg.dynamicloadhost.VIEW";
        public static final String DEX_PATH = "/mnt/sdcard/DynamicLoadHost/plugin.apk";
    
        protected Activity mProxyActivity;
        protected int mFrom = FROM_INTERNAL;
    
        public void setProxy(Activity proxyActivity) {
            Log.d(TAG, "setProxy: proxyActivity= " + proxyActivity);
            mProxyActivity = proxyActivity;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            if (savedInstanceState != null) {
                mFrom = savedInstanceState.getInt(FROM, FROM_INTERNAL);
            }
            if (mFrom == FROM_INTERNAL) {
                super.onCreate(savedInstanceState);
                mProxyActivity = this;
            }
            Log.d(TAG, "onCreate: from= " + mFrom);
        }
    
        protected void startActivityByProxy(String className) {
            if (mProxyActivity == this) {
                Intent intent = new Intent();
                intent.setClassName(this, className);
                this.startActivity(intent);
            } else {
                Intent intent = new Intent(PROXY_VIEW_ACTION);
                intent.putExtra(EXTRA_DEX_PATH, DEX_PATH);
                intent.putExtra(EXTRA_CLASS, className);
                mProxyActivity.startActivity(intent);
            }
        }
    
        @Override
        public void setContentView(View view) {
            if (mProxyActivity == this) {
                super.setContentView(view);
            } else {
                mProxyActivity.setContentView(view);
            }
        }
    
        @Override
        public void setContentView(View view, LayoutParams params) {
            if (mProxyActivity == this) {
                super.setContentView(view, params);
            } else {
                mProxyActivity.setContentView(view, params);
            }
        }
    
        @Deprecated
        @Override
        public void setContentView(int layoutResID) {
            if (mProxyActivity == this) {
                super.setContentView(layoutResID);
            } else {
                mProxyActivity.setContentView(layoutResID);
            }
        }
    
        @Override
        public void addContentView(View view, LayoutParams params) {
            if (mProxyActivity == this) {
                super.addContentView(view, params);
            } else {
                mProxyActivity.addContentView(view, params);
            }
        }
    }

    说明:相信大家一看代码就明白了,其中setProxy方法的作用就是为了让宿主程序能够接管自己的执行,一旦被接管以后,其所有的执行均通过proxy,且Context也变成了宿主程序的Context,也许这么说比较形象:宿主程序其实就是个空壳,它只是把其它apk加载到自己的内部去执行,这也就更能理解为什么资源访问变得很困难,你会发现好像访问不到apk中的资源了,的确是这样的,但是目前我还没有很好的方法去解决。
    2. 入口activity的实现

    public class MainActivity extends BaseActivity {
    
        private static final String TAG = "Client-MainActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            initView(savedInstanceState);
        }
    
        private void initView(Bundle savedInstanceState) {
            mProxyActivity.setContentView(generateContentView(mProxyActivity));
        }
    
        private View generateContentView(final Context context) {
            LinearLayout layout = new LinearLayout(context);
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
                    LayoutParams.MATCH_PARENT));
            layout.setBackgroundColor(Color.parseColor("#F79AB5"));
            Button button = new Button(context);
            button.setText("button");
            layout.addView(button, LayoutParams.MATCH_PARENT,
                    LayoutParams.WRAP_CONTENT);
            button.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(context, "you clicked button",
                            Toast.LENGTH_SHORT).show();
                    startActivityByProxy("com.ryg.dynamicloadclient.TestActivity");
                }
            });
            return layout;
        }
    
    }

    说明:由于访问不到apk中的资源了,所以界面是代码写的,而不是写在xml中,因为xml读不到了,这也是个大问题。注意到主界面中有一个button,点击后跳到了另一个activity,这个时候是不能直接调用系统的startActivity方法的,而是必须通过宿主程序中的proxy来执行,原因很简单,首先apk本书没有Context,所以它无法调起activity,另外由于这个子activity是apk中的,通过宿主程序直接调用它也是不行的,因为它对宿主程序来说是不可见的,所以只能通过proxy来调用,是不是感觉很麻烦?但是,你还有更好的办法吗?

    3. 子activity的实现

    package com.ryg.dynamicloadclient;
    
    import android.graphics.Color;
    import android.os.Bundle;
    import android.view.ViewGroup.LayoutParams;
    import android.widget.Button;
    
    public class TestActivity extends BaseActivity{
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Button button = new Button(mProxyActivity);
            button.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
                    LayoutParams.MATCH_PARENT));
            button.setBackgroundColor(Color.YELLOW);
            button.setText("这是测试页面");
            setContentView(button);
        }
    
    }

    说明:代码很简单,不用介绍了,同理,界面还是用代码来写的。

    运行效果

    1. 首先看apk安装时的运行效果

    2. 再看看未安装时被宿主程序执行的效果

    说明:可以发现,安装和未安装,执行效果是一样的,差别在于:首先未安装的时候由于采用了反射,所以执行效率会略微降低,其次,应用的标题发生了改变,也就是说,尽管apk被执行了,但是它毕竟是在宿主程序里面执行的,所以它还是属于宿主程序的,因此apk未安装被执行时其标题不是自己的,不过这也可以间接证明,apk的确被宿主程序执行了,不信看标题。最后,我想说一下这么做的意义,这样做有利于实现模块化,同时还可以实现插件机制,但是问题还是很多的,最复杂的两个问题:资源的访问和activity生命周期的管理,期待大家有好的解决办法,欢迎交流。

    代码下载:

    https://github.com/singwhatiwanna/dynamic-load-apk

    http://download.csdn.net/detail/singwhatiwanna/7121505

    展开全文
  • Lodop 动态加载模板,动态加载数据

    千次阅读 2017-09-26 18:00:26
    最近需要使用Lodop打印控件,所以就研究了一下,期间从网上找了诸多的东西,基本全是对HTML进行打印的,没有找到我想要的,就只好自己动手,丰衣足食。 这篇文章主要讲述的是Lodop与数据的结合使用,官网和网上能...

    最近需要使用Lodop打印控件,所以就研究了一下,期间从网上找了诸多的东西,基本全是对HTML进行打印的,没有找到我想要的,就只好自己动手,丰衣足食。

    这篇文章主要讲述的是Lodop与数据的结合使用,官网和网上能找到的东西本文不在复述,只是记录在使用过程中自己遇到的问题和相应解决方法,本人笔拙,有不足之处希望大家多提意见;

    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    开发环境:

      Myeclipse 10.7 + Oracle11g   Windows 10;

    主要技术:

      Spring MVC + Mybatis + JQuery;

    参考文档:Lodop技术手册6.2.1.5;

    Lodop相应程序及技术文档请到官网下载:http://www.lodop.net/index.html

    对于Lodop有简单了解之后观看本文效果更佳;

    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    再次声明:本文针对的是Lodop的模板打印,相关基础问题只提到部分,其余自行去官网或者其他渠道获得;

    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    需要解决的问题:

      一:新增模板并把Lodop生成的模板存到Oracle数据库;

        1.1:自主添加模板名称;

        1.2:关闭打印设计之后自动返回模板内容代码;

        1.4:对返回的数据进行存储;

      二::对已创建的模板进行修改(包括打印设计和打印设置),动态从Oracle数据库加载;

        2.1:单独修改模板名称;

        2.2:动态加载已有模板属性;

      三:调用已创建模板,并将数据库查询到的数据进行动态加载;

        3.1:参数值传递;

    相关问题解决思路:

      一:新增模板并保存到数据库:

        1.1:新增模板并自定义模板名称

          在进行模板新增之前先进行模板名称输入,使用LODOP.PRINT_INIT(modelName);   modelName 为自定义变量值进行接收输入的模板名称,然后可以设置一些插件的基本属性

        如:LODOP.SET_PRINT_MODE("PROGRAM_CONTENT_BYVAR",true);//生成程序时,内容参数有变量用变量,无变量用具体值

        然后进行调用 Lodop 打印设计 LODOP.PRINT_DESIGN(); 方法,即可进行新增模板;

        1.2:关闭打印设计之后自动返回模板

          在Lodop 打印设计页面有相应生成程序代码按钮

          

          当我们在打印设计页面有内容时点击这个会弹出一个包含这个模板所有内容代码的框

          

          这里面包含:模板名称、内容具体项、内容项的位置、内容项的属性等内容,Lodop原本的做法是将模板保存在安装目录的一个以模板名称命名的.ini文件中,然后现在要做的就是

          在打印设计关闭的时候将上面代码返回到后台,然后丢进数据库进行保存起来;

          下面先说下数据库结构:printModelName  VARCHAR2(20)--模板名称; printModelContent  CLOB--模板内容 ; printModelContentField  VARCHAR2(500)--模板需传值字段;

          在进行数据库存储的时候,我是将内容代码的第一行LODOP.PRINT_INIT(modelName);与其他内容进行了分割,单独提取出了模板名称,这个是为了方便模板的维护,然后模板其他所有内容全

          部存在printModelContent 字段中,然后又对内容进行提取,得出这个模板中所有需要进行打印的字段,以“,”分割存进printModelContentField 里面(为什么这么做后面会提到),

          然后说下打印设计关闭获得上述代码的方法 (官网案例35):

          

          if (LODOP.CVERSION) {
            CLODOP.On_Return=function(TaskID,Value){

            var code = Value;

            console.log(Value);
            LODOP.PRINT_DESIGN();
          }

          这个Value 就是我们需要的数据,传到后台之后使用.spit();进行两次分割,第一次分割是为了分割出模板名称,所以用的是.spit(";",2); 第二次分割是为了得到模板需要打印的字段名称,

          所以用的是.split(";"); 那么至此我们就得出了上述三个我们需要的字段,然后将他们丢进数据库就行了;

      二::对已创建的模板进行修改

        2.1:维护模板名称

          因为上面我们在数据库对模板名称是单独存储的,所以在需要更改模板名称时直接进行printModelName 字段进行Update 就OK了;

        2.2:修改已创建模板

          首先我们需要先将已经保存的模板加载出来,这里我是依据模板管理表的主键ID来进行调用的,从数据库查出该条记录之后,首先进行加载模板名称,然后将模板剩余内容动态进行拼接到名称

          之后,方法如下

          var aaa = JSON.parse(data);//----对查询出的数据进行JSON转换
          LODOP.PRINT_INIT(aaa[0].printModelName); //----加载模板名称
          var datas = aaa[0].printModelContent;//----剩余模板内容

          //----对剩余模板内容进行动态拼接
          var script = document.createElement('script');

          script.type = 'text/javascript';
          script.appendChild(document.createTextNode(datas));
          document.body.appendChild(script);

          然后调用 打印关闭获得程序代码方法,确保我们在进行修改之后修改的内容可以保存到数据库。至此就是对已有模板的修改;

      三:调用已创建模板,打印测试

        通过以上两步,完成了模板新增、存储、修改,下面就是要进行调用了。

        首先,生成的程序代码是LODOP.ADD_PRINT_TEXTA("userName",213,389,100,20,"userName"); 这样的,经过测试发现LODOP.ADD_PRINT_TEXTA("userName",213,389,100,20,obj.userName);

        这样的可以动态加载,但是这样肯定是不行的,然后从Lodop官网找到了LODOP.SET_PRINT_STYLEA(varItemNameID, strStyleName,varStyleValue);//----varItemNameID,需要替换的项目名称;

        strStyleName,打印风格名称;varStyleValue,需要替换的项目值(与前面项目名称对应),就比如,此时我需要给userName进行赋值,就需要将LODOP.SET_PRINT_STYLEA的属性值写成如下格式:

        LODOP.SET_PRINT_STYLEA(“userName”, “CONTENT”,obj.userName);//----注意双引号, obj为数据库查询出的值;那么我们怎么获取需要替换的项目名称呢,此时上面我在进行模板存储的时候分割出

        的内容项打印字段就派上用场了,此时对printModelContentField 进行分割, 然后使用for循环进行遍历就可以动态的对模板进行赋值,相应语句为:

        var str = aaa[0].printModelContentField.split(",");

        for(var i=0;i<str.length;i++){

          LODOP.SET_PRINT_STYLEA(str[i],"CONTENT",printObj[str[i]]);
        }

        此时,我们在进行调用LODOP.PREVIEW();//打印预览 方法,就可以看到我们取到的数据库的值;

    以上,就是在下对于Lodop的一点个人的浅显的理解,如有不足之处请多多指教。

    如果需要转载请注明出处。

    展开全文
  • APK动态加载框架(DL)解析

    万次阅读 多人点赞 2014-10-10 00:20:57
    好久没有发布新的文章,这次打算发表一下我这几个月的一个核心研究成果:APK动态加载框架(DL)。这段时间我致力于github的开源贡献,开源了2个比较有用且有意义的项目,一个是PinnedHeaderExpandableListView,另一...

    转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/39937639 (来自singwhatiwanna的csdn博客)

    前言

    好久没有发布新的文章,这次打算发表一下我这几个月的一个核心研究成果:APK动态加载框架(DL)。这段时间我致力于github的开源贡献,开源了2个比较有用且有意义的项目,一个是PinnedHeaderExpandableListView,另一个是APK动态加载框架。具体可以参见我的github:https://github.com/singwhatiwanna

    本次要介绍的是APK动态加载框架(DL),这个项目除了我以外,还有两个共同开发者:田啸(时之沙),宋思宇。

    为了更好地理解本文,你需要首先阅读Android apk动态加载机制的研究这一系列文章,分别为:

    Android apk动态加载机制的研究

    Android apk动态加载机制的研究(二):资源加载和activity生命周期管理

    另外,这个开源项目我起了个名字,叫做DL。本文中的DL均指APK动态加载框架。

    项目地址

    https://github.com/singwhatiwanna/dynamic-load-apk,欢迎star和fork。

    运行效果图:

    意义

    这里说说这个开源项目的意义。首先要说的是动态加载技术(或者说插件化)在技术驱动型的公司中扮演这相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和cpu占用,还可以实现热插拔,即在不发布新版本的情况下更新某些模块。

    我几个月前开始进行这项技术的研究,当时查询了很多资料,没有找到很好的开源。目前淘宝、微信等都有成熟的动态加载框架,包括apkplug,但是它们都是不开源的。还有github上有一个开源项目AndroidDynamicLoader,其思想是通过Fragment 以及 schema的方式实习的,这是一种可行的技术方案,但是还有限制太多,这意味这你的activity必须通过Fragment去实现,这在activity跳转和灵活性上有一定的不便,在实际的使用中会有一些很奇怪的bug不好解决,总之,这还是一种不是特别完备的动态加载技术。然后,我发现,目前针对动态加载这一块成熟的开源基本还是空白的,不管是国内还是国外。而在公司内部,动态加载作为一项核心技术,也不可能是初级开发人员所能够接触到的,于是,我决定做一个成熟点的开源,期待能填补这一块空白。

    DL功能介绍

    DL支持很多特性,而这些特性使得插件的开发过程变得透明、高效。

    1. plugin无需安装即可由宿主调起。

    2. 支持用R访问plugin资源
    3. plugin支持Activity和FragmentActivity(未来还将支持其他组件)

    4. 基本无反射调用 

    5. 插件安装后仍可独立运行从而便于调试

    6. 支持3种plugin对host的调用模式:
       (1)无调用(但仍然可以用反射调用)。
       (2)部分调用,host可公开部分接口供plugin调用。 这前两种模式适用于plugin开发者无法获得host代码的情况。

       (3)完全调用,plugin可以完全调用host内容。这种模式适用于plugin开发者能获得host代码的情况。

    7. 只需引入DL的一个jar包即可高效开发插件,DL的工作过程对开发者完全透明

    8. 支持android2.x版本

    架构解析

    如果大家阅读过本文头部提到的两篇文章,那么对DL的架构应该有大致的了解,本文就不再从头开始介绍了,而是从如下变更的几方面进行解析,这些优化使得DL的功能和之前比起来更加强大更加易用使用易于扩展。

    1. DL对activity生命周期管理的改进

    2. DL对类加载器的支持(DLClassLoader)

    3. DL对宿主(host)和插件(plugin)通信的支持

    4. DL对插件独立运行的支持

    5. DL对activity随意跳转的支持(DLIntent)

    6. DL对插件管理的支持(DLPluginManager)

    其中5和6属于加强功能,目前正在dev分支上进行开发(本文暂不介绍),其他功能均在稳定版分支master上。

    DL对activity生命周期管理的改进

    大家知道,DL最开始的时候采用反射去管理activity的生命周期,这样存在一些不便,比如反射代码写起来复杂,并且过多使用反射有一定的性能开销。针对这个问题,我们采用了接口机制,将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理,并且没有采用反射,当我们想增加一个新的生命周期方法的时候,只需要在接口中声明一下同时在代理activity中实现一下即可,下面看一下代码:

    接口DLPlugin

    public interface DLPlugin {
    
        public void onStart();
        public void onRestart();
        public void onActivityResult(int requestCode, int resultCode, Intent data);
        public void onResume();
        public void onPause();
        public void onStop();
        public void onDestroy();
        public void onCreate(Bundle savedInstanceState);
        public void setProxy(Activity proxyActivity, String dexPath);
        public void onSaveInstanceState(Bundle outState);
        public void onNewIntent(Intent intent);
        public void onRestoreInstanceState(Bundle savedInstanceState);
        public boolean onTouchEvent(MotionEvent event);
        public boolean onKeyUp(int keyCode, KeyEvent event);
        public void onWindowAttributesChanged(LayoutParams params);
        public void onWindowFocusChanged(boolean hasFocus);
        public void onBackPressed();
    }
    在代理类DLProxyActivity中的实现

    ...
        @Override
        protected void onStart() {
            mRemoteActivity.onStart();
            super.onStart();
        }
    
        @Override
        protected void onRestart() {
            mRemoteActivity.onRestart();
            super.onRestart();
        }
    
        @Override
        protected void onResume() {
            mRemoteActivity.onResume();
            super.onResume();
        }
    
        @Override
        protected void onPause() {
            mRemoteActivity.onPause();
            super.onPause();
        }
    
        @Override
        protected void onStop() {
            mRemoteActivity.onStop();
            super.onStop();
        }
    ...
    说明:通过上述代码应该不难理解DL对activity生命周期的管理,其中mRemoteActivity就是DLPlugin的实现。

    DL对类加载器的支持

    为了更好地对多插件进行支持,我们提供了一个DLClassoader类,专门去管理各个插件的DexClassoader,这样,同一个插件就可以采用同一个ClassLoader去加载类从而避免多个classloader加载同一个类时所引发的类型转换错误。

    public class DLClassLoader extends DexClassLoader {
        private static final String TAG = "DLClassLoader";
    
        private static final HashMap<String, DLClassLoader> mPluginClassLoaders = new HashMap<String, DLClassLoader>();
    
        protected DLClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
            super(dexPath, optimizedDirectory, libraryPath, parent);
        }
    
        /**
         * return a available classloader which belongs to different apk
         */
        public static DLClassLoader getClassLoader(String dexPath, Context context, ClassLoader parentLoader) {
            DLClassLoader dLClassLoader = mPluginClassLoaders.get(dexPath);
            if (dLClassLoader != null)
                return dLClassLoader;
    
            File dexOutputDir = context.getDir("dex", Context.MODE_PRIVATE);
            final String dexOutputPath = dexOutputDir.getAbsolutePath();
            dLClassLoader = new DLClassLoader(dexPath, dexOutputPath, null, parentLoader);
            mPluginClassLoaders.put(dexPath, dLClassLoader);
    
            return dLClassLoader;
        }
    }

    DL对宿主(host)和插件(plugin)通信的支持

    这一点很重要,因为往往宿主需要和插件进行各种通信,因此DL对宿主和插件的通信做了很好的支持,目前总共有3中模式,如下图所示:


    下面分别介绍上述三种模式,针对上述三种模式,我们分别提供了3组例子,其中:

    1. depend_on_host:插件完全依赖宿主的模式,适合于能够能到宿主的源代码的情况

    其中host指宿主工程,plugin指插件工程

    2. depend_on_interface:插件部分依赖宿主的模式,或者说插件依赖宿主提供的接口,适合能够拿到宿主的接口的情况

    其中host指宿主工程,plugin指插件工程,common指接口工程

    3. main:插件不依赖宿主的模式,这是DL推荐的模式

    其中host指宿主工程,plugin指插件工程


    模式1:这是DL推荐的模式,对应的工程目录为main。在这种模式下,宿主和插件不需要通信,两者是独立开发的,宿主引用DL的jar包(dl-lib.jar),插件也需要引用DL的jar包,但是不能放入到插件工程的libs目录下面,换句话说,就是插件编译的时候依赖jar包但是打包成apk的时候不要把jar包打进去,这是因为,dl-lib.jar已经在宿主工程中存在了,如果插件中也有这个jar包,就会发生类链接错误,原因很简单,内存中有两份一样的类,重复了。至于support-v4也是同样的道理。对于eclipse很简单,只需要在插件工程中创建一个目录,比如external-jars,然后把dl-lib.jar和support-v4.jar放进去,同时在.classpath中追加如下两句即可:

    <classpathentry kind="lib" path="external-jars/dl-lib.jar"/>
    <classpathentry kind="lib" path="external-jars/android-support-v4.jar"/>

    这样,编译的时候就能够正常进行,但是打包的时候,就不会把上面两个jar包打入到插件apk中。

    至于ant环境和gradle,解决办法不一样,具体方法后面再补上,但是思想都是一样的,即:插件apk中不要打入上述2个jar包。

    模式2:插件部分依赖宿主的模式,或者说插件依赖宿主提供的接口,适合能够拿到宿主的接口的情况。在这种模式下,宿主放出一些接口并实现一些接口,然后给插件调用,这样插件就可以访问宿主的一些服务等

    模式3:插件完全依赖宿主的模式,适合于能够能到宿主的源代码的情况。这种模式一般多用在公司内部,插件可以访问宿主的所有代码,但是,这样插件和宿主的耦合比较高,宿主一动,插件就必须动,比较麻烦


    具体采用哪种方式,需要结合实际情况来选择,一般来说,如果是宿主和插件不是同一个公司开发,建议选择模式1和模式2;如果宿主和插件都在同一个公司开发,那么选择哪个都可以。从DL的实现出发,我们推荐采用模式1,真的需要通信的话采用模式2,尽量不要采用模式3.

    DL对插件独立运行的支持

    为了便于调试,采用DL所开发的插件都可以独立运行,当然,这要分情况来说:

    对于模式1,如果插件想独立运行,只需要把external-jars下的jar包拷贝一份到插件的libs目录下即可

    对于模式2,只需要提供一个宿主接口的默认实现即可

    对于模式3,只需要apk打包时把所引用的宿主代码打包进去即可,具体方式可以参看sample/depend_on_host目录。

    在开发过程中,应该先开启插件的独立运行功能以便于调试,等功能开发完毕后再将其插件化。

    DLIntent和DLPluginManager

    这两项都属于加强功能,目前正在dev分支进行code review,大家感兴趣可以去dev分支上查看,等验证通过即merge到稳定版master分支。

    DLIntent:通过DLIntent来完成activity的无约束调起

    DLPluginManager:对宿主的所有插件提供综合管理功能。

    开发规范

    目前DL已经达到了第一个稳定版,经过大量机型的验证,目前得出的结论是DL是可靠的(兼容到android2.x),可以用在实际的应用开发中。但是,我们知道,动态加载是一个技术壁垒,其很难达到一种完美的状态,毕竟,让一个apk不安装跑起来,这是多么不可思议的事情。因此,希望大家辩证地去看这个问题,下面列出我们目前还不支持的功能,或者说是一种开发规范吧,希望大家在开发过程中去遵守这个规范,这样才能让插件稳定地跑起来。


    DL 1.0开发规范

    1. 目前不支持service

    2. 目前只支持动态注册广播

    3. 目前支持Activity和FragmentActivity,这也是常用的activity

    4. 目前不支持插件中的assets

    5. 调用Context的时候,请适当使用that,大部分常用api是不需要用that的,但是一些不常用api还是需要用that来访问。that是apk中activity的基类BaseActivity系列中的一个成员,它在apk安装运行的时候指向this,而在未安装的时候指向宿主程序中的代理activity,由于that的动态分配特性,通过that去调用activity的成员方法,在apk安装以后仍然可以正常运行。

    6. 慎重使用this,因为this指向的是当前对象,即apk中的activity,但是由于activity已经不是常规意义上的activity,所以this是没有意义的,但是,当this表示的不是Context对象的时候除外,比如this表示一个由activity实现的接口。


    希望能够给大家带来一些帮助,希望大家多多支持!

    本开源项目地址:https://github.com/singwhatiwanna/dynamic-load-apk,欢迎大家star和fork。

    展开全文
  • 一直写静态代码,没做过数据动态加载。比如静态html页面中: ``` <div class="title"></div> 图片"/> 文章介绍 ``` 一个真实项目中title,img,content肯定都是动态加载的。要怎么写...
  • angular9组件动态加载实现

    万次阅读 2020-07-19 16:21:57
    angular9动态组件前言指令的创建动态组件的核心代码动态组件加载的html动态组件的tsad-item.tsad.component.ts组件统一注册组件的映射效果图参考资料 前言 按条件加载组件,实现组件的灵活切换,减少大量ngIf的使用,在...
  • C# 动态加载Dll

    千次阅读 2014-07-24 13:46:41
    1、新建测试dll及方法,用vs2010新建winform程序,具体代码如下: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing;...
  • Unity学习笔记13——代码动态加载Prefab预设体

    万次阅读 多人点赞 2016-05-09 19:40:17
    在进行一些功能开发的时候,我们常常将一些能够复用的对象制作成.prefab的预设物体,然后将预设体存放到Resources目录之下,使用时再动态加载到场景中并进行实例化。例如:子弹、特效甚至音频等,都能制作成预设体。...
  • java动态加载类和静态加载类

    千次阅读 2016-10-26 16:23:40
    什么是动态加载类 什么是静态加载类 Class.forName 不仅表示类的类类型,还代表了动态加载类。编译时加载是静态加载类, 运行时加载是动态加载类。 请大家区分编译 运行。 二.为何要使用动态加载类 我们写了一个程序...
  • 85个web用动态加载

    千次下载 热门讨论 2013-09-22 16:37:45
    85个web用动态加载
  • ViewStub动态加载布局提高UI加载性能

    千次阅读 2016-06-14 16:18:22
    当ViewStub 变为可见,或者 调用了inflate() 方法,这个Layout资源会被加载。然后ViewStub会将加载的View或者Views 替换他自己在父布局中的位置因此ViewStub 一直存在,直到调用了setVisibility或者in
  • Echarts简介  echarts,缩写来自Enterprise Charts,商业级数据图表,一个纯Javascript的图表库,来自百度。。。我想应该够简洁了 使用Echarts  目前,就官网的文档显示,使用echarts有两种方式,echarts3之前的...
  • 他的动态加载是如何实现的 是在页面加载完后 根据页面中处理用到那个js/CSS文件 再去加载文件 还是如何。。 我在调用下面2个方法时 JSLoader.loadStyleSheet("css/style.css"); .... JSLoader.loadJavaScript(...
  • 动态加载的数据,静态数据是20个slide 能对整齐。动态加载的数据只有15个却不能对齐![图片说明](https://img-ask.csdn.net/upload/201904/22/1555925727_411340.png) ``` var mySwiper = new this.$Swiper("....
  • CocosCreator 动态加载与远程加载资源汇总 概述 所有需要通过 cc.loader.loadRes 动态加载的资源,都必须放置在 resources 文件夹或它的子文件夹下。如果一份资源仅仅是被 resources 中的其它资源所依赖,而不需要...
  • 访问控制第一层级:根据当前登录用户动态加载菜单。
  • DL动态加载框架技术文档

    万次阅读 多人点赞 2014-10-20 00:45:32
    DL动态加载框架技术文档DL技术交流群:29969245 1. Android apk动态加载机制的研究2. Android apk动态加载机制的研究(二):资源加载和activity生命周期管理3. APK动态加载框架DL解析4. Android 使用动态加载...
  • 我有2个js文件一个在index.html通过script加载另外一个js需要通过第一个js里面的button点击加载。如何加载?有源码吗?关闭之后如何清除之前加载的js
  • unity动态加载Resources.Load方法

    万次阅读 多人点赞 2016-04-08 09:46:25
    Resources.Load:使用这种方式加载资源,首先需要下Asset目录下创建一个名为Resources的文件夹,这个命名是U3D规定的方式,然后把资源文件放进去,当然也可以在Resources中再创建子文件夹,代码加载时需要添加相应的...
  • 为了更好地阅读本文,你需要先阅读Android apk动态加载机制的研究这篇文章,在此文中,博主分析了Android中apk的动态加载机制,并在文章的最后指出需要解决的两个复杂问题:资源的访问和activity生命周期的管理,而...

空空如也

1 2 3 4 5 ... 20
收藏数 61,224
精华内容 24,489
关键字:

动态加载