IntelliJ IDEA/Android Studio的插件库,统计代码行数可以用到插件:Statistic。
Android dp 和 CSS px
最近在给WebView前端传递标题栏高度的时候,传递的是实际像素Pixel,结果前端直接设置这个数值的时候,发现太高了。搜索了一下,发现前端的CSS样式设置的像素值是逻辑像素,这个逻辑像素跟实际像素是不同的,对于Android来说,就是设备的dp数值。
参考文章如下:
前情提要:设计师给的设计稿是1242分辨率的(iPhone6p),标注的字体大小是54px,前端工程师写的H5页面是18px,效果正常且能自适应iPhone6。来调查一下 为什么是标注数值除以3?为什么自动适应iPhone6?css里的px和Android的dp有怎样的关系?
在进行具体的分析之前,首先得知道下面这些关键性基本概念(术语)。
物理像素(physical pixel)
一个物理像素是显示器(手机屏幕)上最小的物理显示单元,在操作系统的调度下,每一个设备像素都有自己的颜色值和亮度值。
设备独立像素(density-independent pixel)
设备独立像素(也叫密度无关像素),可以认为是计算机坐标系统中得一个点,这个点代表一个可以由程序使用的虚拟像素(比如: css像素),然后由相关系统转换为物理像素。
所以说,物理像素和设备独立像素之间存在着一定的对应关系,这就是接下来要说的设备像素比。
设备像素比(device pixel ratio)
设备像素比(简称dpr)定义了物理像素和设备独立像素的对应关系,它的值可以按如下的公式的得到:
|
1 |
设备像素比 = 物理像素 / 设备独立像素 // 在某一方向上,x方向或者y方向 |
在javascript中,可以通过window.devicePixelRatio获取到当前设备的dpr。
在css中,可以通过-webkit-device-pixel-ratio,-webkit-min-device-pixel-ratio和 -webkit-max-device-pixel-ratio进行媒体查询,对不同dpr的设备,做一些样式适配(这里只针对webkit内核的浏览器和webview)。
iPhone6的物理像素是750 x 1334,设备像素比(devicePixelRatio)是2,设备独立像素是375 x 667 (750/2 x 1334/2)
iPhone6p的物理像素是1080 x 1920,但logic pixel是1242 x 2208, 设备像素比(devicePixelRatio)是3,设备独立像素是414 x 736 (1242/3 x 2208/3)
css里面有
物理像素和逻辑像素的概念,在这里,逻辑像素可以理解为设备独立像素
其实,logic point更适合翻译成逻辑点。无论是逻辑像素还是逻辑点,只需要理解一个逻辑点(逻辑像素)需要一个或一个以上的物理像素来展示就可以了。
那么,为什么iPhone6p是这样奇葩的逻辑点呢?
- 如果逻辑点分辨率用 360x640, 360x640@3x 正好是 1080x1920。但是逻辑pt分辨率 360x640 就会比 iPhone 6的 375x667 还低,也就是说相同字号的情况下,iPhone 6如果一行显示了25个字,而 iPhone 6 Plus按这个逻辑pt方案,一行就会只能显示24 个字了。
- 如果逻辑点分辨率用 540x960,540x960@2x 正好是 1080x1920。但是iOS UI 元素尺寸在屏幕上的实际物理面积一下子就变小了,比如标签栏或导航栏按钮的物理高度只有原来的 81.5% ,点击面积就只有iPhone 6 的0.815*0.815=66.4%,用户点击就困难了。
- 如果物理分辨率做到1242x2208就减少了一个从1242压缩到1080的过程,但是1242的物理分辨率是在1080p和2k屏之前的尺寸,功耗和成本都会提升,而且这样非1440的2k分辨率的屏幕采购也是问题。
至于为什么一定是 414x736,有人估计应该是在 5.5inch 和 ppi=461 这两个前提限定的情况下,按这个 414x736 pt 分辨率,屏幕上 UI 元素操作物理大小最接近 iPhone 6上的表现。
Android世界中更加复杂,这里列举了几款手机的基本数据
| Nexus 4 | Nexus 6 | Nexus 6p | LG L24 | Mi 4c | |
|---|---|---|---|---|---|
| 物理像素 | 768 x 1280 | 1440 x 2560 | 1440 x 2560 | 1440 x 2560 | 1080 x 1920 |
| 设备像素比(dpr) | 2 | 3.5 | 3.3 | 4 | 3 |
| 设备独立像素(dip/dp) | 384 x 640 | 412 x 732 | 435 x 773 | 360 x 640 | 360 x 640 |
Android提供了获取density的方法:
|
1 |
float density = context.getResources().getDisplayMetrics().density; |
density的官方定义是The logical density of the display,翻译过来是屏幕的逻辑密度,其实就是之前提到的设备像素比(dpr)。
在浏览器中,获取设备像素比就比较简单:
|
1 |
window.devicePixelRatio |
比如,我写了一个网页,用手机浏览器打开:
|
1 |
<button onclick="alert('window.devicePixelRatio='+window.devicePixelRatio);">show devicePixelRatio</button> |
结果显示与Android的density的结果是一致的。
注:<meta name="viewport" content="width=device-width, initial-scale=1">
By specifying width=”device-width”, you are asking the browser to apply a scaling factor to its screen pixels. One css pixel occupies one or more screen pixels. How many more? This value is called the css pixel ratio.
回顾一下
同一台设备,它的设备像素比(dpr)是确定的,无论通过Android api 获取还是浏览器 js/css 获取。
The css px is actually Device Independent Pixel(dip), and it is a common pattern to use px as dp in css.
css中的px具有与Android中的dp等同的效果
参考链接
Python发送form-data请求提交表单数据
最近在处理服务器接口的时候,需要使用Python模拟发送表单数据到服务器,搜索了一下,找到如下例子:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# encoding=utf8 import urllib import urllib2 import httplib import os HOST = 'xx.xx.xx.xx' PORT = 8000 P_ID = xx U_NAME = 'xxx' API_KEY = 'xxx' API_SEC = 'xxx' UP_APK = xx def sign(src, key): HEX_CHARS = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' ] from hashlib import sha1 import hmac hashed = hmac.new(key, src, sha1) sv = str(hashed.digest()) ret = '' for b in bytearray(sv): bite = b & 0xff ret += HEX_CHARS[ bite >> 4 ] ret += HEX_CHARS[ bite & 0xf ] return ret def encode_multipart_formdata(fields, files): """ fields is a sequence of (name, value) elements for regular form fields. files is a sequence of (name, filename, value) elements for data to be uploaded as files Return (content_type, body) ready for httplib.HTTP instance """ import random BOUNDARY = '----------%s' % ''.join(random.sample('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ', 62)) CRLF = '\r\n' L = [] for (key, value) in fields: L.append('--' + BOUNDARY) L.append('Content-Disposition: form-data; name="%s"' % key) L.append('') L.append(value) for (key, filename, value) in files: L.append('--' + BOUNDARY) L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) L.append('Content-Type: %s' % get_content_type(filename)) L.append('') L.append(value) L.append('--' + BOUNDARY + '--') L.append('') body = CRLF.join(L) content_type = 'multipart/form-data; boundary=%s' % BOUNDARY return content_type, body, BOUNDARY def get_content_type(filename): import mimetypes return mimetypes.guess_type(filename)[0] or 'application/octet-stream' def post_apk_sign(apk_file): filename = os.path.basename(apk_file) url = 'http://' + HOST + ':' + str(PORT) + 'xxx' fields = [('un', U_NAME), ('p_id', str(P_ID)), ('up_type', str(UP_APK))] with open(apk_file, 'r') as f: files = [('apk_file', filename, f.read())] content_type, body, boundary = encode_multipart_formdata(fields, files) si = sign(API_KEY + str(P_ID) + str(UP_APK) + U_NAME, API_SEC) # print si req = urllib2.Request(url) req.add_header('api_key', API_KEY) req.add_header('sign', si) req.add_header('Content-Type', 'multipart/form-data; boundary='+boundary) resp = urllib2.urlopen(req, body) res = resp.read() print res if __name__=='__main__': # b38d73bb278800dd3c0c3cf11ad20c106d17944c # print(sign(b'123test123456', b'456')) apk_file = 'xxx.apk' post_apk_sign(apk_file) |
参考链接
rsync同步报告错误"cannot delete non-empty directory"
最近在执行rsync的时候,发现报告错误信息cannot delete non-empty directory,如下:
|
1 2 3 4 |
$ rsync -avzpP --delete -e "ssh -p $PORT" $USER@$SERVER:$WWW_DIR $WWW_DST cannot delete non-empty directory: xxxxx cannot delete non-empty directory: xxxxx cannot delete non-empty directory: xxxxx |
解决方法是增加--delete-excluded即可,如下:
|
1 |
$ rsync -avzpP --delete --delete-excluded -e "ssh -p $PORT" $USER@$SERVER:$WWW_DIR $WWW_DST |
参考链接
ubuntu 18.04更新snap慢或者失败
|
1 2 3 4 5 6 7 8 |
$ sudo snap install snap-store $ sudo snap install snap-store-proxy $ sudo snap install snap-store-proxy-client # 更新已经安装的软件 $ sudo snap refresh |
参考链接
Jenkins阻止Execute shell输出命令内容
默认情况下,Jenkins在构建的时候会输出脚本中命令的具体内容,原因是在执行脚本的时候,使用的是-e参数执行的。
但是当我们的脚本中存在用户名,密码等关键信息的时候,这些内容会被输出到编译日志中,这样就容易导致安全问题。
解决办法是在脚本的最前面增加#!/bin/bash,来覆盖脚本执行的默认参数,如下图:
Centos 7配置Jenkins构建Android持续集成(离线内网环境)
安装配置Jenkins
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# 要求Java 1.8 $ yum install java $ yum install subversion $ yum install tomcat $ /bin/systemctl restart tomcat.service $ wget https://get.jenkins.io/war/2.254/jenkins.war $ cp jenkins.war /usr/share/tomcat/webapps/ $ /bin/systemctl restart tomcat.service # 服务器访问地址 # http://158.220.155.188:8080/jenkins/ # apache $ yum install httpd $ /bin/systemctl restart httpd.service # apache 默认工作目录 /var/www/html/ |
为了安全考虑,首先需要解锁Jenkins
Gradle传递System Property
最近在搭建Jenkins环境,实现Android自动化编译的过程中,由于内网服务器不能访问外网,因此只能配置Robolectric访问内网的服务器。
根据官方文档 Configuring Robolectric,发现如果要更改Robolectric默认的下载服务器链接地址,需要在项目的每个lib库中都配置如下参数,才能实现:
|
1 2 3 4 5 6 7 8 |
android { testOptions { unitTests.all { systemProperty 'robolectric.dependency.repo.url', 'https://local-mirror/repo' systemProperty 'robolectric.dependency.repo.id', 'local' } } } |
但是这样配置有一个问题,就是没办法动态调整链接地址。
而我们使用
|
1 |
$ bash gradlew clean build -Drobolectric.dependency.repo.url=http://127.0.0.1/jcenter |
手工指定的参数,并没有在编译的时候生效。
参数丢失的原因是因为测试用例在新的JVM中执行,传入的参数不会自动带给新创建的JVM。
这时需要在Gradle脚本中将读到的值重新设到系统属性里面,才可以被Java程序读到。
|
1 2 3 4 5 6 7 8 9 10 11 |
android { testOptions { unitTests.all { //命令行下 单元测试可能卡住的问题 jvmArgs '-noverify' //robolectric外部指定下载资源链接的参数,使用 -D 参数指定 bash gradlew clean build -Drobolectric.dependency.repo.url=http://127.0.0.1/jcenter systemProperty 'robolectric.dependency.repo.url', System.getProperty("robolectric.dependency.repo.url") systemProperty 'robolectric.dependency.repo.id', System.getProperty("robolectric.dependency.repo.id") } } } |
每个项目的build.gradle中都增加上面的配置之后,就可以保证在外部编译的时候动态指定参数了。
在 Android Studio Chipmunk | 2021.2.1 Patch 1 以及以上的版本,可能会发生如下报错:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
URI is not absolute java.lang.IllegalArgumentException: URI is not absolute at java.base/java.net.URL.fromURI(URL.java:692) at java.base/java.net.URI.toURL(URI.java:1116) at org.robolectric.internal.dependency.MavenArtifactFetcher.getRemoteUrl(MavenArtifactFetcher.java:138) at org.robolectric.internal.dependency.MavenArtifactFetcher.fetchToStagingRepository(MavenArtifactFetcher.java:145) at org.robolectric.internal.dependency.MavenArtifactFetcher.fetchArtifact(MavenArtifactFetcher.java:65) at org.robolectric.internal.dependency.MavenDependencyResolver.lambda$getLocalArtifactUrls$0(MavenDependencyResolver.java:80) at org.robolectric.internal.dependency.MavenDependencyResolver.whileLocked(MavenDependencyResolver.java:100) at org.robolectric.internal.dependency.MavenDependencyResolver.getLocalArtifactUrls(MavenDependencyResolver.java:75) at org.robolectric.internal.dependency.MavenDependencyResolver.getLocalArtifactUrls(MavenDependencyResolver.java:65) at org.robolectric.internal.dependency.MavenDependencyResolver.getLocalArtifactUrl(MavenDependencyResolver.java:116) at org.robolectric.plugins.LegacyDependencyResolver.getLocalArtifactUrl(LegacyDependencyResolver.java:89) at org.robolectric.plugins.DefaultSdkProvider$DefaultSdk.getJarPath(DefaultSdkProvider.java:146) at org.robolectric.internal.AndroidSandbox$SdkSandboxClassLoader.<init>(AndroidSandbox.java:102) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490) at org.robolectric.util.inject.Injector.inject(Injector.java:250) at org.robolectric.util.inject.Injector.lambda$memoized$1(Injector.java:232) at org.robolectric.util.inject.Injector$MemoizingProvider.get(Injector.java:498) at org.robolectric.util.inject.Injector.getInstanceInternal(Injector.java:224) at org.robolectric.util.inject.Injector.resolveDependencies(Injector.java:296) at org.robolectric.util.inject.Injector.inject(Injector.java:248) at org.robolectric.util.inject.Injector.lambda$memoized$1(Injector.java:232) at org.robolectric.util.inject.Injector$MemoizingProvider.get(Injector.java:498) at org.robolectric.util.inject.Injector.getInstanceInternal(Injector.java:224) at org.robolectric.util.inject.Injector.getInstance(Injector.java:208) at org.robolectric.util.inject.Injector.access$700(Injector.java:96) at org.robolectric.util.inject.Injector$ScopeBuilderProvider.create(Injector.java:564) at org.robolectric.util.inject.Injector$ScopeBuilderProvider.lambda$get$0(Injector.java:547) at com.sun.proxy.$Proxy19.build(Unknown Source) at org.robolectric.internal.SandboxManager.getAndroidSandbox(SandboxManager.java:57) at org.robolectric.RobolectricTestRunner.getSandbox(RobolectricTestRunner.java:285) at org.robolectric.RobolectricTestRunner.getSandbox(RobolectricTestRunner.java:68) at org.robolectric.internal.SandboxTestRunner$2.evaluate(SandboxTestRunner.java:236) at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.robolectric.internal.SandboxTestRunner$1.evaluate(SandboxTestRunner.java:93) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38) at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62) at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) at com.sun.proxy.$Proxy2.processTestClass(Unknown Source) at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176) at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129) at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100) at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60) at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133) at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71) at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) |
该报错的原因是 Android Studio 没有正确读取我们配置的 robolectric.dependency.repo.url 环境变量,导致无法成功下载,当然也可能是由于高版本不支持 HTTP ,链接地址必须是 HTTPS 导致此问题,这个没有深究。
解决方法是,在命令行执行
|
1 |
$ bash gradlew clean build -Drobolectric.dependency.repo.url=http://127.0.0.1/jcenter |
确保在当前用户目录下的 .m2 目录下,成功完成 robolectric 需要的全部文件的下载。这样,执行测试用例的时候,就可以从本地获取依赖文件,而不需要再去服务器上获取。
参考链接
Ubuntu Server 18.04 LTS隐藏Tomcat-9.0.16.0的版本号与操作系统类型
推荐方案
另外更推荐的方法是通过 Tomcat 的配置文件完成,而不是修改代码,具体配置方法为:
在 conf/server.xml 配置文件中的 <Host> 配置项中添加如下配置:
|
1 |
<Valve className="org.apache.catalina.valves.ErrorReportValve" showReport="false" showServerInfo="false" /> |
配置项说明:
- showReport:默认值为true,默认显示报错信息
- showServerInfo:默认值为true,默认显示Tomcat的版本号
其他方案
其他的方案跟 Ubuntu 14.04隐藏Tomcat-7.0.52的版本号与操作系统类型 是一致的,但是具体的细节上存在不小的差异,还是需要记录一下。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ cd ~ $ mkdir catalina $ cd catalina $ cp /usr/share/tomcat9/lib/catalina.jar . $ unzip catalina.jar $ cd org/apache/catalina/util $ vim ServerInfo.properties |
可以看到里面的内容如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. server.info=Apache Tomcat/9.0.16 (Ubuntu) server.number=9.0.16.0 server.built=Sep 11 2019 19:47:51 UTC |
直接注释掉里面的内容,如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # server.info=Apache Tomcat/9.0.16 (Ubuntu) # server.number=9.0.16.0 # server.built=Sep 11 2019 19:47:51 UTC |
修改完成后,把修改完成的数据存储到catalina.jar中。
|
1 2 3 4 5 6 7 |
$ cd ~ $ cd catalina $ sudo apt install openjdk-11-jdk-headless $ jar uvf catalina.jar org/apache/catalina/util/ServerInfo.properties |
把修改后的catalina.jar放回到Tomcat的目录下面:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ cd ~ $ cd catalina $ sudo unlink /usr/share/tomcat9/lib/catalina.jar $ sudo mv /usr/share/java/tomcat9-catalina.jar /usr/share/java/tomcat9-catalina.jar.old $ sudo cp catalina.jar /usr/share/java/ $ sudo chmod +r /usr/share/java/catalina.jar $ cd /usr/share/tomcat9/lib $ sudo ln -s ../../java/catalina.jar catalina.jar |
重启Tomcat的服务
|
1 |
$ sudo service tomcat9 restart |
参考链接
腾讯云Ubuntu Server 16.04.7 LTS升级系统到Ubuntu Server 18.04.5 LTS之后letsencrypt证书更新出现异常“ImportError: cannot import name _remove_dead_weakref”
腾讯云Ubuntu Server 16.04.7 LTS升级系统到Ubuntu Server 18.04.5 LTS之后,letsencrypt证书更新出现异常,如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ sudo ./letsencrypt-auto Error: couldn't get currently installed version for /opt/eff.org/certbot/venv/bin/letsencrypt: Traceback (most recent call last): File "/opt/eff.org/certbot/venv/bin/letsencrypt", line 7, in <module> from certbot.main import main File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/main.py", line 2, in <module> from certbot._internal import main as internal_main File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/_internal/main.py", line 6, in <module> import logging.handlers File "/usr/lib/python2.7/logging/__init__.py", line 26, in <module> import sys, os, time, cStringIO, traceback, warnings, weakref, collections File "/usr/lib/python2.7/weakref.py", line 14, in <module> from _weakref import ( ImportError: cannot import name _remove_dead_weakref |
原因为系统版本变化过大导致以前安装的Python组件不能适应最新的系统,最简单的方法就是删除之前安装的Python组件,让letsencrypt重新安装即可。
|
1 2 3 4 5 |
$ sudo apt-get install python-pip $ sudo rm -rf /opt/eff.org/ $ sudo ./letsencrypt-auto |
参考链接
Cannot renew certificate “ImportError: cannot import name _remove_dead_weakref”