第一步:下载下载地址:https://www.macz.com/mac/6487.html
第二步:解压后直接打开,会弹出一个对话框,这时在你的touchbar上就可以看到了
第一步:下载下载地址:https://www.macz.com/mac/6487.html
第二步:解压后直接打开,会弹出一个对话框,这时在你的touchbar上就可以看到了
自定义WordPress
插件的时候,需要判断当前用户的角色是不是网站管理员,如果是管理员的情况下才允许使用插件功能,否则禁止使用。
对于自定义主题的情况,可以使用如下代码:
1 2 3 4 5 6 7 8 9 |
function is_administrator() { // wp_get_current_user函数仅限在主题的functions.php中使用 $currentUser = wp_get_current_user(); if(!empty($currentUser->roles) && in_array('administrator', $currentUser->roles)) return True; // 是管理员 else return False; // 非管理员 } |
对于自定义插件的情况,可以使用如下代码:
1 2 3 4 5 6 7 8 9 |
function is_administrator() { if( is_user_logged_in() ) { //用户已登录,检查用户角色 global $current_user; if((!empty($current_user)) && (!empty($current_user->roles)) && in_array('administrator', $current_user->roles)) return True; // 是管理员 } return False; // 非管理员 } |
更简单的代码如下:
1 2 3 4 |
function is_administrator() { if( current_user_can( 'manage_options' ) ) { return True; } return False; // 非管理员 } |
重新安装的ubuntu 18.04系统上初次安装python3-pip
之后,执行升级命令,出现如下错误信息:
1 2 3 4 5 6 7 |
$ sudo apt-get install python3-pip $ sudo pip3 install --upgrade pip Traceback (most recent call last): File "/usr/local/bin/pip3", line 7, in <module> from pip._internal import main ModuleNotFoundError: No module named 'pip._internal' |
解决方法为重新升级安装一次 pip,如下:
1 |
$ python3 -m pip install --upgrade pip |
今天在执行brew upgrade
的时候出现如下错误:
1 2 3 4 |
$ brew upgrade Updating Homebrew... Error: Cask 'java7' is unreadable: undefined method `undent' for #<String:0x00007fcff5421ec8> |
原因为某次更新之后,配置文件增加了某些不必要的字段。更要命的是,不能执行卸载命令来删除出问题的安装包。解决方法为删除这个字段。
方案如下:
1 2 3 4 5 6 7 8 9 10 11 |
$ find "$(brew --prefix)/Caskroom/"java7'/.metadata' -type f -name '*.rb' | xargs grep 'EOS.undent' --files-with-matches | xargs sed -i '' 's/EOS.undent/EOS/' $ find "$(brew --prefix)/Caskroom/"java'/.metadata' -type f -name '*.rb' | xargs grep 'EOS.undent' --files-with-matches | xargs sed -i '' 's/EOS.undent/EOS/' $ brew uninstall java7 $ brew cleanup $ brew update $ brew upgrade |
WebView
如何截取长图X5内核中WebView
如何截取长图日常开发中,遇到为WebView
截取长图算是一种常见的需求。网上聪明的程序员们提供了多种截取WebView
长图的方法,这为我们的开发提供了很多便利。现在,也有很多APP是集成了X5内核的,网上对于X5内核的截长图方案介绍比较少,所以这里我整理了对WebView
截取长图的比较通用可行的方法,并且对使用了x5内核的WebView
的截图方法进行分享。
WebView
截长图方案普通WebView
截取长图,这里是指项目中没有集成X5内核的情况。利用Google
文档上的api可以顺利截图。以Android5.0
为版本分界线,截图采用不同的处理方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** * 对WebView进行截屏,虽然使用过期方法,但在当前Android版本中测试可行 * * @param webView * @return */ private static Bitmap captureWebViewKitKat(WebView webView) { Picture picture = webView.capturePicture(); int width = picture.getWidth(); int height = picture.getHeight(); if (width > 0 && height > 0) { Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); picture.draw(canvas); return bitmap; } return null; } } |
在Android5.0及以上版本,Android对WebView
进行了优化,为了减少内存使用和提高性能,使用WebView
加载网页时只绘制显示部分。如果我们不做处理,仍然使用上述代码截图的话,就会出现只截到屏幕内显示的WebView
内容,其它部分是空白的情况。
这时候,我们通过调用WebView.enableSlowWholeDocumentDraw()
方法可以关闭这种优化,但要注意的是,该方法需要在WebView
实例被创建前就要调用,否则没有效果。
另外这个方法一旦开启,会影响到整个进程中的WebView
实例,并且没有办法关闭。
这个代码的本质是设置了一个全局变量,并且没有提供关闭接口。其真实调用的代码如下:
1 2 3 4 |
private static boolean sRecordWholeDocumentEnabledByApi = false; static void enableSlowWholeDocumentDraw() { sRecordWholeDocumentEnabledByApi = true; } |
我们在WebView
实例被创建前加入代码:
1 2 3 |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { android.webkit.WebView.enableSlowWholeDocumentDraw(); } |
另外,当应用存在多个进程的时候,比如消息推送进程,LBS定位进程存在的情况下,务必确保只在主进程中初始化这个设置,否则运行时可能报错。
根据Google
文档中描述,capturePicture()
方法已不鼓励使用,推荐我们通过webView
的onDraw(Canvas)
去获取图像,所以这里我们去拿到网页的宽高后,就调用webView.draw(Canvas)
方法生成webView
截图。
1 2 3 4 5 6 7 8 9 |
private void captureWebViewLollipop(WebView webView) { float scale = webView.getScale(); int width = webView.getWidth(); int height = (int) (webView.getContentHeight() * scale + 0.5); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); webView.draw(canvas); return bitmap; } |
使用X5内核截取长图有两种方法,并且都可以不用考虑版本问题,这为我们提供了方便。在X5内核下,如果使用WebView
的onDraw(Canvas)
方法,会出现或多或少的问题,所以对这个方法弃坑了。以下是两个截图方法:
snapshotWholePage(Canvas, boolean, boolean)
在X5
内核中提供了一个截取整个WebView
界面的方法snapshotWholePage(Canvas, boolean, boolean)
,但是这个方法有个缺点,就是不以屏幕上WebView
的宽高截图,只是以WebView
的contentWidth
和contentHeight
为宽高截图,所以截出来的图片会不怎么清晰,但作为缩略图效果还是不错了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private static Bitmap captureX5WebViewUnsharp(Context context, WebView webView) { if (webView == null) { return null; } if (context == null) { context = webView.getContext(); } int width = webView.getContentWidth(); int height = webView.getContentHeight(); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); webView.getX5WebViewExtension().snapshotWholePage(canvas, false, false); return bitmap; } |
capturePicture()
截取清晰长图如果想要在X5
内核下截到清晰的长图,不能使用snapshotWholePage()
,依然可以采用capturePicture()
。X5内核下使用capturePicture()
进行截图,可以直接拿到WebView
的清晰长图,但这是个Deprecated
的方法,使用的时候要做好异常处理。
以上是WebView
截长图方法的总结和分享,对X5内核的截图也是尝试了多种途径最后找到满意的解决方案。另外,截长图会占用大量内存,容易触发OOM,所以代码中也要注意对OOM的处理。
在使用了X5
内核的项目中,使用WebView
截取长图的判断逻辑可以是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 有x5内核没有生效,并且Android版本是5.0及以上时,调用enableSlowWholeDocumentDraw()方便截取长图 if (!isX5Enabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { android.webkit.WebView.enableSlowWholeDocumentDraw(); } /* 创建WebView ×/ ... // 网页截图 public void captureWholePage() { try { Bitmap bitmap = captureWebView(); /* 对拿到的bitmap根据需要进行处理 */ } catch (OutOfMemoryError oom) { /* 对OOM做处理 } } |
目前(2020/08/01
)之前版本的X5 SDK
,如果编译APK
的时候指定targetSdkVersion
版本高于 28
(Android O
)的情况下,调用snapshotWholePage(Canvas, boolean, boolean)
可能会无法获取到截图,图片内容全黑。
观察日志发生如下报错:
1 2 3 4 5 6 7 |
2020-07-31 17:14:18.538 16536-16536/com.xxxx.xxx W/com.xxxx.xxx: Accessing hidden field Landroid/graphics/Canvas;->mBitmap:Landroid/graphics/Bitmap; (greylist-max-p, reflection, denied) 2020-07-31 17:14:18.539 16536-16536/com.xxxx.xxx W/System.err: java.lang.NoSuchFieldException: No field mBitmap in class Landroid/graphics/Canvas; (declaration of 'android.graphics.Canvas' appears in /system/framework/framework.jar) 2020-07-31 17:14:18.540 16536-16536/com.xxxx.xxx W/System.err: at java.lang.Class.getDeclaredField(Native Method) 2020-07-31 17:14:18.540 16536-16536/com.xxxx.xxx W/System.err: at com.tencent.smtt.os.SMTTAdaptation.a(TbsJavaCore:115) 2020-07-31 17:14:18.540 16536-16536/com.xxxx.xxx W/System.err: at com.tencent.smtt.webkit.WebViewChromiumExtension.a(TbsJavaCore:2519) 2020-07-31 17:14:18.540 16536-16536/com.xxxx.xxx W/System.err: at com.tencent.tbs.core.webkit.tencent.TencentWebViewProxy.snapshotWholePage(TbsJavaCore:2366) 2020-07-31 17:14:18.540 16536-16536/com.xxxx.xxx W/System.err: at com.tencent.tbs.core.webkit.adapter.X5WebViewAdapter.snapshotWholePage(TbsJavaCore:851) |
原因为从API 29
(Android P
)开始,Google
对于某些反射调用私有方法的行为进行了限制,比如动态反射赋值android.graphics.Canvas.java
的私有变量mBitmap
。这些调用会被抛出异常阻止。
For the past decade, AysncTask has been one of the most widely used solutions for writing concurrent code in Android. However, it earned very controversial reputation. On the one hand, AsyncTask powered, and still powers, many Android applications. On the other hand, most professional Android developers openly dislike this API.
All in all, I’d say that Android community has love-hate relationship with AsyncTask. But there are big news: the era of AsyncTask is about to end because a commit that deprecated it had just landed in Android Open Source Project.
In this post I’ll review the official statement motivating AsyncTask’s deprecation, as well as the real reasons why it had to be deprecated. As you’ll see, these are different sets of reasons. In addition, towards the end of this article, I’ll share my thoughts on the future of Android’s concurrency APIs.
The official deprecation of AsyncTask, as well as the motivation for that decision, were introduced with this commit. The newly added first paragraph of Javadoc states:
AsyncTask was intended to enable proper and easy use of the UI thread. However, the most common use case was for integrating into UI, and that would cause Context leaks, missed callbacks, or crashes on configuration changes. It also has inconsistent behavior on different versions of the platform, swallows exceptions from
doInBackground
, and does not provide much utility over usingExecutor
s directly.
While that’s the official statement by Google, there are several inaccuracies there which are worth pointing out.
Fist of all, AsyncTask has never been intended to “enable proper and easy use of the UI thread”. It was intended to offload long-running operations from UI thread to background threads, and then deliver the results of these operations back to UI thread. I know, I’m nitpicking here. However, in my opinion, when Google deprecates API that they themselves invented and promoted for years, it would be more respectful towards developers who use this API today, and will continue to use for years to come, to invest more effort into deprecation message to prevent further confusion.
That said, the more interesting part of this deprecation message is this: “that would cause Context leaks, missed callbacks, or crashes on configuration changes”. So, Google basically states that the most common use case for AsyncTask automatically results in very serious problems. However, there are many high-quality applications out there which use AsyncTask and work flawlessly. Even some classes inside AOSP itself use AsyncTask. How comes they don’t experience these problems?
To answer this question, let’s discuss the relationship between AsyncTask and memory leaks in details.
The way your web content behaves on mobile can be dramatically different from the desktop experience. Remote debugging with Chrome DevTools lets you
继续阅读通过Chrome浏览器进行Android调试/Remote Debugging on Android with Chrome
目前在对Android的代码进行功能测试的时候,需要服务器返回一个数据来测试整个流程是否正确。不希望引入第三方的JAR包,因此需要一个特别简单的HTTP服务器。
网上查询了一下,找到可用的代码如下:
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Date; import java.util.StringTokenizer; // The tutorial can be found just here on the SSaurel's Blog : // https://www.ssaurel.com/blog/create-a-simple-http-web-server-in-java // Each Client Connection will be managed in a dedicated Thread public class JavaHTTPServer implements Runnable{ static final File WEB_ROOT = new File("."); static final String DEFAULT_FILE = "index.html"; static final String FILE_NOT_FOUND = "404.html"; static final String METHOD_NOT_SUPPORTED = "not_supported.html"; // port to listen connection static final int PORT = 8080; // verbose mode static final boolean verbose = true; // Client Connection via Socket Class private Socket connect; public JavaHTTPServer(Socket c) { connect = c; } public static void main(String[] args) { try { ServerSocket serverConnect = new ServerSocket(PORT); System.out.println("Server started.\nListening for connections on port : " + PORT + " ...\n"); // we listen until user halts server execution while (true) { JavaHTTPServer myServer = new JavaHTTPServer(serverConnect.accept()); if (verbose) { System.out.println("Connecton opened. (" + new Date() + ")"); } // create dedicated thread to manage the client connection Thread thread = new Thread(myServer); thread.start(); } } catch (IOException e) { System.err.println("Server Connection error : " + e.getMessage()); } } @Override public void run() { // we manage our particular client connection BufferedReader in = null; PrintWriter out = null; BufferedOutputStream dataOut = null; String fileRequested = null; try { // we read characters from the client via input stream on the socket in = new BufferedReader(new InputStreamReader(connect.getInputStream())); // we get character output stream to client (for headers) out = new PrintWriter(connect.getOutputStream()); // get binary output stream to client (for requested data) dataOut = new BufferedOutputStream(connect.getOutputStream()); // get first line of the request from the client String input = in.readLine(); // we parse the request with a string tokenizer StringTokenizer parse = new StringTokenizer(input); String method = parse.nextToken().toUpperCase(); // we get the HTTP method of the client // we get file requested fileRequested = parse.nextToken().toLowerCase(); // we support only GET and HEAD methods, we check if (!method.equals("GET") && !method.equals("HEAD")) { if (verbose) { System.out.println("501 Not Implemented : " + method + " method."); } // we return the not supported file to the client File file = new File(WEB_ROOT, METHOD_NOT_SUPPORTED); int fileLength = (int) file.length(); String contentMimeType = "text/html"; //read content to return to client byte[] fileData = readFileData(file, fileLength); // we send HTTP Headers with data to client out.println("HTTP/1.1 501 Not Implemented"); out.println("Server: Java HTTP Server from SSaurel : 1.0"); out.println("Date: " + new Date()); out.println("Content-type: " + contentMimeType); out.println("Content-length: " + fileLength); out.println(); // blank line between headers and content, very important ! out.flush(); // flush character output stream buffer // file dataOut.write(fileData, 0, fileLength); dataOut.flush(); } else { // GET or HEAD method if (fileRequested.endsWith("/")) { fileRequested += DEFAULT_FILE; } File file = new File(WEB_ROOT, fileRequested); int fileLength = (int) file.length(); String content = getContentType(fileRequested); if (method.equals("GET")) { // GET method so we return content byte[] fileData = readFileData(file, fileLength); // send HTTP Headers out.println("HTTP/1.1 200 OK"); out.println("Server: Java HTTP Server from SSaurel : 1.0"); out.println("Date: " + new Date()); out.println("Content-type: " + content); out.println("Content-length: " + fileLength); out.println(); // blank line between headers and content, very important ! out.flush(); // flush character output stream buffer dataOut.write(fileData, 0, fileLength); dataOut.flush(); } if (verbose) { System.out.println("File " + fileRequested + " of type " + content + " returned"); } } } catch (FileNotFoundException fnfe) { try { fileNotFound(out, dataOut, fileRequested); } catch (IOException ioe) { System.err.println("Error with file not found exception : " + ioe.getMessage()); } } catch (IOException ioe) { System.err.println("Server error : " + ioe); } finally { try { in.close(); out.close(); dataOut.close(); connect.close(); // we close socket connection } catch (Exception e) { System.err.println("Error closing stream : " + e.getMessage()); } if (verbose) { System.out.println("Connection closed.\n"); } } } private byte[] readFileData(File file, int fileLength) throws IOException { FileInputStream fileIn = null; byte[] fileData = new byte[fileLength]; try { fileIn = new FileInputStream(file); fileIn.read(fileData); } finally { if (fileIn != null) fileIn.close(); } return fileData; } // return supported MIME Types private String getContentType(String fileRequested) { if (fileRequested.endsWith(".htm") || fileRequested.endsWith(".html")) return "text/html"; else return "text/plain"; } private void fileNotFound(PrintWriter out, OutputStream dataOut, String fileRequested) throws IOException { File file = new File(WEB_ROOT, FILE_NOT_FOUND); int fileLength = (int) file.length(); String content = "text/html"; byte[] fileData = readFileData(file, fileLength); out.println("HTTP/1.1 404 File Not Found"); out.println("Server: Java HTTP Server from SSaurel : 1.0"); out.println("Date: " + new Date()); out.println("Content-type: " + content); out.println("Content-length: " + fileLength); out.println(); // blank line between headers and content, very important ! out.flush(); // flush character output stream buffer dataOut.write(fileData, 0, fileLength); dataOut.flush(); if (verbose) { System.out.println("File " + fileRequested + " not found"); } } } |
最近在集成极光推送的时候,需要集成极光提供的魅族推送SDK, 其实也就是把魅族官方的推送SDK进行了简单的封装。
但是带编译的时候报告如下错误:
1 |
Entry name 'AndroidManifest.xml' collided |
反复检查许久,不得要领。
经过解压提供的 AAR 包后,继续解压缩里面的 classes.jar ,发现 JAR 包里面包含 AndroidManifest.xml 。
导致在 Android Gradle plugin 3.6之后的版本编译出现异常。
如果想彻底解决这个问题,需要移除 JAR 包中的 AndroidManifest.xml 。
1 |
Error:Execution failed for task ':app:transformClassesWithProfilers-transformForXXXXDebug'. |
但是当被调试的手机是Android 10系统的时候,不会出现报错。当插入的手机是 Android 6.1系统的时候,报告上面的错误信息。
在命令行下执行
1 |
$ bash gradlew clean build |
却一直都是成功的。
百思不解。
经过研究发现,是在运行调试配置信息界面中开启了 "Enable advanced profiling" 功能的时候,才会出现上面的情况。
关闭这个功能就正常了。
具体配置参考下图: