手记

android插件化——支付宝如何在不安装淘票票的情况下加载淘票票原生App

  • 效果

  • 构建项目模块


1、alipaystander 为通讯接口(lib)
2、app为支付宝项目(module)
3、taopiaopiao 淘票票项目(module)

  • alipaystander 通讯接口

主要功能是为了解决app与taopiaopiao之间的通讯

package tsou.cn.alipaystander;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;

/**
 * Created by Administrator on 2018/4/10 0010.
 */

public interface AlipayInterface {
    public void onCreate(Bundle savedInstanceState);

    public void onStart();

    public void onResume();

    public void onPause();

    public void onStop();

    public void onDestroy();

    public void onSaveInstanceState(Bundle outState);

    public boolean onTouchEvent(MotionEvent event);

    public void onBackPressed();

    /**
     * 需要支付宝注入个淘票票上下文
     */
    public void attach(AppCompatActivity appCompatActivity);
}

注意:在为安装的apk中上下文系统是无法注入的,需要主模块传入

  • 引入alipaystander

在app与taopiaopiao模块中的build.gradle引入

compile project(':alipaystander')
  • 创建taopiaopiao项目

1、taopiaopiao主页

package tsou.cn.taopiaopiao;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

/**
 * 没有安装
 * <p>
 * 上下文   一般都是系统注入
 */
public class MainActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView imageView = (ImageView) findViewById(R.id.img);
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //系统注入的上下文:MainActivity.this
                Toast.makeText(that,"点击",Toast.LENGTH_SHORT).show();
                startActivity(new Intent(that,SceondActivity.class));
            }
        });
    }
}

注意:taopiaopiao项目为外部apk上下文需要传入,startActivity也要重新

2、构建BaseActivty

package tsou.cn.taopiaopiao;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;

import tsou.cn.alipaystander.AlipayInterface;

/**
 * Created by Administrator on 2018/4/10 0010.
 */

public class BaseActivity extends AppCompatActivity implements AlipayInterface {

    protected AppCompatActivity that;

    /**
     * super.setContentView(layoutResID);
     * <p>
     * 最终是调用了系统给我们注入的上下文
     *
     * @param layoutResID
     */
    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        if (that == null) {
            super.setContentView(layoutResID);
        } else {
            that.setContentView(layoutResID);
        }

    }

    @Override
    public View findViewById(@IdRes int id) {
        if (that == null) {
            return super.findViewById(id);
        } else {
            return that.findViewById(id);
        }
    }

    @Override
    public ClassLoader getClassLoader() {
        if (that == null) {
            return super.getClassLoader();
        } else {
            return that.getClassLoader();
        }
    }

    @NonNull
    @Override
    public LayoutInflater getLayoutInflater() {
        if (that == null) {
            return super.getLayoutInflater();
        } else {
            return that.getLayoutInflater();
        }
    }

    @Override
    public Window getWindow() {
        if (that == null) {
            return super.getWindow();
        } else {
            return that.getWindow();
        }
    }

    @Override
    public WindowManager getWindowManager() {
        if (that == null) {
            return super.getWindowManager();
        } else {
            return that.getWindowManager();
        }
    }

    @Override
    public void startActivity(Intent intent) {
        if (that == null) {
            super.startActivity(intent);
        } else {
            Intent newIntent = new Intent();
            newIntent.putExtra("className", intent.getComponent().getClassName());
            that.startActivity(newIntent);
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {

    }

    @Override
    public void onStart() {

    }

    @Override
    public void onResume() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onStop() {

    }

    @Override
    public void onDestroy() {

    }

    @Override
    public void onSaveInstanceState(Bundle outState) {

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return false;
    }

    @Override
    public void onBackPressed() {

    }

    @Override
    public void attach(AppCompatActivity appCompatActivity) {
        this.that = appCompatActivity;
    }

}

需要上下文的都需要重新,编译taopiaopiao项目产生apk,放到手机sd卡根目录下。
注意:有时debug模式下打包,有时无法实现app向tiaopiaopiao的跳转,需要打正式包!

  • 在app项目中使用DexClassLoader加载第三方APK
package tsou.cn.alipay;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;

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

import dalvik.system.DexClassLoader;

/**
 * Created by Administrator on 2018/4/10 0010.
 */

public class PluginManager {

    private DexClassLoader dexClassLoader;
    private Resources resources;
    private Context context;
    private String entryActivityName;
    private static final PluginManager ourInstance = new PluginManager();

    public static PluginManager getInstance() {
        return ourInstance;
    }

    private PluginManager() {
    }

    public void loadPath(String path) {
        File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
        dexClassLoader = new DexClassLoader(path, dexOutFile.getAbsolutePath(), null, context.getClassLoader());

        // 获取包名
        PackageManager packageManager = context.getPackageManager();
        PackageInfo packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
        entryActivityName = packageInfo.activities[0].name;

        //实例化resources
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, path);
            resources = new Resources(assetManager, context.getResources().getDisplayMetrics(),
                    context.getResources().getConfiguration());
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public DexClassLoader getDexClassLoader() {
        return dexClassLoader;
    }

    public Resources getResources() {
        return resources;
    }

    public String getEntryActivityName() {
        return entryActivityName;
    }

    public void setContext(Context context) {
        this.context = context.getApplicationContext();
    }
}

因为加载的apk为外部的,所以getDexClassLoader()与getResources()需要重写

  • 创建ProxyActivity承载taopiaopiao主页
package tsou.cn.alipay;

import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import tsou.cn.alipaystander.AlipayInterface;

/**
 * Created by Administrator on 2018/4/10 0010.
 * <p>
 * 专门去插桩使用
 */

public class ProxyActivity extends AppCompatActivity {
    //要跳转的 淘票票Activity
    private String className;
    private AlipayInterface alipayInterface;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        className = getIntent().getStringExtra("className");
        /**
         * 通过className不能拿到类名
         * 原生是加载过的类名
         */
        //  Class clazz = getClassLoader().loadClass(className);
        //   Class.forName(className);
        try {
            //加载该Activity的字节码对象
            Class activityClass = getClassLoader().loadClass(className);
            Constructor constructor = activityClass.getConstructor(new Class[]{});
            //创建该Activity的示例
            Object newInstance = constructor.newInstance(new Object[]{});
            //程序健壮性检查
            if (newInstance instanceof AlipayInterface) {
                /**
                 * 使用定义接口标准
                 * 传递生命周期
                 */
                alipayInterface = (AlipayInterface) newInstance;
                //将代理Activity的实例传递给三方Activity
                alipayInterface.attach(this);
                //创建bundle用来与三方apk传输数据
                Bundle bundle = new Bundle();
                //调用三方Activity的onCreate,
                alipayInterface.onCreate(bundle);
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    /**
     * 重新,通过className拿到类名
     *
     * @return
     */
    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getDexClassLoader();
    }

    /**
     * @return
     */
    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getResources();
    }

    @Override
    protected void onStart() {
        if (alipayInterface != null)
            alipayInterface.onStart();
        super.onStart();
    }

    @Override
    protected void onResume() {
        if (alipayInterface != null)
            alipayInterface.onResume();
        super.onResume();
    }

    @Override
    protected void onPause() {
        if (alipayInterface != null)
            alipayInterface.onPause();
        super.onPause();
    }

    @Override
    protected void onStop() {
        if (alipayInterface != null)
            alipayInterface.onStop();
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        if (alipayInterface != null)
            alipayInterface.onDestroy();
        super.onDestroy();
    }

    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent intent1 = new Intent(this, ProxyActivity.class);
        intent1.putExtra("className",className);
        super.startActivity(intent1);
    }
}
  • 添加sd权限
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  • 实现app加载taopiaopiao项目
package tsou.cn.alipay;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.File;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    /**
     * 加载
     */
    private Button mLoad;
    /**
     * 点击
     */
    private Button mClick;
    String[] mPermissionList = new String[]{
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        PluginManager.getInstance().setContext(this);
        initView();
    }

    private void initView() {
        mLoad = (Button) findViewById(R.id.load);
        mLoad.setOnClickListener(this);
        mClick = (Button) findViewById(R.id.click);
        mClick.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            default:
                break;
            case R.id.load://加载
                // 缺少权限时, 进入权限配置页面
                ActivityCompat.requestPermissions(MainActivity.this,mPermissionList, 100);
                break;
            case R.id.click://点击
                Intent intent = new Intent(this, ProxyActivity.class);
                // intent.putExtra("className", "淘票票的全类名   MainActivity");
                intent.putExtra("className", PluginManager.getInstance().getEntryActivityName());
                startActivity(intent);
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 100:
                boolean camera = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                boolean readExternalStorage = grantResults[1] == PackageManager.PERMISSION_GRANTED;
                if (grantResults.length > 0 && camera && readExternalStorage) {
                    File file = new File(Environment.getExternalStorageDirectory(), "plugin.apk");
                    PluginManager.getInstance().loadPath(file.getAbsolutePath());
                } else {
                    Toast.makeText(this.getApplicationContext(),"请设置必要权限",Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }
}
  • taopiaopiao项目更改UI页面

例如:在taopiaopiao主页弹出一个Toast,

//系统注入的上下文:MainActivity.this
                Toast.makeText(that,"点击",Toast.LENGTH_SHORT).show();

注意:上下文,一定是传递过来的,本地上下文无效

注意tiaopiaopiao中的activity不需要进行注册

  • 在taopiaopiao中的activity的跳转
startActivity(new Intent(that,SceondActivity.class));
package tsou.cn.taopiaopiao;

import android.os.Bundle;

public class SceondActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sceond);
    }
}

需要对startActivity进行重新

1、在BaseActivity中进行传递

    @Override
    public void startActivity(Intent intent) {
        if (that == null) {
            super.startActivity(intent);
        } else {
            Intent newIntent = new Intent();
            newIntent.putExtra("className", intent.getComponent().getClassName());
            that.startActivity(newIntent);
        }
    }

2、在承载页面ProxyActivity中重新

 @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent intent1 = new Intent(this, ProxyActivity.class);
        intent1.putExtra("className",className);
        super.startActivity(intent1);
    }

实现页面跳转!
(后续需要继续跳转其他的activity直接使用startActivity即可,但是activity要继承BaseActivity,同样新的activity不需要在清单文件中注册)

Demo地址:https://download.csdn.net/download/huangxiaoguo1/10341038

7人推荐
随时随地看视频
慕课网APP

热门评论

简但示例说明了使用代理来动态加载apk的方式,赞

查看全部评论