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

Posted 王永迪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Small及Weex的定制化APP方案相关的知识,希望对你有一定的参考价值。

前言

smallandroidios平台比较出名的轻巧的跨平台插件化框架,也正是被这一点吸引,决定将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

以上是关于基于Small及Weex的定制化APP方案的主要内容,如果未能解决你的问题,请参考以下文章

Weex容器助力UC浏览器国际化之路

Activity插件化解决方案

网易云基于Kubernetes的深度定制化实践

点我达骑手Weex最佳实践

Weex原理及架构剖析

Weex Workshop 挑战赛,等你来战!