flutter 踩坑: gradle build 报错

当把Android Studio或者VS code配置好之后,按照官方教程里的写的开始run的时候,出现了以下报错:

Launching lib\main.dart on emulator-5554 in debug mode...
* Error running Gradle:
Exit code 1 from: E:\project\myflutter\android\gradlew.bat app:properties:


FAILURE: Build failed with an exception.



* What went wrong:

A problem occurred configuring root project 'android'.

> Could not resolve all files for configuration ':classpath'.

   > Could not download kotlin-reflect.jar (org.jetbrains.kotlin:kotlin-reflect:1.1.3-2)

      > Could not get resource 'https://jcenter.bintray.com/org/jetbrains/kotlin/kotlin-reflect/1.1.3-2/kotlin-reflect-1.1.3-2.jar'.

         > Could not HEAD 'https://jcenter.bintray.com/org/jetbrains/kotlin/kotlin-reflect/1.1.3-2/kotlin-reflect-1.1.3-2.jar'.

            > Connect to d29vzk4ow07wi7.cloudfront.net:443 [d29vzk4ow07wi7.cloudfront.net/143.204.132.100, d29vzk4ow07wi7.cloudfront.net/143.204.132.57, d29vzk4ow07wi7.cloudfront.net/143.204.132.53, d29vzk4ow07wi7.cloudfront.net/143.204.132.159] failed: Read timed out

   > Could not download kotlin-stdlib.jar (org.jetbrains.kotlin:kotlin-stdlib:1.1.3-2)

      > Could not get resource 'https://jcenter.bintray.com/org/jetbrains/kotlin/kotlin-stdlib/1.1.3-2/kotlin-stdlib-1.1.3-2.jar'.

         > Could not HEAD 'https://jcenter.bintray.com/org/jetbrains/kotlin/kotlin-stdlib/1.1.3-2/kotlin-stdlib-1.1.3-2.jar'.

            > Connect to d29vzk4ow07wi7.cloudfront.net:443 [d29vzk4ow07wi7.cloudfront.net/143.204.132.100, d29vzk4ow07wi7.cloudfront.net/143.204.132.57, d29vzk4ow07wi7.cloudfront.net/143.204.132.53, d29vzk4ow07wi7.cloudfront.net/143.204.132.159] failed: Read timed out

   > Could not download protobuf-java.jar (com.google.protobuf:protobuf-java:3.0.0)

      > Could not get resource 'https://jcenter.bintray.com/com/google/protobuf/protobuf-java/3.0.0/protobuf-java-3.0.0.jar'.

         > Could not GET 'https://jcenter.bintray.com/com/google/protobuf/protobuf-java/3.0.0/protobuf-java-3.0.0.jar'.

            > Connect to d29vzk4ow07wi7.cloudfront.net:443 [d29vzk4ow07wi7.cloudfront.net/143.204.132.100, d29vzk4ow07wi7.cloudfront.net/143.204.132.57, d29vzk4ow07wi7.cloudfront.net/143.204.132.53, d29vzk4ow07wi7.cloudfront.net/143.204.132.159] failed: Read timed out

   > Could not download bcpkix-jdk15on.jar (org.bouncycastle:bcpkix-jdk15on:1.56)

      > Could not get resource 'https://jcenter.bintray.com/org/bouncycastle/bcpkix-jdk15on/1.56/bcpkix-jdk15on-1.56.jar'.

         > Could not HEAD 'https://jcenter.bintray.com/org/bouncycastle/bcpkix-jdk15on/1.56/bcpkix-jdk15on-1.56.jar'.

            > Remote host closed connection during handshake

   > Could not download bcprov-jdk15on.jar (org.bouncycastle:bcprov-jdk15on:1.56)

      > Could not get resource 'https://jcenter.bintray.com/org/bouncycastle/bcprov-jdk15on/1.56/bcprov-jdk15on-1.56.jar'.

         > Could not HEAD 'https://jcenter.bintray.com/org/bouncycastle/bcprov-jdk15on/1.56/bcprov-jdk15on-1.56.jar'.

            > Connect to d29vzk4ow07wi7.cloudfront.net:443 [d29vzk4ow07wi7.cloudfront.net/143.204.132.57, d29vzk4ow07wi7.cloudfront.net/143.204.132.53, d29vzk4ow07wi7.cloudfront.net/143.204.132.159, d29vzk4ow07wi7.cloudfront.net/143.204.132.100] failed: Read timed out

   > Could not download fastutil.jar (it.unimi.dsi:fastutil:7.2.0)

      > Could not get resource 'https://jcenter.bintray.com/it/unimi/dsi/fastutil/7.2.0/fastutil-7.2.0.jar'.

         > Could not HEAD 'https://jcenter.bintray.com/it/unimi/dsi/fastutil/7.2.0/fastutil-7.2.0.jar'.

            > Connect to d29vzk4ow07wi7.cloudfront.net:443 [d29vzk4ow07wi7.cloudfront.net/143.204.132.100, d29vzk4ow07wi7.cloudfront.net/143.204.132.57, d29vzk4ow07wi7.cloudfront.net/143.204.132.53, d29vzk4ow07wi7.cloudfront.net/143.204.132.159] failed: Read timed out

   > Could not download ecj.jar (org.eclipse.jdt.core.compiler:ecj:4.6.1)

      > Could not get resource 'https://jcenter.bintray.com/org/eclipse/jdt/core/compiler/ecj/4.6.1/ecj-4.6.1.jar'.

         > Could not HEAD 'https://jcenter.bintray.com/org/eclipse/jdt/core/compiler/ecj/4.6.1/ecj-4.6.1.jar'.

            > Remote host closed connection during handshake

   > Could not download jimfs.jar (com.google.jimfs:jimfs:1.1)

      > Could not get resource 'https://jcenter.bintray.com/com/google/jimfs/jimfs/1.1/jimfs-1.1.jar'.

         > Could not HEAD 'https://jcenter.bintray.com/com/google/jimfs/jimfs/1.1/jimfs-1.1.jar'.

            > Connect to d29vzk4ow07wi7.cloudfront.net:443 [d29vzk4ow07wi7.cloudfront.net/143.204.132.100, d29vzk4ow07wi7.cloudfront.net/143.204.132.57, d29vzk4ow07wi7.cloudfront.net/143.204.132.53, d29vzk4ow07wi7.cloudfront.net/143.204.132.159] failed: Read timed out

   > Could not download lombok-ast.jar (com.android.tools.external.lombok:lombok-ast:0.2.3)

      > Could not get resource 'https://jcenter.bintray.com/com/android/tools/external/lombok/lombok-ast/0.2.3/lombok-ast-0.2.3.jar'.

         > Could not HEAD 'https://jcenter.bintray.com/com/android/tools/external/lombok/lombok-ast/0.2.3/lombok-ast-0.2.3.jar'.

            > Connect to d29vzk4ow07wi7.cloudfront.net:443 [d29vzk4ow07wi7.cloudfront.net/143.204.132.57, d29vzk4ow07wi7.cloudfront.net/143.204.132.53, d29vzk4ow07wi7.cloudfront.net/143.204.132.159, d29vzk4ow07wi7.cloudfront.net/143.204.132.100] failed: Read timed out

   > Could not download guava.jar (com.google.guava:guava:22.0)

      > Could not get resource 'https://jcenter.bintray.com/com/google/guava/guava/22.0/guava-22.0.jar'.

         > Could not HEAD 'https://jcenter.bintray.com/com/google/guava/guava/22.0/guava-22.0.jar'.

            > Connect to d29vzk4ow07wi7.cloudfront.net:443 [d29vzk4ow07wi7.cloudfront.net/143.204.132.57, d29vzk4ow07wi7.cloudfront.net/143.204.132.53, d29vzk4ow07wi7.cloudfront.net/143.204.132.159, d29vzk4ow07wi7.cloudfront.net/143.204.132.100] failed: Read timed out

   > Could not download antlr4.jar (org.antlr:antlr4:4.5.3)

      > Failed to download SHA1 for resource 'https://jcenter.bintray.com/org/antlr/antlr4/4.5.3/antlr4-4.5.3.jar'.

         > Could not GET 'https://jcenter.bintray.com/org/antlr/antlr4/4.5.3/antlr4-4.5.3.jar.sha1'.

            > Read timed out

   > Could not download juniversalchardet.jar (com.googlecode.juniversalchardet:juniversalchardet:1.0.3)

      > Failed to download SHA1 for resource 'https://jcenter.bintray.com/com/googlecode/juniversalchardet/juniversalchardet/1.0.3/juniversalchardet-1.0.3.jar'.

         > Could not GET 'https://jcenter.bintray.com/com/googlecode/juniversalchardet/juniversalchardet/1.0.3/juniversalchardet-1.0.3.jar.sha1'.

            > Read timed out

   > Could not download proguard-base.jar (net.sf.proguard:proguard-base:5.3.3)

      > Could not get resource 'https://jcenter.bintray.com/net/sf/proguard/proguard-base/5.3.3/proguard-base-5.3.3.jar'.

         > Could not HEAD 'https://jcenter.bintray.com/net/sf/proguard/proguard-base/5.3.3/proguard-base-5.3.3.jar'.

            > Read timed out

   > Could not download commons-compress.jar (org.apache.commons:commons-compress:1.12)

      > Could not get resource 'https://jcenter.bintray.com/org/apache/commons/commons-compress/1.12/commons-compress-1.12.jar'.

         > Could not HEAD 'https://jcenter.bintray.com/org/apache/commons/commons-compress/1.12/commons-compress-1.12.jar'.

            > Read timed out



* Try:

Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.



* Get more help at https://help.gradle.org



BUILD FAILED in 6m 10s
Please review your Gradle project setup in the android/ folder.
Exited (sigterm)

看提示就是没有下载下来么,手动访问提示中出现的地址,发现能访问。胡乱尝试了一些办法后,发现都没用。

最后解决方案是:

  1. 保持翻墙状态
  2. 不断的run以触发gradle initial

这样你就发现报错每次都会减少一点,最后就跑成功了 😅

flutter踩坑:开启Android模拟器

按照flutter官方教程进行到了Android setup中的Set up the Android emulator

一切就绪之后开始启动模拟器,一直报error code 1

网上找了一堆信息,好像都不是说这个问题。

问了身边的Android工程师,解释说可能是JDK没装

可是,JDK和模拟器有啥关系?

根据以往踩坑经验error code 1都是系统层面的,想起之前在linux 上安装虚拟机踩过的坑,突然觉得是不是和硬件有关系? 顺藤摸瓜,发现这里面用到了Intel的虚拟化技术

重启,进BIOS,发现Intel 虚拟化没打开,打开后重新进入。

问题解决了。

官方文档中有一个Enable VM acceleration

但是怎么Enable没有说的很清楚,所以如果发现以上问题先查下机器的设置。

Github Issue Robot 思路

需求

在项目开源(公开)后,使用者会对项目提供意见、反馈、建议等。这些issue当中会存在「不合要求」的issue,并对项目开发产生干扰。

在这种情况下,假设出现了「不合格」的issue,如果要进行人工处理,将是比较庞大的工作,而我们并不期望这些琐事占用我们太多的时间。

目标

使用🤖来判断issue是否「合格」。如果「不合格」,则直接关闭issue。

实现

基于以上,我们要构建这样的🤖,需要具备:

  1. 项目本身要有Webhook可被调用
  2. 机器人要有对项目的权限

Github配置

  1. 开通项目的Webhook,并设置screctpayload地址(回调)
  2. 为机器人开通帐号,并获取身份标识token

基本时序

程序由webhook触发,因此本程序是一个服务端运行的程序。具体时序图:

流程

当接受到一次调用,程序处理逻辑:

实现

搭建框架

由于是纯接口操作,我们只需要构建一个服务端。本质上用任何一个可实现服务端的语言/框架都可以,选择我们较为熟悉的js语法,并通过node来构建web服务。为了减少底层工作,比如「文件操作」、「HTTP协议」处理,我们选择让koa框架来处理这些事,我们专注于自身业务逻辑。

模块划分

  1. 主模块 用来整合其他模块并处理网络请求
  2. github模块 处理向github发送的各种命令
  3. 日志模块 调试用,并记录请求
  4. 业务逻辑模块 对接受到的消息进行判断和处理

参考

  1. https://github.com/ant-design/ant-bot
  2. https://github.com/iview/iview-bot

vue feedback 指令开发

功能需求

<template>
  <span v-feedback="{activeClass:'span-active'}"></span>
</template>
<style>
.span-active {
  background: #000;
}
</style>

当该span被点时,.span-active生效。此处span可以是任意的html元素或组件

以鼠标操作举例,所谓的点是指左键被按下,按下时样式生效,松开后失效

为什么要使用指令方式

1. 直接使用组件

<feedback :active="{activeClass: 'span-active'}">
  <span></span>
</feedback>

由于vue的组件必须包含一个根节点,实际上是

<div class="span-active">
  <span></span>
</div>

此时出现了尴尬,span本身是一个inline元素,加上feedback后变成了block,并且feedback被使用的时候并不知道slot进来的元素是什么,如果要知道势必又要增加复杂度。

2. 使用混合

<template>
  <span></span>
</template>
<script>
import feedback from '@/components/feedback.js'
export default {
  mixin: {
    feedback
  },
  data () {
    return {
      activeClassName: 'span-active'
    }
  }
}
</script>

mixin是当前使用的方式,但是对使用人员要求更高,需要同时知道两个组件的内部实现。

实现思路

该指令需要干以下几件事:

  1. 为绑定指令的组件添加点击的监听,包括mouseupmousedowntouchestarttouchendtouchcancel
  2. 当事件发生时,为元素添加相应的activeClass
  3. 当元素本身是disabled的时候,去除样式的响应,实际上是去除监听

初始化的时候,先获得绑定的元素,并且添加监听器。在监听到有关事件的时候,添加、删除样式

export default {
  bind (el, binding) {
    const className = binding.value.activeClass
    const disabled = !!binding.value.disabled
    if (!disabled) {
      el.addEventListener('mousedown', ()=> {el.classList.add(className)})
      el.addEventListener('touchstart',  ()=> {el.classList.add(className)})
      el.addEventListener('touchcancel',  ()=> {el.classList.remove(className)})
      el.addEventListener('touchend',  ()=> {el.classList.remove(className)})
      el.addEventListener('mouseup',  ()=> {el.classList.remove(className)})
    }
  }
}

到此时,一切都很美好。

在实际应用中,这个组件极有可能点击后发生了改变,比如从!disabled转为了disabled状态,需要我们对其进行进一步处理。这个看起来很简单,!disabled看起来并不需要处理,原来的事件应该保留。只有disabled的状况需要把原有的监听器去除掉。

export default {
  bind (el, binding) {
    const className = binding.value.activeClass
    const disabled = !!binding.value.disabled
    if (!disabled) {
      el.addEventListener('mousedown', ()=> {el.classList.add(className)})
      el.addEventListener('touchstart',  ()=> {el.classList.add(className)})
      el.addEventListener('touchcancel',  ()=> {el.classList.remove(className)})
      el.addEventListener('touchend',  ()=> {el.classList.remove(className)})
      el.addEventListener('mouseup',  ()=> {el.classList.remove(className)})
    }
  },
  componentUpdated (el, binding) {
    const className = binding.value.activeClass
    const disabled = !!binding.value.disabled
    if (disabled) {
      el.removeEventListener('mousedown', ()=> {el.classList.add(className)})
      el.removeEventListener('touchstart',  ()=> {el.classList.add(className)})
      el.removeEventListener('touchcancel',  ()=> {el.classList.remove(className)})
      el.removeEventListener('touchend',  ()=> {el.classList.remove(className)})
      el.removeEventListener('mouseup',  ()=> {el.classList.remove(className)})
    }
  }
}

运行后,发现监听器没有被去除掉,因为 removeEventListener 应该移除之前添加的监听器,因为使用了箭头函数,所以后面的监听器并不是之前的监听器。那我们把箭头函数换成统一的函数

function addClass (el, className) {
  el.classList.add(className)
}

function removeClass (el, className) {
  el.classList.remove(className)
}

export default {
  bind (el, binding) {
    const className = binding.value.activeClass
    const disabled = !!binding.value.disabled
    if (!disabled) {
      el.addEventListener('mousedown', addClass(el, className))
      el.addEventListener('touchstart',  addClass(el, className))
      el.addEventListener('touchcancel',  removeClass(el, className))
      el.addEventListener('touchend',  removeClass(el, className))
      el.addEventListener('mouseup',  removeClass(el, className))
    }
  },
  componentUpdated (el, binding) {
    const className = binding.value.activeClass
    const disabled = !!binding.value.disabled
    if (disabled) {
      el.removeEventListener('mousedown', addClass(el, className))
      el.removeEventListener('touchstart',  addClass(el, className))
      el.removeEventListener('touchcancel',  removeClass(el, className))
      el.removeEventListener('touchend',  removeClass(el, className))
      el.removeEventListener('mouseup',  removeClass(el, className))
    }
  }
}

报错了,想当然是不行的。addEventListener 的第二个参数

listener 必须是一个实现了 EventListener 接口的对象,或者是一个函数

如果只传入函数,那又产生了一个问题,addClassremoveCLass无法取得elclassName的。这里面还存在一个作用域的问题。最后想了一个办法,就是利用eventTarget来替代el,并且把className放在Dom上。

function addClass (event) {
  const el = event.currentTarget
  const className = event.currentTarget.getAttribute('data-active-class')
  el.classList.add(className)
}

function removeClass (event) {
  const el = event.currentTarget
  const className = event.currentTarget.getAttribute('data-active-class')
  el.classList.remove(className)
}

function feedback (el, className, option) {
  el.setAttribute('data-active-class', className)
  if (option === 'add') {
    el.addEventListener('mousedown', addClass)
    el.addEventListener('touchstart', addClass)
    el.addEventListener('touchcancel', removeClass)
    el.addEventListener('touchend', removeClass)
    el.addEventListener('mouseup', removeClass)
  } else if (option === 'remove') {
    el.removeEventListener('mousedown', addClass)
    el.removeEventListener('touchstart', addClass)
    el.removeEventListener('touchcancel', removeClass)
    el.removeEventListener('touchend', removeClass)
    el.removeEventListener('mouseup', removeClass)
  }
}
export default {
  bind (el, binding) {
    const className = binding.value.activeClass
    const disabled = !!binding.value.disabled
    if (!disabled) {
      feedback(el, className, 'add')
    }
  },
  componentUpdated (el, binding) {
    const className = binding.value.activeClass
    const disabled = !!binding.value.disabled
    const oldDisabled = !!binding.oldValue.disabled
    if (oldDisabled !== disabled) {
      if (disabled) {
        feedback(el, className, 'remove')
      } else {
        feedback(el, className, 'add')
      }
    }
  },
  unbind (el, binding) {
    const className = binding.value.activeClass
    feedback(el, className, 'remove')
  }
}

优化及未尽事宜

  1. disabled是不是可以不通过指令传入
  2. 把监听和添加样式从el转到vnode