-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 209 KB
/
content.json
1
{"meta":{"title":"小宇宙","subtitle":"燃烧吧","description":"不忘初心,方得始终","author":"sunwei","url":"http://sw926.github.io"},"pages":[{"title":"","date":"2023-02-23T02:20:24.452Z","updated":"2023-02-23T02:20:24.452Z","comments":true,"path":"404.html","permalink":"http://sw926.github.io/404.html","excerpt":"","text":""},{"title":"Categories","date":"2023-02-23T02:20:24.463Z","updated":"2023-02-23T02:20:24.463Z","comments":true,"path":"categories/index.html","permalink":"http://sw926.github.io/categories/index.html","excerpt":"","text":""},{"title":"Tags","date":"2023-02-23T02:20:24.536Z","updated":"2023-02-23T02:20:24.536Z","comments":true,"path":"tags/index.html","permalink":"http://sw926.github.io/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"ADB 命令整理","slug":"ADB-命令整理","date":"2020-05-14T02:45:27.000Z","updated":"2023-02-23T02:20:24.463Z","comments":true,"path":"2020/05/14/ADB-命令整理/","link":"","permalink":"http://sw926.github.io/2020/05/14/ADB-%E5%91%BD%E4%BB%A4%E6%95%B4%E7%90%86/","excerpt":"","text":"显示当前的 Activity 栈:1adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p' 设置 prop1adb shell setprop <name> <value> Firebase 是使用这个来控制是否开启 DebugView 1adb shell setprop debug.firebase.analytics.app <package_name> 读取的使用 getprop 1adb shell getprop debug.firebase.analytics.app 在代码里面也可以读取: 1234567891011fun readProp(prop: String): String? { try { val process = Runtime.getRuntime().exec("getprop $prop") val ir = InputStreamReader(process.inputStream) val input = BufferedReader(ir) return input.readLine() } catch (e: IOException) { e.printStackTrace() } return null} 这样就可以通过 adb 命令来控制某个功能的开关 获取设备版本号1","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Kotlin 补坑","slug":"Kotlin-补坑","date":"2020-04-27T03:50:37.000Z","updated":"2023-02-23T02:20:24.463Z","comments":true,"path":"2020/04/27/Kotlin-补坑/","link":"","permalink":"http://sw926.github.io/2020/04/27/Kotlin-%E8%A1%A5%E5%9D%91/","excerpt":"","text":"Kotlin 的 Iterable 遍历基本上都是使用 forEach,在 for 语句中可以方便的使用 break 跳出循环,但是 forEach 代码块中不能使用 break,之前一直以为使用 return@forEach 能够跳出遍历,起到和 break 一样的作用,其实这是非常错误的,return@forEach 只能跳出本次 forEach 的代码块,相当于 continue。 究其原因,先看 forEach 的函数声明: 123public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit { for (element in this) action(element)} forEach 是一个 inline 函数,action 是一个高阶函数,Kotlin的 return 只能返回具名函数或者匿名函数,并且在 Lambda 表达式内禁止使用 return,但是如果 Lambda 表达式是内敛的,return 也可以内敛,那么就可以使用 return,不过这个 return 也只能跳出具名函数或者匿名函数,要跳出 Lambda,需要使用 return 加标签的形式,也就是 return@forEach 的形式,例如: 12345678fun main() { listOf(1, 2, 3, 4, 5).forEach { if (it == 3) { return@forEach } println(it) }} return@forEach 的标签 forEach 是编译器自动生成的,有一定的误导性,如果我们自己加标签的话就是: 12345678fun main() { listOf(1, 2, 3, 4, 5).forEach action@{ if (it == 3) { return@action } println(it) }} return@action 跳出的是 inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit 的 action 的 匿名函数,如果把 action 单独声明,就更加明了: 123456789fun main() { val action = fun(item: Int) { if (item == 3) { return } println(item) } listOf(1, 2, 3, 4, 5).forEach(action)} action 中的 return 是结束本次的匿名函数,是不可能跳出 forEach 遍历的。 如果先结束遍历,可以再外层添加一个匿名函数,用 return加 Label 来返回这个函数 12345678run { listOf(1, 2, 3, 4, 5).forEach action@{ if (it == 3) { return@run } println(it) }}","categories":[{"name":"Kotlin","slug":"Kotlin","permalink":"http://sw926.github.io/categories/Kotlin/"}],"tags":[{"name":"Kotlin","slug":"Kotlin","permalink":"http://sw926.github.io/tags/Kotlin/"}],"keywords":[{"name":"Kotlin","slug":"Kotlin","permalink":"http://sw926.github.io/categories/Kotlin/"}]},{"title":"Kotlin的非空断言调用","slug":"Kotlin的非空断言调用","date":"2020-04-22T01:08:33.000Z","updated":"2023-02-23T02:20:24.463Z","comments":true,"path":"2020/04/22/Kotlin的非空断言调用/","link":"","permalink":"http://sw926.github.io/2020/04/22/Kotlin%E7%9A%84%E9%9D%9E%E7%A9%BA%E6%96%AD%E8%A8%80%E8%B0%83%E7%94%A8/","excerpt":"","text":"Kotlin 中使用 if 进行判空后就可以在 if 代码块中安全使用了,但这只针对 val 类型的变量,非空的 var 类型变量在 代码块中使用还是会显示编译错误,但是在 let 代码块内就可以安全使用,原因有可能是变量可以在其他线程置为 null,例如: 1234567891011121314151617181920212223import java.util.concurrent.ThreadLocalRandomclass Foo { val name = "bar"}var foo: Foo? = Foo()fun main() { Thread(Runnable { test1() }).start() Thread.sleep(1000) foo = null Thread.sleep(3000)}fun test1() { foo?.let { Thread.sleep(2000) println(it.name) }} 在 println(foo!!.name) 一定会抛出空指针异常。如果把 if 换成 let, 程序就能正常打印了: 123456fun test2() { foo?.let { Thread.sleep(2000) println(it.name) }} 通过反编译来生成 Java 代码: 123456789101112131415161718192021public static final void test1() { if (foo != null) { Thread.sleep(2000L); Foo var10000 = foo; if (var10000 == null) { Intrinsics.throwNpe(); } String var0 = var10000.getName(); System.out.println(var0); }}public static final void test2() { Foo var10000 = foo; if (var10000 != null) { Foo var0 = var10000; Thread.sleep(2000L); String var5 = var0.getName(); System.out.println(var5); }} 两个方法的主要区别是 test2() 首先执行了一次赋值 Foo var10000 = foo;,我的理解是 Foo 对象是保存在堆上,foo 是一个静态的引用,Foo var10000 = foo; 这一步操作确保了在函数使用的是函数内的临时变量,不会被修改。","categories":[{"name":"Kotlin","slug":"Kotlin","permalink":"http://sw926.github.io/categories/Kotlin/"}],"tags":[{"name":"Kotlin","slug":"Kotlin","permalink":"http://sw926.github.io/tags/Kotlin/"}],"keywords":[{"name":"Kotlin","slug":"Kotlin","permalink":"http://sw926.github.io/categories/Kotlin/"}]},{"title":"Java Time","slug":"Java-Time","date":"2019-11-01T16:05:11.000Z","updated":"2023-02-23T02:20:24.463Z","comments":true,"path":"2019/11/02/Java-Time/","link":"","permalink":"http://sw926.github.io/2019/11/02/Java-Time/","excerpt":"","text":"Java Time 简介Java Time 是 Java8 新加入的一个包,用来替换 Calendar、Date 这些 Java 类。Java Time 符合 JSR310 规范,JSR310 实际上有两个日期概念。第一个是Instant,它大致对应于java.util.Date类,因为它代表了一个确定的时间点,即相对于标准Java纪元(1970年1月1日)的偏移量;但与java.util.Date类不同的是其精确到了纳秒级别。第二个对应于人类自身的观念,比如LocalDate和LocalTime。他们代表了一般的时区概念,要么是日期(不包含时间),要么是时间(不包含日期),类似于java.sql的表示方式。此外,还有一个MonthDay,它可以存储某人的生日(不包含年份)。每个类都在内部存储正确的数据而不是像java.util.Date那样利用午夜12点来区分日期,利用1970-01-01来表示时间。 Java Time 中所有的时间对象都是 Immutable 的,是不可变的,和 String 一样,对象一旦生成就不能修改它的属性,这个保证了它们都是线程安全的。 在 Android 开发中使用 Java Time 需要 minSdkVersion 最低 Android O,api level 26,这基本是不可能的,现阶段使用 Java Time 需要使用替代方案,这个推荐两种:joda-time 和 Threetenabp。joda-time 的开发者也是 JSR310 的制定者,和 JSR310 api 基本一致,有少许差别,Threetenabp 是一个 backport 版本,和 JSR310 api 完全一致。为了以后迁移方便,最好还是使用 Threetenabp。Threetenabp 引入方法很简单,添加 dependencies 1implementation 'com.jakewharton.threetenabp:threetenabp:1.2.3' 在 Application 中进行初始化 1234@Override public void onCreate() { super.onCreate(); AndroidThreeTen.init(this);} Java Time 使用Java Time 由5个包组成: 12345java.time // 基础包java.time.chrono // 年表,用于不同的日历系统java.time.format // 格式化和解析时间和日期java.time.temporal // 包括底层框架和扩展特性java.time.zone // 包含时区支持的类 我们主要使用的是基础包和 format,如果要使用农历、伊斯兰历会用到 chrono。我们从最常用的类开始介绍。 LocalDateTime、LocalDate、LocalTime这三个类都是无时区的本地时间,分别表示时间日期、日期、时间,最简单的构造方法是通过 now() 方法: 123val localDateTime = LocalDateTime.now() // 当前日期和时间 2020-04-21T09:55:27.429053val localDate = LocalDate.now() // 当前日期 2020-04-21val localTime = LocalTime.now() // 当前时间 09:55:27.429661 它们之间可以通过一些方法转换: 12345localDateTime.toLocalDate() // LocalDateTime 转 LocalDatelocalDateTime.toLocalTime() // LocalDateTime 转 LocalTimelocalDate.atTime(localTime) // LocalDate 转 LocalDateTimelocalTime.atDate(localDate) // LocalTime 转 LocalDateTime 简单来说就是 LocalDateTime 可以提取时间和日期,LocalDate 填上时间成为 LocalDateTime, LocalTime 填上日期成为 LocalDateTime。 日期操作可以总结为以下几个: 加 plus 减 minus 设置 with 转换 to 结合 at at 系列方法localDate.atTime(localTime) 就是一个日期结合时间成为一个 LocalDateTime,同样,LocalDateTime 结合时区成为 ZonedDateTime : 1234val localDateTime = LocalDateTime.now()println(localDateTime)val zonedDateTime = localDateTime.atZone(ZoneId.of("UTC"))println(zonedDateTime) ZonedDateTime 的输出就有时区了: 122020-04-21T10:16:44.9387732020-04-21T10:16:44.938773Z[UTC] at 系列方法就是对内容的扩展。 plus/minus 系列方法使用 plus/minus 开头的方法是对当前对象加/减一个单位生成一个新的对象,例如: 1234val localDateTime = LocalDateTime.now()val nextMonth = localDateTime.plusMonths(1) // 加一个月val yesterday = localDateTime.minusDays(1) // 减一天val preHour = localDateTime.minusHours(1) // 减一小时 with 系列方法with 开头的方法是设置对应的属性的值生成一个新的对象,例如: 12val localDateTime = LocalDateTime.now()val jan = localDateTime.withMonth(1) // 设置为一月 to 系列方法to 开头的方法是转换的操作,例如: 1val localDate = localDateTime.toLocalDate() 转换只能是同级或者向下转换,例如同级转换: 12val nanoOfDay = LocalTime.now().toNanoOfDay()val epocSecond = ZonedDateTime.now().toEpochSecond() LocalTime 只能转换为从今天0点开始的多少纳秒,ZonedDateTime 可以转换为 epoc 时间戳。 需要注意,所有对象都是 Immutable 的,以上的操作都是在原始对象的基础上生成一个新的对象,不会修改原来的对象。 时间对比Java Time 都实现了 equals 方法和 Comparable,想比较两个时间的先后,可以直接通过 compareTo() 方法: 1234val dateTime1 = LocalDateTime.now()val dateTime2 = dateTime1.minusNanos(1)dateTime2.compareTo(dateTime1) // 0 相等,负数 dateTime2 在 dateTime1 之前,正数 dateTime2 在 dateTime1 之后 使用 Kotlin 的话就更加直观: 1234567if (dateTime1 > dateTime2) { // dateTime1 在 dateTime2 之后} else if (dateTime1 == dateTime2) { // dateTime1 dateTime2 相同} else { // dateTime1 在 dateTime1 之前} 计算时间差时间差可以使用 ChronoUnit 计算: 12345val localDate = LocalDate.of(2012, 12, 1)val localDate2 = LocalDate.of(2012, 11, 1)val days = ChronoUnit.DAYS.between(localDate2, localDate)val months = ChronoUnit.MONTHS.between(localDate2, localDate)val years = ChronoUnit.YEARS.between(localDate2, localDate) 这个时间差计算的是绝对的时间差,一个月是一年的时间除以12,2012-12-1和2012-11-1月份相差1,2012-12-1和2012-11-10月份相差0。 考虑时区的转换如果不考虑时区,LocalDateTime、LocalDate、LocalTime 这三个类已经够用了,如果考虑时区,就会遇到这样的问题: 服务器接口是 UTC 时间,客户端需要转换为用户本地的世界 用户输入的时间是当前时区的时间,服务器接口要求是 UTC 时间 等等 … 这就涉及到一些比较复杂的转换规则,我们先给 Java Time 的类按照包含内容的丰富程度拍个序,从小到大依次为: LocalDate/LocalTime LocalDateTime/Instant OffsetDateTime ZonedDateTime Instant 是指从 0时区1970-01-01 0点的偏移时间,它能表示绝对时间,可以通过打印看一下它和 LocalDateTime 的区别: 12345val ms = System.currentTimeMillis()val instant = Instant.ofEpochMilli(ms)println("$instant")println("${LocalDateTime.now()}") 输出为: 122020-04-21T04:03:53.886Z2020-04-21T12:03:53.947636 Instant 多了一个 Z,用来表示是 UTC 时间,看样子 Instant 的内容应该丰富些,但是 Instant 不能进行日期计算,而且通过 LocalDateTime 获取 Instant 使用的是 toInstant(),从 api 命名也显示它们是一个平级的关系。 回到第一个问题,服务器的时间格式如果是标准的 iso 字符串,例如 2020-04-21T04:03:53.886Z,我们直接用 Instant.parse() 就可以,如果是时间戳,使用 Instant.ofEpochMilli() 或者 Instant.ofEpochSecond(),如果是 2012-12-01 12:30:10,使用如下方法: 123val instant = LocalDateTime .parse("2012-12-01 12:30:10", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) .toInstant(ZoneOffset.UTC) 这样我们成功将服务器接口里面的时间转换为了 Instant 对象,下一步就可以将 Instant 转换为 LocalDateTime 来使用: 12LocalDateTime.ofInstant(instant, ZoneId.systemDefault()) // 默认时区LocalDateTime.ofInstant(instant, ZoneOffset.ofHours(8)) // 指定时区 同理,用户输入的时间为转换为 UTC,假设用户在东8区 12val now = LocalDateTime.now()val instant = now.toInstant(ZoneOffset.ofHours(8)) 还有一种简单的方法: 1OffsetDateTime.now().toInstant() OffsetDateTime 和 ZonedDateTime 区别OffsetDateTime 和 ZonedDateTime 都能表示绝对时间,都带有时区和时间日期。 我们的知道时区的概念,0 时区 0 点的时候,北京时间是 8 点,OffsetDateTime 对象中就带有这个偏移量,打印 OffsetDateTime: 1println(OffsetDateTime.now()) 输出为: 12020-04-21T13:00:40.823521+08:00 当前时间是东八区下午一点,如果打印 ZonedDateTime: 1println(ZonedDateTime.now()) 输出为: 12020-04-21T13:02:56.629844+08:00[Asia/Shanghai] 多了一个 ZoneId,通过这个 ZoneId,同一个时区下也可能时间不同,例如澳大利亚是是实行夏时令的,夏时令(Daylight Saving Time:DST),又称“日光节约时制”和“夏令时间”,在这一制度实行期间所采用的统一时间称为“夏令时间”。一般在天亮早的夏季人为将时间调快一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。澳大利亚夏时令时间为 10-7 到 4-1,我们测试一下: 123val zoneId = ZoneId.of("Australia/Sydney")println(Instant.parse("2018-05-01T08:00:00.000Z").atZone(zoneId))println(Instant.parse("2018-11-01T08:00:00.000Z").atZone(zoneId)) 输出的结果为: 122018-05-01T18:00+10:00[Australia/Sydney]2018-11-01T19:00+11:00[Australia/Sydney] 同一个城市,UTC时间都是早上8点,但是5月1号悉尼是18点,11月1号悉尼是19点。这也说明了 ZonedDateTime 包含的内容要比 OffsetDateTime 多。","categories":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}],"tags":[{"name":"Java Time","slug":"Java-Time","permalink":"http://sw926.github.io/tags/Java-Time/"}],"keywords":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}]},{"title":"Tips:Java Thread","slug":"Tips-Java-Thread","date":"2019-07-03T04:35:25.000Z","updated":"2023-02-23T02:20:24.462Z","comments":true,"path":"2019/07/03/Tips-Java-Thread/","link":"","permalink":"http://sw926.github.io/2019/07/03/Tips-Java-Thread/","excerpt":"","text":"先看一个经典的多线程的例子:1234567891011121314151617181920212223242526272829class A { private var value = 0 fun increase() { value++ } fun printValue() { println("value = $value") }}fun main() { val a = A() for (i in 0 until 100) { Thread(Runnable { for (j in 0 until 100000) { a.increase() } }).start() } // 等待所有线程执行完,activeCount 根据环境不同,为 1 或 2 while (Thread.activeCount() > 2) { Thread.yield() } a.printValue()} 输出的结果可能每次都不一样同步的方法: synchronized lock 使用 Atomic 加上时间的统计,测试一下速度: 123456789101112131415161718fun main() { val start = System.currentTimeMillis() val a = A() for (i in 0 until 100) { Thread(Runnable { for (j in 0 until 100000) { a.increase() } }).start() } while (Thread.activeCount() > 2) { Thread.yield() } a.printValue() println("spent: ${System.currentTimeMillis() - start}ms")} 首先使用 synchronized1234@Synchronizedfun increase() { value++} 花费时间 550ms 左右。 还有一种方式是使用同步代码块: 1234567private val lock = Any()fun increase() { synchronized(lock) { value++ }} 花费时间比使用 synchronized 函数要少一点,在 480ms 左右。 使用 Lock123456private val lock = ReentrantLock()fun increase() { lock.lock() value++ lock.unlock()} 花费时间 310ms 左右 最后使用 Atomic12345private var value = AtomicLong()fun increase() { value.incrementAndGet()} 花费时间 230ms 左右。","categories":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}],"tags":[{"name":"Java Thread","slug":"Java-Thread","permalink":"http://sw926.github.io/tags/Java-Thread/"}],"keywords":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}]},{"title":"Tips:sdkman","slug":"Tips-sdkman","date":"2019-06-11T02:32:37.000Z","updated":"2023-02-23T02:20:24.462Z","comments":true,"path":"2019/06/11/Tips-sdkman/","link":"","permalink":"http://sw926.github.io/2019/06/11/Tips-sdkman/","excerpt":"","text":"安装https://sdkman.io/install 123$ curl -s "https://get.sdkman.io" | bash$ source "$HOME/.sdkman/bin/sdkman-init.sh"$ sdk version 安装 JDK1sdk list java 输出: 123456789101112131415161718192021222324================================================================================Available Java Versions================================================================================ 19.0.0-grl 11.0.2-zulufx 1.0.0-rc-16-grl 13.ea.24-open 10.0.2-zulu 12.0.1-sapmchn 10.0.2-open 12.0.1-zulu 9.0.7-zulu 12.0.1-open 9.0.4-open 12.0.1.j9-adpt 8.0.212-zulu 12.0.1.hs-adpt 8.0.212-amzn 12.0.1-librca 8.0.212.j9-adpt 11.0.3-librca 8.0.212.hs-adpt 11.0.3-sapmchn 8.0.212-librca 11.0.3-zulu 8.0.202-zulu 11.0.3-amzn 8.0.202-amzn 11.0.3.j9-adpt 8.0.202-zulufx 11.0.3.hs-adpt 7.0.222-zulu 11.0.2-open 7.0.181-zulu================================================================================+ - local version* - installed> - currently in use================================================================================ 大部分人应该都是使用的 Oracle 版本的 JDK,各个版本的区别可以参考文章: 1https://www.oschina.net/news/99836/time-to-look-beyond-oracles-jdk 然后安装: 1sdk install java 8.0.212-zulu","categories":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}],"tags":[{"name":"sdkman","slug":"sdkman","permalink":"http://sw926.github.io/tags/sdkman/"}],"keywords":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}]},{"title":"Tips:SpringBoot Properties","slug":"Tips-SpringBoot-Properties","date":"2019-05-16T02:11:04.000Z","updated":"2023-02-23T02:20:24.462Z","comments":true,"path":"2019/05/16/Tips-SpringBoot-Properties/","link":"","permalink":"http://sw926.github.io/2019/05/16/Tips-SpringBoot-Properties/","excerpt":"","text":"新建项目里面都有一个 application.properties,我们可以在这个文件中添加一些属性, 12test.foo=Hellotest.bar=World 在组件中可以通过添加 @Value 注解来读取 123456789101112131415@RestController@RequestMapping("/test")public class TestController { @Value("${test.foo}") private String foo; @Value("${test.bar}") private String bar; @GetMapping("/hello") public String hello() { return foo + " " + bar; }} test/hello 接口输出的是 Hello World,我们可以通过 Java 的 JVM Args 在运行的时候覆盖这些属性: 1java -Dtest.bar=bar -jar xxx.jar 这样 test/hello 接口输出的是 Hello bar,那么 SystemProperties 有没有这些属性呢,实验一下,修改 hello() 为 1234@GetMapping("/hello")public String hello() { return foo + " " + bar + ", from system: " + SystemProperties.get("test.foo") + " " + SystemProperties.get("test.bar");} 输出结果为:Hello bar, from system: null bar,看来 application.properties 是不会写入 SystemProperties 的,另外 @Value 不是直接读取 application.properties 的值,而是好几个源,有优先级的。@Value 可以注入一下的资源: 注入普通字符串 注入操作系统属性 注入表达式结果 注入其他Bean属性:注入beanInject对象的属性another 注入文件资源 注入URL资源 示例: 1234567891011121314151617@Value("normal")private String normal; // 注入普通字符串@Value("#{systemProperties['os.name']}")private String systemPropertiesName; // 注入操作系统属性@Value("#{ T(java.lang.Math).random() * 100.0 }")private double randomNumber; //注入表达式结果@Value("#{valueTestBean.name}")private String fromAnotherBean; // 注入其他Bean属性:注入beanInject对象的属性another,类具体定义见下面@Value("classpath:resources/static/value_test.dat")private Resource resourceFile; // 注入文件资源@Value("http://www.baidu.com")private Resource testUrl; // 注入URL资源 Properties 的加载顺序可以查看官方文档 https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#boot-features-external-config","categories":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}],"tags":[{"name":"Spring Boot","slug":"Spring-Boot","permalink":"http://sw926.github.io/tags/Spring-Boot/"}],"keywords":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}]},{"title":"Tips:Mysql 显示 BIT","slug":"Tips-Mysql-显示-BIT","date":"2019-05-15T01:41:17.000Z","updated":"2023-02-23T02:20:24.462Z","comments":true,"path":"2019/05/15/Tips-Mysql-显示-BIT/","link":"","permalink":"http://sw926.github.io/2019/05/15/Tips-Mysql-%E6%98%BE%E7%A4%BA-BIT/","excerpt":"","text":"在数据库中创建一个表,添加一个 is_new 的 column,类型为 BIT: 12345CREATE TABLE `test_table`( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `is_new` BIT NOT NULL DEFAULT b'0'); 插入一条数据库: 1INSERT INTO `test_table` SET `is_new`=1; 然后查询所有的数据: 1SELECT * FROM `test_table`; 显示结果为: 1234567mysql> select * from test_table;+----+--------+| id | is_new |+----+--------+| 1 | |+----+--------+1 row in set (0.00 sec) 数据插入成功了,但是 is_new 的值不显示,换个图形化工具,可以查到这个值是 1,那么在命令行怎么查看呢?两种方法: 123SELECT `id`, bin(`is_new`) FROM `test_table`;SELECT `id`, ord(`is_new`) FROM `test_table`; 123456789101112131415mysql> SELECT `id`, bin(`is_new`) FROM `test_table`;+----+---------------+| id | bin(`is_new`) |+----+---------------+| 1 | 1 |+----+---------------+1 row in set (0.00 sec)mysql> SELECT `id`, bin(`is_new`) FROM `test_table`;+----+---------------+| id | bin(`is_new`) |+----+---------------+| 1 | 1 |+----+---------------+1 row in set (0.00 sec) 结果都是一样的,但是这两个函数有什么区别呢?首先是 BIN 12BIN(N)返回N的二进制值的字符串表示,其中N是一个长(BIGINT)数字。这等同于CONV(N,10,2)。返回NULL,如果N为NULL。 123456mysql> select bin(2);+--------+| bin(2) |+--------+| 10 |+--------+ 2 显示成了二进制形式 10。 ORD() 有点复杂: 123456ORD(str)如果字符串str的最左边的字符是一个多字节字符返回该字符,用这个公式其组成字节的数值计算的代码: (1st byte code)+ (2nd byte code . 256)+ (3rd byte code . 2562) ...如果最左边的字符不是一个多字节字符,ORD()返回相同的值如ASCII()函数。 **这个函数接受的是字符串,我们就认为它的作用是返回参数的第一个字符的 ASCII, 1234567891011121314151617181920212223mysql> select ord(1);+--------+| ord(1) |+--------+| 49 |+--------+1 row in set (0.00 sec)mysql> select ord(2);+--------+| ord(2) |+--------+| 50 |+--------+1 row in set (0.00 sec)mysql> select ord('abc');+------------+| ord('abc') |+------------+| 97 |+------------+1 row in set (0.00 sec) 那么这个函数为什么能正常显示 is_new 呢,因为 is_new 是 BIT 类型。 1234567mysql> select ord(b'1');+-----------+| ord(b'1') |+-----------+| 1 |+-----------+1 row in set (0.01 sec) 就是说,BIN 是把 is_new 按二进制形式显示,ORD 是显示的 is_new 的 ASCII,或者转成了字符串? BIT 对应 Java 的布尔类型,BIT 默认只有一个比特位,那么 0 就是 false, 其他是 true,下面的设置方式在 mysql 中都是允许的 12345678INSERT INTO `test_table`SET `is_new`=1;INSERT INTO `test_table`SET `is_new`=b'1';INSERT INTO `test_table`SET `is_new`= FALSE;INSERT INTO `test_table`SET `is_new`= TRUE;","categories":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}],"tags":[{"name":"MySql","slug":"MySql","permalink":"http://sw926.github.io/tags/MySql/"}],"keywords":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}]},{"title":"MySql错误排除","slug":"MySql错误排除","date":"2019-04-08T06:09:01.000Z","updated":"2023-02-23T02:20:24.462Z","comments":true,"path":"2019/04/08/MySql错误排除/","link":"","permalink":"http://sw926.github.io/2019/04/08/MySql%E9%94%99%E8%AF%AF%E6%8E%92%E9%99%A4/","excerpt":"","text":"最大文件打开数量的问题使用 ulimit -a 可以查看系统对单个进程的限制 12345678910➜ ~ ulimit -a-t: cpu time (seconds) unlimited-f: file size (blocks) unlimited-d: data seg size (kbytes) unlimited-s: stack size (kbytes) 8192-c: core file size (blocks) 0-v: address space (kbytes) unlimited-l: locked-in-memory size (kbytes) unlimited-u: processes 1418-n: file descriptors 7168 ulimit -n 是打开的最大文件数量限制,在 Mac 上可能是 256,如果是用了分表,默认情况下一个表对应一个文件,如果表的数量超过 256,打开超过256个文件的时候会报错。 解决方法: 1)mysql 的 my.cnf 文件 1innodb_file_per_table = OFF 关闭每个 table 建立一个文件,如果还是不行 2)修改最大文件打开数","categories":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}],"tags":[{"name":"MySql","slug":"MySql","permalink":"http://sw926.github.io/tags/MySql/"}],"keywords":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}]},{"title":"Redis命令","slug":"Redis命令","date":"2019-03-20T07:36:57.000Z","updated":"2023-02-23T02:20:24.461Z","comments":true,"path":"2019/03/20/Redis命令/","link":"","permalink":"http://sw926.github.io/2019/03/20/Redis%E5%91%BD%E4%BB%A4/","excerpt":"","text":"连接: 1redis-cli -h {host} -p {port} 设置一个: 12127.0.0.1:6379> set aaa bbbOK 获取值,成功的话返回值,失败时 nil 1234127.0.0.1:6379> get aaa"bbb"127.0.0.1:6379> get ddd(nil) 值是否存在,若 key 存在,返回 1 ,否则返回 0: 12127.0.0.1:6379> exists aaa(integer) 1 获取keys: 12345127.0.0.1:6379> keys aaa1) "aaa"127.0.0.1:6379> keys ddd(empty list or set)127.0.0.1:6379> 获取过期时间,当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间。: 1234127.0.0.1:6379> ttl aaa(integer) -1127.0.0.1:6379> ttl ddd(integer) -2 设置过期时间: 12redis> EXPIRE cache_page 30 # 设置过期时间为 30 秒(integer) 1 列出所有数据库: 123CONFIG GET databases1) "databases"2) "16" 1234127.0.0.1:6379[1]> INFO keyspace# Keyspacedb0:keys=10,expires=10,avg_ttl=140298db1:keys=3774,expires=3770,avg_ttl=73246701 选择数据库 1select 1","categories":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}],"tags":[{"name":"Redis","slug":"Redis","permalink":"http://sw926.github.io/tags/Redis/"}],"keywords":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}]},{"title":"Jvm Args","slug":"jvm-args","date":"2019-03-14T03:51:35.000Z","updated":"2023-02-23T02:20:24.461Z","comments":true,"path":"2019/03/14/jvm-args/","link":"","permalink":"http://sw926.github.io/2019/03/14/jvm-args/","excerpt":"","text":"查看信息查看正在运行的 Java 进程 1jps -l -l 是显示程序名 使用 jinfo 可以查看 Java 进程的信息: 1jinfo {pid} help 信息: 123456789101112Usage: jinfo <option> <pid> (to connect to a running process)where <option> is one of: -flag <name> to print the value of the named VM flag -flag [+|-]<name> to enable or disable the named VM flag -flag <name>=<value> to set the named VM flag to the given value -flags to print VM flags -sysprops to print Java system properties <no option> to print both VM flags and system properties -h | -help to print this help message 示例输出结果如下: 123456789101112131415Java System Properties:#Thu Mar 14 15:03:26 CST 2019gopherProxySet=falseawt.toolkit=sun.lwawt.macosx.LWCToolkitfile.encoding.pkg=sun.iojava.specification.version=10sun.cpu.isalist=...VM Flags:...VM Arguments:...Launcher Type: SUN_STANDARD 单独输出命令: 12jinfo -flags {pid} # 只输出 VM FLagsjinfo -sysprops {pid} # 只输出 Java System Properties 查看单个选项,例如 1jinfo -flag CICompilerCount 8982 输出: 1-XX:CICompilerCount=4 使用 jinfo 可以在运行时修改部分 VM Flag,可以修改的 Flag 可以通过命令查看: 1java -XX:+PrintFlagsInitial | grep manageable 启动参数例如运行时候要访问外网,可以设置代理 1-Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=1087 示例: 1java -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=1087 -jar app-0.0.1-SNAPSHOT.jar 可以通过: 1jinfo -sysprops {pid} | grep proxy 查看设置 proxy 是否成功 JVM 性能调优计较重要的 jvm 参数为: -Xmx 最大可用内存 -Xms 初始内存 -Xmn 整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。 -Xss 设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。 -XX:NewRatio=4 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 -XX:SurvivorRatio=4 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6 -XX:MaxPermSize=16m 设置持久代大小为16m。 -XX:MaxTenuringThreshold=0 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。 查看 heap1jhsdb jmap --heap --pid {pid}","categories":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}],"tags":[{"name":"jvm","slug":"jvm","permalink":"http://sw926.github.io/tags/jvm/"}],"keywords":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}]},{"title":"Spring Boot 发起Http请求","slug":"Spring-Boot-发起Http请求","date":"2019-02-22T07:05:23.000Z","updated":"2023-02-23T02:20:24.461Z","comments":true,"path":"2019/02/22/Spring-Boot-发起Http请求/","link":"","permalink":"http://sw926.github.io/2019/02/22/Spring-Boot-%E5%8F%91%E8%B5%B7Http%E8%AF%B7%E6%B1%82/","excerpt":"","text":"在 SpringBoot 发起 http request 一般使用 RestTemplate,在 Android 开发中,使用最多的是 OkHttp,当然在 SpringBoot 中也可以使用,但是不能做到开箱即用,还要添加 Json 解析的 Adapter,不然 RestTemplate 方便。 RestTemplate 可以直接使用,我们可以使用 @Configuration 进行配置 1234567891011121314151617181920@Configurationpublic class RestTemplateConfig { @Bean @ConditionalOnMissingBean({ClientHttpRequestFactory.class}) public ClientHttpRequestFactory requestFactory() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(15000); factory.setReadTimeout(15000); return factory; } @Bean @ConditionalOnMissingBean({RestTemplate.class}) public RestTemplate nimRestTemplate(ClientHttpRequestFactory factory) { RestTemplate restTemplate = new RestTemplate(factory); return restTemplate; }} 之后就可以直接发起请求: 12345678910111213@Servicepublic class TestService { @Resource private RestTemplate restTemplate; public UserResult test() { String url = "http://localhost:3000/test"; return restTemplate.getForObject(url, UserResult.class); }} 添加 Header123HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.MULTIPART_FORM_DATA);HttpEntity<String> entity1 = new HttpEntity<>(headers)","categories":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}],"tags":[{"name":"Spring Boot","slug":"Spring-Boot","permalink":"http://sw926.github.io/tags/Spring-Boot/"}],"keywords":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}]},{"title":"vps安装Ngrok和Nginx","slug":"vps安装Ngrok和Nginx","date":"2019-02-20T02:54:43.000Z","updated":"2023-02-23T02:20:24.461Z","comments":true,"path":"2019/02/20/vps安装Ngrok和Nginx/","link":"","permalink":"http://sw926.github.io/2019/02/20/vps%E5%AE%89%E8%A3%85Ngrok%E5%92%8CNginx/","excerpt":"","text":"使用的是 vultr 的 vps,系统是 Ubuntu,ssh 登录直接是 root 用户,根据这篇文章安装 Ngrokhttps://blog.csdn.net/yjc_1111/article/details/79353718 准备工作首先安装 git 和 go: 1apt-get install build-essential golang mercurial git 载源码,当然也可以不安装git,但是需要手动上传代码到需要的位置。此处使用非官方地址,修复了部分包无法获取(摘自网络): 1git clone https://github.com/tutumcloud/ngrok.git ngrok 代码在 /root/ngrok 目录。 编译需要一个域名,比如 sw926.com, 12345678910111213cd ngrokNGROK_DOMAIN="sw926.com"openssl genrsa -out base.key 2048openssl req -new -x509 -nodes -key base.key -days 10000 -subj "/CN=$NGROK_DOMAIN" -out base.pemopenssl genrsa -out server.key 2048openssl req -new -key server.key -subj "/CN=$NGROK_DOMAIN" -out server.csropenssl x509 -req -in server.csr -CA base.pem -CAkey base.key -CAcreateserial -days 10000 -out server.crt 执行完替换证书: 1cp base.pem assets/client/tls/ngrokroot.crt 编译: 1make release-server release-client 启动服务端1./bin/ngrokd -tlsKey=server.key -tlsCrt=server.crt -domain="sw926.com" -httpAddr=":8000" -httpsAddr=":4443" 客户的编译客户端: 1GOOS=darwin GOARCH=amd64 make release-client 将编译好的客户端下载到本地: 1scp root@{vps地址}:/root/ngrok/bin/darwin_amd64/ngrok ./ 我们可以把这个文件放到 bin 目录,之后需要新建一个 ngrok.conf 文件,输入以下内容 12server_addr: "sw926.com:4443"trust_host_root_certs: false 然后启动客户端: 1ngrok -config=./ngrok.conf -subdomain="ngrok" localhost:3000 如果配置成功,会看的以下内容: 1234567Tunnel Status onlineVersion 1.7/1.7Forwarding http://ngrok.sw926.com:8000 -> localhost:3000Forwarding https://ngrok.sw926.com:8000 -> localhost:3000Web Interface 127.0.0.1:4040# Conn 0Avg Conn Time 0.00ms 127.0.0.1:4040 是本地管理的界面,localhost:3000 是本地启动的服务,我们访问 ngrok.sw926.com:8000,其实就是访问的 localhost:3000 ngrok 在服务的开机启动https://zhuanlan.zhihu.com/p/25167530 新建一个启动脚本,目录为 /root/ngrok.sh,输入以下内容 123cd /root/ngroknohup ./bin/ngrokd -tlsKey=server.key -tlsCrt=server.crt -domain="sw926.com" -httpAddr=":8000" -httpsAddr=":443"exit 0 在 /etc/init.d 下新建一个 ngrok 文件,输入以下内容: 1234567891011121314#!/bin/sh#chkconfig:2345 70 30#description:ngrokngrok_path=/rootcase "$1" in start) echo "start ngrok service.." sh ${ngrok_path}/ngrok.sh ;; *) exit 1 ;;esac 赋予权限: 1sudo chmod 755 /etc/init.d/ngrok 然后注册服务: 123cd /etc/init.dsudo update-rc.d ngrok defaultssudo update-rc.d start 70 2 3 4 5 启动服务: 1sudo service ngrok start 使用 nginx 转发安装: 1sudo apt-get install nginx 启动: 1sudo /etc/init.d/nginx start 访问 vps,就会看到 niginx 的 welcome 页面 配置转发http://www.wxapp-union.com/portal.php?mod=view&aid=1148https://segmentfault.com/q/1010000004277269 在服务器中新建/etc/nginx/conf.d/ngrock.sw926.com.conf文件,添加以下内容: 123456789101112131415161718192021222324server { listen 80; server_name *.ngrok.up2m.win; ## send request back to apache ## location / { proxy_pass http://127.0.0.1:8000; #Proxy Settings proxy_redirect off; #proxy_set_header Host downloads.openwrt.org; proxy_set_header Host $host:8000; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504; proxy_max_temp_file_size 0; proxy_connect_timeout 90; proxy_send_timeout 90; proxy_read_timeout 90; proxy_buffer_size 4k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k; }} 重载 nginx: 1nginx -s reload 客户端快捷方式我使用的是 zsh,可以在 ~/.zshenv 文件添加一个函数: 1234function ngrok_start{ ngrok -config=/Users/xxx/.config/ngrok/ngrok.conf -subdomain="$1" $2} 启动的时候就简单一些了: 1ngrok_start web localhost:3000","categories":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}],"tags":[{"name":"Nginx","slug":"Nginx","permalink":"http://sw926.github.io/tags/Nginx/"},{"name":"Ngrok","slug":"Ngrok","permalink":"http://sw926.github.io/tags/Ngrok/"}],"keywords":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}]},{"title":"Spring Boot Gradle","slug":"Spring-Boot-Gradle","date":"2019-02-14T02:34:46.000Z","updated":"2023-02-23T02:20:24.461Z","comments":true,"path":"2019/02/14/Spring-Boot-Gradle/","link":"","permalink":"http://sw926.github.io/2019/02/14/Spring-Boot-Gradle/","excerpt":"","text":"翻译自官方文档: 添加插件必须要添加的是 org.springframework.boot,这个插件已经上传到 Gradle 插件网站上,可以直接添加: 123plugins { id 'org.springframework.boot' version '2.1.0.RELEASE'} 这个插件会自动检测工程中的其他插件,从而做出对应的行为,例如添加了 java 插件,就有有生成 jar 文件的 task,使用 java 一般至少要添加: 12apply plugin: 'java'apply plugin: 'io.spring.dependency-management' 依赖管理Spring Boot 通过 starter 添加一系列的依赖,这些依赖的版本通过插件和 starter 指定,在项目中添加 starter 的时候不要写版本号,例如: 1234dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'} 如果要修改其中一个特定依赖的版本,可以使用下面的方式: 1ext['slf4j.version'] = '1.7.20' 完整的列表在:https://github.com/spring-projects/spring-boot/blob/v2.1.0.RELEASE/spring-boot-project/spring-boot-dependencies/pom.xml","categories":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}],"tags":[{"name":"Gradle","slug":"Gradle","permalink":"http://sw926.github.io/tags/Gradle/"},{"name":"Spring Boot","slug":"Spring-Boot","permalink":"http://sw926.github.io/tags/Spring-Boot/"}],"keywords":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}]},{"title":"Spring Boot Start","slug":"Spring-Boot-Start","date":"2019-02-13T02:30:56.000Z","updated":"2023-02-23T02:20:24.461Z","comments":true,"path":"2019/02/13/Spring-Boot-Start/","link":"","permalink":"http://sw926.github.io/2019/02/13/Spring-Boot-Start/","excerpt":"","text":"首先添加 Gradle 的插件: 1234567891011buildscript { ext { springBootVersion = '2.0.6.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") }} 然后添加 starter: 123456// startercompile 'org.springframework.boot:spring-boot-starter-web'compile 'org.springframework.boot:spring-boot-starter-thymeleaf'compile 'org.springframework.boot:spring-boot-starter-amqp'compile 'org.springframework.boot:spring-boot-configuration-processor'testCompile 'org.springframework.boot:spring-boot-starter-test' 注意不要写版本号 mysql创建用户: 1CREATE USER 'username'@'host' IDENTIFIED BY 'password'; 授权: 12GRANT all privileges ON databasename.tablename TO 'username'@'host';flush privileges; 说明: privileges:用户的操作权限,如SELECT,INSERT,UPDATE等,如果要授予所的权限则使用ALL databasename:数据库名 tablename:表名,如果要授予该用户对所有数据库和表的相应操作权限则可用表示,如.*","categories":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}],"tags":[{"name":"Spring Boot","slug":"Spring-Boot","permalink":"http://sw926.github.io/tags/Spring-Boot/"}],"keywords":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}]},{"title":"读懂 Gradle 的 DSL","slug":"读懂-Gradle-的-DSL","date":"2019-02-02T04:16:42.000Z","updated":"2023-02-23T02:20:24.461Z","comments":true,"path":"2019/02/02/读懂-Gradle-的-DSL/","link":"","permalink":"http://sw926.github.io/2019/02/02/%E8%AF%BB%E6%87%82-Gradle-%E7%9A%84-DSL/","excerpt":"","text":"现在 Android 开发免不了要和 Gradle 打交道,所有的 Android 开发肯定都知道这么在 build.gradle 中添加依赖,或者添加配置批量打包,但是真正理解这些脚本的人恐怕很少。其实 Gradle 的 build.gradle 可以说是一个代码文件,熟悉 Java 的人理解起来很简单的,之所以不愿意去涉及,主要感觉没有必要去研究。要能看懂 build.gradle,除了要了解 Groovy 的语法,还要了解 Gradle 的构建流程,要研究还是要花一些时间的,所以这篇文章可以让一个 Java 程序员在一个小时内看懂 Gradle 的脚本。 Gradle 简单介绍Gradle 构建由 Project 和 Task 组成,Project 保存项目的属性,例如 name,版本号,代码文件位置。Task 也是 Project 的一部分,但是它是可执行的任务,我们最常使用的 build 就是一个 Task,Task 可以依赖于另外一个 Task,一个 Task 在执行的时候,它依赖的 Task 会先执行。这样,当我们 build 的时候,这个 Task 可能依赖很多的 Task,比如代码检查、注解处理,这样一层层的依赖,最终通过 build Task 全部执行。 Gradle 和 GroovyGradle 和 Groovy 这两个名字很容易让人产生混淆,这里先解释一下,Groovy 是一门编程语言,和 Java 一样。Gradle 和一个自动化构建工具,其他知名的构建工具还有 Maven 和 Ant。什么自动化构建工具?用 Android 来举例,打包一个 Apk 文件要做很多工作,代码预处理,lint代码检查、处理资源、编译 Java 文件等等,使用自动化构建工具,一个命令就可以生成 Apk 了。 Gradle 的 DSL 目前支持两种语言的格式,Groovy 和 Kotlin,Kotlin 格式的 DSL 是在 5.0 引入的,相比 Groovy,Kotlin 使用的人数更多,也更好理解,在这儿主要介绍 Groovy 格式的 DSL。 介绍一下什么是 DSL,DSL 是 Domain Specific Language 的缩写,既领域专用语言。Gradle 的 DSL 专门用于配置项目的构建,不能做其他工作,而像 Java 、C/C++ 这些就属于通用语言,可以做任何工作。 我们还要理解什么是脚本文件。在写代码 Java 代码时,程序是从 main() 函数开始执行的,只有在 main() 中调用的代码才会执行。但是脚本文件不一样,只要在文件内写的代码都会执行,Groovy 是支持脚本文件的,我们配置好 Groovy 的开发环境,新建一个文件 test.groovy,输入以下内容: 1234String hello = "Hello World!"println(hello)println("The End") 然后运行: 1groovy test.groovy 输出结果为: 12Hello World!The End 虽然没有 main 函数,但是里面的代码都执行了。很明显,build.gradle 就是一个 Groovy 的脚本文件,里面就是 Groovy 代码,里面添加的所有代码都会运行,我们可以试验以下,随便打开一个 Gradle 格式的项目,在 build.gradle 最下面添加一些 Java 代码: 12String hello = "Hello World!"System.out.println(hello) 然后执行: 1./gradlew -q # -q 是不输出额外的信息 我们会看到输出了 Hellow World,说明我们添加的代码被执行了,那么为什么可以在 build.gradle 里面写 Java 代码呢?这是因为 Groovy 是支持 Java 的语法的,在 Groovy 文件写 Java 代码是完全没有问题的。 build.gradle 的执行方式现在总结一下,build.gradle 就是一个 Groovy 格式脚本文件,里面是 Groovy 或者 Java 代码,构建的时候会顺序执行,但是打开 build.gradle,可能还是一头雾水,一个个字符和大括号组成的东西到底是什么鬼?我们来看一下最长使用的 dependencies: 1234567dependencies { // This dependency is found on compile classpath of this component and consumers. implementation 'com.google.guava:guava:26.0-jre' // Use JUnit test framework testImplementation 'junit:junit:4.12'} implementation 也可以这样写: 1implementation('com.google.guava:guava:26.0-jre') implementation 其实就是一个函数,在 Groovy 中,函数调用可以使用空格加参数的形式调用,例如 12345void foo(String params1, int param2) { println("param1 = $params1, param2 = $param2")}foo "aaa", 2 implementation 'com.google.guava:guava:26.0-jre' 就是调用了 implementation 函数添加了一个依赖。以此类推,dependencies 也是一个函数,在 IDEA 中,我们可以直接 Ctrl 加鼠标左键点击进去看它的声明: 12345public interface Project extends Comparable<Project>, ExtensionAware, PluginAware { // ... void dependencies(Closure configureClosure); // ...} 我们看到 dependencies 是 Project 一个方法,为什么可以在 build.gradle 调用 Project 的方法呢,官方文档里面有相关的介绍。一个 Gradle 项目一般有一个 settings.gradle 文件和一个 build.gradle 文件,settings.gradle 用来配置目录结构,子工程就是在 settings.gradle 里面配置,Project 和 build.gradle 是一一对应的关系,Gradle 的构建流程如下: 1、生成一个 Settings 对象,执行 settings.gradle 对这个对象进行配置 2、使用 Settings 对象生成工程结构,创建 Project 对象 3、对所有 Project 执行对应的 build.gradle 进行配置 build.gradle 就是对 Project 的操作,例如,在 build.gradle 中输入以下代码 1println "project name is ${this.name}" 输出结果为: project name is java_demo,java_demo 就是我们的 project name,我们可以认为对 this 的操作就是对 project 的操作。 Groovy 也是有语法糖的,类的属性可以直接使用名字,例如 Project 的有两个函数: 12Object getVersion();void setVersion(Object version); 那么这就说明 Project 有一个 version 属性,在 build.gradle 中我们可以这样来使用: 12version = "1.0" // 赋值,调用 setVersion()println version // 读取,调用 getVersion() 在 Project 中没有 getter 方法的属性是不能赋值的,例如 name,我们可以输出 name 的值,但是 name = "demo" 是错误的。 所以,在 build.gradle 中的代码就是修改 Project,方式就是修改属性或者调用相关的方法,plugins 方法是添加插件,repositories 方法是添加代码仓库, Groovy 闭包闭包可以认为是可以执行的代码块,Groovy 中闭包的声明和执行方式如下: 12345Closure closure = { String item -> println(item)}closure("Hello") // 执行 和 Lambda 表达式很像,但是 Groovy 的闭包可以先声明,然后设置代理来执行,例如我们声明一个闭包: 123Closure closure = { sayHello()} 这个闭包里面执行了 sayHello() 函数,但是我们没有在任何地方声明这个函数,在 Java 中,这是个编译错误,但是 Groovy 是允许的,完整的执行的例子如下: 123456789101112Closure closure = { sayHello()}class Foo { void sayHello() { println("Hello!!!") }}def foo = new Foo()closure.delegate = fooclosure() 输出结果为: 1Hello!!! 我们为闭包设置了一个代理 delegate,只要这个代理有 sayHello() 方法,代码就能执行,这就是为什么我们查看 Project 的源码,里面很多函数参数类型都是 Closure,例如: 12void repositories(Closure configureClosure);void dependencies(Closure configureClosure); repositories 在 build.gradle 中是这样调用的: 12345repositories { // Use jcenter for resolving your dependencies. // You can declare any Maven/Ivy/file repository here. jcenter()} 我们通过 IDE 进入 jcenter() 的声明,进入的是: 123public interface RepositoryHandler extends ArtifactRepositoryContainer { // ...} 由于没看过源码,我也只能猜,我猜 repositories 这个闭包的 delegate 是一个 RepositoryHandler,通过执行 RepositoryHandler 的方法,为工程添加 Repository Plugin来看我们使用最多的 dependencies 123456789dependencies { // This dependency is found on compile classpath of this component and consumers. implementation 'com.google.guava:guava:26.0-jre' implementation('com.google.guava:guava:26.0-jre') // Use JUnit test framework testImplementation 'junit:junit:4.12'} 在 Java 和 Android 项目中 implementation 是一定会用到的,但是一个 Gradle Basic 项目是没有 implementation 的,实际上,在 dependencies 是不能直接添加任何依赖的。 这里我们有说一下 Gradle 怎么解决依赖。 Gradle 空白项目没有编译 Java 项目的能力,但是它能从仓库下载依赖的库并且配置到 Project 中。在我们编译 Java 项目的时候,一个配置是不够的,至少要有个测试版,正式版,两个版本依赖的库可能是不一样的,两个版本部分代码也是不一样的,那么我们怎么区分呢?在 Gradle 中,是通过 configurations,也就是配置,每个配置可以单独的添加依赖,在编译的时候,也就是执行某个 Task 的时候,通过读取配置中的依赖来添加 classpath,例如: 12345678910111213141516171819202122repositories { mavenCentral()}configurations { test release}dependencies { test 'org.apache.commons:commons-lang3:3.0' release 'org.slf4j:slf4j-log4j12:1.7.2'}task buildTest { doLast { println configurations.test.name println configurations.test.asPath }} 执行 ./gradlew buildTest -q,输出结果为: 12test/Users/xxx/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.0/8873bd0bb5cb9ee37f1b04578eb7e26fcdd44cb0/commons-lang3-3.0.jar 如果在 buildTest 这个 Task 中进行编译工作的话,我们就可以直接读取 configurations.test 的路径设置为 classpath。 implementation 就是通过添加了一个 implementation 配置来实现的。这个配置是通过: 1234567plugins { // Apply the java plugin to add support for Java id 'java' // Apply the application plugin to add support for building an application id 'application'} 添加的,我们通过 plugins 可以给 Project 添加属性,Tasks,配置,例如我们写一个最简单的插件: 12345678910111213141516171819package com.demoimport org.gradle.api.Pluginimport org.gradle.api.Projectclass DemoPlugin implements Plugin<Project> { void apply(Project project) { project.task("hello") { doLast { println "Hello World" } } project.configurations { demoCompile } }} 这个插件为 Project 添加了一个 Task,添加了一个配置,我们将这个文件 DemoPlugin.groovy 放在项目根目录下的 buildSrc/src/main/groovy/demo/ 下,就可以在 build.gradle 中直接使用了: 1apply plugin: com.demo.DemoPlugin buildscript对于 buildscript,例如: 12345678910buildscript { repositories { mavenCentral() google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.3.0' }} 它的作用是为构建脚本提供依赖,例如我们在项目中使用了 Android 的 Plugin,这个 Plugin 的要从哪找下载?这就需要在 buildscript 中指定。","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Gradle","slug":"Gradle","permalink":"http://sw926.github.io/tags/Gradle/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Gradle","slug":"Gradle","date":"2019-01-30T07:10:40.000Z","updated":"2023-02-23T02:20:24.460Z","comments":true,"path":"2019/01/30/Gradle/","link":"","permalink":"http://sw926.github.io/2019/01/30/Gradle/","excerpt":"","text":"安装和新建工程安装: 1brew install gradle 初始化一个工程,在工程文件夹下执行: 1gradle init 选择 java-application,脚本格式为groovy,工程目录结构如下: 1234567891011121314151617181920.├── build.gradle├── gradle│ └── wrapper│ ├── gradle-wrapper.jar│ └── gradle-wrapper.properties├── gradlew├── gradlew.bat├── settings.gradle└── src ├── main │ ├── java │ │ └── demo │ │ └── App.java │ └── resources └── test ├── java │ └── demo │ └── AppTest.java └── resources build.gradle 为构建文件,settings.gradle 工程配置文件,子工程的目录就是在 settings.gradle 里面配置。 Gradle 项目一般会使用 wrapper,这样项目在其他其他设备运行的时候就无需安装 Gradle了。上面的项目,我们构建的时候应该使用 gradlew,而不是使用 gradle, 1./gradlew -v gradlew 是一个 shell 脚本文件,这个脚本会检查系统是否安装了指定版本的 Gradle,如果没有安装会自动下载安装 Gradle,在 Mac 上安装位置为: 1~/.gradle/wrapper/dists wrapper 的好处一是会自动下载 Gradle,二是构建项目使用的是 gradle-wrapper.properties文件中指定的 Gradle 版本,避免了因为使用不用版本的 Gradle 构建项目而出现的问题。 理论上 Gradle 工程只要一个 build.gradle 就足够了,如果工程没有配置 wrapper,可以使用命令添加: 1gradle wrapper --gradle-version 5.1.1 --distribution-type all 后面两个命令可以指定 wrapper 的版本和类型,--distribution-type 有两个,all 会下载全部的代码和文档,bin 是只下载最基本的文件。对已经配置好 wrapper 的工程使用 wrapper 命令可以修改 wrapper 的版本和类型。 Gradle 构建脚本Gradle 和 Maven、Ant 不用的地方是它构建文件就是代码文件,build.gradle 其实就是一个 groovy 格式的代码文件,从 5.0 开始 Gradle 支持 Kotlin 格式的构建文件 build.gradle.kts。我们可以直接在构建文件里面写代码,Groovy 允许直接使用 Java 代码,Kotlin 只能使用 Java 代码。 我们在 build.gradle 加入以下代码: 12String hello = "Hello World!"System.out.println(hello) 执行 ./gradlew -q,输出结果为: 12345678910111213Hello World!Welcome to Gradle 5.1.1.To run a build, run gradlew <task> ...To see a list of available tasks, run gradlew tasksTo see a list of command-line options, run gradlew --helpTo see more detail about a task, run gradlew help --task <task>For troubleshooting, visit https://help.gradle.org 脚步文件中所有代码都会执行,所有我们写在 build.gradle 文件中的代码无需手动调用。 子工程在 root 工程下新建一个文件,在文件夹内新建一个 build.gradle,然后需要把这个子工程包含到 root 工程中,方法是在 settings.gradle 里面添加: 1include ":sub_project" 我们可以使用 ./gradlew projects 来验证,输出结果为: 123456789------------------------------------------------------------Root project------------------------------------------------------------Root project 'java_demo'\\--- Project ':sub_project'To see a list of the tasks of a project, run gradlew <project-path>:tasksFor example, try running gradlew :sub_project:tasks settings.gradle 也是代码文件,include 对应的函数是: 1void include(String... projectPaths); groovy 的语法允许我们这样调用: 1include ":sub1", ":sub2" include 的参数 projectPaths 不是文件路径,而是工程路径,工程路径分割符为 :,默认工程路径和文件路径是对应的,例如 1:sub_project:sub_sub_project 对应文件路径: 1$rootDir/sub_project/sub_sub_project 工程目录为: 123Root project 'java_demo'\\--- Project ':sub_project' \\--- Project ':sub_project:sub_sub_project' 文件目录为: 123java_demo└── sub_project └── sub_sub_project 工程路径和文件路径不需要对应的,例如子工程文件路径 java_demo/sub_project/sub_sub_project 为,是个三级目录,我们想把它作为一个二级工程,路径为 :sub_project2,settings.gradle 文件可以修改为: 123rootProject.name = 'java_demo'include ":sub_project",":sub_project2"project(":sub_project2").projectDir = file("sub_project/sub_sub_project") 工程目录就变为了: 123Root project 'java_demo'+--- Project ':sub_project'\\--- Project ':sub_project2' 我们做的工作是首先通过 include 声明工程结构,root project 为 java_demo,有两个二级项目,然后通过 project(":sub_project2").projectDir = file("sub_project/sub_sub_project") 指定项目的路径。 Project生命周期Project 和 build.gradle 是一对一的关系,Gradle 会在初始化阶段为每个 build.gradle 文件创建一个 Project 实例,过程如下: 创建一个 Settings 实例 执行 settings.gradle 脚本,对 Settings 对象进行配置 使用配置后的 Settings 对象创建工程目录 最后执行每个 build.gradle 脚本 TaskTask 创建方式如下: 123task myTask(type: SomeType, dependsOn: SomeTask) { // 配置 Task} Task 包括一系列 Action,可以通过 doLast 想向队尾添加一个 Action,通过 doFirst 向队首添加一个 Action,dependsOn 指定依赖的 Task,执行的时候依赖的 Task 会首先执行。type 相当于 extends,例如 Android 工程中的 clean: 123task clean(type: Delete) { delete rootProject.buildDir} clean 是一个 Delete 的扩展,可以使使用 Delete 的 delete 方法。 Configuration 和 DependencyGradle 会帮助我们自动下载依赖的库,配置方法是添加 repositories 和 dependencies 123456repositories { mavenCentral()}dependencies { // ...} 在 Java 和 Android 项目中,使用 implementation 可以添加要依赖的库,但是在空白的 Gradle 中是没有 implementation 的,implementation 是通过插件引入的,它本质上是一个分组的名称。Gradle 会把依赖进行分组,通过 Configuration 来进行配置,我们添加两个依赖分组,并且添加依赖: 123456789configurations { myDependencyGroup1 myDependencyGroup2}dependencies { myDependencyGroup1 'org.apache.commons:commons-lang3:3.0' myDependencyGroup2 'org.slf4j:slf4j-log4j12:1.7.2'} 可以通过一个 Task 输出分组中的依赖: 12345678task showDeps { doLast { println 'group1' println configurations.myDependencyGroup1.asPath println 'group1' println configurations.myDependencyGroup2.asPath }} 输出结果为: 1234group1/Users/sunwei/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.0/8873bd0bb5cb9ee37f1b04578eb7e26fcdd44cb0/commons-lang3-3.0.jargroup1/Users/sunwei/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-log4j12/1.7.2/7539c264413b9b1ff9841cd00058c974b7cd1ec9/slf4j-log4j12-1.7.2.jar:/Users/sunwei/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.2/81d61b7f33ebeab314e07de0cc596f8e858d97/slf4j-api-1.7.2.jar:/Users/sunwei/.gradle/caches/modules-2/files-2.1/log4j/log4j/1.2.17/5af35056b4d257e4b64b9e8069c0746e8b08629f/log4j-1.2.17.jar 这样在构建的时候,可以读取 Configuration 配置的依赖,加入 classpath。 部分DSLallprojects {}对所有 Project 进行配置,例如对所有 Project 添加 repositories 123456allprojects { repositories { google() jcenter() }} Plugin通过 Plugin 可以对 Project 进行扩展,例如,添加一个 Task,添加一个 Configuration: 1234567891011121314class DemoPlugin implements Plugin<Project> { void apply(Project project) { project.task("hello") { doLast { println "Hello World" } } project.configurations { demoCompile } }} 这个类可以直接添加在 build.gradle 中,也可以放在 buildSrc 目录下,Gradle 会自动包含 buildSrc 目录下的文件。","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Gradle","slug":"Gradle","permalink":"http://sw926.github.io/tags/Gradle/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Shell 记录","slug":"Shell记录","date":"2019-01-16T02:15:58.000Z","updated":"2023-02-23T02:20:24.460Z","comments":true,"path":"2019/01/16/Shell记录/","link":"","permalink":"http://sw926.github.io/2019/01/16/Shell%E8%AE%B0%E5%BD%95/","excerpt":"","text":"如果使用了 Time Machine,自动备份的时候回首先在当前创建备份文件,可以使用以下命令查看文件: 1sudo tmutil listlocalsnapshots / 删除对应的文件: 1tmutil deletelocalsnapshots 2017-11-27-005359 查看当前文件夹下的文件和目录的大小: 1du -sh * 加上排序: 1du -sh * | sort 按照文件夹和文件分类后排序: 1du -sh * | sort -n 从小到大排序: 1du -sh * | sort -r 列出前10个: 1du -sh * | sort | head 列出后10个: 1du -sh * | sort | tail 收到指定信号后执行相关操作: 123EXIT = 0trap exiting SIGINT exiting() { echo "Ctrl-C trapped" ; EXIT=1; exit 0; }","categories":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}],"tags":[{"name":"sh","slug":"sh","permalink":"http://sw926.github.io/tags/sh/"}],"keywords":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}]},{"title":"Android 状态栏操作","slug":"Android-状态栏操作","date":"2019-01-14T03:59:01.000Z","updated":"2023-02-23T02:20:24.460Z","comments":true,"path":"2019/01/14/Android-状态栏操作/","link":"","permalink":"http://sw926.github.io/2019/01/14/Android-%E7%8A%B6%E6%80%81%E6%A0%8F%E6%93%8D%E4%BD%9C/","excerpt":"","text":"实际开发中 Android 状态栏遇到很多问题,为了 App 稳定性,就简单的使用了黑色状态栏,遇到的关于状态栏的问题有: 不同 Android 版本的显示问题,4.4 一下不可修改,4.4是半透明,5.0及以上可以全透明 状态栏背景修改 状态改变后界面错乱,例如一个 View 是显示在状态栏下面的,切换到另外一个 View 后状态栏颜色改变了 显示隐藏状态或者修改状态栏背景后界面会闪一下 业务需求方面: Splash 页要求状态栏隐藏或者透明,进入主页后状态栏改变 Pager 之前切换状态栏改变 新的页面状态栏改变,返回后状态栏恢复 同一个 View 内状态栏动态改变,例如滑动的时候 等等 网上很多文章都是是 Theme 修改状态,这个方法是可行的,但是仅靠 Theme 是不够的,有很多需求都是需要动态改变的状态栏的,我希望能达到的效果是尽量使用代码来实现,不调用系统的私有 Api,不使用反射,同时尽量满足产品需求。 首先是程序的闪屏页,从 App 启动到 LAUNCHER Activity 的 onCreate 这段时间,用户看到的是一个空白的页面,系统会加载 window 的背景,还没有到加载 xml 的 layout 的阶段,这时我们能做的: 控制导航栏,隐藏或者透明 控制状态栏,隐藏或者透明(半透明),高版本修改背景或者文字颜色 修改 window background 不让界面显示,效果就是点击 App 图标后没有任何反应,直到 LAUNCHER Activity 的 onCreate 为了测试,在 MainActivity 的 setContentView 之前添加 Thread.sleep(3000) ,也可以添加到 Application onCreate 里面,模拟 App 启动时间为 3 秒: 123456override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Thread.sleep(3000) setContentView(R.layout.activity_main)} App 启动效果如下: Window 区域是白屏,如果希望将白屏设置为一个图片,可以修改 theme ,首先添加 style: 123<style name="AppTheme.Splash"> <item name="android:windowBackground">@drawable/splash_background</item></style> 修改 Application 的 theme 为 AppTheme.Splash, 123456789101112131415161718192021<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.sw926.toolkitdemo"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".TransStatsActivity"></activity> </application></manifest> 这样整个 App 的默认 Window 背景都修改了,所以需要在 Activit 的 onCreate 里面将 theme 改回来: 1234567override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Thread.sleep(3000) setTheme(R.style.AppTheme) setContentView(R.layout.activity_main)} 这时启动画面就不是空白了: 接下来优化启动画面,主要做一下几点优化: 状态栏:首先透明,不能透明就隐藏 导航栏:只能透明,在 theme 中是不能隐藏导航栏的 关于导航栏和状态栏,在 theme 资源文件中: 4.4 以下版本可以隐藏状态栏 4.4 版本可以设置状态栏和导航栏为渐进的半透明 5.0 及以上版本可以修改导航栏和状态栏的背景颜色 6.0 及以上版本可以设置状态栏为 light 或者 dark,算是一种修改状态栏文本颜色的方法 在 4.4 以下版本的手机中,只能控制状态栏的显示和隐藏: 123<style name="AppTheme.TransStats"> <item name="android:windowFullscreen">true</item></style> android:windowFullscreen 为 true 的时候隐藏状态栏 在 4.1 模拟器上的效果为: 在 4.4 版本的手机上,可以状态栏和导航栏半透明渐变,需要在 res 文件夹下新建一个 values-v19 文件夹,添加以下的 style: 12345<style name="AppTheme.TransStats"> <item name="android:windowFullscreen">false</item> <item name="android:windowTranslucentStatus">true</item> <item name="android:windowTranslucentNavigation">true</item></style> 这个配置相当于覆盖 4.4 以下的 AppTheme.TransStats,在 4.4 上的效果为: 在 5.0 及以上的手机上,我们需要新建 res/values-v21,作用同样是覆盖低版本的 AppTheme.TransStats, 123456789101112131415161718<style name="AppTheme.TransStats"> <item name="android:windowFullscreen">false</item> <!--如果 windowDrawsSystemBarBackgrounds 为 false,状态栏和导航栏背景都是黑色的--> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:windowTranslucentStatus">false</item> <item name="android:statusBarColor">@android:color/transparent</item> <item name="android:windowTranslucentNavigation">false</item> <item name="android:navigationBarColor">@android:color/transparent</item> <!--或者使用系统半透明效果--> <!--<item name="android:windowTranslucentStatus">true</item>--> <!--<item name="android:windowTranslucentNavigation">true</item>--></style> 在 5.0 模拟器上自定义颜色的效果,全透明: 使用系统的半透明的效果: 在 6.0 及以上的版本可以使用亮色主题的状态栏,这时状态栏文本颜色为灰色。新建文件夹 res/values-v23,添加 style: 1234567891011121314151617181920<style name="AppTheme.TransStats"> <item name="android:windowFullscreen">false</item> <!--如果 windowDrawsSystemBarBackgrounds 为 false,状态栏和导航栏背景都是黑色的--> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:windowTranslucentStatus">false</item> <item name="android:statusBarColor">@android:color/transparent</item> <item name="android:windowTranslucentNavigation">false</item> <item name="android:navigationBarColor">@android:color/transparent</item> <!--或者使用系统半透明效果--> <!--<item name="android:windowTranslucentStatus">true</item>--> <!--<item name="android:windowTranslucentNavigation">true</item>--> <item name="android:windowLightStatusBar">true</item></style>","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Android Studio编辑器一直显示错误的问题","slug":"Android-Studio编辑器一直显示错误的问题","date":"2019-01-14T03:08:07.000Z","updated":"2023-02-23T02:20:24.460Z","comments":true,"path":"2019/01/14/Android-Studio编辑器一直显示错误的问题/","link":"","permalink":"http://sw926.github.io/2019/01/14/Android-Studio%E7%BC%96%E8%BE%91%E5%99%A8%E4%B8%80%E7%9B%B4%E6%98%BE%E7%A4%BA%E9%94%99%E8%AF%AF%E7%9A%84%E9%97%AE%E9%A2%98/","excerpt":"","text":"周一上班打开电脑,打开 Android Studio,显示有几个文件出现错误,又是同样的问题,直接 Build,没有任何错误。 这在 Android Studio 上经常遇到,应该是编辑器的 Index 没有刷新,解决方法很简单: 1File -> Invalidate Caches/Restart 有几个选项: Just Restart Invalidate Invalidate and Restart 最后一个选项肯定是能解决问题的,但是会重启,重新建立索引,重新建立缓存,项目比较大的话,10分钟没有了,电脑风扇还会呼呼的转。为了不重启就能解决这个问题,先选择 Invalidate 试试,点击之后没有反应,直接运行,错误还变多了,现在编辑器显示的错误是找不到一下的引用: 123import android.support.v4.app.Fragmentimport androidx.navigation.findNavControllerimport kotlinx.android.synthetic.main.fragment_home.* 项目代码是没有问题的,可以运行,那么就是 Android Studio 的问题了,应该和 Gradle 有关, 12import android.support.v4.app.Fragmentimport androidx.navigation.findNavControlle 是在 Gradle 文件中配置的引用。 1import kotlinx.android.synthetic.main.fragment_home.* 是 Gradle 插件生成的代码。 那么,执行一下 Sync Project with Gradle Files,问题解决了,下次就不用重启 Android Studio 了,先执行: 1File -> Invalidate Caches/Restart -> Invalidate 然后 1Sync Project with Gradle Files","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Android Studio","slug":"Android-Studio","permalink":"http://sw926.github.io/tags/Android-Studio/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"神器 Karabiner Elements 配置","slug":"Karabiner-Elements","date":"2019-01-09T06:54:55.000Z","updated":"2023-02-23T02:20:24.460Z","comments":true,"path":"2019/01/09/Karabiner-Elements/","link":"","permalink":"http://sw926.github.io/2019/01/09/Karabiner-Elements/","excerpt":"","text":"Karabiner Elements,应该是 Mac 上必备的神器了,特别是使用 HHKB 的话。 简单配置 Simple ModificationsHHKB 的键位不用改,但是 Mac 原生的键盘需要改键位,打开 Karabiner-Elements Preferences,切换到 Simple Modifications,Target Device 选择 Apple Internal Keybord / Trackpad(No manufacturer name)。 键盘名字可能不一样,只要确定是要修改的 Mac 键盘就行,修改方式很简单,点击左下角 Add item,选择 From key 和 To key,From key 为要修改的键,To key 为要修改成什么键。 Mac 原生键盘和 HHKB 统一的话使用以下配置,也就是左边 control 和 caps lock 交换位置,delete 和 \\ 交换位置。 ![image](/images/屏幕快照 2019-01-09 15.02.22.png) 使用现成的规则1Complex Modifications -> Add rule -> Import more rules from the Internet(open a web browser) 搜索 Emac,添加 Emacs key bindings,添加完之后对应的规则为: Key Bindings (C-x key strokes) 123C-x C-c Quit application (post command-q)C-x C-f Open file (post command-o)C-x C-s Save file (post command-s) Key Bindings (control+keys) 123456789control+d forward deletecontrol+h deletecontrol+i tabcontrol+[ escapecontrol+m returncontrol+bfnp arrow keyscontrol+v page downcontrol+a (Microsoft Office) homecontrol+e (Microsoft Office) end Key Bindings (option+keys) 123option+v page upoption+bf option+arrow keysoption+d option+forward delete Bash Style Key Bindings 12control+w Delete the word behind point (option+delete)control+u Delete backward from point to the beginning of the line. 自定义添加完规则后有一些不完善的地方,例如,我喜欢用 Control + j/l 作为跳单词的快捷键,也就是代替 opt 加左右方向键,另外,设置完快捷键后 hhkb 在 vim 上方向组合键部分无效。 修改规则的时候只看比较重要的地方,我们要修改地方都是在 rules 下,rules 是一个数组,每条规则是一个 manipulators,manipulators 主要结构如下: 1https://pqrs.org/osx/karabiner/json.html#complex_modifications-manipulator-definition Move Caret to Previous WordMove Caret to Next Word","categories":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}],"tags":[{"name":"Karabiner Elements","slug":"Karabiner-Elements","permalink":"http://sw926.github.io/tags/Karabiner-Elements/"}],"keywords":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}]},{"title":"Python 环境管理","slug":"virtualenv","date":"2019-01-07T09:54:05.000Z","updated":"2023-02-23T02:20:24.460Z","comments":true,"path":"2019/01/07/virtualenv/","link":"","permalink":"http://sw926.github.io/2019/01/07/virtualenv/","excerpt":"","text":"virtualenv安装: 1pip install virtualenv Mac 上要添加 sudo 每个 virtualenv 使用文件夹进行区分,新建一个 evn: 1virtualenv my_project 进入 my_project 文件夹,这时 env 还没有激活,激活 evn: 1source bin/activate 这时命令会变成: 1(my_project) ➜ my_project my_project 使用的是默认的 python 版本,如果要指定 env 的版本 1virtualenv -p /usr/local/bin/python3 my_project2 激活后 python 的版本就会改变: 12(my_project2) ➜ my_project2 python --versionPython 3.7.1 创建 env 是需要注意的一个命令是 –system-site-packages,加上这个命令会使用系统已经按照的第三方包,不加就算一个纯净的 env 版本。如果要关闭 env,输入 deactivate 就可以了。 如果要删除 env,直接删除对应的文件夹就行。 virtualenvwrapper一个文件夹一个 env,看上去非常方便,但是时间一长,谁知道自己创建了几个 env 的文件夹,管理起来不方便,所有我们需要有一个工具帮助我们使用 virtualenv。virtualenvwrapper 是 virtualenv,可以帮助我们管理 env 环境。 安装: 1pip3 install virtualenvwrapper 添加环境变量: 1234if [ -f /usr/local/bin/virtualenvwrapper.sh ]; then export WORKON_HOME=$HOME/.virtualenvs source /usr/local/bin/virtualenvwrapper.shfi 以后我们所有的 env 都会安装到 ~/.virtualenvs 文件夹下。如果打开名命令行的时候显示: 1234567/usr/bin/python: No module named virtualenvwrappervirtualenvwrapper.sh: There was a problem running the initialization hooks.If Python could not import the module virtualenvwrapper.hook_loader,check that virtualenvwrapper has been installed forVIRTUALENVWRAPPER_PYTHON=/usr/bin/python and that PATH isset properly. 那么把 VIRTUALENVWRAPPER_PYTHON 设置为 Python3。 创建一个 virtualenv: 1mkvirtualenv venv 创建的时候 virtualenv 参数同样适用: 1mkvirtualenv -p /usr/local/bin/python3 py3 其他命令: 12345678910111213lsvirtualenv -b # 列出虚拟环境workon [虚拟环境名称] # 切换虚拟环境lssitepackages # 查看环境里安装了哪些包cdvirtualenv [子目录名] # 进入当前环境的目录cpvirtualenv [source] [dest] # 复制虚拟环境deactivate # 退出虚拟环境rmvirtualenv [虚拟环境名称] # 删除虚拟环境 pyenv使用 virtualenv 和 virtualenvwrapper 可以方便的管理 python 环境,但是现在我们系统中只有两个版本 python2 和 python3,如果一个项目要求我们的 python 版本是 3.6,但是系统的 python 版本是 3.7,我们该怎么做呢?我们可以下载 python3.6,安装到一个文件夹,然后使用 -p 新建一个 virtualenv 环境。但是这样的话首先下载安装就很麻烦,需要打开浏览器,下载,解压,安装,其次鬼知道我们安装了多少个 python 版本,时间一长,很难管理,所以我们需要一个工具 pyenv。 安装: 1brew install pyenv 添加环境变量: 1eval "$(pyenv init -)" 查看 python 版本列表: 1pyenv install --list 安装对应的版本: 1pyenv install 3.6.7 结果出错了: 12345678910111213141516BUILD FAILED (OS X 10.14.2 using python-build 20180424)Inspect or clean up the working tree at /var/folders/1d/_h3bhfsd33z3yf4x566ncqkw0000gn/T/python-build.20190107184844.88127Results logged to /var/folders/1d/_h3bhfsd33z3yf4x566ncqkw0000gn/T/python-build.20190107184844.88127.logLast 10 log lines: File "/private/var/folders/1d/_h3bhfsd33z3yf4x566ncqkw0000gn/T/python-build.20190107184844.88127/Python-3.6.7/Lib/ensurepip/__main__.py", line 5, in <module> sys.exit(ensurepip._main()) File "/private/var/folders/1d/_h3bhfsd33z3yf4x566ncqkw0000gn/T/python-build.20190107184844.88127/Python-3.6.7/Lib/ensurepip/__init__.py", line 204, in _main default_pip=args.default_pip, File "/private/var/folders/1d/_h3bhfsd33z3yf4x566ncqkw0000gn/T/python-build.20190107184844.88127/Python-3.6.7/Lib/ensurepip/__init__.py", line 117, in _bootstrap return _run_pip(args + [p[0] for p in _PROJECTS], additional_paths) File "/private/var/folders/1d/_h3bhfsd33z3yf4x566ncqkw0000gn/T/python-build.20190107184844.88127/Python-3.6.7/Lib/ensurepip/__init__.py", line 27, in _run_pip import pip._internalzipimport.ZipImportError: can't decompress data; zlib not availablemake: *** [install] Error 1 Google 的解决方法,首先: 1xcode-select --install 如果不行的话: 12CFLAGS="-I$(brew --prefix openssl)/include -I$(xcrun --show-sdk-path)/usr/include" \\LDFLAGS="-L$(brew --prefix openssl)/lib" 最终还是不行,尝试用 brew 安装 zlib 1brew install zlib 然后添加环境变量: 12LDFLAGS="-L/usr/local/opt/zlib/lib"CPPFLAGS="-I/usr/local/opt/zlib/include" 但是仍然没有用,最后: 1sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target / 成功! 12345678➜ ~ pyenv install 3.6.7python-build: use openssl from homebrewpython-build: use readline from homebrewDownloading Python-3.6.7.tar.xz...-> https://www.python.org/ftp/python/3.6.7/Python-3.6.7.tar.xzInstalling Python-3.6.7...python-build: use readline from homebrewInstalled Python-3.6.7 to /Users/sunwei/.pyenv/versions/3.6.7 然后查看 python 版本: 123➜ ~ pyenv versions* system (set by /Users/sunwei/.pyenv/version) 3.6.7 当前使用的是系统中的 python,我们切换成刚才安装的 3.6.7: 123➜ ~ pyenv global 3.6.7➜ ~ python --versionPython 3.6.7 设置全局版本: 1pyenv global 3.6.7 只对当前目录有效: 1pyenv local 3.6.7 对当前 shell 有效: 1pyenv shell 3.6.7 和 virtualenv 结合使用,需要安装插件: 1brew install pyenv-virtualenv 新建一个指定 python 版本的 virtualenv: 12# pyenv virtualenv <版本号> <文件夹>pyenv virtualenv 3.6.7 my_env 新建的 virtualenv 需要指定目录,还是不好管理,那么终极解决方案,使用 pyenv-virtualenvwrapper: 1brew install pyenv-virtualenvwrapper 安装完后需要激活一下: 1pyenv virtualenvwrapper 然后是使用,我们想新建一个 python3.6.7 版本的 virtualenvwrapper, 123pyenv shell 3.6.7pip install virtualenvwrapper # 第一次使用新的 Python 环境需要安装此包,否则创建的虚拟环境 Python 版本仍为系统默认mkvirtualenv python3 检查一下结果: 123➜ ~ workon py3.6.7(py3.6.7) ➜ ~ python --versionPython 3.6.7 大公告成! 参考: http://codingpy.com/article/virtualenv-must-have-tool-for-python-development/https://blog.csdn.net/jeikerxiao/article/details/53635267https://lisupy.github.io/2018/10/01/2018-10-01-Mojave%E4%BD%BF%E7%94%A8pyenv%E5%AE%89%E8%A3%85python/#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95https://my.oschina.net/OHC1U9jZt/blog/2243919https://zhuanlan.zhihu.com/p/30859003","categories":[{"name":"Python","slug":"Python","permalink":"http://sw926.github.io/categories/Python/"}],"tags":[{"name":"Python","slug":"Python","permalink":"http://sw926.github.io/tags/Python/"}],"keywords":[{"name":"Python","slug":"Python","permalink":"http://sw926.github.io/categories/Python/"}]},{"title":"Android全屏切换","slug":"Android全屏切换","date":"2019-01-07T06:14:33.000Z","updated":"2023-02-23T02:20:24.459Z","comments":true,"path":"2019/01/07/Android全屏切换/","link":"","permalink":"http://sw926.github.io/2019/01/07/Android%E5%85%A8%E5%B1%8F%E5%88%87%E6%8D%A2/","excerpt":"","text":"Android Framework 中很多地方使用了标志位,View 的 SystemUiVisibility 就是一种,虽然只是一个 Int 类型,但是转换成二进制,每一位都可以做一个开关。 添加一个标志: 1uiOptions = uiOptions or View.SYSTEM_UI_FLAG_LOW_PROFILE 移除一个标志: 1uiOptions = uiOptions and View.SYSTEM_UI_FLAG_LOW_PROFILE.inv() 视频播放进入全屏,要隐藏状态栏,Actionbar,导航栏。需要注意的是全屏和横竖屏切换是两个独立的东西。 其实在 Android 上没有一个全屏的 Api,做全屏的功能需要一列的操作进行组合,而且要考虑版本的兼容。 首先,有 Actionbar 的话要隐藏: 12345if (activity is AppCompatActivity) { activity.supportActionBar?.hide()} else { activity.actionBar?.hide()} 然后我们开始设置 systemUiVisibility 123456789101112var uiOptions = activity.window.decorView.systemUiVisibilityuiOptions = uiOptions or View.SYSTEM_UI_FLAG_LOW_PROFILEuiOptions = uiOptions or View.SYSTEM_UI_FLAG_FULLSCREENuiOptions = uiOptions or View.SYSTEM_UI_FLAG_HIDE_NAVIGATIONif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { uiOptions = uiOptions or View.SYSTEM_UI_FLAG_IMMERSIVE uiOptions = uiOptions or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY}activity.window.decorView.systemUiVisibility = uiOptions 为了兼容低版本,还要设置 window 的 flag 1234val attrs = activity.window.attributesattrs.flags = attrs.flags or WindowManager.LayoutParams.FLAG_FULLSCREENattrs.flags = attrs.flags or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ONactivity.window.attributes = attrs 要退出全屏,把进入全屏时的设置全部清除就可以了,最终的代码为: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253fun enterFullScreen(activity: Activity) { if (activity is AppCompatActivity) { activity.supportActionBar?.hide() } else { activity.actionBar?.hide() } val attrs = activity.window.attributes attrs.flags = attrs.flags or WindowManager.LayoutParams.FLAG_FULLSCREEN attrs.flags = attrs.flags or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON activity.window.attributes = attrs var uiOptions = activity.window.decorView.systemUiVisibility uiOptions = uiOptions or View.SYSTEM_UI_FLAG_LOW_PROFILE uiOptions = uiOptions or View.SYSTEM_UI_FLAG_FULLSCREEN uiOptions = uiOptions or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { uiOptions = uiOptions or View.SYSTEM_UI_FLAG_IMMERSIVE uiOptions = uiOptions or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY } activity.window.decorView.systemUiVisibility = uiOptions}fun existFullScreen(activity: Activity) { if (activity is AppCompatActivity) { activity.supportActionBar?.show() } else { activity.actionBar?.show() } val attrs = activity.window.attributes attrs.flags = attrs.flags and WindowManager.LayoutParams.FLAG_FULLSCREEN.inv() attrs.flags = attrs.flags and WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON.inv() activity.window.attributes = attrs var uiOptions = activity.window.decorView.systemUiVisibility uiOptions = uiOptions and View.SYSTEM_UI_FLAG_LOW_PROFILE.inv() uiOptions = uiOptions and View.SYSTEM_UI_FLAG_FULLSCREEN.inv() uiOptions = uiOptions and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION.inv() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { uiOptions = uiOptions and View.SYSTEM_UI_FLAG_IMMERSIVE.inv() uiOptions = uiOptions and View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY.inv() } activity.window.decorView.systemUiVisibility = uiOptions}","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"常用的Android Dependencies","slug":"常用的Android-Dependencies","date":"2019-01-04T03:16:00.000Z","updated":"2023-02-23T02:20:24.459Z","comments":true,"path":"2019/01/04/常用的Android-Dependencies/","link":"","permalink":"http://sw926.github.io/2019/01/04/%E5%B8%B8%E7%94%A8%E7%9A%84Android-Dependencies/","excerpt":"","text":"2019.01.04 启用 DataBinding:在项目根目录下的 gradle.properties 下添加: 1android.databinding.enableV2=true 在 app 目录下的 build.gradle 下添加: 1234567891011android { // ... dataBinding { enabled = true } // ...} lifecycle123implementation 'android.arch.lifecycle:common-java8:1.1.1'implementation 'android.arch.lifecycle:extensions:1.1.1'implementation 'android.arch.lifecycle:runtime:1.1.1' 上面的依赖使用的是 Java8,不用添加 apt 或者 kapt 或者 annotationProcessor,但是需要设置 compileOptions: 123456789101112android { // ... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // ...} Navigation根目录下: 1234567buildscript { // ... dependencies { // ... classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha09" }} app 目录下: 1apply plugin: "androidx.navigation.safeargs" 添加依赖: 12345implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha09" // use -ktx for Kotlinimplementation "android.arch.navigation:navigation-ui-ktx:1.0.0-alpha09" // use -ktx for Kotlin// optional - Test helpersandroidTestImplementation "android.arch.navigation:navigation-testing-ktx:1.0.0-alpha06" // use -ktx for Kotlin 在 Android Studio 3.2 中,还要开启 Experimental 设置: 1Preferences -> Experimental 剩下的就是在 res 目录下新建 navigation 文件夹,添加一个 navigation.xml 文件","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Flutter笔记20181227","slug":"Flutter笔记20181227","date":"2018-12-27T10:01:56.000Z","updated":"2023-02-23T02:20:24.459Z","comments":true,"path":"2018/12/27/Flutter笔记20181227/","link":"","permalink":"http://sw926.github.io/2018/12/27/Flutter%E7%AC%94%E8%AE%B020181227/","excerpt":"","text":"Flutter 的 Widget 有两种,StatelessWidget 和 StatefulWidget,前者是 immutable 的,一旦创建不能改变,后者是是可以改变的。Flutter 使用的是响应式布局,简单说就是 UI 随着 state 而改变,我们要做的只是修改 state,没有 setText()、setColor() 这些操作了。 引入图资源在 pubspec.yaml 中添加 123flutter: assets: - images/entry_bg.png 文件目录名字可以自定义,不同分辨率的区分使用 1.0x、2.0x这样,例如 1images\\2.0x\\entry_bg.png","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Flutter","slug":"Flutter","permalink":"http://sw926.github.io/tags/Flutter/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Java Time","slug":"Java-Time","date":"2018-12-27T03:10:41.000Z","updated":"2023-02-23T02:20:24.459Z","comments":true,"path":"2018/12/27/Java-Time/","link":"","permalink":"http://sw926.github.io/2018/12/27/Java-Time/","excerpt":"","text":"时间和日期的处理是一个非常复杂的事情,不仅仅有年月日,还有时区,星期,各种时间格式的转换,最复杂的,各种日历之间的转换。现在的公历,也叫格里高利历,其实算是最简单的日历了,因为基本上能通过数学计算来预测,至于伊斯兰历、中国农历,则要结合天文观测,要和公历相互转换是非常复杂的。 Java 处理时间和日期一般是通过 Date 和 Calendar,这两个 Api 也是非常难用的 Api,相信用过的人都有体会。Java 上当然不可能有没有一个好用的 Time Api,所以有了 JSR310。在 Java8 中可以使用 java.time,如果项目不允许使用 Java8 及以上的版本,那么还有替代方案,Joda-Time 和 Threetenabp,Threetenabp 和 java.time 的 Api 兼容,Joda-Time 和 java.time 的 Api 不同,但是很相似。 使用 java.time,主要会用到下面的类: LocalTime 时间 LocalDate 日期 LocalDateTime 时间和日期 ZonedDateTime 时间日期加时区 Year 年 YearMonth 年月 MonthDay 一月的一天 Instant 距离 1970-01-01 的时间 另外还有时区 Zone,和偏移 Offset 123456789101112131415161718192021fun main(args: Array<String>) { val nowTime = LocalTime.now() val nowDate = LocalDate.now() val nowDateTime = LocalDateTime.now() val nowZonedDateTime = ZonedDateTime.now() val year = Year.now() val yearMonth = YearMonth.now() val monthDay = MonthDay.now() val instant = Instant.now() println(nowTime) println(nowDate) println(nowDateTime) println(nowZonedDateTime) println(year) println(yearMonth) println(monthDay) println(instant)} 输出结果为: 1234567811:29:18.2204982018-12-272018-12-27T11:29:18.2208862018-12-27T11:29:18.223397+08:00[Asia/Shanghai]20182018-12--12-272018-12-27T03:29:18.235191Z 以上的类都有一个特点,就是 immutable,一旦创建就不可改变。immutable 有什么好处呢,那就是线程安全,如果持有上面的任何一个类的引用,在使用的使用不用担心这个引用的内容会被改变。immutable 是怎么实现的呢?上面的类的所有公开的方法都不能改变对象的内容。 如果我们想修改一个日期或月份怎么办呢?答案是无法修改,只能重新生成一个对象,例如: 12val date1 = LocalDate.now()val date2 = date1.withMonth(1) withMonth 不会改变 date1,它会返回一个新的对象,所以 java.time 和时间相关的对象是没有 setter 方法的。 对于程序员来说,时间最简单的表述方式就是一个 Long 类型的变量,值是距离零时区 1970-01-01 零点的时间,但是这个时间不是人类可读的,使用 Calendar 的时候,一个 Calendar 对象包含的所有日期的信息,年月日时区都有,而且有 setter 方法,但是 java.time 不是这样的, LocalDate 只有日期的信息 LocalTime 只有时间的信息 LocalDateTime 虽然有日期和时间,但是没有时区 我们平常使用的比较多的是将一个 Long 类型的时间戳,或者一个时间字符串转换为时间对象,时间戳和字符串都是包含完整时间信息的,我们需要注意的就是不要在转换的过程中丢失时区, 例如时间时间戳: 11545888556889 转换为 Instant 为: 12val instant = Instant.ofEpochMilli(1545888556889)print(instant) 输出结果为: 12018-12-27T05:29:16.889Z 是零时区的,这是我们将这个 Instant 转换为 Local 开头的时间: 123val data = LocalDate.ofInstant(instant, ZoneId.systemDefault())val time = LocalTime.ofInstant(instant, ZoneId.systemDefault())val dataTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()) 可以看到必须要加上时区,这时候还是很安全的,但是从字符串转换的话就很容易出错了,例如字符串: 12018-12-27T05:29:16.889Z 我们使用 parse 方法: 123456789101112val str = "2018-12-27T05:29:16.889Z"val dateTime = ZonedDateTime.parse(str)println(dateTime)val localDate = dateTime.toLocalDate()val localTime = dateTime.toLocalTime()val localDateTime = dateTime.toLocalDateTime()println(ZoneId.systemDefault())println(localDate)println(localTime)println(localDateTime) 输出结果为: 123452018-12-27T05:29:16.889ZAsia/Shanghai2018-12-2705:29:16.8892018-12-27T05:29:16.889 2018-12-27T05:29:16.889Z 是零时区的时间,当前我们的时区是东八区,但是 LocalTime、LocalDate 和 LocalDateTime 都是按零时区显示的,用户在使用的时候,看到的就是错误的时间。 要解决这个问题,需要首先将 ZonedDateTime 的时区转换为东八区,方法为 123val str = "2018-12-27T05:29:16.889Z"val dateTime = ZonedDateTime.parse(str).withZoneSameInstant(ZoneId.systemDefault())println(dateTime) 输出结果为: 12018-12-27T13:29:16.889+08:00[Asia/Shanghai]","categories":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/tags/Java/"}],"keywords":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}]},{"title":"Android Room","slug":"Android-room","date":"2018-06-13T09:16:23.000Z","updated":"2023-02-23T02:20:24.459Z","comments":true,"path":"2018/06/13/Android-room/","link":"","permalink":"http://sw926.github.io/2018/06/13/Android-room/","excerpt":"","text":"说明Android Room 作为 Android Architecture 的 orm 部分,接入是非常简单的。 首先明确 Room 能做什么,简单概括,能让你把一行 SQL 语句变成对象,是现在最好用的 ORM 框架,当然这是废话,官方能拿的出来,肯定要比第三方的要好。首先体验一下: 简单的插入数据,不用写 SQL: 12345@Insert(onConflict = OnConflictStrategy.REPLACE)fun insertAll(vararg record: Record)@Insert(onConflict = OnConflictStrategy.REPLACE)fun insert(record: Record) 查询数据,一行 SQL,一个函数声明搞定 12@Query("SELECT * FROM Record")fun getAll(): List<Record> 而且 SQL 是有代码提示和语法检查的。 下面,我们在现有项目上使用 Room,使用新的orm框架,而不用对数据库结构做任何修改。 添加 Room1234def room_version = "1.1.0" // or, for latest rc, use "1.1.1-rc1"implementation "android.arch.persistence.room:runtime:$room_version"kapt "android.arch.persistence.room:compiler:$room_version" Entity现在的项目数据量有个 Record table,创建的 SQL 语句为 123456CREATE TABLE Record ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, date INTEGER UNIQUE NOT NULL, records TEXT, need_sync INTEGER DEFAULT 0); 主键为 _id,data 为整型,存储的是时间,records 为字符串,是一个 json 对象,need_sync 是一个 Boolean,以整型存储在数据库中。 我想做的: _id 在数据类中要名字是 id date 为 UNIQUE date 直接读取为 LocalDate 对象 records 直接读取为 Bean 对象 need_sync 直接读取为 Boolean,数据类中的名字当然也要改 最终的 Entity 类声明为: 123456789101112@Entity(tableName = "Record", indices = [(Index(value = arrayOf("date"), unique = true))])class Record { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") var id: Long = 0 var date: LocalDate? = null var records: Bean? = null @ColumnInfo(name = "need_sync") var needSync = false} 除了类声明,我们不需要 SQL 语句去创建 Table DaoRecordDao,用来访问数据对象: 123456789101112131415161718192021222324@Daointerface RecordDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertAll(vararg record: Record) @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(record: Record) @Delete fun delete(record: Record) @Delete fun deleteAll(vararg record: Record) @Update fun update(record: Record) @Query("SELECT * FROM Record") fun getAll(): List<Record> @Query("SELECT * FROM Record WHERE date = :date") fun getByDate(date: LocalDate): List<Record>} 直接的插入、删除、更新是不用写 SQL,自定义的查询还是需要写 SQL,可以直接在 SQL 语句中绑定函数参数,只要在参数名字前加 “:” 就可以了,像 getByDate,:date 就是标识函数的 date 参数就是 SQL 中的参数: 12@Query("SELECT * FROM Record WHERE date = :date")fun getByDate(date: LocalDate): List<Record> @Query 里面的 SQL 语句不仅有代码提示,而且有语法检查,写错 table name 和 函数参数名都会报错的,手残党的福音啊。 DatabaseEntity 和 Dao 都声明好了,现在要创建数据库了,还记得之前怎么做的吗: 继承 SQLiteOpenHelper onCreate 执行 SQL 语句创建数据库 onUpgrade 进行 Migration execSQL 获取 Cursor 把 Cursor 转换为对象 如果使用 Room 呢? 没有 SQLiteOpenHelper 了,也没有 onCreate 了: 12345@Database(entities = [(Record::class)], version = 10)abstract class AppDataBase : RoomDatabase() { abstract fun recordDao(): RecordDao} 数据库创建的声明完成了,使用了 Room,要接受这几点: 不需要 SQLiteOpenHelper 不需要 Cursor 可能连 SQLiteDatabase 也不需要 TypeConverters上面是创建数据库的全部代码了吗?当然不是,我们还需要 TypeConverters 和 Migration, date 在数据里面以 Long 的形式存储,读取的时候需要转换为 LocalDate 对象,存储的时候需要把 LocalDate 转换为 Long,对于一种转换关系,我们只需要声明一个静态函数就可以了,名字随意,参数和返回值的类型为对应的转换关系。 123456789101112131415class DbTypeConverters { companion object { @TypeConverter @JvmStatic fun toLocalDate(value: Long): LocalDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneId.systemDefault()).toLocalDate() @TypeConverter @JvmStatic fun toLocalDate(value: LocalDate): Long = value.atStartOfDay(ZoneId.of("UTC")).toInstant().toEpochMilli() }} 然后把 TypeConverters 添加到 Database 123456@Database(entities = [(Record::class)], version = 10)@TypeConverters(DbTypeConverters::class)abstract class AppDataBase : RoomDatabase() { abstract fun recordDao(): RecordDao} Migrations数据库升级,我们需要声明的是一个 Migration, 1234567891011121314151617181920object PeriodDbMigration { @JvmField val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { // update } } @JvmField val MIGRATION_3_4 = object : Migration(3, 4) { override fun migrate(db: SupportSQLiteDatabase) { // update } }} 和 SQLiteOpenHelper 的 onUpgrade 一样,只不过每次升级拆分为一次 Migration 操作。 然后就是创建数据库: 通过 addMigrations 添加数据库升级的操作, 1234val database = Room.databaseBuilder(application, PeriodDataBase.class, "database.db") .addMigrations(PeriodDbMigration.MIGRATION_1_2, PeriodDbMigration.MIGRATION_3_4) .allowMainThreadQueries() .build(); allowMainThreadQueries() 是允许在主线程上进行操作,对于之前在主线程上读写数据的同学们,先加上 allowMainThreadQueries(),然后慢慢优化吧。 使用一般会把数据作为单例使用,然后调用 Dao 中函数就可以了: 1val all = database.recordDao().getAll()","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Room","slug":"Room","permalink":"http://sw926.github.io/tags/Room/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Android Keyboard开发","slug":"Android-Keyboard开发","date":"2018-05-16T08:17:43.000Z","updated":"2023-02-23T02:20:24.459Z","comments":true,"path":"2018/05/16/Android-Keyboard开发/","link":"","permalink":"http://sw926.github.io/2018/05/16/Android-Keyboard%E5%BC%80%E5%8F%91/","excerpt":"","text":"要开始做 Android 键盘的开发,最好首先参考一下 Api Demo 里面的 SoftKeyboard 例子。这个例子比较老了,导入到 Android Studio 之后会发现无法运行,这是因为没有 Launcher Activity,在 Run/Debug Configurations 里面把 Launch Options 设置为 Nothing 后就能运行了,当然运行后在桌面是看不懂图标的,需要在设置里面添加输入法,之后再输入文本的时候就能调出刚才安装的输入法了。设置界面是在 AndroidManifest.xml 里面声明的: 12345678<activity android:name=".ImePreferences" android:exported="false" android:label="@string/settings_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> </intent-filter></activity> 跳转到 ImePreferences.java,发现它是一个 Activity,继承自 PreferenceActivity,不过有个一个警告,意思是 PreferenceActivity 的子类不能 export,那么修改一一下 AndroidManifest.xml 12345678<activity android:name=".ImePreferences" android:exported="false" android:label="@string/settings_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> </intent-filter></activity> 警告消失了,这是个输入法的设置界面,总是到系统设置里面找不太方便,我们效仿其他输入法,在程序列表里面添加一个主界面,既然 ImePreferences 是 Activity 那么我们之间 start 这个 Activity 就可以了: 12val intent = Intent(this, ImePreferences::class.java)startActivity(intent)","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Dagger2","slug":"Dagger2","date":"2018-02-24T08:40:57.000Z","updated":"2023-02-23T02:20:24.458Z","comments":true,"path":"2018/02/24/Dagger2/","link":"","permalink":"http://sw926.github.io/2018/02/24/Dagger2/","excerpt":"","text":"Dagger是一个注入工具,何为注入,我们要生产一批机器人,每个机器人都有一个控制器,我们可以在机器人内部 new 出一个控制器: 123class Robot { val controller = Controller()} 上面的代码 Robot 和 Controller 耦合,修改一下上面的代码,从外部传入控制器,这就叫注入: 1class Robot(val controller: Controller) 这样做的好处就是修改了控制器,但是不用修改机器人的代码,一般情况下,我们需要把控制器声明为接口,这样一个机器人就可以兼容不同的控制器 123456789101112131415161718192021222324252627282930313233interface Controller { fun move() fun stop()}class BasicController : Controller { override fun move() { // ... } override fun stop() { // ... }}class AdvancedController : Controller { override fun move() { // ... } override fun stop() { // .. }}class Robot(val controller: Controller)fun createRobot(controller: Controller) = Robot(controller)fun test() { val basicRobot = createRobot(BasicController()) val advancedRobot = createRobot(AdvancedController())} 上面的代码就是精简版的Dagger,当然结构天差地别,但是思路差不多,下面开始讲Dagger。Dagger是个注入框架,帮助我们来实现注入的功能,拿上面的例子来说,我们写好了 Robot 和 各种 Controller 的代码,Dagger 帮我们将他们联系起来,也就是实现函数 createRobot 的功能。Dagger2 的功能是通过编译器生成中间代码来实现的,编译器可以为我们生成代码,但是要生成什么代码是需要我们指定的,拿上面的例子来说,我们需要为 Robot 注入一个 Controller,我们需要指定: Controller 的构造方法 需要注入的成员变量 在什么地方注入 未使用 Dagger 之前,代码是这样的: 1234567class Controllerclass Robot(val controller: Controller)// ...val controller = Controller()val robot = Robot(controller) 现在我们来改写代码,首先要指定 Controller 的构造方法,在 Controller 的构造函数添加 @Inject 注解: 1class Controller @Inject constructor() 指定需要注入的变量 123class Robot { @Inject lateinit var controller: Controller} 现在编译一下,等待 Dagger 生成中间代码,Dagger为我们生成以下的代码: Controller_Factory.java 123456789101112public final class Controller_Factory implements Factory<Controller> { private static final Controller_Factory INSTANCE = new Controller_Factory(); @Override public Controller get() { return new Controller(); } public static Factory<Controller> create() { return INSTANCE; }} Robot_MembersInjector.java 1234567891011121314151617181920public final class Robot_MembersInjector implements MembersInjector<Robot> { private final Provider<Controller> controllerProvider; public Robot_MembersInjector(Provider<Controller> controllerProvider) { assert controllerProvider != null; this.controllerProvider = controllerProvider; } public static MembersInjector<Robot> create(Provider<Controller> controllerProvider) { return new Robot_MembersInjector(controllerProvider); } @Override public void injectMembers(Robot instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } instance.controller = controllerProvider.get(); }} 非常好,Dagger 为我们生成了一个 Controller 的构造类 Controller_Factory,我们可以通过 1Controller_Factory.create() 获取一个单例的 Controller 对象,或者通过: 123Controller_Factory().get()// orController_Factory.create().get() 创建一个新的 Controller 对象,如果要注入到 Robot,需要使用 Robot_MembersInjector 的 injectMembers 的函数,改造后的最终代码是 1234567891011class Controller @Inject constructor()class Robot { @Inject lateinit var controller: Controller constructor() { val factory = Controller_Factory.create() val injector = Robot_MembersInjector.create(factory) injector.injectMembers(this) }} 现在添加一个注入的成员变量 power: 1234567891011121314151617class Controller @Inject constructor()class Power @Inject constructor()class Robot { @Inject lateinit var controller: Controller @Inject lateinit var power: Power constructor() { val controllerFactory = Controller_Factory.create() val powerFactory = Power_Factory.create() val injector = Robot_MembersInjector.create(controllerFactory, powerFactory) injector.injectMembers(this) }} 这时应该祭出 Componet 了,我们来声明一个 AppComponet: 1234@Componentinterface AppComponent { fun inject(robot: Robot)} 然后使用 Component 来注入变量,Dagger 会根据 AppComponent 生成一个 DaggerAppComponent: 12345678class Robot { @Inject lateinit var controller: Controller @Inject lateinit var power: Power constructor() { DaggerAppComponent.builder().build().inject(this) }} 我们来分析一下 DaggerAppComponent 的源码: 123456789101112131415161718192021222324252627282930313233343536public final class DaggerAppComponent implements AppComponent { private MembersInjector<Robot> robotMembersInjector; private DaggerAppComponent(Builder builder) { assert builder != null; initialize(builder); } public static Builder builder() { return new Builder(); } public static AppComponent create() { return new Builder().build(); } @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.robotMembersInjector = Robot_MembersInjector.create(Controller_Factory.create(), Power_Factory.create()); } @Override public void inject(Robot robot) { robotMembersInjector.injectMembers(robot); } public static final class Builder { private Builder() {} public AppComponent build() { return new DaggerAppComponent(this); } }} DaggerAppComponent 为我们构造了 Robot_MembersInjector ,在 public void inject(Robot robot) 调用了 injectMembers 方法,如果我们把 Robot 的代码复制一遍,新建一个 Robot2 类,AppComponet 修改为: 12345@Componentinterface AppComponent { fun inject(robot: Robot) fun inject(robot: Robot2)} DaggerAppComponent 有什么变化呢?它会多一个 robot2MembersInjector 成员变量, 12345678910111213141516171819@SuppressWarnings("unchecked")private void initialize(final Builder builder) {this.robotMembersInjector = Robot_MembersInjector.create(Controller_Factory.create(), Power_Factory.create());this.robot2MembersInjector = Robot2_MembersInjector.create(Controller_Factory.create(), Power_Factory.create());}@Overridepublic void inject(Robot robot) {robotMembersInjector.injectMembers(robot);}@Overridepublic void inject(Robot2 robot) {robot2MembersInjector.injectMembers(robot);} 同理,在 Componet 添加多个 inject,会生成对应的 MembersInjector。现在我们注入的 Power 和 Controller 是不是单例的呢?不是的。来看 Robot_MembersInjector 的 injectMembers 函数: 12345678@Overridepublic void injectMembers(Robot instance) {if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference");}instance.controller = controllerProvider.get();instance.power = powerProvider.get();} 对应的 MembersInjector 的 get 方法都是 new 出一个对象。现在我们想把 Power 注入变成单例的,先加个 @Singleton: 12@Singletonclass Power @Inject constructor() 编译一下,报错了… 1234Error:(11, 2) 错误: com.sw926.dagger2example.AppComponent (unscoped) may not reference scoped bindings:@dagger.Component()^ @Singleton class com.sw926.dagger2example.Power Component 是连接 Provider 和 Injector 的桥梁,*@Singleton* 是作用域,不在一个域看来不让连接,那么给 Component 也加上注解: 12345@Singleton@Componentinterface AppComponent { fun inject(robot: Robot)} 编译通过了,再来看 DaggerAppComponent 的源码,powerProvider 部分改变了,变成了 1this.powerProvider = DoubleCheck.provider(Power_Factory.create()); DoubleCheck 的源码不用贴了,作用就是能保证 Provider get 的时候返回的是单例,而且是安全的,而是是懒加载的,很完美。 作为一个严谨的程序,一个 Power 哪里够用,我们需要一个备用的,也就是说,需要两个单例的 Power,现在 Module 要登场了。Module 是构造方法的仓库,我们把 Power 的注解去掉,改为在 Module 中提供构造方法,然后在 Component 中指定 Module 123456789101112131415class Power@Moduleclass AppModule { @Provides @Singleton fun providePower() = Power()}@Singleton@Component(modules = [(AppModule::class)])interface AppComponent { fun inject(robot: Robot)} 现在添加一个 BackUp Power 12345678910111213141516171819202122232425262728293031323334class Power(val name: String)// 添加一个 BackUp 注解@Qualifier@Documented@Retention(RUNTIME)public @interface BackUp {}// ...@Moduleclass AppModule { @Singleton @Provides fun providePower(): Power = Power("main") @BackUp @Singleton @Provides fun provideBackUpPower(): Power = Power("backup")}class Robot { @Inject lateinit var controller: Controller @Inject lateinit var power: Power @field:[Inject BackUp] lateinit var backUpPower: Power constructor() { DaggerAppComponent.builder().build().inject(this) }} 在注入 Power的时候,默认是 main, 如果添加了 @BackUp 注解,就是 backup,Robot_MembersInjector 会有三个 Provider 123456789@Overridepublic void injectMembers(Robot instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } instance.controller = controllerProvider.get(); instance.power = powerProvider.get(); instance.backUpPower = backUpPowerProvider.get();} 是时候来验证一下是否是单例了,我们来创建两个 Robot ,看他们的 Power 和 BackUpPower 是否一样: 1234val robot1 = Robot()val robot2 = Robot()Log.d("Dagger2Test", "robot1 :(${robot1.power}, ${robot1.backUpPower}), \\nrobot2: (${robot2.power}, ${robot2.backUpPower}") 运行结果 12robot1 :(com.sw926.dagger2example.Power@5f1ad48, com.sw926.dagger2example.Power@862e7e1), robot2: (com.sw926.dagger2example.Power@dc97906, com.sw926.dagger2example.Power@24c8bc7 说好的单例呢,怎么能骗人呢?大神们当然不会骗人,那肯定是我们的使用方式不对了,我们再来看看代码: 123456789101112@Singleton@Providesfun providePower(): Power = Power("main")// ...@Singleton@Component(modules = [(AppModule::class)])interface AppComponent { fun inject(robot: Robot)} 我们把注入分为三个部分: 提供者(Provider) 接受者,Robot 中的成员变量 power 提供者和接受者直接的桥梁、纽带,也就 AppComponent Dagger 中,使用 Scope 注解的 Provider 提供的对象在作用域内唯一,这个唯一性由谁来控制呢?当然是 Component,每个 Component 只能确保自己注入时的作用域唯一,上面的例子每个 Robot 都创建了一个 AppComponent,所以注入的对象不相同,如果我们把 AppComponent 放在 Application 中创建,那么注入的对象就是全局唯一对象了: 12345678910111213141516171819202122232425class App : Application() { companion object { lateinit var appComponent: AppComponent } override fun onCreate() { super.onCreate() appComponent = DaggerAppComponent.builder().build() }}// ...class Robot { @Inject lateinit var controller: Controller @Inject lateinit var power: Power @field:[Inject BackUp] lateinit var backUpPower: Power constructor() { App.appComponent.inject(this) }} 运行结果 12robot1 :(com.sw926.dagger2example.Power@862e7e1, com.sw926.dagger2example.Power@dc97906), robot2: (com.sw926.dagger2example.Power@862e7e1, com.sw926.dagger2example.Power@dc97906 如果同一个作用域内希望获取两个 Power,那么必须要起个名字区分一下,Qualifier 就是用来区别作用域内的两个对象,我们也可以用 @Named,相当于为第二个 Power起了一个名字: 1234567891011121314151617181920212223@Moduleclass AppModule { @Singleton @Provides fun providePower(): Power = Power("main") @Named("backup") @Provides @Singleton fun provideBackUpPower(): Power = Power("backup")}class Robot { @Inject lateinit var controller: Controller @Inject lateinit var power: Power @field:[Inject Named("backup")] lateinit var backUpPower: Power constructor() { App.appComponent.inject(this) }} 现在我们有了一个全局的 AppComponent,放在 Application 里面,管理 App 全局唯一的对象,现在我想有一个 Activity 生命周期的 Component,放在 每个 Activity 里面,Activity 的生命周期肯定在 App 的声明周期里面,所以 ActivityComponent 需要能够注入 AppComponent 注入的对象,现在 AppComponent 能够注入 Power BackUpPower,那么 ActivityComponent 也需要能够注入,这是需要用到 dependencies: 123456@ForActivity@Component(dependencies = [(AppComponent::class)], modules = [(ActivityModule::class)])interface ActivityComponent { fun inject(mainActivity: MainActivity)} 我们为 ActivityComponent 设置了一个作用域 @ForActivity,ActivityComponent 依赖于 AppComponent,现在来看看这样做有什么用。 注入一个对象需要一个 Provider,Provider 有以下几种形式: 指定类的构造函数 1class Controller @Inject constructor() 使用 Provider 函数 123@Singleton@Providesfun providePower(): Power = Power("main") 从 dependencies 读取 前两种不用说了,来说说第三种,ActivityComponent 的 module 是 ActivityModule 123456789@Moduleclass ActivityModule { @ForActivity @Provides fun providePowerName(@Named("backup") power: Power): String { return power.name }} 在 providePowerName 需要参数 @Named(“backup”) power: Power,这个 power 哪里找?当然是 Dagger 帮我们找,Provider 的三种形式,第一种没有,ActivityModule 里面没有,AppModule 里面有,但是怎么建立连接呢,很简单,在 AppComponent 写一个函数就行 123456789@Singleton@Component(modules = [(AppModule::class)])interface AppComponent { fun inject(robot: Robot) @Named("backup") fun getBackUpPower(): Power} 为什么写一个函数就行,源码我也没看过,就当做这是 Dagger 的协议吧,编译后会生成对应的函数 1234@Overridepublic Power getBackUpPower() { return provideBackUpPowerProvider.get();} 现在 ActivityComponent 的 module ActivityModule 找到了对应的 Provider,就可以正常提供 Power Name 了。 有了 AppComponent、ActivityComponent,下面就要添加 FragmentComponent了,Fragment 依赖于 Activity,那么我们这样做,FragmentComponent 只能由 ActivityComponent 创建,这就要用到 SubComponent,FragmentComponent 使用 @Subcomponent 注解,同时必须注明一个 Builder: 12345678910@ForFragment@Subcomponent(modules = [(FragmentModule::class)])interface FragmentComponent { @Subcomponent.Builder interface Builder { fun build(): FragmentComponent }} ActivityComponent 改写为: 123456789@ForActivity@Component(dependencies = [(AppComponent::class)], modules = [(ActivityModule::class)])interface ActivityComponent { fun inject(mainActivity: MainActivity) fun fragmentComponent(): FragmentComponent.Builder} 在 ActivityModule 里面指明 subcomponents : 123456789@Module(subcomponents = [(FragmentComponent::class)])class ActivityModule { @ForActivity @Provides fun providePowerName(@Named("backup") power: Power): String { return power.name }} 编译完成之后我们就可以使用 ActivityComponent 创建一个 FragmentComponent 了: 1234val activityComponent = DaggerActivityComponent.builder().appComponent(App.appComponent).build()activityComponent.inject(this)val fragmentComponent = activityComponent.fragmentComponent().build() 最后说一下 Module 的 includes,也就是一个 Module 可以包含一组 Module 12345678910111213141516171819202122232425262728293031323334@Moduleclass ActivityModule2 { @Provides fun provideException(): Exception { return Exception("test Exception ") }}@Module(subcomponents = [(FragmentComponent::class)], includes = [(ActivityModule2::class)])class ActivityModule { @ForActivity @Provides fun providePowerName(@Named("backup") power: Power): String { return power.name }}class MainActivity : AppCompatActivity() { @Inject lateinit var exception: Exception override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val activityComponent = DaggerActivityComponent.builder().appComponent(App.appComponent).build() activityComponent.inject(this) Log.d("Dagger2Test", "Exception: $exception") }} 运行结果: 1D/Dagger2Test: Exception: java.lang.Exception: test Exception 以上,使用 Dagger 好几年了,终于把思路理的比较清晰了,在此抛砖引玉,如果错误,欢迎指正。","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Dagger2","slug":"Dagger2","permalink":"http://sw926.github.io/tags/Dagger2/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"ConstraintLayout(约束布局)笔记","slug":"ConstraintLayout","date":"2016-11-08T06:15:59.000Z","updated":"2023-02-23T02:20:24.458Z","comments":true,"path":"2016/11/08/ConstraintLayout/","link":"","permalink":"http://sw926.github.io/2016/11/08/ConstraintLayout/","excerpt":"","text":"相对位置类似于RelativeLayout横轴(Horizontal Axis) Left, Right, Start, End竖轴(Vertical Axis)Top, Bottom, Text Baseline可用的属性有 12345678910111213layout_constraintLeft_toLeftOflayout_constraintLeft_toRightOflayout_constraintRight_toLeftOflayout_constraintRight_toRightOflayout_constraintTop_toTopOflayout_constraintTop_toBottomOflayout_constraintBottom_toTopOflayout_constraintBottom_toBottomOflayout_constraintBaseline_toBaselineOflayout_constraintStart_toEndOflayout_constraintStart_toStartOflayout_constraintEnd_toStartOflayout_constraintEnd_toEndOf Margins(边距)提供View.GONE时的间距 123456layout_goneMarginStartlayout_goneMarginEndlayout_goneMarginLeftlayout_goneMarginToplayout_goneMarginRightlayout_goneMarginBottom 居中和偏差设置好位置后默认是居中的 12345<android.support.constraint.ConstraintLayout ...> <Button android:id="@+id/button" ... app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent/> </> 偏差(Bias)默认是居中的可以使用一下两个参数调整 12layout_constraintHorizontal_biaslayout_constraintVertical_bias 例如 123456<android.support.constraint.ConstraintLayout ...> <Button android:id="@+id/button" ... app:layout_constraintHorizontal_bias="0.3" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent/> </> 可见行为(Visibility behavior)GONE控件在一般情况下不会显示和占用布局空间,但是他们的尺寸是保留的 但是在计算布局时,GONE控件会计算在内 如果有其他控件被GONE的控件约束,这种约束仍然存在,但是所有的margins都会变为0 如果想保留margins,使用layout_goneMargin*属性 尺寸约束(Dimensions constraints)最小尺寸约束12android:minWidthandroid:minHeight 在WRAP_CONTENT是有效 空间尺寸约束对于androdi:layout_width和android:layout_height有三种方式 使用一个固定的尺寸,例如123dp 使用WRAP_CONTENT 使用0dp, 相当于MATCH_CONSTRAINTMATCH_PARENT不支持约束布局!!!MATCH_CONSTRAINT的left/right top/bottom设置为parent 比例(Ratio)123<Button android:layout_width="wrap_content" android:layout_height="0dp" app:layout_constraintDimensionRatio="1:1" /> 值可以是一个float,页可以是“width:height”的形式也可以把宽和高都设为MATCH_CONSTRAINT (0dp),设置在Ratio的时候添加“W”或“H”以适应宽度活高度例如 12345<Button android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintDimensionRatio="H,16:9" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent"/> 按钮的比例是16:9,宽度会匹配parent的约束 链(Chains)比较难理解,简单的认为是一组控件在横轴或纵轴首尾相连,就是一条链 创建链一组控件在一个方向,首尾的控件连接到parent,中间的控件双向连接,如下图 Chain Header链被第一个控件控制,也就是最左边或者最顶端的 Margins in chains如果在连接上指定边距,会计算这些边距。在spread chains中,边距会从分配的控件中扣除 Chain Style为header设置layout_constraintHorizontal_chainStyle或者layout_constraintVertical_chainStyle CHAIN_SPREAD – 元素将被展开 Weighted chain – 在 CHAIN_SPREAD 模式, 如果有控件为MATCH_CONSTRAINT,这个控件会占据所有可用的空间 CHAIN_SPREAD_INSIDE – 两端不会分配空间 CHAIN_PACKED – 所有元素打包在一起,空间分配到两端 Weighted chainsspreed chain默认所有元素占据他们所需的空间,如果有元素为MATCH_CONSTRAINT,他们会使用所有的空白区域,使用layout_constraintHorizontal_weight和layout_constraintVertical_weight可用控制他们的比例。","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"ConstraintLayout","slug":"ConstraintLayout","permalink":"http://sw926.github.io/tags/ConstraintLayout/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Find命令","slug":"find命令","date":"2016-11-04T14:53:05.000Z","updated":"2023-02-23T02:20:24.458Z","comments":true,"path":"2016/11/04/find命令/","link":"","permalink":"http://sw926.github.io/2016/11/04/find%E5%91%BD%E4%BB%A4/","excerpt":"","text":"#find 1$ find <指定目录> <指定条件> <指定动作> 如果什么参数也不加,find默认搜索当前目录及其子目录,并且不过滤任何结果(也就是返回所有文件),将它们全都显示在屏幕上。 例子 1find . -name "file*" | xarges -rm","categories":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}],"tags":[{"name":"bash","slug":"bash","permalink":"http://sw926.github.io/tags/bash/"}],"keywords":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}]},{"title":"Git Rebase","slug":"Git-rebase","date":"2016-09-28T03:17:46.000Z","updated":"2023-02-23T02:20:24.458Z","comments":true,"path":"2016/09/28/Git-rebase/","link":"","permalink":"http://sw926.github.io/2016/09/28/Git-rebase/","excerpt":"","text":"将目标分支合并到当前分支 1git rebase developer 将远程分支合并到当前分支 1git rebase origin/developer 如果遇到冲突,rebase中断,解决冲突,使用 1git add . 添加修改,然后 1git rebase --continue 或者放弃rebase 1git rebase --abort","categories":[{"name":"Git","slug":"Git","permalink":"http://sw926.github.io/categories/Git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://sw926.github.io/tags/Git/"}],"keywords":[{"name":"Git","slug":"Git","permalink":"http://sw926.github.io/categories/Git/"}]},{"title":"Product Flavors","slug":"Product-Flavors","date":"2016-09-22T04:33:37.000Z","updated":"2023-02-23T02:20:24.458Z","comments":true,"path":"2016/09/22/Product-Flavors/","link":"","permalink":"http://sw926.github.io/2016/09/22/Product-Flavors/","excerpt":"","text":"使用添加 productFlavors123456productFlavors { production { } debug { }} 指定包名12345productFlavors { debug { applicationId "com.xxx.xxx.debug" }} 添加manifestPlaceholders12345678910productFlavors { debug { applicationId "com.xxx.xxx.debug" manifestPlaceholders = [UMENG_CHANNEL_VALUE: "web"] } // 批量处理,为每个flavor添加manifestPlaceholders productFlavors.all { flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name] }} ##添加buildConfigFiled 1buildConfigField "boolean", "Production", "false" ##每次打包吧mapping文件拷贝出来 12345678910111213applicationVariants.all { variant -> if (variant.getBuildType().isMinifyEnabled()) { variant.assemble.doLast { copy { from variant.mappingFile into "${rootDir}/mappings" rename { String fileName -> "mapping-${variant.name}-${variant.versionCode}.txt" } } } }}","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"vps配置Shadowsocks","slug":"vps基础安全配置","date":"2016-09-20T03:53:13.000Z","updated":"2023-02-23T02:20:24.457Z","comments":true,"path":"2016/09/20/vps基础安全配置/","link":"","permalink":"http://sw926.github.io/2016/09/20/vps%E5%9F%BA%E7%A1%80%E5%AE%89%E5%85%A8%E9%85%8D%E7%BD%AE/","excerpt":"","text":"添加一个普通用户12useradd xxxpasswd xxx 设置用户文件夹 1chown xxx /home/xxx 上传ssh公钥 客户端 1scp ~/.ssh/id_rsa.pub xxx@{host}:~/ 服务器端 123456mkdir .sshmv id_rsa.pub .ssh/authorized_keyschown -R xxx:xxx .sshchmod 700 .sshchmod 600 .ssh/authorized_keys 修改ssh端口号修改配置文件 1vi /etc/ssh/sshd_config 123修改端口号:Port xxx将密码认证设置成NO:PasswordAuthentication no;同时将root直接登录系统取消:PermitRootLogin no 重启ssh服务 1/etc/init.d/ssh restart 添加ssh快捷登录,在~/.ssh/config(没有就创建)加入 12345Host xxx hostname 111.111.111.111 user username port 1111 安装ShadowSoks参考 https://github.com/shadowsocks/shadowsocks-libev在Ubuntu上可以 12sudo apt updatesudo apt install shadowsocks-libev 自己编译安装 安装git,下载源码 12apt-get install git -ygit clone https://github.com/madeye/shadowsocks-libev.git 编译安装123456cd shadowsocks-libevsudo apt-get install --no-install-recommends build-essential autoconf libtool libssl-dev \\ gawk debhelper dh-systemd init-system-helpers pkg-config asciidoc xmlto apg libpcre3-devdpkg-buildpackage -b -us -uc -icd ..sudo dpkg -i shadowsocks-libev*.deb 配置文件格式 1234567{ "server":"servier_ip", "server_port":8388, "password":"password", "timeout":60, "method":"rc4-md5"} 启动 1ss-server -c config.json -f /tmp/ss.pid 使用supervisor托管参考 https://blog.phpgao.com/supervisor_shadowsocks.html 安装123pip install supervisor// 或者easy_install supervisor 初始化123# 初始化配置文件# 此命令会在 /etc/下创建一个示例配置文件echo_supervisord_conf > /etc/supervisord.conf 配置12345678910# 在/etc/supervisord.conf文件最后添加shadowsocks实例,代码如下# 这一段配置如果配置错误,会导致supervisor的启动失败[program:shadowsocks]command = ss-server -c /home/gzm/config2.jsonuser = userautostart = trueautoresart = truestderr_logfile = /var/log/supervisor/ss.stderr.logstdout_logfile = /var/log/supervisor/ss.stdout.log 更新和运行123456789# 运行的时候使用-c指定配置文件supervisord -c /etc/supervisord.conf# 如果不指定配置文件supervisord# 那么配置文件会依次再下面的文件夹中寻找# $CWD/supervisord.conf# $CWD/etc/supervisord.conf# /etc/supervisord.conf 在web查看12345678# 在配置文件后加上服务器配置信息[inet_http_server]port = 127.0.0.1:9001username = userpassword = 123# 最后不要忘了reload使之生效!supervisorctl reload 如果将回环地址127.0.0.1换为服务器的IP地址,就可以可以远程管理supervisor了","categories":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}],"tags":[{"name":"ss","slug":"ss","permalink":"http://sw926.github.io/tags/ss/"}],"keywords":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}]},{"title":"Android Databinding编辑xml时进行预览","slug":"Android-Databinding编辑xml时进行预览","date":"2016-09-20T03:30:03.000Z","updated":"2023-02-23T02:20:24.457Z","comments":true,"path":"2016/09/20/Android-Databinding编辑xml时进行预览/","link":"","permalink":"http://sw926.github.io/2016/09/20/Android-Databinding%E7%BC%96%E8%BE%91xml%E6%97%B6%E8%BF%9B%E8%A1%8C%E9%A2%84%E8%A7%88/","excerpt":"","text":"使用了Databinding后,编辑xml的时候,一些属性就无法预览了,比如TextView的text,ImageView的src。这时可以使用Designtime Layout Attributes,参考 http://tools.android.com/tips/layout-designtime-attributes http://tools.android.com/tech-docs/tools-attributes 例如 12345<TextView...android:text="@{viewModel.text}"tools:text="这是标题"/> 这个属性只会在Android Studio预览时有效,预览时会覆盖 android: 属性,编译时会删除,不用担心自己预览效果时随便填的文本会被发布出去。而且可以预览ListView的item,container的Fragment,merge。 使用Databinding时需要注意,声明prefix的时候必须放在xml的根节点,也就是 layout节点,包括xmlns:android,xmlns:app,xmlns:tools,不然Designtime Layout Attributes在Preview时显示不出来 12345<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> ...</layout>","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Android Studio","slug":"Android-Studio","permalink":"http://sw926.github.io/tags/Android-Studio/"},{"name":"Databinding","slug":"Databinding","permalink":"http://sw926.github.io/tags/Databinding/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Android Studio 2.2新功能预览","slug":"Android-Studio-2-2新功能预览","date":"2016-06-02T02:57:06.000Z","updated":"2023-02-23T02:20:24.457Z","comments":true,"path":"2016/06/02/Android-Studio-2-2新功能预览/","link":"","permalink":"http://sw926.github.io/2016/06/02/Android-Studio-2-2%E6%96%B0%E5%8A%9F%E8%83%BD%E9%A2%84%E8%A7%88/","excerpt":"","text":"升级SDK可用Background多加了个按钮,可用一边写代码一边下载SDK Instant Run修改代码一秒启动 APK analyzer 分析任何的APK 查看APK下载包的大小,解压后的实际大小 反编译资源文件,甚至能还原layout中的资源id,还有,代码,代码,代码,重要的事情说三遍,可以和APKTOOL,dex2jar说拜拜 分析dex,显示每部分的方法数,直观的告诉你是怎么超过64k的打开方法:Build -> Analyz APKConstraintLayout 改进的Manifest Editor下方添加了一个Merge Manifest,可用查看APK最终的Manifest,分析Manifest里面的东西都是从哪儿过来的,跳转到对应的Manifest 全新的Project Structure dependency可视化,贴心的提醒那些依赖有新版本了,一键升级到最新版本 添加依赖直接搜索,方便的配置使用debug还是release感觉Google在干微软的活 NDK支持 不用experimental Gradle plugin了 支持external build systems,可用用CMAKE了(虽然我不知道这是干什么) 干货,调试的时候直接从java跳到C/C++代码!!!这是要抛弃java的节奏吗 命令行build,直接下载缺失的sdkgradle.properties中添加 1android.builder.sdkDownload = true 编译的时候直接下载没有安装的sdk和工具,如果用过bundle,npm install,你会更了解这是做什么的有了这个功能,在服务上进行编译更方便,基本一个命令就搞定了 可视化编程 首先,scroll在编辑的时候可以滑动的 添加了blueprint mode,像x光一样,可用直接查看layout的全部的结构 ConstraintLayout,关于这个,我想说,同学,你知道安利吗,不对,你知道c#、xib吗。再一次,google干了微软事。上面的是调侃,其实我觉得ConstraintLayout以后会是首选的布局模式,就像Fragment一样,这是google对布局大的改进,减少布局层级,可视化编程,提高编程效率。和Databinding结合,借助Android Studio提供的工具,可用将程序员画布局中解脱出来,去关注逻辑上的实现。 接上个,Google丧心病狂的提供了普通布局转换到ConstraintLayout工具 Editor 直接拖Firebase的代码到editor 不知道代码怎么用了,右键Find Sample Code,显示sample code Leak检查,静态引用了Context会显示警告 annotitions, @WorkThread, @AnyThread, @RequiresApi,@Dimension,@Px @Keep 你懂的 生成动态权限代码,如果你Activity中使用了相机权限,但是没有对Android6.0的动态权限适配,可以直接使用Android Studio生成相关的代码 移除unused resource,没有用到的string可用一键删除了 Expresso test简单来说,录制对App的操作,然后播放,这不是monkey,播放脚本和屏幕大小无关。这会大大的减少初级测试人员,缩短测试时间。录制的脚本可用在云端测试,可用在任何尺寸的机器上测试。 总的来说,新版的Android Studio对开发者表现了极大的诚意。Preview版本的Android Studio下载地址:http://tools.android.com/recentGoogle I/O上对Preview 2.2/2.3版本的介绍:https://www.youtube.com/watch?v=csaXml4xtN8","categories":[],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Android Studio","slug":"Android-Studio","permalink":"http://sw926.github.io/tags/Android-Studio/"}],"keywords":[]},{"title":"Android Studio插件整理","slug":"Studio插件整理","date":"2016-05-17T00:10:24.000Z","updated":"2023-02-23T02:20:24.457Z","comments":true,"path":"2016/05/17/Studio插件整理/","link":"","permalink":"http://sw926.github.io/2016/05/17/Studio%E6%8F%92%E4%BB%B6%E6%95%B4%E7%90%86/","excerpt":"","text":"Android ButterKnife Zelezny 自动生成ButterKnife注解Android Parcelable code generator 自动生成ParcelableSelectorChapek for Android 根据drawable名称自动生成SelectorLifecycle Sorter 根据Activity和Fragment的生命周期进行排序GsonFormat 根据Json生成GsonGradle Killer 一键杀Task","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Android Studio","slug":"Android-Studio","permalink":"http://sw926.github.io/tags/Android-Studio/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"今天做的一件蠢事,Git Reset --Hard的恢复","slug":"今天做的一件蠢事","date":"2016-05-04T10:23:54.000Z","updated":"2023-02-23T02:20:24.457Z","comments":true,"path":"2016/05/04/今天做的一件蠢事/","link":"","permalink":"http://sw926.github.io/2016/05/04/%E4%BB%8A%E5%A4%A9%E5%81%9A%E7%9A%84%E4%B8%80%E4%BB%B6%E8%A0%A2%E4%BA%8B/","excerpt":"","text":"本来想把博客的工程传到github的gh-pages分支,所以 12git initgit add --all 然后并没有进行commit,之后犯了一个错误,头脑发热执行了 1git reset --hard sublime sidebar一闪,工程目录空了,当时就蒙逼了… 马上Google,Google打不开,Shadowsocks也闹别扭了,百度一下,一屏的广告,果断关闭,bing… 还好已经add了,能够恢复,但是方法有点曲折:找到最近的添加 123456$ find .git/objects -type f | xargs ls -lt | sed 5q-r--r--r-- 1 sunwei staff 32393 May 4 18:31 .git/objects/00/29cb49889118b45b5ad762d8eb3f8f4ac26e87-r--r--r-- 1 sunwei staff 83 May 4 18:31 .git/objects/04/8cdb0ecc50736bebe669ce7183269fd5dd8cfa-r--r--r-- 1 sunwei staff 80 May 4 18:31 .git/objects/06/3b0e4ce79bbd23403f7e8ebfb71fb7779f869a-r--r--r-- 1 sunwei staff 1093 May 4 18:31 .git/objects/07/872072704114b91681e2e6f9697ce1521b64d2-r--r--r-- 1 sunwei staff 647 May 4 18:31 .git/objects/0f/951a9026a31df92690dcd2165b7c852fb27afa 然后 1git cat-file -p {id} > {file} id为 1.git/objects/00/29cb49889118b45b5ad762d8eb3f8f4ac26e87 “.git/objects/”后面的字符串,去掉“/”上面的id就是 10029cb49889118b45b5ad762d8eb3f8f4ac26e87 这个命令是把git object读取出来保存成文件我是第一次add,有几百个文件,手工打命令肯定是不行了,写个脚本吧首先把git objects保存到文件 1find .git/objects -type f | xargs ls| sed >a.txt 新建一个r文件夹,写个ruby脚本 12345678i = 0File.open("a.txt") do |file| file.each_line do |line| id = line[13,2] + line[16, 38] system 'git cat-file -p %s > ./r/%d.md' % [id, i] i = i + 1 endend 保存成recovery.ruby,执行 1ruby recovery.ruby 所有的文件都会恢复到r文件夹,但是目录结构和名字是没有了,只能一个个找了。好在我只有19个post,几张图片,用下全局搜索,很快都找到了。得到一个教训,git reset –hard,git clean -df, rm -r, rm -rf慎用","categories":[{"name":"Git","slug":"Git","permalink":"http://sw926.github.io/categories/Git/"}],"tags":[{"name":"Git","slug":"Git","permalink":"http://sw926.github.io/tags/Git/"}],"keywords":[{"name":"Git","slug":"Git","permalink":"http://sw926.github.io/categories/Git/"}]},{"title":"Dagger2 Module","slug":"Dagger2 Model","date":"2016-05-03T13:23:16.000Z","updated":"2023-02-23T02:20:24.456Z","comments":true,"path":"2016/05/03/Dagger2 Model/","link":"","permalink":"http://sw926.github.io/2016/05/03/Dagger2%20Model/","excerpt":"","text":"新建工程使用Android Studio,新建一个空白工程,最小支持Android 4.0 添加Dagger2 依赖修改app目录下的build.gradle 123456789101112131415161718192021buildscript { repositories { jcenter() } dependencies { classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' }}...apply plugin: 'com.neenbedankt.android-apt'...dependencies {... compile 'com.google.dagger:dagger:2.4' compile 'org.glassfish:javax.annotation:10.0-b28'} 在空白工程上测试一下Dagger2依赖注入的可以通过https://github.com/android-cn/blog/tree/master/java/dependency-injection来了解一下。通过之前的了解,实现一个简单的例子 12345678910111213141516171819202122232425262728HttpEngine.java@Singletonpublic class HttpEngine { @Inject public HttpEngine() { }}DataComponent.java@Singleton@Componentpublic interface DataComponent { void inject(MainActivity mainActivity);}MainActivity.javapublic class MainActivity extends AppCompatActivity { @Inject HttpEngine mHttpEngine; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerDataComponent.builder().build().inject(this); }} Dagger2自动生成的代码上面的代码可以为MainActivity注入一个HttpEngine对象,现在有两个问题,怎么注入的,注入的是单例的对象吗。查看Dagger2生成的代码首先查看MainActivity_MembersInjector.java 12345678910111213141516171819202122232425public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> { private final Provider<HttpEngine> mHttpEngineProvider; public MainActivity_MembersInjector(Provider<HttpEngine> mHttpEngineProvider) { assert mHttpEngineProvider != null; this.mHttpEngineProvider = mHttpEngineProvider; } public static MembersInjector<MainActivity> create(Provider<HttpEngine> mHttpEngineProvider) { return new MainActivity_MembersInjector(mHttpEngineProvider); } @Override public void injectMembers(MainActivity instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } instance.mHttpEngine = mHttpEngineProvider.get(); } public static void injectMHttpEngine( MainActivity instance, Provider<HttpEngine> mHttpEngineProvider) { instance.mHttpEngine = mHttpEngineProvider.get(); }} MainActivity_MembersInjector从名字就能看出,为MainActivity注入Members。在构造的时候传入一个Provider对象,在injectMembers函数中通过Provider的get获取一个HttpEngine对象,注入到MainActivity中,在MainActivity中,被赋值的成员函数用@Inject标识。到这里,明白了怎么向MainActivity注入Members的,但是Provider怎么提供要注入的对象的,还是不知道。 DaggerDataComponent.java 123456789101112131415161718192021222324252627282930313233343536public final class DaggerDataComponent implements DataComponent { private MembersInjector<MainActivity> mainActivityMembersInjector; private DaggerDataComponent(Builder builder) { assert builder != null; initialize(builder); } public static Builder builder() { return new Builder(); } public static DataComponent create() { return builder().build(); } @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.mainActivityMembersInjector = MainActivity_MembersInjector.create(HttpEngine_Factory.create()); } @Override public void inject(MainActivity mainActivity) { mainActivityMembersInjector.injectMembers(mainActivity); } public static final class Builder { private Builder() {} public DataComponent build() { return new DaggerDataComponent(this); } }} 在DaggerDataComponent的initialize()函数中,创建了一个 MainActivity_MembersInjector 的实例,传入的是一个 HttpEngine_Factory 的实例。在inject()函数中会调用MainActivity_MembersInjector的injectMembers()函数进行注入。 HttpEngine_Factory.java 1234567891011public enum HttpEngine_Factory implements Factory<HttpEngine> { INSTANCE; @Override public HttpEngine get() { return new HttpEngine(); } public static Factory<HttpEngine> create() { return INSTANCE; } HttpEngine_Factory提供了HttpEngine的构造方法,使用enum的方式保证单例模式。 Provider,Injector,Component至此,一个最简单的注入就完成了,通过上面的例子,可以将Dagger2的注入分为三个部分,Provider,Injector,Component。Provider提供对象或者说构造方法,Injector负责将Provider提供的对象进行注入,至于Injector要使用哪个Provider,由Component来进行指定。 上面的例子,有两个疑问: 我们没有在任何地方手动的指定HttpEngine和DataComponent的关联,DaggerDataComponent是怎么找到HttpEngine的构造方法的呢? 怎么使用Dagger2创建一个不是单例的Provider 第一个问题,和Android apt有关,以后慢慢研究。第二个问题,可以使用 @Provides 123456789@mdulepublic class DataModule { @Singleton @Provides HttpEngine provideHttpEngine() { return new HttpEngine(); }} 修改component,指定Module 123456@Singleton@Component(modules = DataModule.class)public interface DataComponent { void inject(MainActivity mainActivity);} 会生成一个新的class 123456789101112131415161718public final class DataModule_ProvideHttpEngineFactory implements Factory<HttpEngine> { private final DataModule module; public DataModule_ProvideHttpEngineFactory(DataModule module) { assert module != null; this.module = module; } @Override public HttpEngine get() { return Preconditions.checkNotNull( module.provideHttpEngine(), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory<HttpEngine> create(DataModule module) { return new DataModule_ProvideHttpEngineFactory(module); }} get方法获取的不再是单例的对象。Module就像是一个Provider的仓库,我们可以定义多个仓库,可以在compont中指定使用那个仓库。 Module关于Module和@Provides,还有一些疑问: 一个Compnot指定多个Module会是什么情况。 Module可以继承吗 如何让@Provides作为一个单例 我们修改一下HttpEngine.java 123public interface HttpEngine { String getTag();} 添加 1234567public class NormalHttpEngine implements HttpEngine { @Override public String getTag() { return "NormalHttpEngine"; }} 1234567public class OkHttpEngine implements HttpEngine { @Override public String getTag() { return "OkHttpEngine"; }} 123456789@Modulepublic class DataModule2 { @Singleton @Provides HttpEngine provideHttpEngine() { return new NormalHttpEngine(); }} DataComponent修改为 12345@Singleton@Component(modules = {DataModule.class, DataModule2.class})public interface DataComponent { void inject(MainActivity mainActivity);} build,会出现error 123Error:(15, 10) error: com.sw926.xyz.com.sw926.xyz.data.HttpEngine is bound multiple times:@Provides com.sw926.xyz.com.sw926.xyz.data.HttpEngine com.sw926.xyz.com.sw926.xyz.data.DataModule.provideHttpEngine()@Provides com.sw926.xyz.com.sw926.xyz.data.HttpEngine com.sw926.xyz.com.sw926.xyz.data.DataModule2.provideHttpEngine() 得出结论,可以指定多个Module,但Provider不能冲突。 修改DataModule2和DataComponent 12345678@Modulepublic class DataModule2 extends DataModule { @Provides HttpEngine provideHttpEngine() { return new NormalHttpEngine(); }} 12345@Singleton@Component(modules = DataModule2.class)public interface DataComponent { void inject(MainActivity mainActivity);} build,仍然出现error 1Error:(13, 16) error: @Provides methods may not override another method. Overrides: @Provides com.sw926.xyz.com.sw926.xyz.data.HttpEngine com.sw926.xyz.com.sw926.xyz.data.DataModule.provideHttpEngine() @Provides 的函数不能被override,但是可以被继承 第三个问题,@Provides 前面加上 @Singleton并不会变成单例的模式,如果想使用单例,可以在Application中创建component,在Activity中进行注入,代码如下: 1234567891011121314public class App extends Application { private DataComponent mDataComponent; @Override public void onCreate() { super.onCreate(); mDataComponent = DaggerDataComponent.builder().build(); } public DataComponent getDataComponent() { return mDataComponent; }} 1234567891011public class MainActivity extends AppCompatActivity { @Inject HttpEngine mHttpEngine; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((App)getApplication()).getDataComponent().inject(this); }} 项目地址: https://github.com/sw926/xyz/tree/dagger2","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Dagger2","slug":"Dagger2","permalink":"http://sw926.github.io/tags/Dagger2/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Java的集合","slug":"Java的集合","date":"2016-04-27T14:21:01.000Z","updated":"2023-02-23T02:20:24.456Z","comments":true,"path":"2016/04/27/Java的集合/","link":"","permalink":"http://sw926.github.io/2016/04/27/Java%E7%9A%84%E9%9B%86%E5%90%88/","excerpt":"","text":"ArrayList和LinkedList的遍历从最常用的ArrayList说起 1public class ArrayList<E> extends AbstractList<E> implements Cloneable, Serializable, RandomAccess RandomAccess这个接口是空的 123456789package java.util;/** * RandomAccess is implemented by {@code List} implementations that support fast * (usually constant time) random access. */public interface RandomAccess { /* empty */} 在遍历的时候,实现了RandomAccess的List 123for (int i=0, i<list.size(); i++) { list.get(i)} 要比 123for (Iterator i=list.iterator(); i.hasNext();) { i.next()} 快,对于LinkedList,是SequentialList 123public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable 也就是说LinkedList遍历的时候要使用迭代器的方法,而不要使用index的方法。经常使用的foreach方法 12for (String s : strings) {} 是使用的Iterator方法,所以遍历ArrayList应该避免使用foreach的方法。为什么这两种遍历方法在这两种数组上的区别?是因ArrayList是基于动态数组的数据结构,而LinkedList是基于列表。 ArrayList和LinkedList的使用场景ArrayList和LinkedList在性能上各有优缺点,都有各自所适用的地方,总的说来可以描述如下:1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。 2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。 3.LinkedList不支持高效的随机元素访问。 4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间 可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。 Vector和Stack通过上图,可以看到还有两个实现了RandomAccess的集合类,Vector和StackVector和ArrayList类似,不过Vector是线程安全的关于ArrayList和Vector区别如下: ArrayList在内存不够时默认是扩展50% + 1个,Vector是默认扩展1倍。 Vector提供indexOf(obj, start)接口,ArrayList没有。 Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销。 参考:http://pengcqu.iteye.com/blog/502676http://www.blogjava.net/lzqdiy/archive/2007/04/22/112578.htmlhttp://www.cnblogs.com/wanlipeng/archive/2010/10/21/1857791.html","categories":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}],"tags":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/tags/Java/"}],"keywords":[{"name":"Java","slug":"Java","permalink":"http://sw926.github.io/categories/Java/"}]},{"title":"使用jenkins进行Android测试","slug":"使用jenkins进行Android测试","date":"2016-04-23T01:02:45.000Z","updated":"2023-02-23T02:20:24.456Z","comments":true,"path":"2016/04/23/使用jenkins进行Android测试/","link":"","permalink":"http://sw926.github.io/2016/04/23/%E4%BD%BF%E7%94%A8jenkins%E8%BF%9B%E8%A1%8CAndroid%E6%B5%8B%E8%AF%95/","excerpt":"","text":"服务器使用的是DigitalOcean,Debian 安装Jenkins添加apt key和source list 123wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | apt-key add -echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.listapt-get update 然后安装Jenkins 1apt-get install jenkins 之后使用8080端口就可以打开jenkins了,第一步是设置密码,之后就是图像界面的操作了在图形界面安装Git Plugin和Gradle Plugin这两个插件为了安全起见,还要配置Jenkins的登录密码 配置主机环境安装Git1sudo apt-get install git-core 安装Android SDK在http://developer.android.com/intl/zh-cn/sdk/index.html找到command line tools下载地址,因是主机是Linux,所以下载Linux版本 12cd /optwget <link you copied here> 然后解压 1tar zxvf <filename of the just downloaded file> 得到android-sdk-linux目录配置Android环境变量 123export ANDROID_HOME="/opt/android-sdk-linux"export PATH="$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$PATH"source /etc/profile 至此,只是安装了SDK Manager,下面开始安装SDK由于使用ssh所以只能在命令行下安装使用 1android update sdk --no-ui 可以安装所有的SDK,一只yes就可以,但是最低档的vps只有20G的空间,所有我们只需要安装最基本的SDK就可以查看一下Android的帮助选择 12345678910111213141516171819202122232425262728293031323334353637383940414243444546# android -h Usage: android [global options] action [action options] Global options: -h --help : Help on a specific command. -v --verbose : Verbose mode, shows errors, warnings and all messages. --clear-cache: Clear the SDK Manager repository manifest cache. -s --silent : Silent mode, shows errors only. Valid actions are composed of a verb and an optional direct object:- sdk : Displays the SDK Manager window.- avd : Displays the AVD Manager window.- list : Lists existing targets or virtual devices.- list avd : Lists existing Android Virtual Devices.- list target : Lists existing targets.- list device : Lists existing devices.- list sdk : Lists remote SDK repository.- create avd : Creates a new Android Virtual Device.- move avd : Moves or renames an Android Virtual Device.- delete avd : Deletes an Android Virtual Device.- update avd : Updates an Android Virtual Device to match the folders of a new SDK.- create project : Creates a new Android project.- update project : Updates an Android project (must already have an AndroidManifest.xml).- create test-project : Creates a new Android project for a test package.- update test-project : Updates the Android project for a test package (must already have an AndroidManifest.xml).- create lib-project : Creates a new Android library project.- update lib-project : Updates an Android library project (must already have an AndroidManifest.xml).- create uitest-project: Creates a new UI test project.- update adb : Updates adb to support the USB devices declared in the SDK add-ons.- update sdk : Updates the SDK by suggesting new platforms to install if available. 我们要使用的是update sdk命令 1234567891011121314151617181920212223242526272829# android -h update sdk Usage: android [global options] update sdk [action options] Global options: -h --help : Help on a specific command. -v --verbose : Verbose mode, shows errors, warnings and all messages. --clear-cache: Clear the SDK Manager repository manifest cache. -s --silent : Silent mode, shows errors only. Action "update sdk": Updates the SDK by suggesting new platforms to install if available.Options: --proxy-port: HTTP/HTTPS proxy port (overrides settings if defined) --proxy-host: HTTP/HTTPS proxy host (overrides settings if defined) -s --no-https : Uses HTTP instead of HTTPS (the default) for downloads. -a --all : Includes all packages (such as obsolete and non-dependent ones.) -f --force : Forces replacement of a package or its parts, even if something has been modified. -u --no-ui : Updates from command-line (does not display the GUI) -p --obsolete : Deprecated. Please use --all instead. -t --filter : A filter that limits the update to the specified types of packages in the form of a comma-separated list of [platform, system-image, tool, platform-tool, doc, sample, source]. This also accepts the identifiers returned by 'list sdk --extended'. -n --dry-mode : Simulates the update but does not download or install anything. 安装SDK的命令就是 1android update sdk -u -a -t <type> 使用命令 1android list sdk -u -a -e 可以查看所有SDK开始安装 12345android update sdk -u -a -t toolsandroid update sdk -u -a -t platform-toolsandroid update sdk -u -a -t build-tools-23.0.3android update sdk -u -a -t android-23android update sdk -u -a -t extra-android-support 如果是64位系统 1sudo apt-get install lib32stdc++6 lib32z1 赋予可执行权限 1sudo chmod -R 755 /opt/android-sdk-linux 重启一下 1sudo shutdown -r now 编译的时候遇到了 Could not find tools.jar所以要配置一下java查看是32位还是64位的系统 1uname -m 下载最新的jdk,地址到官网去找 123456wget --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u91-b14/jdk-8u91-linux-x64.tar.gzmkdir /opt/jdktar -zxf jdk-8u5-linux-x64.tar.gz -C /opt/jdktar -zxf jdk-8u91-linux-x64.tar.gz -C /opt/jdkupdate-alternatives --install /usr/bin/java java /opt/jdk/jdk1.8.0_91/bin/java 1052update-alternatives --install /usr/bin/javac javac /opt/jdk/jdk1.8.0_91/bin/javac 1052 如果主机上已经安装过其他的java版本使用 12update-alternatives --display javaupdate-alternatives --display javac 查看一下priority,确保update-alternatives –install后面的数字大于旧的的java 至此,Jenkins和Android SDK已经配置完毕,悲剧的是最终没有运行成功,服务器内存不足,最终编译失败。 参考:https://www.digitalocean.com/community/tutorials/how-to-build-android-apps-with-jenkinshttps://www.digitalocean.com/community/tutorials/how-to-manually-install-oracle-java-on-a-debian-or-ubuntu-vps","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Jenkins","slug":"Jenkins","permalink":"http://sw926.github.io/tags/Jenkins/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Python Env","slug":"python env","date":"2016-01-08T06:39:39.000Z","updated":"2023-02-23T02:20:24.455Z","comments":true,"path":"2016/01/08/python env/","link":"","permalink":"http://sw926.github.io/2016/01/08/python%20env/","excerpt":"","text":"安装 pyenv使用 git 把 pyenv 下载到用户目录: 12$ cd$ git clone git://github.com/yyuu/pyenv.git .pyenv 然后需要修改环境变量,使用 Bash Shell 的输入 12$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile $ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile 使用 Zsh Shell 的输入 12$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc 最后添加 pyenv init 1$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile #Bash 或 1$ echo 'eval "$(pyenv init -)"' >> ~/.zshrc #Zsh 输入以下命令重启 Shell 1$ exec $SHELL 然后就可以使用 pyenv 了。 更新 pyenv使用 git 更新 pyenv 12$ cd ~/.pyenv$ git pull 安装Python版本列出所有的版本 1$ pyenv install --list 或者 12345678910111213141516171819$ pyenv install minicondapython-build: definition not found: minicondaThe following versions contain `miniconda' in the name: miniconda-latest miniconda-2.2.2 miniconda-3.0.0 miniconda-3.0.4 miniconda-3.0.5 ... miniconda3-3.16.0 miniconda3-3.18.3 miniconda3-3.19.0See all available versions with `pyenv install --list'.If the version you need is missing, try upgrading pyenv: cd /Users/sunwei/.pyenv/plugins/python-build/../.. && git pull && cd - 安装卸载 12$ pyenv install ...$ pyenv uninstall ... 查看已经安装的 1$ pyenv versions 设置全局版本1$ pyenv global ... 设置文件夹版本1$ pyenv local ... 安装pyenv virtualenv1$ git clone https://github.com/yyuu/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv 或者 1$ brew install pyenv-virtualenv 新建一个virtual env1$ pyenv virtualenv 2.7.10 my-virtual-env-2.7.10 list env1$ pyenv virtualenvs 使用evn12$ pyenv activate <name>$ pyenv deactivate 参考https://github.com/yyuu/pyenv-virtualenvhttp://huangziwei.com/tech/setting-up-scientific-python-environment-in-os-x-10-10-using-miniconda/","categories":[{"name":"Python","slug":"Python","permalink":"http://sw926.github.io/categories/Python/"}],"tags":[{"name":"Python","slug":"Python","permalink":"http://sw926.github.io/tags/Python/"}],"keywords":[{"name":"Python","slug":"Python","permalink":"http://sw926.github.io/categories/Python/"}]},{"title":"Gson","slug":"Gson","date":"2015-11-19T04:51:38.000Z","updated":"2023-02-23T02:20:24.455Z","comments":true,"path":"2015/11/19/Gson/","link":"","permalink":"http://sw926.github.io/2015/11/19/Gson/","excerpt":"","text":"基本使用方法Gson deserialize需要三个东西 判断变量是是否需要deserialize 在json中对应的name 需要deserialize成什么类型 哪些变量会被deserialize所有public变量和使用 @Expose 注解的变量都会deserialize如果不希望public变量被deserialize,可以使用声明为transient 指定对应json中的name使用注解 1@SerializedName("type") 可以指定json中的name,如果没有注解,默认使用变量的名字作为json中的name例如 123public class Data { public int type;} 对应json 123{ "type" : 1} 需要deserialize成什么类型声明成什么类型就会deserialize成什么类型最简单的例子 123String json = "{\\"type\\": 1}";Gson gson = new GsonBuilder().create();Data data = gson.from(json, Data.class); 如果是jsonArray,需要使用TypeToken 123Type type = new TypeToken<Collection<Data>>() { }.getType();gson.from(json, type); 进阶设置方法处理日期格式对于这种变态的日期格式 1Mon Nov 16 12:25:14 +0800 2015 应该怎么处理呢?简单粗暴的方法是在class中声明一个叫date的String变量,使用的时候parse。优雅的办法是 123GsonBuilder builder = new GsonBuilder();builder.setDateFormat("EEE MMM dd HH:mm:ss Z yyyy");Gson gson = builder.create(); 然后 1@SerializedName("date") public Data date; Gson会自动把json中name为date的String解析为Date对象。 如果json中的日期不是String,是个long或者int类型呢,如果还想优雅的使用,可以注册TypeAdapter 12345678910111213141516public class DateTypeAdapter implements JsonDeserializer<Date>, JsonSerializer<Date> { @Override public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { if (json != null && json.isJsonPrimitive()) { long time = json.getAsLong(); return new Date(time); } return null; } @Override public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(src.getTime()); }} 然后 123GsonBuilder builder = new GsonBuilder();builder.registerTypeAdapter(Date.class, new DateTypeAdapter());Gson gson = builder.create(); 处理enum同理,使用TypeAdapter 1234567891011121314public enum SimpleDataType { one(1), two(2); int value; SimpleDataType(int value) { this.value = value; } public int getValue() { return value; }} 12345678910111213141516public class DataTypeAdapter implements JsonSerializer<SimpleDataType>, JsonDeserializer<SimpleDataType> { @Override public SimpleDataType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { for (SimpleDataType simpleDataType : SimpleDataType.values()) { if (simpleDataType.getValue() == json.getAsInt()) { return simpleDataType; } } return null; } @Override public JsonElement serialize(SimpleDataType src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(src.getValue()); }} 特殊处理处理不规范的json有时候服务器返回的json不够规范,例如:正常情况下应该是个对象数组 123{ "images" : [{...}, {...}]} 为空时应该是这样 123{ "images" : []} 或者这样 123{ "images" : null} 但是,有时候不负责任的服务器返回是这样的 123{ "images" : ""} Gson解析的时候就会报错,因为类型不匹配,Gson会按照ArrayList类型来解析,但实际上json里面是个String,这个怎么办?万能的TypeAdater是可以解决的,也就是手工解析。也行你会说我的Image里面有180个属性,一个一个get要到猴年马月啊。好吧,使用了Gson之后整个人都变懒了。就算使用TypeAdpater,也不用全部手工解析。 12345678910111213141516public class ImagesTypeAdapter implements JsonDeserializer<ArrayList<Image>> { @Override public ArrayList<Image> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { ArrayList<Image> arrayList = new ArrayList<>(); if (json != null && json.isJsonArray()) { JsonArray jsonArray = json.getAsJsonArray(); for (int i = 0; i < jsonArray.size(); i++) { JsonObject object = jsonArray.get(i).getAsJsonObject(); Image image = context.deserialize(object, Image.class); arrayList.add(image); } } return arrayList; }} 解决思路就算解析到ArrayList类型时,判断JsonElement是否是数组,如果不是数组的话就返回一个空的ArrayList。如果是数组,我们可以使用JsonDeserializationContext一个一个单独解析Image,这样下来,解析的过程并不复杂。也许有人说,这样代码有点多,可以更简单啊,这样就可以了 12345678910public class ImagesTypeAdapter implements JsonDeserializer<ArrayList<Image>> { @Override public ArrayList<Image> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { if (json != null && json.isJsonArray()) { return context.deserialize(json, typeOfT); } return new ArrayList<>(); }} 如果你这样写遇到问题的话,可以上一下StackOverflow。","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Gson","slug":"Gson","permalink":"http://sw926.github.io/tags/Gson/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Nginx","slug":"nginx","date":"2015-11-19T04:47:57.000Z","updated":"2023-02-23T02:20:24.455Z","comments":true,"path":"2015/11/19/nginx/","link":"","permalink":"http://sw926.github.io/2015/11/19/nginx/","excerpt":"","text":"安装nginx1brew install nginx ##启动nginx 1sudo nginx 关闭1sudo nginx -s quit","categories":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}],"tags":[{"name":"Nginx","slug":"Nginx","permalink":"http://sw926.github.io/tags/Nginx/"}],"keywords":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}]},{"title":"Webpack生成静态页面","slug":"webpack生成静态页面","date":"2015-11-17T08:31:46.000Z","updated":"2023-02-23T02:20:24.455Z","comments":true,"path":"2015/11/17/webpack生成静态页面/","link":"","permalink":"http://sw926.github.io/2015/11/17/webpack%E7%94%9F%E6%88%90%E9%9D%99%E6%80%81%E9%A1%B5%E9%9D%A2/","excerpt":"","text":"首先添加一个production的config 12345678910111213141516171819202122232425262728293031323334353637383940414243444546var path = require('path');var config = { entry: path.resolve(__dirname, '../../app/index.js'), output: { path: path.resolve(__dirname, '../../public/'), publicPath: '/', filename: 'bundle.js' }, resolve: { root: [ path.resolve(__dirname, '../../app/'), path.resolve(__dirname, '../../node_modules/') ], extensions: ['', '.js', '.jsx', '.css', '.scss', '.ejs', '.png', '.jpg'] }, module: { loaders: [ { test: /\\.jsx$/, exclude: /node_modules/, loader: 'babel' }, { test: /\\.js$/, exclude: /node_modules/, loader: 'babel', query: {stage: 0} }, { test: /\\.(css)$/, loader: 'style-loader!css-loader' }, { test: /\\.scss$/, loader: 'style!css!sass' }, { test: /\\.(jpe?g|png|gif|svg)$/i, loader: 'url?limit=10000&name=img/[hash:8].[name].[ext]' } ] }};module.exports = config;","categories":[{"name":"Node.js","slug":"Node-js","permalink":"http://sw926.github.io/categories/Node-js/"}],"tags":[{"name":"React","slug":"React","permalink":"http://sw926.github.io/tags/React/"},{"name":"webpack","slug":"webpack","permalink":"http://sw926.github.io/tags/webpack/"}],"keywords":[{"name":"Node.js","slug":"Node-js","permalink":"http://sw926.github.io/categories/Node-js/"}]},{"title":"部署服务器","slug":"部署服务器","date":"2015-11-16T06:50:44.000Z","updated":"2023-02-23T02:20:24.455Z","comments":true,"path":"2015/11/16/部署服务器/","link":"","permalink":"http://sw926.github.io/2015/11/16/%E9%83%A8%E7%BD%B2%E6%9C%8D%E5%8A%A1%E5%99%A8/","excerpt":"","text":"通过ssh登录服务器ssh登录12ssh username@server 配置本地hosts在/etc/hosts加入1{服务器id} server例如1123.123.123.123 server配置ssh免输入密码12cd ~/.sshssh-keygen -t rsa 上传到服务器1scp ~/.ssh/id_rsa.pub username@server:~/.ssh/id_rsa.pub在服务器操作1234ssh username@servercd ~/.sshcat id_rsa.pub >> authorized_keysrm id_rsa.pub安装MySql12apt-get updatesudo apt-get install mysql-server 安装Ruby安装 RVM 1$ curl -L https://get.rvm.io | bash 重新登录下服务器,检查rvm是否安装成功 1rvm info 安装ruby 12rvm install 2.2.2rvm use 2.2.2 --default","categories":[{"name":"Ruby","slug":"Ruby","permalink":"http://sw926.github.io/categories/Ruby/"}],"tags":[{"name":"Ruby","slug":"Ruby","permalink":"http://sw926.github.io/tags/Ruby/"}],"keywords":[{"name":"Ruby","slug":"Ruby","permalink":"http://sw926.github.io/categories/Ruby/"}]},{"title":"Android Test","slug":"Android Test","date":"2015-09-08T06:59:11.000Z","updated":"2023-02-23T02:20:24.454Z","comments":true,"path":"2015/09/08/Android Test/","link":"","permalink":"http://sw926.github.io/2015/09/08/Android%20Test/","excerpt":"","text":"新建工程的时候会自动创建一个androidTest我们首先在androidTest下新建一个Activity test 123456789101112131415161718192021public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> { private MainActivity mMainActivity; private TextView mTextView; public MainActivityTest() { super(MainActivity.class); } @Override protected void setUp() throws Exception { super.setUp(); mMainActivity = getActivity(); mTextView = (TextView) mMainActivity.findViewById(R.id.tv_text); } public void testPreconditions() { assertNotNull("mFirstTestActivity is null", mMainActivity); assertNotNull("mFirstTestText is null", mTextView); }} 需要注意,在setUp中获取Activity的引用,每个test case必须以test开头,testPreconditions用了测试是否正常初始化了测试环境。运行测试时,Activity是在UI线程上运行,test是在另一个线程上运行,test可以获取Activity和view的引用,我们可以测试一个view的状态是否正确 1234567891011@MediumTestpublic void testButton() { final View decorView = mMainActivity.getWindow().getDecorView(); ViewAsserts.assertOnScreen(decorView, mBtnTest); final ViewGroup.LayoutParams layoutParams = mBtnTest.getLayoutParams(); assertNotNull(layoutParams); assertEquals(layoutParams.width, WindowManager.LayoutParams.WRAP_CONTENT); assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);} 这个测试用例用了测试一个按钮是否在Activity中,LayoutParams是否正确。测试函数使用了@MediumTest的注解,这类注解表示测试用例的执行时间@SmallTests < 100ms@MediumTests < 2s@LargeTests < 120s具体教程可以查看 https://plus.google.com/+AndroidDevelopers/posts/TPy1EeSaSg8 在test中不能直接改变UI的状态,下面的用例用来测试button的点击行为 12345@MediumTestpublic void testClickButton() { TouchUtils.clickView(this, mBtnTest); assertTrue(View.GONE == mTextView.getVisibility());} TouchUtils提供了view的模拟触屏操作,它被设计用来安全的从test线程向UI线程发送event,是不能直接在UI线程中使用的。","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Android Menu","slug":"Android Menu","date":"2015-08-06T04:06:54.000Z","updated":"2023-02-23T02:20:24.454Z","comments":true,"path":"2015/08/06/Android Menu/","link":"","permalink":"http://sw926.github.io/2015/08/06/Android%20Menu/","excerpt":"","text":"关于menu,在Activity中和Fragment中都搞不清楚。 在Activity中创建menu 12345@Overridepublic boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true;} onCreateOptionsMenu只会调用一次,返回值为true时显示菜单,为false不显示菜单menu的xml文件如下 12345678910111213141516171819<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_new" android:orderInCategory="100" android:title="@string/new" app:showAsAction="never"/> <item android:id="@+id/action_delete" android:orderInCategory="101" android:title="@string/delete" app:showAsAction="never"/> <item android:id="@+id/action_exit" android:icon="@drawable/icon" android:orderInCategory="102" android:title="@string/exit" app:showAsAction="never"/></menu> 首先 showAsAction 有三个可选项,always,ifRoom,neveralways 用于显示在actionbar上ifRoom 如果空间不足时显示会显示在三个点的菜单中never 永远显示在3个点中android:orderInCategory是菜单的排序 12345@Overridepublic boolean onPrepareOptionsMenu(Menu menu) { menu.findItem(R.id.new).setVisible(true); return true;} 每次显示菜单前调用,来控制菜单的显示隐藏,返回true显示菜单,返回false隐藏菜单。 123456789@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { } } return super.onOptionsItemSelected(item); }","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"升级Android Studio 1.3","slug":"升级Android Studio 1.3","date":"2015-07-31T07:21:05.000Z","updated":"2023-02-23T02:20:24.454Z","comments":true,"path":"2015/07/31/升级Android Studio 1.3/","link":"","permalink":"http://sw926.github.io/2015/07/31/%E5%8D%87%E7%BA%A7Android%20Studio%201.3/","excerpt":"","text":"终于等到了1.3正式版,必须要升级一下。一下内容来自http://android-developers.blogspot.jp/2015/07/get-your-hands-on-android-studio-13.htmlAndroid Studio 1.3是今年最大的功能升级,包含新的内存检查工具,提升的测试功能的支持,还有C++完整的编辑功能和调试功能。 性能和测试工具 原生的内存查看工具 Android Memory (HPROF) Viewer 内存分配跟踪工具 APK Tests in Module 代码和SDK管理 App权限注解 Data Binding Support SDK自动升级和SDK Manager C++支持 C++的支持需要Gradle2.5设置NDK路径另外要使用Experimental Gradle Plugin http://tools.android.com/tech-docs/new-build-system/gradle-experimental","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Android Studio","slug":"Android-Studio","permalink":"http://sw926.github.io/tags/Android-Studio/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Dagger2笔记 Provider和Compoent","slug":"Dagger2笔记 Provider和Compoent","date":"2015-07-30T09:14:29.000Z","updated":"2023-02-23T02:20:24.454Z","comments":true,"path":"2015/07/30/Dagger2笔记 Provider和Compoent/","link":"","permalink":"http://sw926.github.io/2015/07/30/Dagger2%E7%AC%94%E8%AE%B0%20Provider%E5%92%8CCompoent/","excerpt":"","text":"在Android Studio中配置Dagger 123456789101112131415161718192021...apply plugin: 'com.neenbedankt.android-apt'buildscript { repositories { jcenter() } dependencies { classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' }}...dependencies { ... apt 'com.google.dagger:dagger-compiler:2.0.1' compile 'com.google.dagger:dagger:2.0.1' compile 'org.glassfish:javax.annotation:10.0-b28'} 我想在Activity中注入一个单例的HttpEngine,HttpEngine的代码如下 123456789101112131415@Singletonpublic class HttpEngine { @Inject public HttpEngine() { } public void getData(GetDataCallback callback) { callback.onCallback("data from http"); } public interface GetDataCallback { void onCallback(String data); }} Activity代码如下 12345678910111213141516171819202122public class MainActivity extends AppCompatActivity { @Inject HttpEngine mHttpEngine; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_get_data).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mHttpEngine.getData(new HttpEngine.GetDataCallback() { @Override public void onCallback(String data) { new AlertDialog.Builder(MainActivity.this).setMessage(data).create().show(); } }); } }); }} 我是这么想的,我用@Inject标识了HttpEngine的构造方法,然后使用 1@Inject HttpEngine mHttpEngine; 就成功注入了一个HttpEngine,这样我就可以使用这个对象了。结果当然是个空指针的错误。怎么样能让Activity和HttpEngine关联起来呢?看一下Dagger2自动生成的代码 HttpEngine_Factory.java12345678910111213@Generated("dagger.internal.codegen.ComponentProcessor")public enum HttpEngine_Factory implements Factory<HttpEngine> {INSTANCE; @Override public HttpEngine get() { return new HttpEngine(); } public static Factory<HttpEngine> create() { return INSTANCE; }} Factory继承自Provider 123public interface Provider<T> { T get();} MainActivity_MembersInjector.java12345678910111213141516171819202122232425@Generated("dagger.internal.codegen.ComponentProcessor")public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> { private final MembersInjector<AppCompatActivity> supertypeInjector; private final Provider<HttpEngine> mHttpEngineProvider; public MainActivity_MembersInjector(MembersInjector<AppCompatActivity> supertypeInjector, Provider<HttpEngine> mHttpEngineProvider) { assert supertypeInjector != null; this.supertypeInjector = supertypeInjector; assert mHttpEngineProvider != null; this.mHttpEngineProvider = mHttpEngineProvider; } @Override public void injectMembers(MainActivity instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } supertypeInjector.injectMembers(instance); instance.mHttpEngine = mHttpEngineProvider.get(); } public static MembersInjector<MainActivity> create(MembersInjector<AppCompatActivity> supertypeInjector, Provider<HttpEngine> mHttpEngineProvider) { return new MainActivity_MembersInjector(supertypeInjector, mHttpEngineProvider); }} MembersInjector.java123public interface MembersInjector<T> { void injectMembers(T instance);} HttpEngine_Factory一个Provider,负责提供HttpEngine的构造方法.MainActivity_MembersInjector是一个MembersInjector,负责向MainActivity进行注入。只要new一个MainActivity_MembersInjector对象,调用injectMembers方法,就能够将HttpEngine注入到MainActivity中。我们需要创建一个Component,Component是Provider和Injector之间的桥梁。 ActivityComponent12345@Singleton@Componentpublic interface ActivityComponent { void inject(MainActivity activity);} Dagger2自动生成一个DaggerActivityComponent DaggerActivityComponent.java12345678910111213141516171819202122232425262728293031323334353637@Generated("dagger.internal.codegen.ComponentProcessor")public final class DaggerActivityComponent implements ActivityComponent { private Provider<HttpEngine> httpEngineProvider; private MembersInjector<MainActivity> mainActivityMembersInjector; private DaggerActivityComponent(Builder builder) { assert builder != null; initialize(builder); } public static Builder builder() { return new Builder(); } public static ActivityComponent create() { return builder().build(); } private void initialize(final Builder builder) { this.httpEngineProvider = ScopedProvider.create(HttpEngine_Factory.create()); this.mainActivityMembersInjector = MainActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), httpEngineProvider); } @Override public void inject(MainActivity activity) { mainActivityMembersInjector.injectMembers(activity); } public static final class Builder { private Builder() { } public ActivityComponent build() { return new DaggerActivityComponent(this); } }} 在MainActivity中调用 1DaggerActivityComponent.create().inject(this); 就成功注入了HttpEngine,至此就理清了Provder和Component的关系。","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Dagger2","slug":"Dagger2","permalink":"http://sw926.github.io/tags/Dagger2/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Android Studio使用greenDao","slug":"Android Studio使用greenDao","date":"2015-05-25T07:46:12.000Z","updated":"2023-02-23T02:20:24.454Z","comments":true,"path":"2015/05/25/Android Studio使用greenDao/","link":"","permalink":"http://sw926.github.io/2015/05/25/Android%20Studio%E4%BD%BF%E7%94%A8greenDao/","excerpt":"","text":"在工程的根目录下新建一个目录,作为DaoGenerator的目录, 1$ mkdir -p ./MyDaoGenerator/src/main/java 目录结构如下 1234.└── src └── main └── java java目录下就是存放代码的目录,在java目录下新建一个包, 1$ mkdir -p com/example/mydaogenerator 新建一个有main函数的class备用 1234public class MyDaoGenerator { public static void main(String[] args) throws Exception { }} 在MyDaoGenerator目录下添加build.gradle,填入以下内容 12345678910111213apply plugin: 'java'apply plugin: 'application'applicationName = 'MyDaoGenerator'mainClassName = 'com.example.mydaogenerator.MyDaoGenerator'repositories { mavenCentral()}dependencies { compile('de.greenrobot:DaoGenerator:+')} MyDaoGenerator目录结构如下 12345678.├── build.gradle└── src └── main └── java └── com └── example └── mydaogenerator 添加了build.gradle之后,Studio会提示是否添加新的gradle文件,添加同步之后,新建module就添加到工程中了,如果错过了添加的提示,就在工程根目录下面的settings.gradle后面加上 1include ':MyDaoGenerator' DaoGenerator就搭建好了,打开Android Studio的task菜单,执行application run 一个完整的DaoGenrator如下 12345678910111213141516171819package com.example.mydaogenerator;import de.greenrobot.daogenerator.DaoGenerator;import de.greenrobot.daogenerator.Entity;import de.greenrobot.daogenerator.Schema;public class MyDaoGenerator { public static void main(String[] args) throws Exception { Schema schema = new Schema(1, "com.example.greendao.dao"); addNote(schema); new DaoGenerator().generateAll(schema, "../app/src/main/java"); } private static void addNote(Schema schema) { Entity note = schema.addEntity("Notes"); note.addIdProperty(); note.addStringProperty("content"); }}","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Android Studio","slug":"Android-Studio","permalink":"http://sw926.github.io/tags/Android-Studio/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Git常用命令","slug":"git不常用命令","date":"2015-04-24T08:59:56.000Z","updated":"2023-02-23T02:20:24.454Z","comments":true,"path":"2015/04/24/git不常用命令/","link":"","permalink":"http://sw926.github.io/2015/04/24/git%E4%B8%8D%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/","excerpt":"","text":"查看tag 1git tag 添加本地tag 1git add tag tag_name 上传tag 1git push origin --tags 删除远程tag 1git push origin :refs/tags/标签名 删除本地 1git tag -d v1.1.4 查看tag代码,需要checkout要查看的tag到新建的分支 1git checkout -b tag3.5 v3.5 在命令行显示当前git分支,mac上在.bash_profile中添加 12为已有代码添加分支 git tag -a v1.2 9fceb02 1234567# Git branch in prompt.parse_git_branch() { git branch 2]]> /dev/null | sed -e '/^[^*]/d' -e 's/* \\(.*\\)/ (\\1)/'} export PS1="\\u@\\h \\W\\[\\033[32m\\]\\$(parse_git_branch)\\[\\033[00m\\] $ " 删除submodule 123456git submodule deinit asubmodule git rm asubmodule# Note: asubmodule (no trailing slash)# or, if you want to leave it in your working treegit rm --cached asubmodulerm -rf .git/modules/asubmodule 创建补丁 1git format-patch -n origin/develop n为多少个commit应用补丁 1git apply --check patch 删除已经上传的ignore的文件 1git rm --cached <文件名>","categories":[{"name":"Git","slug":"Git","permalink":"http://sw926.github.io/categories/Git/"}],"tags":[{"name":"git","slug":"git","permalink":"http://sw926.github.io/tags/git/"}],"keywords":[{"name":"Git","slug":"Git","permalink":"http://sw926.github.io/categories/Git/"}]},{"title":"自定义View的measure","slug":"自定义View的measure","date":"2015-04-20T16:00:00.000Z","updated":"2023-02-23T02:20:24.453Z","comments":true,"path":"2015/04/21/自定义View的measure/","link":"","permalink":"http://sw926.github.io/2015/04/21/%E8%87%AA%E5%AE%9A%E4%B9%89View%E7%9A%84measure/","excerpt":"","text":"onMeasure函数的两个参数,每个参数包含一个mode和一个size 123@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {} 通过以下方法可以取到 1234int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec); mode有三种 123MeasureSpec.EXACTLYMeasureSpec.AT_MOSTMeasureSpec.UNSPECIFIED MeasureSpec.EXACTLY控件的大小是固定的,如果layout_width或者layout_height是固定的大小或者match_parent MeasureSpec.AT_MOST指定了控件允许的最大的大小,例如layout_width或者layout_height是wrap_content,会给定一个最大允许的大小 MeasureSpec.UNSPECIFIED没有指定大小 通过mode和size,计算控件所需的大小,调用 1void setMeasuredDimension(int measuredWidth, int measuredHeight) 保存计算结果结果","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"关于Android签名的种种","slug":"关于Android签名的种种","date":"2015-04-15T16:00:00.000Z","updated":"2023-02-23T02:20:24.453Z","comments":true,"path":"2015/04/16/关于Android签名的种种/","link":"","permalink":"http://sw926.github.io/2015/04/16/%E5%85%B3%E4%BA%8EAndroid%E7%AD%BE%E5%90%8D%E7%9A%84%E7%A7%8D%E7%A7%8D/","excerpt":"","text":"创建一个新的证书 1234567891011121314151617181920$ keytool -genkeypair -alias "test" -keyalg "RSA" -keystore "test.keystore"Enter keystore password:Re-enter new password:What is your first and last name? [Unknown]: 张三What is the name of your organizational unit? [Unknown]: www.zhansan.comWhat is the name of your organization? [Unknown]: www.zhansan.comWhat is the name of your City or Locality? [Unknown]: BeijingWhat is the name of your State or Province? [Unknown]: BeijingWhat is the two-letter country code for this unit? [Unknown]: CNIs CN=张三, OU=www.zhansan.com, O=www.zhansan.com, L=Beijing, ST=Beijing, C=CN correct? [no]: yEnter key password for <test> (RETURN if same as keystore password): -genkeypair:生成一对非对称密钥;-alias:指定密钥对的别名,该别名是公开的;-keyalg:指定加密算法,本例中的采用通用的RAS加密算法;-keystore:密钥库的路径及名称,不指定的话,默认在操作系统的用户目录下生成一个”.keystore”的文件 查看证书库 12345678910 keytool -list -keystore test.keystoreEnter keystore password:Keystore type: JKSKeystore provider: SUNYour keystore contains 1 entrytest, Apr 16, 2015, PrivateKeyEntry,Certificate fingerprint (SHA1): 88:AB:10:48:04:9E:2A:71:10:BD:1E:94:83:80:67:16:01:E2:47:11 导出到证书文件 123$ keytool -export -alias test -file test.crt -keystore test.keystoreEnter keystore password:Certificate stored in file <test.crt> 导入证书文件 12345$ keytool -import -keystore test.keystore -file test.crtEnter keystore password:Certificate already exists in keystore under alias <test>Do you still want to add it? [no]: yCertificate was added to keystore 查看证书的信息 123456789101112131415161718192021$ keytool -printcert -file "test.crt"Owner: CN=张三, OU=www.zhansan.com, O=www.zhansan.com, L=Beijing, ST=Beijing, C=CNIssuer: CN=张三, OU=www.zhansan.com, O=www.zhansan.com, L=Beijing, ST=Beijing, C=CNSerial number: 5f6822b2Valid from: Thu Apr 16 12:37:01 CST 2015 until: Wed Jul 15 12:37:01 CST 2015Certificate fingerprints: MD5: 0C:4C:4F:8C:5D:65:89:0E:05:E4:F1:2B:EE:E2:A6:96 SHA1: 88:AB:10:48:04:9E:2A:71:10:BD:1E:94:83:80:67:16:01:E2:47:11 SHA256: 08:25:05:C9:5A:9E:6A:94:DB:69:37:29:45:5F:E7:AE:F2:91:06:AE:43:DA:87:7A:75:1F:FA:CB:7A:C2:94:30 Signature algorithm name: SHA256withRSA Version: 3Extensions:#1: ObjectId: 2.5.29.14 Criticality=falseSubjectKeyIdentifier [KeyIdentifier [0000: 72 73 55 78 1D 5F 6E 39 D4 D7 ED 3E 5C 2F B7 32 rsUx._n9...>\\/.20010: E6 E2 AD 45 ...E]] 删除一个条目 1$ keytool -delete -keystore test.keystore -alias test 有时候如果App中集成了微博或者微信等分享功能,使用的时候会验证App的签名,如果想要在Eclipse中调试的时候也能进行分享,就需要将生产证书修改为调试证书。调试证书的alias为androiddebugkey,密码为android修改证书别名 1$ keytool -changealias -alias test -destalias androiddebugkey -keystore test.keystore 结果如下 12345678910$ keytool -list -keystore test.keystoreEnter keystore password:Keystore type: JKSKeystore provider: SUNYour keystore contains 1 entryandroiddebugkey, Apr 16, 2015, PrivateKeyEntry,Certificate fingerprint (SHA1): ED:7D:96:5E:A8:C1:D7:4F:42:97:91:A4:57:BC:44:82:65:65:18:14 修改证书密码 1$ keytool -keypasswd -alias androiddebugkey -keypass 234567 -new android -storepass 123456 -keystore test.keystore 修改证书库密码 1$ keytool -storepasswd -storepass 123456 -new android -keystore test.keystore 签名apk 1$ jarsigher -verbose -keystore test.keystore test.apk test","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Android 黑色主题的Logcat Color","slug":"Android 黑色主题的Logcat color","date":"2015-04-09T16:00:00.000Z","updated":"2023-02-23T02:20:24.453Z","comments":true,"path":"2015/04/10/Android 黑色主题的Logcat color/","link":"","permalink":"http://sw926.github.io/2015/04/10/Android%20%E9%BB%91%E8%89%B2%E4%B8%BB%E9%A2%98%E7%9A%84Logcat%20color/","excerpt":"","text":"Info #629655 Debug #6897BB Warning #CC7832 Error #BC3F3C Assert #FF0000 或者 #9876AA 打开Preference,搜索logcat,点击save as,新建一个Schema name 取消勾选 然后就可以修改颜色了","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Android Studio","slug":"Android-Studio","permalink":"http://sw926.github.io/tags/Android-Studio/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"Android Studio工程使用Grade Wrapper","slug":"Android Studio工程使用Grade Wrapper","date":"2015-04-06T16:00:00.000Z","updated":"2023-02-23T02:20:24.453Z","comments":true,"path":"2015/04/07/Android Studio工程使用Grade Wrapper/","link":"","permalink":"http://sw926.github.io/2015/04/07/Android%20Studio%E5%B7%A5%E7%A8%8B%E4%BD%BF%E7%94%A8Grade%20Wrapper/","excerpt":"","text":"配置Gradle的环境很简单,从网上下载Gradle包,解压到任意文件夹,将bin目录添加到环境变量,输入gradle -v,如果能正确看到版本号,说明gradle配置成功 1234567891011121314$ gradle -v------------------------------------------------------------Gradle 2.3------------------------------------------------------------Build time: 2015-02-16 05:09:33 UTCBuild number: noneRevision: 586be72bf6e3df1ee7676d1f2a3afd9157341274Groovy: 2.3.9Ant: Apache Ant(TM) version 1.9.3 compiled on December 23 2013JVM: 1.7.0_71 (Oracle Corporation 24.71-b01)OS: Mac OS X 10.10.2 x86_64 在导入工程的时候选择Use local gradle distribution,可以使用本地的Gradle,Android Studio会忽略Gradle Wrapper。 但是推荐使用Gradle Wrapper,Gradle Wrapper为工程指定了一个Gradle版本,导入工程的时候,会自动下载对应版本的Gradle,这样在team中就不会出现多个版本的Gradle。 使用Android Studio新建工程的时候会自动生成Wrapper,在根目录下生成一个gradle文件夹,里面有gradle-wrapper.jar和gradle-wrapper.properties两个文件,gradle-wrapper.properties内容如下 #Tue Apr 07 14:27:52 CST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\\://services.gradle.org/distributions/gradle-2.3-bin.zip distributionUrl指定了gradle的下载地址 如果工程根目录中没有gradle这个文件夹,可以使用命令生成wrapper 1$ gradle init wrapper 使用的是你本地gradle的版本 如果想指定gradle的版本,在build.gradle中添加一个task task wrapper(type: Wrapper) { gradleVersion = '2.2.1' } 执行 1$ gradle wrapper wrapper会修改为刚才指定的版本 使用了gradle wrapper,在命令行下,应该使用gradlew,例如打包的时候,应该 1$ ./gradlew assembleRelease 这样使用的时wrapper指定的gradle版本进行编译。 导入工程的时候,Gradle project应该选择工程的根目录,有时候后面会跟上gradle,应该去掉,然后选择Use default gradle wrapper(recommended) 点击OK,然后就悲剧的开始读条,显示正在下载gradle。。。 一个gradle的二进制zip包有40多个M,没有一个好翻墙渠道,那要下载好一会了,而且只读条,没有进度,不知道什么时候下载好,这个忍不了!mac下,gradle的wrapper文件在**~/.gradle/wrapper**目录下,目录结构如下 123456789[~/.gradle/wrapper] $ tree.└── dists └── gradle-2.3-bin └── a48v6zq5mdp1uyn9rwlj56945 ├── gradle-2.3-bin.zip.lck └── gradle-2.3-bin.zip.part3 directories, 2 files 这是下载到一半的情况,我实在忍受不了,说以强制退出了。通过上面的目录结构,结合 https://services.gradle.org/distributions/gradle-2.3-bin.zip 可以推断出gradle wrapper在下载的时候临时文件的保存位置,a48v6zq5mdp1uyn9rwlj56945是url的hash,至于怎么得出的,通过分析gradle的源码,找到了urlhash的计算方法 12345678910public static String getHash(String string) { try { MessageDigest messageDigest = MessageDigest.getInstance("MD5"); byte[] bytes = string.getBytes(); messageDigest.update(bytes); return new BigInteger(1, messageDigest.digest()).toString(36); } catch (Exception e) { throw new RuntimeException("Could not hash input string.", e); } } 下载对应的gradle的zip包,拷贝到wrapper目录,在工程目录下执行./gradlew assembleRelease,应该不会看到下载gradle的信息了。","categories":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}],"tags":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/tags/Android/"},{"name":"Gradle","slug":"Gradle","permalink":"http://sw926.github.io/tags/Gradle/"}],"keywords":[{"name":"Android","slug":"Android","permalink":"http://sw926.github.io/categories/Android/"}]},{"title":"使用Jekyll和github搭建博客","slug":"使用Jekyll和github搭建博客","date":"2015-04-02T16:00:00.000Z","updated":"2023-02-23T02:20:24.452Z","comments":true,"path":"2015/04/03/使用Jekyll和github搭建博客/","link":"","permalink":"http://sw926.github.io/2015/04/03/%E4%BD%BF%E7%94%A8Jekyll%E5%92%8Cgithub%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2/","excerpt":"","text":"经过两天的摸索,终于在github上成功搭建了一个简单的博客。对于一个懂一点git,会搭ruby环境,没做过网页的Android程序猿来说,能搭建起了,已经很满足了。 搭建环境之前,首先要配置好ruby,bunlder 新建文件夹 1$ mkdir myblog 在根目录下添加一个Gemfile,输入以下内容 12source 'https://rubygems.org'gem 'github-pages' 在根目录执行 1$ bundle install 我的命令执行如下 123456789101112131415161718$ bundle installUsing RedCloth 4.2.9Using i18n 0.7.0Using json 1.8.2Using minitest 5.5.1Using thread_safe 0.3.5Using tzinfo 1.2.2Using activesupport 4.2.1Using blankslate 2.1.2.4Using hitimes 1.2.2Using timers 4.0.1Using celluloid 0.16.0Using fast-stemmer 1.0.2Using classifier-reborn 2.0.3Using coffee-script-source 1.9.1...Your bundle is complete!Use `bundle show [gemname]` to see where a bundled gem is installed. 至此,可以认为jekyll已经搭好了 之后的操作参考了这个帖子 搭建一个免费的,无限流量的Blog----github Pages和Jekyll入门 参考上面的帖子,新建了_layout,_posts,index.html,先不要push到github 在根目录运行 1$ jekyll serve 用浏览器打开http://localhost:4000 如果能正常看到网页,整个blog就已经搭建成功了 虽然能够正常运行,但页面太简陋了,所以从github拷贝了一个模板,方法就是利用gitbub pages生成一个默认的页面,然后将这个页面修改成为模板。 上传到github的时候,只要把生成的静态网页上传就行 123456$ cd _site$ git init$ git add --all$ git commit "first commit"$ git remote add origin "你的git Repositories地址"$ git push -u origin master 打开{username}.github.io,就能看到刚才写的博客","categories":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}],"tags":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/tags/Other/"}],"keywords":[{"name":"Other","slug":"Other","permalink":"http://sw926.github.io/categories/Other/"}]}]}