【Android】基于Small及Weex的定制化APP方案

基于Small及Weex的定制化APP方案

前言

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'
      }
    ]
  }
}

示例图片

top-home.jpg
top-launch.jpg
top-mine.jpg

打包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'
      }
    ]
  }
}

示例图片

bottom-weex-home.jpg
bottom-native-home.jpg
bottom-mine.jpg

结语

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • 1.框架的选择:本次选择的插件化框架在经过数次比较之后选择了国产大神开发的目前在市场上较为流行的small插件化框...
    凤天凌阅读 3,513评论 8 11
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,454评论 25 707
  • 最近几年移动开发业界兴起了「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优...
    斜杠时光阅读 3,940评论 1 36
  • 天天挤地铁上下班,每天经历两次高峰期,日子久了,也能从中悟出点道理,经历些好玩的事情,这也算是挤地铁的一点...
    齐鲁云阅读 238评论 0 2
  • 小豆豆是一个淘气的女孩子,他在上小学一年级时已经被退过3次学了,但是,她的妈妈帮她找着了一个学校,那里有着电车...
    王睿思阅读 197评论 0 0