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
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" 功能的时候,才会出现上面的情况。
关闭这个功能就正常了。
具体配置参考下图:
创建目录
|
1 2 3 4 5 |
$ cd ~ $ mkdir Android $ cd Android |
下载安装配置dex2jar
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ wget https://github.com/pxb1988/dex2jar/files/1867564/dex-tools-2.1-SNAPSHOT.zip // 本站 wget https://www.mobibrw.com/wp-content/uploads/2020/07/dex-tools-2.1-SNAPSHOT.zip $ unzip dex-tools-2.1-SNAPSHOT.zip $ ln -s dex-tools-2.1-SNAPSHOT dex2jar $ cd dex2jar $ chmod +x *.sh $ ln -s d2j-dex2jar.sh dex2jar $ echo 'export PATH=$PATH:~/Android/dex2jar' >> ~/.bashrc $ source ~/.bashrc # 直接从APK转换,2.1版本开始支持multidex $ dex2jar xxx.apk |
Enjarify 是一个将Dalvik字节码转化为等价的Java字节码,然后可以用一系列的Java分析工具去分析Android应用。之前我们一直使用Dex2jar来完成这个工作,不过Dex2jar已经算是一个非常老的工具咯。Dex2jar在绝大部分情况下都挺不错的,但是如果碰到了些模糊的特性或者特殊案例就会出错或者默默地吐出一些错误的结果。相比之下,Enjarify在设计的时候就考虑到了尽可能多的情况,特别是对于那些Dex2jar不起作用的情况下。另外,Enjarify能够有效地处理Unicode编码的类名、常量,隐式类型转换、正常处理流程中的移除处理等等。
下载安装配置Enjarify
|
1 2 3 4 5 6 7 8 9 10 |
$ cd ~/Android $ git clone https://github.com/Storyyeller/enjarify.git //本站下载 wget https://www.mobibrw.com/wp-content/uploads/2020/07/enjarify.zip $ cd enjarify # 只能在~/Android/enjarify目录下执行 $ enjarify yourapp.apk |
下载安装配置apktool
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ cd ~/Android $ mkdir apktool $ cd apktool $ wget https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.4.1.jar // 本站下载 wget https://www.mobibrw.com/wp-content/uploads/2020/07/apktool_2.4.1.jar_.zip $ ln -s apktool_2.4.1.jar apktool.jar $ echo 'export PATH=$PATH:~/Android/apktool' >> ~/.bashrc $ echo "alias apktool='java -jar ~/Android/apktool/apktool.jar'" >> ~/.bashrc $ source ~/.bashrc # 提取资源文件相关 $ apktool d xxx.apk |
下载安装配置jd-gui
|
1 2 3 4 5 6 7 |
$ cd ~/Android $ wget https://github.com/java-decompiler/jd-gui/releases/download/v1.6.6/jd-gui-osx-1.6.6.tar # 本站下载 wget https://www.mobibrw.com/wp-content/uploads/2020/07/jd-gui-osx-1.6.6.tar $ tar xvf jd-gui-osx-1.6.6.tar |
以下代码是Dart中定义变量的方法:
|
1 2 3 4 5 6 |
main() { var a = 1; int b = 10; String s = "hello"; dynamic c = 0.5; } |
你可以明确指定某个变量的类型,如int bool String,也可以用var或 dynamic来声明一个变量,Dart会自动推断其数据类型。
注意:没有赋初值的变量都会有默认值null
如果你绝不想改变一个变量,使用final或const,不要使用var或其他类型,一个被final修饰的变量只能被赋值一次,一个被const修饰的变量是一个编译时常量(const常量毫无疑问也是final常量)。可以这么理解:final修饰的变量是不可改变的,而const修饰的表示一个常量。
注意:实例变量可以是final的但不能是const的
下面用代码说明:
|
1 2 3 |
var count = 10; final Num = count; // final 只能赋值一次 const Num1 = 10; // const赋值必须是编译时常量 |
区别一:final 要求变量只能初始化一次,并不要求赋的值一定是编译时常量,可以是常量也可以不是。而 const 要求在声明时初始化,并且赋值必需为编译时常量。
区别二:final 是惰性初始化,即在运行时第一次使用前才初始化。而 const 是在编译时就确定值了。
Dart有如下几种内建的数据类型:
|
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 |
main() { // numbers var a = 0; int b = 1; double c = 0.1; // strings var s1 = 'hello'; String s2 = "world"; // booleans var real = true; bool isReal = false; // lists var arr = [1, 2, 3, 4, 5]; List<String> arr2 = ['hello', 'world', "123", "456"]; List<dynamic> arr3 = [1, true, 'haha', 1.0]; // maps var map = new Map(); map['name'] = 'zhangsan'; map['age'] = 10; Map m = new Map(); m['a'] = 'a'; //runes,Dart 中 使用runes 来获取UTF-32字符集的字符。String的 codeUnitAt and codeUnit属性可以获取UTF-16字符集的字符 var clapping = '\u{1f44f}'; print(clapping); // 打印的是拍手emoji的表情 // symbols print(#s == new Symbol("s")); // true } |
Dart是一个面向对象的编程语言,所以即使是函数也是一个对象,也有一种类型Function,这就意味着函数可以赋值给某个变量或者作为参数传给另外的函数。虽然Dart推荐你给函数加上返回值,但是不加返回值的函数同样可以正常工作,另外你还可以用=>代替return语句,比如下面的代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// 声明返回值 int add(int a, int b) { return a + b; } // 不声明返回值 add2(int a, int b) { return a + b; } // =>是return语句的简写 add3(a, b) => a + b; main() { print(add(1, 2)); // 3 print(add2(2, 3)); // 5 print(add3(1, 2)); // 3 } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
sayHello({String name}) { print("hello, my name is $name"); } sayHello2({name: String}) { print("hello, my name is $name"); } main() { // 打印 hello, my name is zhangsan sayHello(name: 'zhangsan'); // 打印 hello, my name is wangwu sayHello2(name: 'wangwu'); } |
可以看到,定义命名参数时,你可以以 {type paramName} 或者 {paramName: type} 两种方式声明参数,而调用命名参数时,需要以 funcName(paramName: paramValue) 的形式调用。
命名参数的参数并不是必须的,所以上面的代码中,如果调用sayHello()不带任何参数,也是可以的,只不过最后打印出来的结果是:hello, my name is null,在Flutter开发中,你可以使用@required注解来标识一个命名参数,这代表该参数是必须的,你不传则会报错,比如下面的代码:
|
1 |
const Scrollbar({Key key, @required Widget child}) |
使用中括号[]括起来的参数是函数的位置参数,代表该参数可传可不传,位置参数只能放在函数的参数列表的最后面,如下代码所示:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
sayHello(String name, int age, [String hobby]) { // 位置参数可以有多个,比如[String a, int b] StringBuffer sb = new StringBuffer(); sb.write("hello, this is $name and I am $age years old"); if (hobby != null) { sb.write(", my hobby is $hobby"); } print(sb.toString()); } main() { // hello, this is zhangsan and I am 20 years old sayHello("zhangsan", 20); // hello, this is zhangsan and I am 20 years old, my hobby is play football sayHello("zhangsan", 20, "play football"); } |
你可以为命名参数或者位置参数设置默认值,如下代码所示:
|
1 2 3 4 5 6 7 8 |
// 命名参数的默认值 int add({int a, int b = 3}) { // 不能写成:int add({a: int, b: int = 3}) return a + b; } // 位置参数的默认值 int sum(int a, int b, [int c = 3]) { return a + b + c; } |
不论在Dart还是Flutter中,必须都需要一个顶层的main()函数,它是整个应用的入口函数,main()函数的返回值是void,还有一个可选的参数,参数类型是List<String>。
你可以将一个函数作为参数传给另一个函数,比如下面的代码:
|
1 2 3 4 5 6 7 8 9 10 11 |
printNum(int a) { print("$a"); } main() { // 依次打印: // 1 // 2 // 3 var arr = [1, 2, 3]; arr.forEach(printNum); } |
你也可以将一个函数赋值给某个变量,比如下面的代码:
|
1 2 3 4 5 6 7 8 9 10 11 |
printNum(int a) { print("$a"); } main() { var f1 = printNum; Function f2 = printNum; var f3 = (int a) => print("a = $a"); f1(1); f2(2); f3(6); } |
大多数函数都是有名称的,比如main() printName()等,但是你也可以写匿名函数,如果你对Java比较熟悉,那下面的Dart代码你肯定也不会陌生:
|
1 2 3 4 5 6 7 8 9 |
test(Function callback) { callback("hello"); } main() { test((param) { // 打印hello print(param); }); } |
匿名函数类似于Java中的接口,往往在某个函数的参数为函数时使用到。
所有的函数都有返回值,如果没有指定return语句,那么该函数的返回值为null。
Dart中的运算符与Java中的类似,比如++a a == b b ? a : b,但是也有一些与Java不太一样的运算符,下面用代码说明:
|
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 |
main() { // 与Java相同的运算符操作 int a = 1; ++a; a++; var b = 1; print(a == b); // false print(a * b); // 3 bool real = false; real ? print('real') : print('not real'); // not real print(real && a == b); // false print(real || a == 3); // true print(a != 2); // true print(a <= b); // false var c = 9; c += 10; print("c = $c"); // c = 19 print(1<<2); // 4 // 与Java不太一样的运算符操作 // is运算符用于判断一个变量是不是某个类型的数据 // is!则是判断变量不是某个类型的数据 var s = "hello"; print(s is String); // true var num = 6; print(num is! String); // true // ~/才是取整运算符,如果使用/则是除法运算,不取整 int k = 1; int j = 2; print(k / j); // 0.5 print(k ~/ j); // 0 // as运算符类似于Java中的cast操作,将一个对象强制类型转换 (emp as Person).teach(); // ??=运算符 如果 ??= 运算符前面的变量为null,则赋值,否则不赋值 var param1 = "hello", param2 = null; param1 ??= "world"; param2 ??= "world"; print("param1 = $param1"); // param1 = hello print("param2 = $param2"); // param2 = world // ?.运算符 var str1 = "hello world"; var str2 = null; print(str1?.length); // 11 print(str2?.length); // null print(str2.length); // 报错 } |
如果你对Java中的建造者模式比较熟悉的话,Dart中的..运算符也很好理解,先看下面的代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Person { eat() { print("I am eating..."); } sleep() { print("I am sleeping..."); } study() { print("I am studying..."); } } main() { // 依次打印 // I am eating... // I am sleeping... // I am studying... new Person()..eat() ..sleep() ..study(); } |
可以看到,使用..调用某个对象的方法(或者成员变量)时,返回值是这个对象本身,所以你可以接着使用..调用这个对象的其他方法,这不就类似于Java中的建造者模式,每次build某个属性时,都返回一个this对象吗。
if / else switch for /while try / catch语句跟Java中都类似,try / catch语句可能稍有不同,下面用一段代码说明:
|
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 |
main() { // if else语句 int score = 80; if (score < 60) { print("so bad!"); } else if (score >= 60 && score < 80) { print("just so so!"); } else if (score >= 80) { print("good job!"); } // switch语句 String a = "hello"; // case语句中的数据类型必须是跟switch中的类型一致 switch (a) { case "hello": print("haha"); break; case "world": print("heihei"); break; default: print("WTF"); } // for语句 List<String> list = ["a", "b", "c"]; for (int i = 0; i < list.length; i++) { print(list[i]); } for (var i in list) { print(i); } // 这里的箭头函数参数必须用圆括号扩起来 list.forEach((item) => print(item)); // while语句 int start = 1; int sum = 0; while (start <= 100) { sum += start; start++; } print(sum); // try catch语句 try { print(1 ~/ 0); } catch (e) { // IntegerDivisionByZeroException print(e); } try { 1 ~/ 0; } on IntegerDivisionByZeroException { // 捕获指定类型的异常 print("error"); // 打印出error } finally { print("over"); // 打印出over } } |
Dart中的类没有访问控制,所以你不需要用private, protected, public等修饰成员变量或成员函数,一个简单的类如下代码所示:
|
1 2 3 4 5 6 7 8 9 |
class Person { String name; int age; String gender; Person(this.name, this.age, this.gender); sayHello() { print("hello, this is $name, I am $age years old, I am a $gender"); } } |
上面的Person类中有3个成员变量,一个构造方法和一个成员方法,看起来比较奇怪的是Person的构造方法,里面传入的3个参数都是this.xxx,而且没有大括号{}包裹的方法体,这种语法是Dart比较独特而简洁的构造方法声明方式,它等同于下面的代码:
|
1 2 3 4 5 |
Person(String name, int age, String gender) { this.name = name; this.age = age; this.gender = gender; } |
要调用Person类的成员变量或成员方法,可以用下面的代码:
|
1 2 3 4 5 |
var p = new Person("zhangsan", 20, "male"); p.sayHello(); // hello, this is zhangsan, I am 20 years old, I am a male p.age = 50; p.gender = "female"; p.sayHello(); // hello, this is zhangsan, I am 50 years old, I am a female |
类除了有跟类名相同的构造方法外,还可以添加命名的构造方法,如下代码所示:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Point { num x, y; Point(this.x, this.y); // 类的命名构造方法 Point.origin() { x = 0; y = 0; } } main() { // 调用Point类的命名构造方法origin() var p = new Point.origin(); var p2 = new Point(1, 2); } |
Dart中使用extends关键字做类的继承,如果一个类只有命名的构造方法,在继承时需要注意,如下代码:
|
1 2 3 4 5 6 7 8 9 10 11 |
class Human { String name; Human.fromJson(Map data) { print("Human's fromJson constructor"); } } class Man extends Human { Man.fromJson(Map data) : super.fromJson(data) { print("Man's fromJson constructor"); } } |
由于Human类没有默认构造方法,只有一个命名构造方法fromJson,所以在Man类继承Human类时,需要调用父类的fromJson方法做初始化,而且必须使用Man.fromJson(Map data) : super.fromJson(data)这种写法,而不是像Java那样将super写到花括号中。
有时候你仅仅只是在某个类的构造方法中,调用这个类的另一个构造方法,你可以这么写:
|
1 2 3 4 5 6 |
class Point { num x, y; Point(this.x, this.y); // 命名构造方法调用了默认的构造方法 Point.alongXAxis(num x) : this(x, 0); } |
一个类的成员方法是一个函数,为这个类提供某些行为。上面的代码中已经有了一些类的成员方法的定义,这些定义方式跟Java很类似,你可以为某个类的成员变量提供getter/setter方法,如下代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
class Rectangle { num left, top, width, height; // 构造方法传入left, top, width, height几个参数 Rectangle(this.left, this.top, this.width, this.height); // right, bottom两个成员变量提供getter/setter方法 num get right => left + width; set right(num value) => left = value - width; num get bottom => top + height; set bottom(num value) => top = value - height; } |
使用abstract修饰一个类,则这个类是抽象类,抽象类中可以有抽象方法和非抽象方法,抽象方法没有方法体,需要子类去实现,如下代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
abstract class Doer { // 抽象方法,没有方法体,需要子类去实现 void doSomething(); // 普通的方法 void greet() { print("hello world!"); } } class EffectiveDoer extends Doer { // 实现了父类的抽象方法 void doSomething() { print("I'm doing something..."); } } |
Dart中有类似于C++中的运算符重载语法,比如下面的代码定义了一个向量类,重载了向量的+ -运算:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Vector { num x, y; Vector(this.x, this.y); Vector operator +(Vector v) => new Vector(x + v.x, y + v.y); Vector operator -(Vector v) => new Vector(x - v.x, y - v.y); printVec() { print("x: $x, y: $y"); } } main() { Vector v1 = new Vector(1, 2); Vector v2 = new Vector(3, 4); (v1 - v2).printVec(); // -2, -2 (v1 + v2).printVec(); // 4, 6 } |
使用enum关键字定义一个枚举类,这个语法跟Java类似,如下代码:
|
1 |
enum Color { red, green, blue } |
mixins是一个重复使用类中代码的方式,比如下面的代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class A { a() { print("A's a()"); } } class B { b() { print("B's b()"); } } // 使用with关键字,表示类C是由类A和类B混合而构成 class C = A with B; main() { C c = new C(); c.a(); // A's a() c.b(); // B's b() } |
|
1 2 3 4 5 6 7 8 9 10 11 |
// 类的静态成员变量和静态成员方法 class Cons { static const name = "zhangsan"; static sayHello() { print("hello, this is ${Cons.name}"); } } main() { Cons.sayHello(); // hello, this is zhangsan print(Cons.name); // zhangsan } |
Java和C++语言都有泛型,Dart语言也不例外,使用泛型有很多好处,比如:
正确指定泛型类型会产生更好的生成代码。
泛型可以减小代码的复杂度
Dart内置的数据类型List就是一个泛型数据类型,你可以往List中塞任何你想的数据类型比如整型、字符串、布尔值等
关于Dart更多的泛型知识点,可以查看这里。
Dart目前已经有很多的库提供给开发者,许多功能不需要开发者自己去实现,只需要导入对应的包即可,使用import语句来导入某个包,比如下面的代码:
|
1 |
import 'dart:html'; |
如果你想导入自己写的某个代码文件,使用相对路径即可,例如当前有一个demo.dart文件,跟该文件同级目录下有个util.dart文件,文件代码如下:
|
1 2 3 4 |
// util.dart文件内容 int add(int a, int b) { return a + b; } |
在demo.dart文件中如果要引用util.dart文件,使用下面的方式导入:
|
1 2 3 4 5 |
// demo.dart import './util.dart'; main() { print(add(1, 2)); } |
你可以使用as关键字为导入的某个包设置一个前缀,或者说别名,比如下面的代码:
|
1 2 3 4 5 6 |
import 'package:lib1/lib1.dart'; import 'package:lib2/lib2.dart' as lib2; // Uses Element from lib1. Element element1 = Element(); // Uses Element from lib2. lib2.Element element2 = lib2.Element(); |
你也可以在导入包时使用show hide关键字来导入某个包中的部分功能,比如下面的代码:
|
1 2 3 4 |
// 只导入foo import 'package:lib1/lib1.dart' show foo; // 导入除了foo的所有其他部分 import 'package:lib2/lib2.dart' hide foo; |
导入包时使用deferred as可以让这个包懒加载,懒加载的包只会在该包被使用时得到加载,而不是一开始就加载,比如下面的代码:
|
1 |
import 'package:greetings/hello.dart' deferred as hello; |
Dart提供了类似ES7中的async await等异步操作,这种异步操作在Flutter开发中会经常遇到,比如网络或其他IO操作,文件选择等都需要用到异步的知识。
async和await往往是成对出现的,如果一个方法中有耗时的操作,你需要将这个方法设置成async,并给其中的耗时操作加上await关键字,如果这个方法有返回值,你需要将返回值塞到Future中并返回,如下代码所示:
|
1 2 3 4 |
Future checkVersion() async { var version = await lookUpVersion(); // Do something with version } |
下面的代码使用Dart从网络获取数据并打印出来:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import 'dart:async'; import 'package:http/http.dart' as http; Future<String> getNetData() async{ http.Response res = await http.get("http://www.baidu.com"); return res.body; } main() { getNetData().then((str) { print(str); }); } |
学过算法的朋友都知道,计算机中的算法其实就是数学运算。所以,再讲解RSA加密算法之前,有必要了解一下一些必备的数学知识。我们就从数学知识开始讲解。
RSA加密算法中,只用到素数、互质数、指数运算、模运算等几个简单的数学知识。所以,我们也需要了解这几个概念即可。
素数又称质数,指在一个大于1的自然数中,除了1和此整数自身外,不能被其他自然数整除的数。这个概念,我们在上初中,甚至小学的时候都学过了,这里就不再过多解释了。
百度百科上的解释是:公因数只有1的两个数,叫做互质数。;维基百科上的解释是:互质,又称互素。若N个整数的最大公因子是1,则称这N个整数互质。
常见的互质数判断方法主要有以下几种:
两个不同的质数一定是互质数。例如,2与7、13与19。
一个质数,另一个不为它的倍数,这两个数为互质数。例如,3与10、5与 26。
相邻的两个自然数是互质数。如 15与 16。
相邻的两个奇数是互质数。如 49与 51。
较大数是质数的两个数是互质数。如97与88。
小数是质数,大数不是小数的倍数的两个数是互质数。例如 7和 16。
2和任何奇数是互质数。例如2和87。
1不是质数也不是合数,它和任何一个自然数在一起都是互质数。如1和9908。
辗转相除法。
指数运算又称乘方计算,计算结果称为幂。nm指将n自乘m次。把nm看作乘方的结果,叫做”n的m次幂”或”n的m次方”。其中,n称为“底数”,m称为“指数”。
模运算即求余运算。“模”是“Mod”的音译。和模运算紧密相关的一个概念是“同余”。数学上,当两个整数除以同一个正整数,若得相同余数,则二整数同余。
两个整数a,b,若它们除以正整数m所得的余数相等,则称a,b对于模m同余,记作: a ≡ b (mod m);读作:a同余于b模m,或者,a与b关于模m同余。例如:26 ≡ 14 (mod 12)。
RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。
假设Alice想要通过一个不可靠的媒体接收Bob的一条私人讯息。她可以用以下的方式来产生一个公钥和一个私钥:
随意选择两个大的质数p和q,p不等于q,计算N=pq。
根据欧拉函数,求得
|
1 |
r = (p-1)(q-1) |
选择一个小于 r 的整数 e,求得 e 关于模 r 的模反元素,命名为d。(模反元素存在,当且仅当e与r互质)
将 p 和 q 的记录销毁。
(N,e)是公钥,(N,d)是私钥。Alice将她的公钥(N,e)传给Bob,而将她的私钥(N,d)藏起来。
假设Bob想给Alice送一个消息m,他知道Alice产生的N和e。他使用起先与Alice约好的格式将m转换为一个小于N的整数n,比如他可以将每一个字转换为这个字的Unicode码,然后将这些数字连在一起组成一个数字。假如他的信息非常长的话,他可以将这个信息分为几段,然后将每一段转换为n。用下面这个公式他可以将n加密为c:
|
1 |
ne ≡ c (mod N) |
计算c并不复杂。Bob算出c后就可以将它传递给Alice。
Alice得到Bob的消息c后就可以利用她的密钥d来解码。她可以用以下这个公式来将c转换为n:
|
1 |
cd ≡ n (mod N) |
得到n后,她可以将原来的信息m重新复原。
解码的原理是:
|
1 |
cd ≡ ne·d(mod N) |
以及
|
1 2 3 |
ed ≡ 1 (modp-1) ed ≡ 1 (modq-1) |
由费马小定理可证明(因为p和q是质数)
|
1 2 3 |
ne·d ≡ n (mod p) ne·d ≡ n (mod q) |
这说明(因为p和q是不同的质数,所以p和q互质)
|
1 |
ne·d ≡ n (mod pq) |
RSA也可以用来为一个消息署名。假如甲想给乙传递一个署名的消息的话,那么她可以为她的消息计算一个散列值(Message digest),然后用她的密钥(private key)加密这个散列值并将这个“署名”加在消息的后面。这个消息只有用她的公钥才能被解密。乙获得这个消息后可以用甲的公钥解密这个散列值,然后将这个数据与他自己为这个消息计算的散列值相比较。假如两者相符的话,那么他就可以知道发信人持有甲的密钥,以及这个消息在传播路径上没有被篡改过。
下面,开始我们的重点环节:编程实践。在开始编程前,我们通过计算,来确定公钥和密钥。
假设p = 3、q = 11(p,q都是素数即可。),则N = pq = 33;
|
1 |
r = (p-1)(q-1) = (3-1)(11-1) = 20; |
根据模反元素的计算公式,我们可以得出,e·d ≡ 1 (mod 20),即e·d = 20n+1 (n为正整数);我们假设n=1,则e·d = 21。e、d为正整数,并且e与r互质,则e = 3,d = 7。(两个数交换一下也可以。)
到这里,公钥和密钥已经确定。公钥为(N, e) = (33, 3),密钥为(N, d) = (33, 7)。
下面我们使用Java来实现一下加密和解密的过程。具体代码如下:
RSA算法实现:
|
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 |
package security.rsa; public class RSA { /* * 加密、解密算法 * * @param key 公钥或密钥 * * @param message 数据 * * @return */ public static long rsa(int baseNum, int key, long message) { if (baseNum < 1 || key < 1) { return 0L; } //加密或者解密之后的数据 long rsaMessage = 0L; //加密核心算法 rsaMessage = Math.round(Math.pow(message, key)) % baseNum; return rsaMessage; } public static void main(String[] args) { //基数 int baseNum = 3 * 11; //公钥 int keyE = 3; //密钥 int keyD = 7; //未加密的数据 long msg = 24L; //加密后的数据 long encodeMsg = rsa(baseNum, keyE, msg); //解密后的数据 long decodeMsg = rsa(baseNum, keyD, encodeMsg); System.out.println("加密前:" + msg); System.out.println("加密后:" + encodeMsg); System.out.println("解密后:" + decodeMsg); } } |
RSA算法结果:
加密前:24
加密后:30
解密后:24
(看程序最清楚了,对于要加密的数字m, m^e%N=c, c就是加密之后的密文。c^d%N=m, 就能解密得到m)
当p和q是一个大素数的时候,从它们的积pq去分解因子p和q,这是一个公认的数学难题。然而,虽然RSA的安全性依赖于大数的因子分解,但并没有从理论上证明破译RSA的难度与大数分解难度等价。
1994年彼得·秀尔(Peter Shor)证明一台量子计算机可以在多项式时间内进行因数分解。假如量子计算机有朝一日可以成为一种可行的技术的话,那么秀尔的算法可以淘汰RSA和相关的衍生算法。(即依赖于分解大整数困难性的加密算法)
另外,假如N的长度小于或等于256位,那么用一台个人电脑在几个小时内就可以分解它的因子了。1999年,数百台电脑合作分解了一个512位长的N。1997年后开发的系统,用户应使用1024位密钥,证书认证机构应用2048位或以上。
虽然RSA加密算法作为目前最优秀的公钥方案之一,在发表三十多年的时间里,经历了各种攻击的考验,逐渐为人们接受。但是,也不是说RSA没有任何缺点。由于没有从理论上证明破译RSA的难度与大数分解难度的等价性。所以,RSA的重大缺陷是无法从理论上把握它的保密性能如何。在实践上,RSA也有一些缺点:
产生密钥很麻烦,受到素数产生技术的限制,因而难以做到一次一密;
分组长度太大,为保证安全性,n 至少也要 600 bits 以上,使运算代价很高,尤其是速度较慢。
1. 合理的使用context–比如我们常使用的Toast,Service,最好使用getApplicationContext(),因为这些在activity结束的时候可能仍在运行
下图展示了一些场景我们该用哪种context(图是盗的,附原文地址https://possiblemobile.com/2013/06/context/ 是国外的大牛写的关于context的使用)
Android Q(Android 10)之前,需要添加权限,如下:
|
1 2 |
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> |
Android Q(Android 10)开始 在App专属目录下本App可以随意操作,无需申请权限,不过App专属目录会在App卸载时跟随删除。看下面几个目录(通过Application的context就可以访问)。
- getFilesDir() :/data/user/0/本应用包名/files
- getCacheDir():/data/user/0/本应用包名/cache
- getExternalFilesDir(null):/storage/emulated/0/Android/data/本应用包名/files
- getExternalCacheDir():/storage/emulated/0/Android/data/本应用包名/cache
getFilesDir和getCacheDir是在手机自带的一块存储区域(internal storage),通常比较小,SD卡取出也不会影响到,App的sqlite数据库和SharedPreferences都存储在这里。所以这里应该存放特别私密重要的东西。
getExternalFilesDir和getExternalCacheDir是在SD卡下(external storage),在sdcard/Android/data/包名/files和sdcard/Android/data/包名/cache下,会跟随App卸载被删除。
files和cache下的区别是,在手机设置-找到本应用-在存储中,点击清除缓存,cache下的文件会被删除,files下的文件不会。
谷歌推荐使用getExternalFilesDir。我们项目的下载是个本地功能,下载完成后是存本地数据库的,不是放网络上的,所以下载的音视频都放到了这下面,项目卸载时跟随App都删除了。getExternalFilesDir方法需要传入一个参数,传入null时得到就是sdcard/Android/data/包名/files,传入其他字符串比如"Picture"得到sdcard/Android/data/包名/files/Picture。
参考代码如下:
|
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 |
/** * 获取app缓存路径 * * @param context Application Context * @return 缓存路径 */ @NonNull public static String getCacheDir(Context context) { String cacheDir; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { //外部存储可用 final File file = context.getExternalCacheDir(); if (null == file) { cacheDir = context.getCacheDir().getPath(); } else { cacheDir = file.getPath(); } } else { //外部存储不可用 cacheDir = context.getCacheDir().getPath(); } return cacheDir; } /** * 获取app文件路径 * * @param context Application Context * @return app文件路径 */ @NonNull public static String getFilesDir(Context context) { String filesDir; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { //外部存储可用 final File file = context.getExternalFilesDir(null); if (null == file) { filesDir = context.getFilesDir().getPath(); } else { filesDir = file.getPath(); } } else { //外部存储不可用 filesDir = context.getFilesDir().getPath(); } return filesDir; } |
IntelliJ IDEA会提示
|
1 |
'try' can use automatic resource management。 |
从 Java 7 build 105版本开始,Java 7的编译器和运行环境支持新的try-with-resources语句,称为ARM 块(Automatic Resource Management) ,自动资源管理。
新的语句支持包括流以及任何可关闭的资源。
|
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 |
public static void filyCopy(File one,File two){ FileInputStream fileInput = null; FileOutputStream fileOutput = null; try { fileInput = new FileInputStream(one); fileOutput = new FileOutputStream(two); byte[] b = new byte[1024]; int len = 0; while((len = fileInput.read(b)) != -1){ fileOutput.write(b, 0, len); } } catch (Exception e) { e.printStackTrace(); } finally {//释放资源 try { if(fileInput != null){ fileInput.close(); } if(fileOutput != null){ fileOutput.close(); } } catch (Exception e2) { e2.printStackTrace(); } } } |
使用try-with-resources语句来简化代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
public static void filyCopy2(File one,File two){ try (FileInputStream fileInput = new FileInputStream(one); FileOutputStream fileOutput = new FileOutputStream(two);){ byte[] b = new byte[1024]; int len = 0; while((len = fileInput.read(b)) != -1){ fileOutput.write(b, 0, len); } } catch (Exception e) { e.printStackTrace(); } } |
在这个例子中,数据流会在try执行完毕后自动被关闭,前提是,这些可关闭的资源必须实现java.lang.AutoCloseable接口。
注:目前java.lang.AutoCloseable接口的子接口或实现类如下:
所有已经子接口:
|
1 |
AsynchronousByteChannel, AsynchronousChannel, BaseStream, ByteChannel, CachedRowSet, CallableStatement, Channel, Clip, Closeable, Connection, DataLine, DirectoryStream, DoubleStream, FilteredRowSet, GatheringByteChannel, ImageInputStream, ImageOutputStream, InterruptibleChannel, JavaFileManager, JdbcRowSet, JMXConnector, JoinRowSet, Line, LongStream, MidiDevice, MidiDeviceReceiver, MidiDeviceTransmitter, Mixer, MulticastChannel, NetworkChannel, ObjectInput, ObjectOutput, Port, PreparedStatement, ReadableByteChannel, Receiver, RMIConnection, RowSet, ScatteringByteChannel, SecureDirectoryStream, SeekableByteChannel, Sequencer, SourceDataLine, StandardJavaFileManager, Statement, Stream, SyncResolver, Synthesizer, TargetDataLine, Transmitter, WatchService, WebRowSet, WritableByteChannel |
所有已知实现类:
|
1 2 3 4 |
AbstractInterruptibleChannel, AbstractSelectableChannel, AbstractSelector, AsynchronousFileChannel, AsynchronousServerSocketChannel, AsynchronousSocketChannel, AudioInputStream, BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter, ByteArrayInputStream, ByteArrayOutputStream, CharArrayReader, CharArrayWriter, CheckedInputStream, CheckedOutputStream, CipherInputStream, CipherOutputStream, DatagramChannel, DatagramSocket, DataInputStream, DataOutputStream, DeflaterInputStream, DeflaterOutputStream, DigestInputStream, DigestOutputStream, FileCacheImageInputStream, FileCacheImageOutputStream, FileChannel, FileImageInputStream, FileImageOutputStream, FileInputStream, FileLock, FileOutputStream, FileReader, FileSystem, FileWriter, FilterInputStream, FilterOutputStream, FilterReader, FilterWriter, Formatter, ForwardingJavaFileManager, GZIPInputStream, GZIPOutputStream, ImageInputStreamImpl, ImageOutputStreamImpl, InflaterInputStream, InflaterOutputStream, InputStream, InputStream, InputStream, InputStreamReader, JarFile, JarInputStream, JarOutputStream, LineNumberInputStream, LineNumberReader, LogStream, MemoryCacheImageInputStream, MemoryCacheImageOutputStream, MLet, MulticastSocket, ObjectInputStream, ObjectOutputStream, OutputStream, OutputStream, OutputStream, OutputStreamWriter, Pipe.SinkChannel, Pipe.SourceChannel, PipedInputStream, PipedOutputStream, PipedReader, PipedWriter, PrintStream, PrintWriter, PrivateMLet, ProgressMonitorInputStream, PushbackInputStream, PushbackReader, RandomAccessFile, Reader, RMIConnectionImpl, RMIConnectionImpl_Stub, RMIConnector, RMIIIOPServerImpl, RMIJRMPServerImpl, RMIServerImpl, Scanner, SelectableChannel, Selector, ServerSocket, ServerSocketChannel, Socket, SocketChannel, SSLServerSocket, SSLSocket, StringBufferInputStream, StringReader,StringWriter,URLClassLoader, Writer, XMLDecoder, XMLEncoder, ZipFile,ZipInputStream, ZipOutputStream |
对于Android用户来说,只有编译工程的 minSdkVersion大于 19(Android 4.4)的时候才能生效。
|
1 |
try-with-resources is only supported if your minSdkVersion is set to 19 or higher. |