原生安卓集成Ionic3+CodePush

很多项目可能是在中途才从纯原生转向Hybrid,那就涉及到将Ionic、React Native等开发的功能模块集成到已有的原生安卓项目中,那么非原生的部分当然就需要热更新了,否则每次都打包apk就失去这么做的意义了。
本人也是在网上查看了一些文章,看了一些相关书本,把项目集成好了决定整理一下写下本文,供大家参考,有不对的地方欢迎指出。

环境:

Android Studio 3.2 + JDK 1.8 + node.js 10.13.0 + ionic 4.3.1 + cordova 8.1.2 + code-push 2.1.9

npm命令搭建Ionic的环境咱就跳过吧(0_0)

参考:

Ionic创建项目以及集成code-push官方文档: https://ionicframework.com/docs/native/code-push/
Ionic集成到原生安卓项目:https://blog.csdn.net/qq_42618969/article/details/81173034

创建项目:

首先打开命令行,进入指定路径(项目路径根据个人情况自行修改),一波连续操作:

ionic start codepush-ionic-test tabs

创建tabs基础ionic项目(这里我并没有选择使用Ionic4,Ionic的资料有点少,所以不想用beta版本给自己找坑)


创建tabs样式的ionic3项目
不需要Ionic Pro SDK
cd codepush-ionic-test

进入项目文件夹

ionic cordova plugin add cordova-plugin-code-push

添加code-push插件


添加cordova可能会比较慢,感觉卡住的话Ctrl + C然后重新添加code-push
npm install --save @ionic-native/code-push

node_modules添加code-push库


添加code-push
ionic cordova platform add android

Ionic添加安卓平台支持(不添加的话项目只是web,苹果平台将android改为ios即可)

npm install

安装code-push-cli(这个一定加上-g表明全局安装,以后就可以省略这步)

npm install -g code-push-cli

登录code-push(这一步会自动打开电脑浏览器,注册账号或者使用github账号登录都行,登录后会给一个token)

code-push login
code-push账号token

将code-push账号token黏贴到命令行

在code-push中创建项目(苹果端替换android为ios)

code-push app add codepush-ionic-test-android android cordova
项目key

如果已经创建了项目忘记了项目的key可以通过这个命令查看

code-push deployment ls codepush-ionic-test-android -k

在项目根目录打开config.xml,将code-push的key添加进去

<platform name="android">
    <preference name="CodePushDeploymentKey" value="*******" />
</platform>
<platform name="ios">
    <preference name="CodePushDeploymentKey" value="*******" />
</platform>

编辑src/app/app.module.ts代码如下(这个有点像安卓中的build.gradle,这里主要是设置CodePush库引用):

import { NgModule, ErrorHandler } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';

import { AboutPage } from '../pages/about/about';
import { ContactPage } from '../pages/contact/contact';
import { HomePage } from '../pages/home/home';
import { TabsPage } from '../pages/tabs/tabs';

import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { CodePush } from '@ionic-native/code-push';

@NgModule({
  declarations: [
    MyApp,
    AboutPage,
    ContactPage,
    HomePage,
    TabsPage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    AboutPage,
    ContactPage,
    HomePage,
    TabsPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    CodePush,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

编辑src/app.component.ts添加热更新的检查更新方法(这个有点类似application初始化):

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

import { HomePage } from '../pages/home/home';

import { CodePush, InstallMode, SyncStatus } from '@ionic-native/code-push';
import { AlertController } from 'ionic-angular/components/alert/alert-controller';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:any = HomePage;

  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen,
              private codePush: CodePush,  private alertCtrl: AlertController) {
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      statusBar.styleDefault();
      splashScreen.hide();
      this.checkCodePush(); //Use the plugin always after platform.ready()
    });
  }

  checkCodePush() {
    
     this.codePush.sync({
      updateDialog: {
       appendReleaseDescription: true,
       descriptionPrefix: "\n\nChange log:\n"   
      },
      installMode: InstallMode.IMMEDIATE
   }).subscribe(
     (data) => {
      console.log('CODE PUSH SUCCESSFUL: ' + data);
      
     },
     (err) => {
      console.log('CODE PUSH ERROR: ' + err);
      
     }
   );
  }

}

接下来在电脑浏览器运行一下,继续命令行

ionic serve
在web端显示效果

到这里就已经将Ionic集成code-push了,最后编译安卓项目

ionic cordova build android

集成到原生安卓项目

Ionic项目中找到安卓项目,就在platforms文件夹里面(ios项目路径也在这),这已经是一个能直接用Android Studio打开的项目了。
1、将其中的CordovaLib整个文件夹拷贝到要集成的原生安卓项目中,在gradle配置一下作为module。
2、将项目中res/xml/config.xml文件拷贝到原生项目中,将Ionic项目中的“org文件夹、io文件夹”拷贝到原生项目的main/java下。
3、接下来是将Ionic中写的功能相关代码拷贝到原生项目中,也就是assets中的www文件夹,里面才是在Ionic中的代码,而热更新便是更新这里面的内容。

其实如果是集成Ionic到原生项目的话,到这里已经完成了,但是code-push还没放进去。

4、在安卓原生项目中添加依赖(这个库在Ionic安卓项目的cordova-plugin-code-push/starter-build-extras.gradle中添加了引用)

implementation 'com.nimbusds:nimbus-jose-jwt:5.1'

5、拷贝Ionic安卓项目中的src/main/java/com到原生安卓项目中,最后原生项目文件结构如图所示(马赛克是原生项目本来的代码):

项目结构

到这里整个项目集成Ionic + code-push就基本完成了,随便找个地方从原生项目跳转进Ionic的功能模块里面(跳转activity就好,跳到io.ionic.starter.MainActivity,记得到AndroidManifest.xml中添加这个Activity,这里为了省事我把Ionic的路径直接一起传过去)

Bundle bundle = new Bundle();
bundle.putString("url", "file:///android_asset/www/index.html");
IntentUtils.startActivity(mActivity, MainActivity.class, bundle);

再稍微改一下io.ionic.starter.MainActivity

public class MainActivity extends CordovaActivity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        // enable Cordova apps to be started in the background
        Bundle extras = getIntent().getExtras();
        if (extras != null && extras.getBoolean("cdvStartInBackground", false)) {
            moveTaskToBack(true);
        }

        // Set by <content src="index.html" /> in config.xml
        loadUrl(getIntent().getStringExtra("url"));
    }
}

这时已经可以运行原生安卓项目了。当然,还有热更新没测试,再回到Ionic项目中,打开源码src文件夹随便修改点东西,然后命令行编译一下

ionic cordova prepare android

这时候再上传推送包到微软的code-push中,命令中的0.0.1是版本号,用原生安卓项目的versionName的值,--description后面跟的是热更新包的说明,-d后面跟的是推送分支,code-push创建项目后便会生成正式线和测试线两个分支,

code-push release codepush-ionic-test-android ./platforms/android/app/src/main/assets/www/ 0.0.1 --description "Your awesome change description" -d "Staging"
检查更新

题外:

1、热更新检查可以不弹框提示采取静默安装,具体的就自行去修改Ionic那部分代码了,毕竟ios审核禁止热更新,还是不能光明正大的。配置在config.xml里面热更新使用的key也可以动态修改,在代码里控制测试线、正式线以及ios和android,自己去做判断就好,简单上几行代码:

//根据平台修改key
if (this.platform.is('android')) {
    key = '';
} else if (this.platform.is('ios')) {
    key = '';
}
//定义检查更新参数
let options = {
    installMode:InstallMode.ON_NEXT_RESUME,
    deploymentKey:key
}
//检查更新方法
checkCodePush() {
  this.codePush.sync(options,(data) => {
    
 }).subscribe(
   (data) => {
    console.log('CODE PUSH SUCCESSFUL: ' + data);
    
   },
   (err) => {
    console.log('CODE PUSH ERROR: ' + err);
    
   }
 );
}

2、检查更新有几个模式,这里用的是启动Ionic的时候检查更新,其它的自己加代码吧,类似这样:

//从后台回到应用时检查更新
this.platform.resume.subscribe(() => {
    this.checkCodePush();
});

3、另外说一下本人在集成过程中踩的一个坑,因为用fiddler抓包,手机设置了代理,将集成好的安卓项目打包release包运行时,发现code-push不检测更新,在代码里找了好几圈,看Android Studio中的Logcat发现日志出现了检测更新的请求,说网络连接有问题,才明白code-push对网络有做检查,release包运行一旦有网络代理就不会发起请求,这是https加密传输的SSL证书问题,具体的就不深究了。
4、这里使用的是微软code-push的服务,也可以自行搭建一个code-push服务端。
5、当然了,可以把www文件夹以及cdvasset.manifest文件放到自定义路径:
一是使用自己的方式更新文件,只需要修改跳转io.ionic.starter.MainActivity时传过去的页面路径即可修改ionic页面访问。

//正常情况下读取文件我们会这样写路径
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "www/index.html"
//在这里需要加个前缀,像原本assets路径类似
String path = "file:" + Environment.getExternalStorageDirectory().getAbsolutePath() + "www/index.html"

还有就是把codepush热更新的路径也改掉,需要修改三个地方:

// com.microsoft.cordova.CodePush中的WWW_ASSET_PATH_PREFIX常量
private static final String WWW_ASSET_PATH_PREFIX = "file:" + Environment.getExternalStorageDirectory().getAbsolutePath() + "/www/";


// org.apache.cordova.ConfigXmlParser中的setStartUrl方法,这里是设置开始页面
launchUrl = "file:" + Environment.getExternalStorageDirectory().getAbsolutePath()+"/www/index.html";


// org.apache.cordova.file.AssetFilesystem中的lazyInitCaches方法,注意这里无须添加“file:”,将原本打开assets路径文件改为普通的打开文件并转换成InputStream对象(FileInputStream是InputStream的子类)
ois = new ObjectInputStream(new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath()+"/cdvasset.manifest"));

最后说一下,code-push一旦进行过热更新后,文件会保存到内部存储中,真正自定义还需要修改CodePush以及Cordova其它代码。

发包对应APP版本范围表达式

1.2.3——仅仅只有1.2.3的版本
*——所有版本
1.2.x——主要版本1,次要版本2的任何修补程序版本
1.2.3 - 1.2.7——1.2.3版本到1.2.7版本
>=1.2.3 <1.2.7——大于等于1.2.3版本小于1.2.7的版本
~1.2.3——大于等于1.2.3版本小于1.3.0的版本
^1.2.3——大于等于1.2.3版本小于2.0.0的版本

完结,感谢阅读~

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