Loading the apk interface in the sd card directory through the proxy Activity mode

Keywords: Android Java Mobile nexus

Dynamic loading and plug-in development are very important.
Today's 360 mobile assistant (Droid Plugin), VirtualApp, Baidu DL and Ctrip Dynamic APK all use this technology.

The general idea of this example is:
1. The apk1 is initialized by a main interface MainActivity. The main interface has only one Button button. After clicking, a Toast pops up. Then we put the compiled apk1 under the SD card of the cell phone root directory.
2. apk2 has a MainActivity interface and a Button on it. After clicking the button, it loads apk1 in SD directory, calls up apk1, clicks the button in apk1, and pops up Toast.

Is that a simple logic? In fact, there are many problems in this area. Let's briefly talk about the problems first.  
1. Actually, clicking on the button of apk2 does not start the interface of apk 1, but manages the interface of APK 1 to a static proxy class Activity, then constructs a button similar to APK 1 with the static proxy activity, and then pops up Toast in the context of the static proxy activity.
2. This example is only in the static proxy Activity class. It makes a simple reflection call to the onCreate method of apk1. The main interface class of the reflected apk1 is essentially a java class. It has no logic in the activity, such as you can't get the layout s and other resources in the activity. So this demo is also a small understanding of dynamic loading and does not involve Activity. Dynamic Agent and binder Mechanism of Four Organizations

Let's first look at the apk code.
MainActivity

package com.example.targetproject;
import android.annotation.SuppressLint;
import android.app.ActionBar.LayoutParams;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {

    public static final String KEY_APK_PATH = "apkPath";
    public static final String KEY_CLASS = "class";

    public static final String DEX_PATH = android.os.Environment
            .getExternalStorageDirectory().getPath() + "/TargetProject.apk";

    protected Activity mProxyActivity;

    public void setProxy(Activity proxyActivity) {
        mProxyActivity = proxyActivity;
    }

    @SuppressLint("NewApi")
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        if (mProxyActivity == null) {
            super.onCreate(savedInstanceState);
            mProxyActivity = this;
        }

        Button button = new Button(mProxyActivity);
        button.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT));
        button.setText("Press me");
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mProxyActivity, "You clicked the button!", Toast.LENGTH_SHORT)
                        .show();
            }
        });

        if (mProxyActivity == this) {
            super.setContentView(button);
        } else {
            mProxyActivity.setContentView(button);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

Briefly speaking, the logical relationship in it.
What does this interface do? That is to create a Button with an Activity as the environment, and then click on the button, will produce a Toast with an Activity as the environment, we first put aside the test apk through dynamic loading, to proxy the situation raised by Activity, first of all, it is an apk file that can run and compile independently, so let's first analyze the activities it needs, I Let's first define an Activity class

protected Activity mProxyActivity;
  • 1
  • 1

In the onCreate method

if (mProxyActivity == null) {
            super.onCreate(savedInstanceState);
            mProxyActivity = this;
        }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

This means that if there is no hosted Active class, native Activity is used. What if there is a hosted Active class? Let's set up the following settings

public void setProxy(Activity proxyActivity) {
        mProxyActivity = proxyActivity;
    }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

In the main interface of apk, add a proxy class Activity, and then create button, Toast in the environment of proxy class Activity
Here's the code to create Button

    Button button = new Button(mProxyActivity);
        button.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT));
        button.setText("Press me");
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mProxyActivity, "You clicked the button!", Toast.LENGTH_SHORT)
                        .show();
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

After creating Button Toast, you need to set it up in an Activity environment, and you need to decide whether it is a native Activity environment or a proxy Activity environment?

if (mProxyActivity == this) {
            super.setContentView(button);
        } else {
            mProxyActivity.setContentView(button);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

After compiling, no problem running apk, put it in the sd card root directory of the mobile phone, the following is the path directory of my mobile phone nexus 5

Then let's look at the code logic for testing the apk.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.test.MainActivity" >

    <Button 
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/load" />

</RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

MainActivity

package com.example.test;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = (Button) this.findViewById(R.id.btn);
        btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                String root = android.os.Environment
                        .getExternalStorageDirectory().getPath()
                        + "/TargetProject.apk";

                Intent intent = new Intent(MainActivity.this,
                        ProxyActivity.class);
                intent.putExtra(ProxyActivity.KEY_APK_PATH, root);
                startActivity(intent);
            }

        });
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

First look at the main interface code, here is a button, click with the past sd card and the directory under the absolute path of apk1, and then to the ProxyActivity class.

String root = android.os.Environment.getExternalStorageDirectory().getPath()
                        + "/TargetProject.apk";
  • 1
  • 2
  • 1
  • 2

Then look at our proxy Activity class

package com.example.test;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.os.Bundle;
import android.util.Log;
import dalvik.system.DexClassLoader;

/**
 * proxy class
 * @author safly
 *
 */
public class ProxyActivity extends Activity{

    public static final String KEY_APK_PATH = "apkPath";  
    public static final String KEY_CLASS = "class";   

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);

        //Gets the specified apk file path and boot class name
        String mDexPath = getIntent().getStringExtra(KEY_APK_PATH);  
        String mClass = getIntent().getStringExtra(KEY_CLASS);

        if (mClass == null) {
            PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(mDexPath, 1);  
            if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) {  
                Log.e("ProxyActivity", ""+packageInfo.activities[0].name);
                mClass = packageInfo.activities[0].name;   
            }  
        }
        launchTargetActivity(mDexPath,mClass); 
    }  

    /**
     * Start the interface in apk with ClassLoader, DexClassLoader and reflection
     * @param mDexPath apk Dynamic loading apk local path
     * @param className The class name of the dynamically loaded class to open
     */
    protected void launchTargetActivity(String mDexPath,String className) {
        Log.e("ProxyActivity", "launchTargetActivity");
        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[] {});

            //A Method of Setting up Agent Activity by Using Reflection Mechanism
            Method setProxy = localClass.getMethod("setProxy",new Class[] { Activity.class });  
            setProxy.setAccessible(true);  
            setProxy.invoke(instance, new Object[] { this });  
            //Invoking onCreate method with reflection mechanism
            Method onCreate = localClass.getDeclaredMethod("onCreate",new Class[] { Bundle.class });  
            onCreate.setAccessible(true);   
            onCreate.invoke(instance, new Object[] { null });  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

Let's talk about what the above code means.
The ProxyActivity class onCreate method obtains the KEY_APK_PATH (absolute path of apk1 under sd card). We also need a class, which is the full name of the main interface of apk1, because we need to call the onCreate method inside, and then add button and toast controls.

if (mClass == null) {
            PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(mDexPath, 1);  
            if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) {  
                Log.e("ProxyActivity", ""+packageInfo.activities[0].name);
                mClass = packageInfo.activities[0].name;   
            }  
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

The log output is as follows

ProxyActivity(23526): com.example.targetproject.MainActivity
  • 1
  • 1

Then look at the code in launchTarget Activity

File dexOutputDir = this.getDir("dex", 0); 
dexOutputPath--/data/data/com.example.test/app_dex 
  • 1
  • 2
  • 1
  • 2

Above is the directory of dex decompressed and released, and the directory of log output. Below is a screenshot.

Then you get a DexClassLoader with the absolute path and app_dex path of the sd card apk1, followed by a ClassLoader. getSystem ClassLoader () object.
The parameters are as follows

(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
  • 1
  • 1

The following method is to get the construction of MainActivity in apk1 and then a new instance

Class<?> localClass = dexClassLoader.loadClass(className);  
            Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});  
            Object instance = localConstructor.newInstance(new Object[] {});
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

Here is the reflection to get setProxy, onCreate method, set the proxy Activity class, and then set the button toast control in the proxy Activity class.

 //A Method of Setting up Agent Activity by Using Reflection Mechanism
            Method setProxy = localClass.getMethod("setProxy",new Class[] { Activity.class });  
            setProxy.setAccessible(true);  
            setProxy.invoke(instance, new Object[] { this });  
            //Invoking onCreate method with reflection mechanism
            Method onCreate = localClass.getDeclaredMethod("onCreate",new Class[] { Bundle.class });  
            onCreate.setAccessible(true);   
            onCreate.invoke(instance, new Object[] { null });  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

This is the logic of this small example. It's also a little introductory understanding of how to start dynamic loading learning for yourself.

Posted by metrathon on Mon, 01 Apr 2019 16:36:30 -0700