Research on the Dynamic Loading Mechanism of Android apk

Source: Internet
Author: User

Reprinted please indicate Source: http://blog.csdn.net/singwhatiwanna/article/details/22597587 (from singwhatiwanna's csdn blog)

Background

The problem is: we know that the apk must be installed before it can run. If it is not installed, it can run well. In fact, this is not completely impossible, although it is relatively difficult to implement. Theoretically, we can use a host program to run some uninstalled apk files. Of course, it can also be implemented in practice, but this requires uninstalled apk files. This is our idea. First of all, we must understand that apk cannot be directly called without installation, but we can use a program (called a Host Program) to dynamically load the apk file and put it in your own process for execution, this article will introduce this method, and there are still many problems with this method, especially resource access. Because the apk is loaded into the host Program for execution, it cannot be obtained from the Context of the Host Program to obtain resources in the apk, such as parts and texts. This is well understood, because the apk does not have a Context, the Context used for execution is the Context of the Host Program, and other people's Context cannot get their own resources, but this problem seems to be solved as follows: extract the resources in the apk to a directory and then operate the resources through files. This is only theoretically feasible and there are still many difficulties. In addition to resource access, there is also a problem with the lifecycle of the activity, because after the apk is loaded and executed by the host Program, its activity is actually a common class. Normally, the lifecycle of an activity is managed by the system. After being taken over by the host program, it is difficult to replace the system to manage the lifecycle of the activity in the apk, however, this problem is better than resource access. For example, we can simulate the activity lifecycle in the Host Program and call the activity lifecycle method in the apk as appropriate. This article does not solve these two problems for the moment, because it is very difficult. This article only introduces the dynamic execution mechanism of apk. Even so, it sounds a little excited, isn't it?

Working Principle

As shown in, the host Program loads the apk to the file system, such as the SD card, and then executes the activity in the apk through an activity called proxy.

In theory, DexClassLoader, PathClassLoader, and URLClassLoader can be used for dynamic apk loading.

DexClassLoader: It can load jar, dex, and apk on the file system.

PathClassLoader: you can load the apk under the/data/app Directory, which also means that it can only load the installed apk

URLClassLoader: Can load the jar in java, but dalvik cannot directly identify the jar, so this method cannot be used in android, although there is also this class

For jar, dex, and apk files, dex and apk can be directly loaded, because they both have or internal dex files, but the original jar files cannot, must be converted to a bytecode file that dalvik can recognize. The conversion tool can use dx in the platform-tools directory of android sdk

Conversion command: dx -- dex -- output = dest. jar src. jar

Implementation of the sample Host Program

1. the main interface is very simple. Put a button and click it to start the apk. I put the apk directly in the SD card. It is actually a truth to download the apk from the Internet to the local and then load it.

    @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);        }    }

After clicking the button, the proxy will be started, and the task of loading and starting the apk will be handed over to it.

2. proxy activity implementation)

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();        }    }}

Note: The program is easy to understand. The idea is as follows: Use DexClassLoader to load the apk. If no class is specified, the main activity is called; otherwise, the specified class is called. The starting process of an activity is as follows: first, use the class loader to load the activity class in the apk and create a new object, then, the setProxy method and onCreate method of the object are called through reflection. The setProxy method is used to submit all the execution of the activity to the proxy (also an activity) in the Host Program ), the onCreate method is the entry of the activity. The onCreate method will be called after setProxy, and the activity will be called at this time.

Implementation of the apk to be executed

1. to allow the proxy to fully take over the execution of all the activities in the apk, You need to define a base class BaseActivity for the activity to process proxy-related tasks in the base class, at the same time, BaseActivity also checks whether a proxy is used. If no proxy is used, the activity logic is still executed in the normal way. That is to say, this apk can be executed according, it can also be executed by the host Program.

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);        }    }}

Note: I believe you will understand the code at a Glance. The setProxy method is used to allow the Host Program to take over its own execution. Once it is taken over, all its execution will go through proxy, in addition, Context has become the Context of the Host Program. It may be quite like this: the Host Program is actually an empty shell, and it just loads other apk files to its own interior for execution, this makes resource access more difficult. You may find that the resources in the apk cannot be accessed, but I have no good solutions yet.
2. portal activity implementation

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;    }}

Note: because the resources in the apk cannot be accessed, the interface is written in code rather than in xml, Because xml cannot be read, which is also a big problem. Note that there is a button in the main interface, and then jump to another activity. At this time, the startActivity method of the system cannot be called directly, but must be executed through the proxy in the Host Program. The reason is very simple, first, the apk book does not have Context, so it cannot start the activity. In addition, because this sub-activity is in the apk, it cannot be called directly through the Host Program, because it is invisible to the Host Program, it can only be called through proxy. Is it very troublesome? But do you have a better solution?

3. Sub-activity implementation

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 ("this is the test page"); setContentView (button );}}

Note: The code is very simple and you don't need to introduce it. Similarly, the interface is still written using code.

Running Effect

1. First, check the Running Effect of apk during installation.

2. Check the effect of running the host program when it is not installed.

Note: you can find that the execution effect is the same for installation and uninstallation. The difference is that the reflection is used when the installation is not completed, so the execution efficiency is slightly reduced. Secondly, the title of the application has changed. That is to say, although the apk is executed, it is executed in the Host Program, so it belongs to the Host Program, therefore, when the apk is not installed and executed, its title is not its own. However, this can also indirectly prove that the apk is indeed executed by the host Program. Finally, I would like to explain the significance of doing so. This will facilitate modularization and implement plug-in mechanisms. However, there are still many and most complex problems: resource access and activity lifecycle management. We look forward to some good solutions. Please contact us.

Download Code:

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

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

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.