前言

small是android与iOS平台比较出名的轻巧的跨平台插件化框架,也正是被这一点吸引,决定将small应用到集团内部的应用引擎模块化方案中,本篇博文主要讲述本人基于small在android平台实现的定制化APP方案(运营自由配置、自由组合、自动打包)~

框架解决问题

  1. 由于公司业务的发展,导致更多的超级app诞生于世,导致app 项目太大、耦合严重,且相关开发人员相互耦合,导致效率低下
  2. app 热修复问题在很多场景是急需的
  3. 对于开发效率方面,采用了weex模块进行快速迭代

在介绍框架之前首先熟悉一下small的原理~

原理介绍

small 插件化方案分为两个步骤

  1. gradle 打包插件机制
  2. 运行期加载机制

打包插件机制

官方说明

将多个app与lib工程编译成so文件

运行期加载机制

Dynamic load classes

官方说明

DexClassLoader不支持”.so”后缀,为了让应用启动时能自动复制插件包到应用存储目录,需要支持”.so”后缀。做法就是模拟 压缩包加载代码块,创建一个dex元素,再反射添加到宿主class loader里的dexPathList。

Dynamic load resources

官方说明

Dynamic register activities

activity 启动过程:

注: Android activities受Instrumentation监控

  1. 由Activity的startActivityForResult方法启动,通过instrumentation的execStartActivity方法激活生命周期。
  2. 在ActivityThread的performLaunchActivity方法中通过instrumentation的newActivity方法实例化。
small 实现方案:

1. 首先在宿主manifest中注册一个命名特殊的占坑activity

<!-- Stub Activities -->
<activity android:name=".A.0" android:launchMode="standard"/>

2. 封装一个instrumentation,替换掉宿主的

(1)、欺骗startActivityForResult(启动过程1)以获得生命周期
(2)、欺骗performLaunchActivity(启动过程2)来创建插件activity实例

ActivityThread thread = currentActivityThread();
Instrumentation base = thread.@mInstrumentation;
Instrumentation wrapper = new InstrumentationWrapper(base);
thread.@mInstrumentation = wrapper;

class InstrumentationWrapper extends Instrumentation {
// 欺骗startActivityForResult获得生命周期
public ActivityResult execStartActivity(..., Intent intent, ...) {
fakeToStub(intent);
base.execStartActivity(args);
}

// 欺骗performLaunchActivity创建实例
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) {
className = restoreToReal(intent, className);
return base.newActivity(cl, className, intent);
}
}

项目构建

按照官方的教程,初始化成功之后,项目结构如下:

框架获取

  1. git clone https://github.com/osmartian/odyssey.git
  2. cd odyssey
  3. npm install

项目结构

small-frame
├── app (宿主app)
│ ├── LaunchActivity
│ │
│ └── SmallApp

├── app.top (topbar框架APP)
│ │
│ └── MainActivity

├── app.bottom (bottombar框架app)
│ │
│ └── MainActivity

├── app.home (首页模块app)
│ └── MainFragment

├── app.weex (weex模块app)
│ │
│ ├── MainActivity
│ │
│ └── MainFragment

├── app.detail (详情模块app)
│ ├── MainActivity
│ │
│ └── SubActivity

├── lib.weex (weex lib 库)

├── lib.martian (公共工具库)

└── lib.style (公共样式库)
└── res
├── colors.xml
├── dimens.xml
└── styles.xml

config驱动文件打包APK流程

打包配置说明

{
// app基本信息配置
baseInfo: {
applicationId: 'com.syswin.toon.bottom',
versionCode: 2,
appIcon: 'bottom',
appName: 'BOTTOM框架',
versionName: '1.0.1'
},
// 框架末班配置
frame: {
// 框架模块uri
uri: 'bottom',
// 可选
tags: []
},
// 配置small打包配置
modules: {
// small 版本
version: '1.0.0',
// 需要打包的app及lib
bundles: [
{
uri: 'bottom',
pkg: 'com.osmartian.small.app.bottom'
},
...
{
uri: 'lib.style',
pkg: 'com.osmartian.small.lib.style'
}
]
}
}

APK生成过程

  1. 动态修改APP基本信息

采用动态修改gradle配置方式,修改AppID、appName、appIcon…

在项目根build.gradle中有如下配置,只需动态修改此配置即可:

ext {
compileSdkVersion = 25
buildToolsVersion = '25.0.2'
applicationId = "com.syswin.toon.walid"
appName = "Walid APK"
appIcon = "top"
minSdkVersion = 15
targetSdkVersion = 25
versionCode = 10
versionName = "1.0.1"
}
  1. 设置框架frame模块

动态设置框架模块uri,用于宿主模块调起

见宿主app模块下com.osmartian.small中的config.java文件:

package com.osmartian.small;

/**
* @Author : walid
* @Data : 2017-03-14 22:36
* @Describe : INDEX URL配置
*/

public class Config {
public static final String INDEX_URI = "bottom?tags=%5B%7B%22name%22%3A%22%E9%A6%96%E9%A1%B5%22%2C%22uri%22%3A%22home%22%7D%2C%7B%22name%22%3A%22%E6%88%91%E7%9A%84%22%2C%22uri%22%3A%22weex%3Furl%3Dhttp%253A%252F%252F172.31.243.44%253A12580%252Fdist%252Fweex%252Fviews%252Fmine%252Fapp.js%22%7D%2C%7B%22name%22%3A%22%E4%B8%AA%E4%BA%BA%E8%B5%84%E6%96%99%22%2C%22uri%22%3A%22weex%3Furl%3Dhttp%253A%252F%252F172.31.243.44%253A12580%252Fdist%252Fweex%252Fviews%252Fuserinfo%252Fapp.js%22%7D%5D";
}
  1. 生成bundle.json small打包需要文件

动态生成bundle.json small打包文件,且copy到android、ios项目

(1)、生成bundle.json文件

const bundlePath = path.join(__dirname, '../../build/output', 'bundle.json')

// 框架模块安装
function packModules(modules) {
console.log(modules)
return new Promise((resolve, reject) => {
fs.writeFile(bundlePath, JSON.stringify(modules), (err) => {
err ? reject(err) : resolve()
})
})

}

(2)、copy 至 android 、 ios项目

// cp -vf build/output/bundle.json android/app/src/main/assets/bundle.json
npm run copy:bundle
  1. 执行small打包 -> so 文件

将需要打包的模块打包成so文件

npm run build:small
  1. 编译生成apk文件

执行npm指令进行apk生成

npm run dev:android 
//
npm run build:android

small 模块跳转操作

1、 跳转h5

  Small.openUri("https://github.com/osmartian/small-frame", getContext());

2、 跳转app module 传值

  Small.openUri("detail?params=我是参数,从首页传送过来的~", getContext());

3、 跳转app module 二级界面

  Small.openUri("detail/sub", getContext());

项目打包APK示例

打包topbar框架APK

  • config 文件配置
{
baseInfo: {
applicationId: 'com.syswin.toon.top',
versionCode: 2,
appIcon: 'top',
appName: 'TOP框架',
versionName: '1.0.1'
},
frame: {
uri: 'top',
tags: [
{
name: '首页',
uri: 'home'
},
{
name: '发起筹款',
uri: `weex?url=${encodeURIComponent(`http://${ipAddress}:12580/dist/weex/views/launch/app.js`)
}`
},
{
name: '我的',
uri: `weex?url=${encodeURIComponent(`http://${ipAddress}:12580/dist/weex/views/mine/app.js`)
}`
}
]
},
modules: {
version: '1.0.0',
bundles: [
{
uri: 'top',
pkg: 'com.osmartian.small.app.top'
},
{
uri: 'home',
pkg: 'com.osmartian.small.app.home'
},
{
uri: 'weex',
pkg: 'com.osmartian.small.app.weex'
},
{
uri: 'lib.weex',
pkg: 'com.osmartian.small.lib.weex'
},
{
uri: 'lib.martian',
pkg: 'com.osmartian.small.lib.martian'
},
{
uri: 'lib.style',
pkg: 'com.osmartian.small.lib.style'
}
]
}
}

示例图片

打包bottombar框架APK

  • config 文件配置
{
baseInfo: {
applicationId: 'com.syswin.toon.bottom',
versionCode: 2,
appIcon: 'bottom',
appName: 'BOTTOM框架',
versionName: '1.0.1'
},
frame: {
uri: 'bottom',
tags: [
{
name: 'Weex首页',
uri: `weex?url=${encodeURIComponent(`http://${ipAddress}:12580/dist/weex/views/home/app.js`)
}`
},
{
name: '原生首页',
uri: `home`
},
{
name: '我的',
uri: `weex?url=${encodeURIComponent(`http://${ipAddress}:12580/dist/weex/views/mine/app.js`)
}`
}
]
},
modules: {
version: '1.0.0',
bundles: [
{
uri: 'bottom',
pkg: 'com.osmartian.small.app.bottom'
},
{
uri: 'home',
pkg: 'com.osmartian.small.app.home'
},
{
uri: 'weex',
pkg: 'com.osmartian.small.app.weex'
},
{
uri: 'detail',
pkg: 'com.osmartian.small.app.detail',
rules: {
sub: 'Sub'
}
},
{
uri: 'lib.weex',
pkg: 'com.osmartian.small.lib.weex'
},
{
uri: 'lib.martian',
pkg: 'com.osmartian.small.lib.martian'
},
{
uri: 'lib.style',
pkg: 'com.osmartian.small.lib.style'
}
]
}
}

示例图片

结语

至此,基于small的定制化APP方案介绍完毕了,此方案还是雏形阶段,也希望业界朋友多多点评、多多吐槽,也希望大家前去start及提一些各自的建议。

项目地址:https://github.com/osmartian/odyssey.git

更多相关文章

  1. redis框架搭建实战(持续更新)
  2. android插件化-apkplug框架启动-02
  3. 【原创】安卓程序员的大革命,Cocovr框架库v3.1问世,像开发iPhone
  4. Android 网络框架学习之Retrofit
  5. 实现基于注解(Annotation)的数据库框架(一)反射的基本了解
  6. android Room框架学习
  7. 推荐一个Emoji框架
  8. Android功能模块化之网络连接状态判断
  9. 基于Android6.0的RIL底层模块分析

随机推荐

  1. 解决android调用系统相机拍照保存时onAct
  2. 路径提供者文档目录是一个安全的位置吗?
  3. 如何在Android 7.0+中检索SD卡的序列号?
  4. Android多线程下载远程图片【转】
  5. 转:Android Studio Error:Connection time
  6. MD5加密,java工具类 String 转变成MD5 St
  7. 没有包lib32z1,lib32ncurses5,lib32stdc+
  8. Android TextView设置字体风格
  9. 如何在Fragment中的WebView中添加“Go Ba
  10. Android自定义控件——开源组件SlidingMe