查看: 234|回复: 0

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

发表于 2018-5-7 08:00:02
效果
支付宝主页面

淘票票主页

淘票票内部页面

构建项目模块

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

alipaystander 通讯接口

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

  1. package tsou.cn.alipaystander;
  2. import android.os.Bundle;
  3. import android.support.v7.app.AppCompatActivity;
  4. import android.view.MotionEvent;
  5. /**
  6. * Created by Administrator on 2018/4/10 0010.
  7. */
  8. public interface AlipayInterface {
  9. public void onCreate(Bundle savedInstanceState);
  10. public void onStart();
  11. public void onResume();
  12. public void onPause();
  13. public void onStop();
  14. public void onDestroy();
  15. public void onSaveInstanceState(Bundle outState);
  16. public boolean onTouchEvent(MotionEvent event);
  17. public void onBackPressed();
  18. /**
  19. * 需要支付宝注入个淘票票上下文
  20. */
  21. public void attach(AppCompatActivity appCompatActivity);
  22. }
复制代码

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

引入alipaystander

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

  1. compile project(':alipaystander')
复制代码
创建taopiaopiao项目

1、taopiaopiao主页

  1. package tsou.cn.taopiaopiao;
  2. import android.content.Intent;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. import android.widget.ImageView;
  6. import android.widget.Toast;
  7. /**
  8. * 没有安装
  9. * <p>
  10. * 上下文 一般都是系统注入
  11. */
  12. public class MainActivity extends BaseActivity {
  13. @Override
  14. public void onCreate(Bundle savedInstanceState) {
  15. super.onCreate(savedInstanceState);
  16. setContentView(R.layout.activity_main);
  17. ImageView imageView = (ImageView) findViewById(R.id.img);
  18. imageView.setOnClickListener(new View.OnClickListener() {
  19. @Override
  20. public void onClick(View v) {
  21. //系统注入的上下文:MainActivity.this
  22. Toast.makeText(that,"点击",Toast.LENGTH_SHORT).show();
  23. startActivity(new Intent(that,SceondActivity.class));
  24. }
  25. });
  26. }
  27. }
复制代码

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

2、构建BaseActivty

  1. package tsou.cn.taopiaopiao;
  2. import android.content.Intent;
  3. import android.os.Bundle;
  4. import android.support.annotation.IdRes;
  5. import android.support.annotation.LayoutRes;
  6. import android.support.annotation.NonNull;
  7. import android.support.v7.app.AppCompatActivity;
  8. import android.view.LayoutInflater;
  9. import android.view.MotionEvent;
  10. import android.view.View;
  11. import android.view.Window;
  12. import android.view.WindowManager;
  13. import tsou.cn.alipaystander.AlipayInterface;
  14. /**
  15. * Created by Administrator on 2018/4/10 0010.
  16. */
  17. public class BaseActivity extends AppCompatActivity implements AlipayInterface {
  18. protected AppCompatActivity that;
  19. /**
  20. * super.setContentView(layoutResID);
  21. * <p>
  22. * 最终是调用了系统给我们注入的上下文
  23. *
  24. * @param layoutResID
  25. */
  26. @Override
  27. public void setContentView(@LayoutRes int layoutResID) {
  28. if (that == null) {
  29. super.setContentView(layoutResID);
  30. } else {
  31. that.setContentView(layoutResID);
  32. }
  33. }
  34. @Override
  35. public View findViewById(@IdRes int id) {
  36. if (that == null) {
  37. return super.findViewById(id);
  38. } else {
  39. return that.findViewById(id);
  40. }
  41. }
  42. @Override
  43. public ClassLoader getClassLoader() {
  44. if (that == null) {
  45. return super.getClassLoader();
  46. } else {
  47. return that.getClassLoader();
  48. }
  49. }
  50. @NonNull
  51. @Override
  52. public LayoutInflater getLayoutInflater() {
  53. if (that == null) {
  54. return super.getLayoutInflater();
  55. } else {
  56. return that.getLayoutInflater();
  57. }
  58. }
  59. @Override
  60. public Window getWindow() {
  61. if (that == null) {
  62. return super.getWindow();
  63. } else {
  64. return that.getWindow();
  65. }
  66. }
  67. @Override
  68. public WindowManager getWindowManager() {
  69. if (that == null) {
  70. return super.getWindowManager();
  71. } else {
  72. return that.getWindowManager();
  73. }
  74. }
  75. @Override
  76. public void startActivity(Intent intent) {
  77. if (that == null) {
  78. super.startActivity(intent);
  79. } else {
  80. Intent newIntent = new Intent();
  81. newIntent.putExtra("className", intent.getComponent().getClassName());
  82. that.startActivity(newIntent);
  83. }
  84. }
  85. @Override
  86. public void onCreate(Bundle savedInstanceState) {
  87. }
  88. @Override
  89. public void onStart() {
  90. }
  91. @Override
  92. public void onResume() {
  93. }
  94. @Override
  95. public void onPause() {
  96. }
  97. @Override
  98. public void onStop() {
  99. }
  100. @Override
  101. public void onDestroy() {
  102. }
  103. @Override
  104. public void onSaveInstanceState(Bundle outState) {
  105. }
  106. @Override
  107. public boolean onTouchEvent(MotionEvent event) {
  108. return false;
  109. }
  110. @Override
  111. public void onBackPressed() {
  112. }
  113. @Override
  114. public void attach(AppCompatActivity appCompatActivity) {
  115. this.that = appCompatActivity;
  116. }
  117. }
复制代码

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

在app项目中使用DexClassLoader加载第三方APK
  1. package tsou.cn.alipay;
  2. import android.content.Context;
  3. import android.content.pm.PackageInfo;
  4. import android.content.pm.PackageManager;
  5. import android.content.res.AssetManager;
  6. import android.content.res.Resources;
  7. import java.io.File;
  8. import java.lang.reflect.InvocationTargetException;
  9. import java.lang.reflect.Method;
  10. import dalvik.system.DexClassLoader;
  11. /**
  12. * Created by Administrator on 2018/4/10 0010.
  13. */
  14. public class PluginManager {
  15. private DexClassLoader dexClassLoader;
  16. private Resources resources;
  17. private Context context;
  18. private String entryActivityName;
  19. private static final PluginManager ourInstance = new PluginManager();
  20. public static PluginManager getInstance() {
  21. return ourInstance;
  22. }
  23. private PluginManager() {
  24. }
  25. public void loadPath(String path) {
  26. File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
  27. dexClassLoader = new DexClassLoader(path, dexOutFile.getAbsolutePath(), null, context.getClassLoader());
  28. // 获取包名
  29. PackageManager packageManager = context.getPackageManager();
  30. PackageInfo packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
  31. entryActivityName = packageInfo.activities[0].name;
  32. //实例化resources
  33. try {
  34. AssetManager assetManager = AssetManager.class.newInstance();
  35. Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
  36. addAssetPath.invoke(assetManager, path);
  37. resources = new Resources(assetManager, context.getResources().getDisplayMetrics(),
  38. context.getResources().getConfiguration());
  39. } catch (InstantiationException e) {
  40. e.printStackTrace();
  41. } catch (IllegalAccessException e) {
  42. e.printStackTrace();
  43. } catch (NoSuchMethodException e) {
  44. e.printStackTrace();
  45. } catch (InvocationTargetException e) {
  46. e.printStackTrace();
  47. }
  48. }
  49. public DexClassLoader getDexClassLoader() {
  50. return dexClassLoader;
  51. }
  52. public Resources getResources() {
  53. return resources;
  54. }
  55. public String getEntryActivityName() {
  56. return entryActivityName;
  57. }
  58. public void setContext(Context context) {
  59. this.context = context.getApplicationContext();
  60. }
  61. }
复制代码

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

创建ProxyActivity承载taopiaopiao主页
  1. package tsou.cn.alipay;
  2. import android.content.Intent;
  3. import android.content.res.Resources;
  4. import android.os.Bundle;
  5. import android.support.annotation.Nullable;
  6. import android.support.v7.app.AppCompatActivity;
  7. import java.lang.reflect.Constructor;
  8. import java.lang.reflect.InvocationTargetException;
  9. import tsou.cn.alipaystander.AlipayInterface;
  10. /**
  11. * Created by Administrator on 2018/4/10 0010.
  12. * <p>
  13. * 专门去插桩使用
  14. */
  15. public class ProxyActivity extends AppCompatActivity {
  16. //要跳转的 淘票票Activity
  17. private String className;
  18. private AlipayInterface alipayInterface;
  19. @Override
  20. protected void onCreate(@Nullable Bundle savedInstanceState) {
  21. super.onCreate(savedInstanceState);
  22. className = getIntent().getStringExtra("className");
  23. /**
  24. * 通过className不能拿到类名
  25. * 原生是加载过的类名
  26. */
  27. // Class clazz = getClassLoader().loadClass(className);
  28. // Class.forName(className);
  29. try {
  30. //加载该Activity的字节码对象
  31. Class activityClass = getClassLoader().loadClass(className);
  32. Constructor constructor = activityClass.getConstructor(new Class[]{});
  33. //创建该Activity的示例
  34. Object newInstance = constructor.newInstance(new Object[]{});
  35. //程序健壮性检查
  36. if (newInstance instanceof AlipayInterface) {
  37. /**
  38. * 使用定义接口标准
  39. * 传递生命周期
  40. */
  41. alipayInterface = (AlipayInterface) newInstance;
  42. //将代理Activity的实例传递给三方Activity
  43. alipayInterface.attach(this);
  44. //创建bundle用来与三方apk传输数据
  45. Bundle bundle = new Bundle();
  46. //调用三方Activity的onCreate,
  47. alipayInterface.onCreate(bundle);
  48. }
  49. } catch (ClassNotFoundException e) {
  50. e.printStackTrace();
  51. } catch (IllegalAccessException e) {
  52. e.printStackTrace();
  53. } catch (InstantiationException e) {
  54. e.printStackTrace();
  55. } catch (NoSuchMethodException e) {
  56. e.printStackTrace();
  57. } catch (InvocationTargetException e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. /**
  62. * 重新,通过className拿到类名
  63. *
  64. * @return
  65. */
  66. @Override
  67. public ClassLoader getClassLoader() {
  68. return PluginManager.getInstance().getDexClassLoader();
  69. }
  70. /**
  71. * @return
  72. */
  73. @Override
  74. public Resources getResources() {
  75. return PluginManager.getInstance().getResources();
  76. }
  77. @Override
  78. protected void onStart() {
  79. if (alipayInterface != null)
  80. alipayInterface.onStart();
  81. super.onStart();
  82. }
  83. @Override
  84. protected void onResume() {
  85. if (alipayInterface != null)
  86. alipayInterface.onResume();
  87. super.onResume();
  88. }
  89. @Override
  90. protected void onPause() {
  91. if (alipayInterface != null)
  92. alipayInterface.onPause();
  93. super.onPause();
  94. }
  95. @Override
  96. protected void onStop() {
  97. if (alipayInterface != null)
  98. alipayInterface.onStop();
  99. super.onStop();
  100. }
  101. @Override
  102. protected void onDestroy() {
  103. if (alipayInterface != null)
  104. alipayInterface.onDestroy();
  105. super.onDestroy();
  106. }
  107. @Override
  108. public void startActivity(Intent intent) {
  109. String className = intent.getStringExtra("className");
  110. Intent intent1 = new Intent(this, ProxyActivity.class);
  111. intent1.putExtra("className",className);
  112. super.startActivity(intent1);
  113. }
  114. }
复制代码
添加sd权限
  1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  2. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
复制代码
实现app加载taopiaopiao项目
  1. package tsou.cn.alipay;
  2. import android.Manifest;
  3. import android.content.Intent;
  4. import android.content.pm.PackageManager;
  5. import android.os.Bundle;
  6. import android.os.Environment;
  7. import android.support.annotation.NonNull;
  8. import android.support.v4.app.ActivityCompat;
  9. import android.support.v7.app.AppCompatActivity;
  10. import android.view.View;
  11. import android.widget.Button;
  12. import android.widget.Toast;
  13. import java.io.File;
  14. public class MainActivity extends AppCompatActivity implements View.OnClickListener {
  15. /**
  16. * 加载
  17. */
  18. private Button mLoad;
  19. /**
  20. * 点击
  21. */
  22. private Button mClick;
  23. String[] mPermissionList = new String[]{
  24. Manifest.permission.WRITE_EXTERNAL_STORAGE,
  25. Manifest.permission.READ_EXTERNAL_STORAGE};
  26. @Override
  27. protected void onCreate(Bundle savedInstanceState) {
  28. super.onCreate(savedInstanceState);
  29. setContentView(R.layout.activity_main);
  30. PluginManager.getInstance().setContext(this);
  31. initView();
  32. }
  33. private void initView() {
  34. mLoad = (Button) findViewById(R.id.load);
  35. mLoad.setOnClickListener(this);
  36. mClick = (Button) findViewById(R.id.click);
  37. mClick.setOnClickListener(this);
  38. }
  39. @Override
  40. public void onClick(View v) {
  41. switch (v.getId()) {
  42. default:
  43. break;
  44. case R.id.load://加载
  45. // 缺少权限时, 进入权限配置页面
  46. ActivityCompat.requestPermissions(MainActivity.this,mPermissionList, 100);
  47. break;
  48. case R.id.click://点击
  49. Intent intent = new Intent(this, ProxyActivity.class);
  50. // intent.putExtra("className", "淘票票的全类名 MainActivity");
  51. intent.putExtra("className", PluginManager.getInstance().getEntryActivityName());
  52. startActivity(intent);
  53. break;
  54. }
  55. }
  56. @Override
  57. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  58. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  59. switch (requestCode) {
  60. case 100:
  61. boolean camera = grantResults[0] == PackageManager.PERMISSION_GRANTED;
  62. boolean readExternalStorage = grantResults[1] == PackageManager.PERMISSION_GRANTED;
  63. if (grantResults.length > 0 && camera && readExternalStorage) {
  64. File file = new File(Environment.getExternalStorageDirectory(), "plugin.apk");
  65. PluginManager.getInstance().loadPath(file.getAbsolutePath());
  66. } else {
  67. Toast.makeText(this.getApplicationContext(),"请设置必要权限",Toast.LENGTH_SHORT).show();
  68. }
  69. break;
  70. }
  71. }
  72. }
复制代码
taopiaopiao项目更改UI页面

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

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

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

图片描述

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

在taopiaopiao中的activity的跳转
  1. startActivity(new Intent(that,SceondActivity.class));
复制代码
  1. package tsou.cn.taopiaopiao;
  2. import android.os.Bundle;
  3. public class SceondActivity extends BaseActivity {
  4. @Override
  5. public void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.activity_sceond);
  8. }
  9. }
复制代码

需要对startActivity进行重新

1、在BaseActivity中进行传递

  1. @Override
  2. public void startActivity(Intent intent) {
  3. if (that == null) {
  4. super.startActivity(intent);
  5. } else {
  6. Intent newIntent = new Intent();
  7. newIntent.putExtra("className", intent.getComponent().getClassName());
  8. that.startActivity(newIntent);
  9. }
  10. }
复制代码

2、在承载页面ProxyActivity中重新

  1. @Override
  2. public void startActivity(Intent intent) {
  3. String className = intent.getStringExtra("className");
  4. Intent intent1 = new Intent(this, ProxyActivity.class);
  5. intent1.putExtra("className",className);
  6. super.startActivity(intent1);
  7. }
复制代码

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

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



回复

使用道具 举报