gradle task一些实践

在使用jenkins与walle结合的时候,需要用到gradle task的一些功能,使用task来做一些事情,就有了一些实践。

task的调用,怎么运行

1、taskname.execute()
2、gradle taskname

比如一个简单的例子

task task1 << { 
     println "Hello" 
} 
task task2 << { 
    println "World" 
} 
task task3(dependsOn: 'task3dependency') << { 
    println "Yu" 
} 
task task3dependency << { 
    println "Duke" 
} 
task tests(dependsOn: ['task1', 'task2', 'task3']) {
}

输出的是
Hello World Duke Yu
这种写法是顺序执行,另外有一种写法是异步执行,如

task tests << { 
    task1.execute()
    task2.execute() 
    task3.execute() 
}

是没法确定结果的,因为闭包是异步的。

再比如,假设我们要在assembleDebug后加一个task(runTask),
可以这样做,利用gradle.taskGraph.afterTask找到task,执行命令
gradle assembleDebug

gradle.taskGraph.afterTask { task ->
    if (task.name == 'assembleDebug') {
        runTask.execute()
    }
}

实际上跑的命令是

assembleDebug
runTask

或者 定义一个task(runTask)依赖于assembleDebug,执行命令
gradle runTask

task runTask(dependsOn: 'assembleDebug') {
}

实际上跑的命令是

assembleDebug
runTask

关于copy

可以在task里直接写copy,比如

gradle.taskGraph.afterTask { task ->
    if (task.name == 'assembleDebug') {
        copy {
            from "${project.buildDir}/outputs/apk/app-debug.apk "
            into "${project.buildDir}/outputs/apk/"
            rename { String fileName ->
                fileName.replace("app-debug.apk", “test.apk”)
            }
        }
    }
}

也可以写一个copy类型的task

task copyApk(typy: copy)  {
    from "${project.buildDir}/outputs/apk/app-debug.apk "
    into "${project.buildDir}/outputs/apk/"
    rename { 
        String fileName ->
        fileName.replace("app-debug.apk", “test.apk”)
    }
}

浅谈nodejs加log4js日志管理

日前有一个项目,是小程序项目,需要加入日志进行监控。小程序采用koa+qcloud2架构,准备使用log4js来做日志管理。基于项目的架构,就引入了koa-log4

首先安装koa-log4

npm install koa-log4 —save

log4js v2.x的配置:

{
  "appenders": {
"access": {
  "type": "dateFile",
  "filename": "logs/access.log",
  "pattern": "-yyyy-MM-dd"
},
"rule-console": {
  "type": "console"
},
"rule-file": {
  "type": "dateFile",
  "filename": "logs/server-",
  "encoding": "utf-8",
  "maxLogSize": 10000000,
  "numBackups": 3,
  "pattern": "yyyy-MM-dd.log",
  "alwaysIncludePattern": true
},
"rule-error": {
  "type": "dateFile",
  "filename": "logs/error-",
  "encoding": "utf-8",
  "maxLogSize": 1000000,
  "numBackups": 3,
  "pattern": "yyyy-MM-dd.log",
  "alwaysIncludePattern": true,
  "level": "error"
}
  },
  "categories": {
"default": {
  "appenders": [
"rule-console"
  ],
  "level": "all"
},
"resLogger": {
  "appenders": [
"rule-file"
  ],
  "level": "info"
},
"errorLogger": {
  "appenders": [
"rule-error"
  ],
  "level": "error"
},
"http": {
  "appenders": [
"access"
  ],
  "level": "info"
}
  }
}

ps:

appenders配置的是具体的策略,分别有console、file。而categories主要是针对多种情况所做的配置,可以对不同的场景配置不同的策略,比如error日志对应rule-error的策略单独放到error的文件,info日志放到普通文件,这样就可以区分错误和普通日志。

获取配置的代码  log4js.getLogger("resLogger")

log4js的使用:

一般来说我们要求统一加日志,要把接口的输入和输出都记录下来,而不用手动去为每个接口去加日志,那样太麻烦了。并且要记录错误日志,在发生问题的时候可以清楚我们的系统究竟出了什么问题,快速定位问题并解决问题。

so,我们基本锁定中间件的方案,加一个中间件,在接口的响应部分包上一层,并且统一捕捉异常。统一处理接口日志和错误日志。也方便将错误替换成其他提示返回给前端。

const debug = require('debug')('koa-weapp-demo')
const logUtil = require('../tools/logUtil.js')

/**
响应处理模块
 */
module.exports = async function (ctx, next) {
  //响应开始时间
  const start = new Date();
  //响应间隔时间
  var ms;
  try {
// 调用下一个 middleware
await next()

ms = new Date() - start;
//记录响应日志
_logUtil.logResponse(ctx, ms);_

// 处理响应结果
// 如果直接写入在 body 中,则不作处理
// 如果写在 ctx.body 为空,则使用 state 作为响应
ctx.body = ctx.body ? ctx.body : {
  code: ctx.state.code !== undefined ? ctx.state.code : 0,
  data: ctx.state.data !== undefined ? ctx.state.data : {}
}
  } catch (e) {
// catch 住全局的错误信息
debug('Catch Error: %o', e)

ms = new Date() - start;
//记录异常日志
_logUtil.logError(ctx, e, ms);_


// 设置状态码为 200 - 服务端错误
ctx.status = 200

// 输出详细的错误信息
ctx.body = {
  code: -1,
  error: e && e.message ? e.message : e.toString()
}
  }
}


const response = require('./middlewares/response')
// 使用响应处理中间件
app.use(response)

而log4js需要自己封装成一个工具类,除了要具备能将响应记录在文件里外,还要有能将普通日志和错误信息写进文件的能力

logUtil我引用这里,感觉写得很好,对我很有帮助,在这个基础上我做了修改,小程序在腾讯云上没有更多的写入权限(/root/logs),所以引用log4js会导致测试环境跑不了,切记https://www.cnblogs.com/smartsensor/p/7838169.html

/**
使用方法:
logUtil.resLogger.info('test')
logUtil.errorLogger.error('test')
logUtil.logInfo('test')
const start = new Date()
var ms
logUtil.logResponse(ctx, ms)
logUtil.logError(ctx, error, ms)
 */

var config = require('../config.js')
if (!config.useQcloudLogin) {
  var log4js = require('log4js');

  var log_config = require('../log4js.json');

  //加载配置文件
  log4js.configure(log_config);

  var logUtil = {};
  //调用预先定义的日志名称
  var resLogger = log4js.getLogger("resLogger");
  var errorLogger = log4js.getLogger("errorLogger");
  var consoleLogger = log4js.getLogger();

  console.log = resLogger.info.bind(resLogger)

  logUtil.resLogger = resLogger
  logUtil.errorLogger = errorLogger
  logUtil.consoleLogger = consoleLogger


  //封装错误日志
  logUtil.logError = function (ctx, error, resTime) {
if (ctx && error) {
  errorLogger.error(formatError(ctx, error, resTime));
}
  };

  //封装响应日志
  logUtil.logResponse = function (ctx, resTime) {
if (ctx) {
  resLogger.info(formatRes(ctx, resTime));
}
  };

  logUtil.logInfo = function (info) {
if (info) {

  consoleLogger.info(formatInfo(info));
}
  };

  var formatInfo = function (info) {
var logText = new String();
//响应日志开始
logText += "\n" + "***************info log start ***************" + "\n";

//响应内容
logText += "info detail: " + "\n" + JSON.stringify(info) + "\n";

//响应日志结束
logText += "*************** info log end ***************" + "\n";

return logText;
  }

  //格式化响应日志
  var formatRes = function (ctx, resTime) {
var logText = new String()
//响应日志开始
logText += "\n" + "*************** response log start ***************" + "\n"

//添加请求日志
logText += formatReqLog(ctx.request, resTime)

//响应状态码
logText += "response status: " + ctx.status + "\n"

//响应内容
logText += "response body: " + "\n" + JSON.stringify(ctx.body) + "\n"

//响应日志结束
logText += "*************** response log end ***************" + "\n"

return logText

  }

  //格式化错误日志
  var formatError = function (ctx, err, resTime) {
var logText = new String()

//错误信息开始
logText += "\n" + "*************** error log start ***************" + "\n"

//添加请求日志
logText += formatReqLog(ctx.request, resTime)

//错误名称
logText += "err name: " + err.name + "\n"
//错误信息
logText += "err message: " + err.message + "\n"
//错误详情
logText += "err stack: " + err.stack + "\n"

//错误信息结束
logText += "*************** error log end ***************" + "\n"

return logText
  };

  //格式化请求日志
  var formatReqLog = function (req, resTime) {

var logText = new String()

var method = req.method
//访问方法
logText += "request method: " + method + "\n"

//请求原始地址
logText += "request originalUrl:  " + req.originalUrl + "\n"

//客户端ip
logText += "request client ip:  " + req.ip + "\n"

logText += "request header:  " + JSON.stringify(req.header) + "\n"

//开始时间
var startTime
//请求参数
if (method === 'GET') {
  logText += "request query:  " + JSON.stringify(req.query) + "\n"
} else {
  logText += "request body: " + "\n" + JSON.stringify(req.body) + "\n"
}
//服务器响应时间
logText += "response time: " + resTime + "\n"

return logText
  }

  module.exports = logUtil
} else {
  var logUtil = {};
  logUtil.resLogger = {};
  logUtil.resLogger.info = function (msg) {
  }
  logUtil.resLogger.error = function (msg) {
  }

  logUtil.errorLogger = {};
  logUtil.errorLogger.info = function (msg) {
  }
  logUtil.errorLogger.error = function (msg) {
  }

  logUtil.consoleLogger = {};
  logUtil.consoleLogger.info = function (msg) {
  }
  logUtil.consoleLogger.error = function (msg) {
  }

  logUtil.logError = function (ctx, error, resTime) {
  }
  logUtil.logResponse = function (ctx, resTime) {
  }
  logUtil.logInfo = function (info) {
  }
  module.exports = logUtil
}

可以将console.log重定向到log4js统一管理,如下:

console.log = resLogger.info.bind(resLogger)

使用koa-log4中间件直接记录log,我没有研究到什么有用的东西,记录的日志也不是有用的。如下

const log4js = require('koa-log4')
app.use(log4js.koaLogger(log4js.getLogger("http"), { level: 'auto' }))

到这里就可以看log4js的威力,基本上可以满足我小程序的日志需求,使用起来挺方便的。

小程序nodejs

如果是非腾讯云,使用qcloud2的时候要记得把water_xxx的index.js里的90行开始4行注释掉。

npm install
npm install xxx —save
pm2 log
pm2 start xxx.json
pm2 stop xxx
pm2 restart xxx
npm run start
npm run initDb
npm run dev

关于android多渠道打包的总结

公司有40个渠道包的需要,我用了一台主机专门打包,最快也要40分钟才能打完,一般都是50分钟,当在发布前发现问题的时候,重新打包导致开发人员噩梦开始,并且推广人员跟着加班,效率很低。

1、gradle参数提速
org.gradle.daemon=true 开启daemon守护进程
gradle assembleDebug –dry-run –no-daemon 11.374 secs
gradle assembleDebug –dry-run –daemon 2.565 secs

org.gradle.parallel=true 并行化编译,模块化项目提升比较多

2、将不常改动的类封装成稳定的底层类,甚至可以建公司的maven仓库等;
jar或者aar
结果:可以抽取的类不多,提速的效果不是很明显 。

3、减少方法数,将方法数降到65535,不使用multidex,可以提升编译速度,也可以提高app启动速度

目前必须引入的第三方类库加起来已经超过65535,目前v3.22.0的方法数是91001,远超65535,实现难度比较大

4、剔除不需要和重复的库
使用gradle :app:dependencies –configuration compile可以分析出该项目的所有第三方包的依赖,
使用 debugCompile和exclude来减少不需要和重复的库
compile(‘com.android.support:support-fragment:25.3.1’) {
exclude group: ‘com.android.support’, module: ‘support-media-compat’
exclude group: ‘com.android.support’, module: ‘support-annotations’
}

5、开发阶段将minSdk设置为21,发布或者调试兼容性的时候将minSdk设置回14
minSdk 14 ———— 1m16s
minSdk 21 ———— 24s

风险:设置后IDE会认为项目默认是支持最低21,导致一些兼容性友好的提示消失了,开发完必须要做低版本的测试。

6、去掉lint步骤
tasks.whenTaskAdded { task ->
if (task.name.equals(“lint”) || (task.name.contains(“lintVital”) && task.name.contains(“Release”))) {
task.enabled = false
}
}

在考拉项目里省去了5*n秒的lint时间(n为Flavors数量)

7、采用可商用的多渠道打包方案
VasDolly 腾讯
Walle 美团 (考拉接入)

原理都差不多,都是找到不被app签名校验的地方(某个文件空间,某个文件),用来记录一些信息,而不破坏之前app的签名,不能重新打包,所以,可以认为这部分信息是不需要被保护的,比如渠道标识。

旧打包方式是采用productFlavors,每打一个渠道包都要重新编译一次,简单计算时间
n * t (60s < t < 90s, n为渠道包数量)

walle采用的是在打包完成后加渠道,简单计算就是打包一个apk的时间,
t + n * t1 (60s < t < 90s, 0s < t1 < 0.2s, n为渠道包数量)
特点:需求是所有的渠道包除了“渠道”这种标识性信息外,其他的信息都必须一致,比如app名字都必须一致。

采用Walle需要解决的问题
1)、解决个性apk名字的问题
区分普通渠道包和定制渠道包,定义n个productFlavors,一个叫genenal,其他的是渠道名,比如oppo等,时间变成
t * m + n * t1 (60s < t < 90s, 0s < t1 < 0.2s, n为渠道包数量,m为需要定制的渠道包数量+1)
2)、360应用宝加固channel失效的问题
先加固再重新使用walle加渠道标识

walle与jenkins结合
release流程:(发包)
->打包(productFlavors)
->walle写入渠道
->360加固,应用宝加固(应用宝加固需要手动)
->360,应用宝walle写入渠道
->渠道包上传七牛
->把渠道包和mapping文件传到共享盘

整个过程大约需要15分钟

debug 流程 :(给内部人员提供测试版本下载)
-> 打包
->上传蒲公英
->展示二维码

整个过程大约需要4分钟

关于小米5 miui9刷xp框架的问题

过程比较久,要有耐心

1、云备份,本地备份
2、如果recovery和system都进不了,卡米了,那就用miflash线刷,注意右下角选择“保留所有数据”
3、进去bootloader,用fastboot命令
fastboot flash recovery twrp-3.0.2-3-gemini.img
进去第三方recovery,先双清,然后adb线刷模式
adb sideload SuperSU-v2.79-SR4-支持7.1.1.zip
adb sideload xposed-v88.2-sdk24%26Android7.0X-arm64-test5.zip
4、启动到system_,miui9欢迎你,此时看到已经root了
adb install XposedInstaller_3.1.2.apk
5、恢复备份,起飞

Android 5.0以上虚拟按键适配Popupwindow

在android5.0以上虚拟按键的机器上弹出自定义的软键盘的时候(popupwindow),被虚拟按键覆盖了,导致软键盘一部分地方按不了。

解决方案:
给自定义的popupwindow设置属性然后弹出来:

popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);_mPopWindow.showAtLocation(getActivity().getWindow().getDecorView(), Gravity.BOTTOM, 0, 0);

在华为荣耀7android5.0上测试通过

关于使用listview时候的一些坑

关于使用listview时候的一些坑

1、listivew如果想在最前面加个view,要用addHeaderView,不要用在每个item里面通过设置gone和visible,因为这样会影响listview的渲染效率,特别是在使用noScrollListview的时候。

2、scrollview嵌套listivew或者其他view的时候,一旦listview的数据发生改变,scrollview会滚到数据更新的那个位置,不让scrollview自己滚动的方法是:focusOn srcollview下面的子view。

3、listview使用addHeaderView后,导致点击回调的position错乱,解决方法是,回调后去调

position = (int) listview.getAdapter().getItemId(position);

而adatper里面要实现

@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}