浩子

学无止境


  • 首页

  • 标签

  • 分类

  • 归档

开发工具整理(转)

发表于 2018-07-14
字数统计: 401 | 阅读时长 ≈ 2

辅助开发工具

转自: https://github.com/389273716/android-skill-summary

  1. json格式化 - 在线工具
  2. android 不透明度16进制值
  3. 在线代码格式化
  4. 手动集成 | Bugtags 是移动时代首选 Bug 管理系统,针对不同的使用场景,Bugtags 具有以下强大特性: - 移动时代首选 Bug 管理系统 | 简单·高效·智能·云端管理
  5. 墨刀 - 猿理
  6. Markdown 语法说明(简体中文版)
  7. 多态-vpn
  8. getlantern/lantern: Open Internet for everyone. Lantern is a free application that delivers fast, reliable and secure access to the open Internet for users in censored regions. It uses a variety of techniques to stay unblocked, including domain fronting, p2p, and pluggable transports.
  9. facebook的Android调试工具Stetho介绍 - forlong401的专栏–有问题上:http://www.androidren.com - 博客频道 - CSDN.NET
  10. 一些浏览器插件,比如同步手机屏幕到pc
  11. 代码搜索
  12. 使用Fiddler调试手机页面请求 - 为程序员服务
  13. searchcode搜索代码
  14. debug插件 debug-bottle
  15. Chrome插件,Chrome商店,谷歌浏览器插件下载 - Chrome插件网
  16. skyhacker2/SQLiteOnWeb-Android: Manage you Sqlite Database in browser
  17. 利用Jenkins玩转Android自动打包发包
  18. 探索 Firebase 在 Android 和 iOS 的使用: 分析
  19. 快速提高Android开发效率的Web工具
  20. Android代码规范利器: Checkstyle
  21. Android Studio +Vim
  22. 一招鲜,吃遍天!这是一篇专供收藏的 Chrome 插件推荐
  23. Android自定义Lint实践
  24. Android Debug Database数据库debug工具
  25. 如何在Windows下像Mac一样优雅开发?
  26. 告诉你三个我用的高效软件工具
  27. 震惊!如此多的 Android 开发技巧!

Android debug 神器

发表于 2018-07-12
字数统计: 1,357 | 阅读时长 ≈ 6

🍼Debug Bottle

  • 简易的HTTP请求嗅探
    转自 https://github.com/kiruto/debug-bottle

Android Java / Kotlin 程序员开发调试工具。Debug Bottle的所有功能均建立在App的debug版本中,不会对release版本产生任何影响。Debug Bottle旨在提高开发效率,把控App质量。

  • 开发文档
  • CHANGELOG
  • TODO

Demo App可在Google Play中下载:

功能

  • 简易的HTTP请求嗅探
  • 3D化Activity视图
  • Shared Preferences编辑器
  • 使用Strict mode调试软件
  • 截获App崩溃,收集崩溃日志
  • 轻松找到可能出现的内存泄漏代码
  • 找到可能出现的UI卡顿代码
  • 简易创造Activity入口,及测试Runnable

Http监听

通过OkHttp的拦截器监听通过App的Http请求,并记录在Debug Bottle的日志中。记录过的日志可以通过分享按钮分享给其他开发者。

3D化视图

通过设置打开“3D”功能,可以更直观地看到当前Activity的layout结构。
本功能支持单点手势旋转,双点手势缩放和位移。

Shared Preferences编辑器

通过Debug Bottle可以简单地编辑App用到的所有Shared Preferences。

使用Strict Mode

Debug Bottle可以在运行时启用或禁用Strict Mode。StrictMode最常用来捕捉应用程序的主线程,它将报告与线程及虚拟机相关的策略违例。一旦检测到策略违例(policy violation),你将获得警告,其包含了一个栈trace显示你的应用在何处发生违例。除了主线程,我们还可以在Handler,AsyncTask,AsyncQueryHandler,IntentService等API中使用StrictMode。更多使用方法介绍,参见官方开发者文档。

崩溃日志

收集所有崩溃的信息,存储成日志文件。

使用Leak Canary

Debug Bottle中编译进了Leak Canary,所有Leak Canary的功能都可以使用了,并且可以通过Debug Bottle来控制开关。Leak Canary的使用方法请参考官方Wiki。

使用Block Canary

加入了支持Kotlin的Block Canary,可以监控UI线程卡死。

进入一切Activity并注入参数

简单的Activity/Runnable入口。可以随心所欲进入任何一个Activity,使用任何参数,甚至可以向App中注入想要测试的Intent。

使用方法

安装嵌入Debug Bottle的App后,你将看到初你App外的新图标🍼。点击进入Debug面板。

搭建方法

1. 加入Gradle依赖

首先在工程Gradle文件中添加snapshot源:

1
2
3
4
5
6
7
8
allprojects {
repositories {
...
maven {
url "https://oss.sonatype.org/content/repositories/snapshots"
}
}
}

然后在主模块中(Application类所在模块)加入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
dependencies {
debugCompile 'com.exyui.android:debug-bottle-runtime:1.1.1'

// 如果你的工程是Java工程,使用此依赖
releaseCompile 'com.exyui.android:debug-bottle-noop-java:1.1.1'
testCompile 'com.exyui.android:debug-bottle-noop-java:1.1.1'

// 如果你的工程是Kotlin工程,使用此依赖
releaseCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.1.1'
testCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.1.1'

compile 'com.android.support:appcompat-v7:23.2.0+'
}

Debug Bottle不仅支持API 23+,还可以支持API 22. 若想使用API 22,请按照下面方式加入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
dependencies {
debugCompile 'com.exyui.android:debug-bottle-runtime:1.0.6-support22'

// 如果你的工程是Java工程,使用此依赖
releaseCompile 'com.exyui.android:debug-bottle-noop-java:1.0.6-support22'
testCompile 'com.exyui.android:debug-bottle-noop-java:1.0.6-support22'

// 如果你的工程是Kotlin工程,使用此依赖
releaseCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.0.6-support22'
testCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.0.6-support22'

compile 'com.android.support:appcompat-v7:22+'
}

若只需支持API23,请按照下面方式加入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
dependencies {
debugCompile 'com.exyui.android:debug-bottle-runtime:1.0.6-support23'

// 如果你的工程是Java工程,使用此依赖
releaseCompile 'com.exyui.android:debug-bottle-noop-java:1.0.6-support23'
testCompile 'com.exyui.android:debug-bottle-noop-java:1.0.6-support23'

// 如果你的工程是Kotlin工程,使用此依赖
releaseCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.0.6-support23'
testCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.0.6-support23'

compile 'com.android.support:appcompat-v7:23+'
}

2. 编辑Manifest

加入Debug Bottle的主Activity:

1
2
3
4
<activity
android:name="com.exyui.android.debugbottle.components.DTDrawerActivity"
android:theme="@style/Theme.AppCompat.Light"
android:label="调试工具"/>

“调试工具”是Debug Bottle在Android launch pad中显示的名称, 可以随意命名。

3. 在Application中插入Debug Bottle代码

首先实现Block Canary运行必要的上下文类:

1
public class AppBlockCanaryContext extends BlockCanaryContext {...}

然后在Application的onCreate中插入代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyApplication extends Application{
@Override
public void onCreate() {
super.onCreate();
DTInstaller.install(this)
.setBlockCanary(new AppBlockCanaryContext(this))
.setOkHttpClient(httpClient)
.setInjector("your.package.injector.ContentInjector")
.setPackageName("your.package")
.enable()
.run();
}
}

或者如果你的工程是Kotlin,你还可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
class MyApplication: Application() {
override fun onCreate() {
super.onCreate()
DTInstaller.install(this)
.setBlockCanary(AppBlockCanaryContext(this))
.setOkHttpClient(httpClient)
.setInjector("your.package.injector.ContentInjector")
.setPackageName("your.package")
.enable()
.run()
}
}

收工。

相关链接

  • Leak Canary
  • Android Performance Monitor
  • Scalpel

License

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Debug Bottle

Copyright 2016 Yuriel (http://exyui.com).

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Debug Bottle 功能实现是基于以下项目的派生:

  • Apache License 2.0
    • Android Performance Monitor
    • Leak Canary
    • Scalpel
    • Bubbles for Android
    • Takt

Java 反射机制

发表于 2018-07-11 | 分类于 Java
字数统计: 11,200 | 阅读时长 ≈ 49

第 16 章 反射(Reflection)

2016-09-29 转自:https://github.com/JustinSDK/JavaSE6Tutorial/blob/master/docs/CH16.md

Java 提供的反射机制允许您于执行时期动态加载类别、检视类别信息、生成对象或操作生成的对象,要举反射机制的一个应用实例,就是在集成开发环境中所提供的方法提示或是类别检视工具,另外像 JSP 中的 JavaBean 自动收集请求信息也使用到反射,而一些软件开发框架(Framework)也常见到反射机制的使用,以达到动态加载用户自定义类别的目的。

即使您暂时用不到反射机制,也建议您花时间看看这个章节,藉由对反射机制的认识,您可以了解 Java 中是如何加载类别的,而且了解到每个被载入的类别在 JVM 中,都以 Class 类别的一个实例存在的事实。


16.1 类别载入与检视

即使您拿到一个类别并对它一无所知,但其实它本身就包括了许多信息,Java 在需要使用到某个类别时才会将类别加载,并在 JVM 中以一个 java.lang.Class 的实例存在,从 Class 实例开始,您可以获得类别的许多讯息。

16.1.1 简介 Class 与类别载入

Java 在真正需要使用一个类别时才会加以加载,而不是在程序启动时就加载所有的类别,因为大多数的使用者都只使用到应用程序的部份资源,在需要某些功能时才加载某些资源,可以让系统的资源运用更有效率(Java 本来就是为了资源有限的小型设备而设计的,这样的考虑是必然的)。

一个 java.lang.Class 对象代表了 Java 应用程序在运行时所加载的类别或接口实例,也用来表达 enum(属于类别的一种)、 annotation(属于接口的一种)、数组、原生型态(Primitive type)、void;Class 类别没有公开的(public)建构方法,Class 对象是由 JVM 自动产生,每当一个类别被加载时,JVM 就自动为其生成一个 Class 对象。

您可以透过 Object 的 getClass() 方法来取得每一个对象对应的 Class 对象,或者是透过 “class” 常量(Class literal),在取得 Class 对象之后,您就可以操作 Class 对象上的一些公开方法来取得类别的基本信息,范例 16.1 简单的使用 getClass() 方法来取得 String 类别的 Class 实例,并从中得到 String 的一些基本信息。

范例 16.1 ClassDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package onlyfun.caterpillar;

public class ClassDemo {
public static void main(String[] args) {
String name = "caterpillar";
Class stringClass = name.getClass();
System.out.println("类别名称:" +
stringClass.getName());
System.out.println("是否为接口:" +
stringClass.isInterface());
System.out.println("是否为基本型态:" +
stringClass.isPrimitive());
System.out.println("是否为数组对象:" +
stringClass.isArray());
System.out.println("父类别名称:" +
stringClass.getSuperclass().getName());
}
}

执行结果:

类别名称:java.lang.String
是否为接口:false
是否为基本型态:false
是否为数组对象:false
父类别名称:java.lang.Object

您也可以直接使用以下的方式来取得 String 类别的 Class 对象:

Class stringClass = String.class;

Java 在真正需要类别时才会加载类别,所谓「真正需要」通常指的是要使用指定的类别生成对象时(或是用户指定要加载类别时,例如使用 Class.forName() 加载类别,或是使用 ClassLoader 的 loadClass() 加载类别,稍后都会说明)。使用类别名称来宣告参考名称并不会导致类别的加载,可以设计一个测试类别的印证这个说法。

范例 16.2 TestClass.java

1
2
3
4
5
6
7
package onlyfun.caterpillar;

public class TestClass {
static {
System.out.println("类别被载入");
}
}

在范例中定义了一个静态区块,「默认」在类别第一次被加载时会执行静态区块(说默认的原因,是因为可以设定加载类别时不执行静态区块,使用 Class 生成对象时才执行静态区块,稍后会介绍),藉由在文本模式下显示讯息,您可以了解类别何时被加载,可以使用范例 16.3 来测试类别加载时机。

范例 16.3 LoadClassTest.java

1
2
3
4
5
6
7
8
9
10
package onlyfun.caterpillar;

public class LoadClassTest {
public static void main(String[] args) {
TestClass test = null;
System.out.println("宣告TestClass参考名称");
test = new TestClass();
System.out.println("生成TestClass实例");
}
}

执行结果:

宣告TestClass参考名称
类别被载入
生成TestClass实例

从执行结果中可以看出,宣告参考名称并不导致 TestClass 类别被加载,而是在使用 “new” 生成对象时才会加载类别。

Class 的讯息是在编译时期就被加入至 .class 档案中,这是 Java 支持执行时期型别辨识(RTTI,Run-Time Type Information或Run-Time Type Identification)的一种方式,在编译时期编译程序会先检查对应的 .class 档案,而执行时期JVM在使用某类别时,会先检查对应的 Class 对象是否已经加载,如果没有加载,则会寻找对应的 .class 档案并载入,一个类别在 JVM 中只会有一个 Class 实例,每个类别的实例都会记得自己是由哪个 Class 实例所生成,您可以使用 getClass() 或 .class 来取得 Class 实例。

每个对象会记得生成它的 Class 实例

图 16.1 每个对象会记得生成它的 Class 实例

在 Java 中,数组也是一个对象,也有其对应的 Class 实例,这个对象是由具相同元素与维度的数组所共享,而基本型态像是 boolean, byte, char, short, int, long, float, double 以及关键词 void,也都有对应的 Class 对象,您可以用类别常量(Class literal)来取得这些对象。

范例 16.4 ClassDemo2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package onlyfun.caterpillar;

public class ClassDemo2 {
public static void main(String[] args) {
System.out.println(boolean.class);
System.out.println(void.class);

int[] iarr = new int[10];
System.out.println(iarr.getClass().toString());

double[] darr = new double[10];
System.out.println(darr.getClass().toString());
}
}

执行结果:

boolean
void
class [I
class [D

在 Java 中数组确实是以对象的形式存在,其对应的类别是由 JVM 自动生成,当您使用 toString() 来显示数组对象的描述时,[表示为数组型态,并加上一个型态代表字,范例中I表示是一个 int 数组,而 D 表示是一个 double 数组,16.2.4 还会对数组对象加以讨论。

16.1.2 使用 Class.forName() 载入类别

在一些应用中,您无法事先知道使用者将加载什么类别,而必须让使用者指定类别名称以加载类别,您可以使用 Class 的静态 forName() 方法实现动态加载类别,范例 16.5 是个简单示范,可以让您可以指定类别名称来获得类别的相关信息。

范例 16.5 ForNameDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package onlyfun.caterpillar;

public class ForNameDemo {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
System.out.println("类别名称:" +
c.getName());
System.out.println("是否为接口:" +
c.isInterface());
System.out.println("是否为基本型态:" +
c.isPrimitive());
System.out.println("是否为数组:" + c.isArray());
System.out.println("父类别:" +
c.getSuperclass().getName());
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("没有指定类别名称");
}
catch(ClassNotFoundException e) {
System.out.println("找不到指定的类别");
}
}
}

在指定类别给 forName() 方法后,如果找不到指定的类别,会丢出 ClassNotFoundException 例外,一个的执行结果如下:

java onlyfun.caterpillar.ForNameDemo java.util.Scanner
类别名称:java.util.Scanner
是否为接口:false
是否为基本型态:false
是否为数组:false
父类别:java.lang.Object

Class 的静态 forName() 方法有两个版本,范例16.5所示范的是只指定类别名称的版本,而另一个版本可以让您指定类别名称、加载类别时是否执行静态区块、指定类别加载器(Class loader):

static Class forName(String name, boolean initialize, ClassLoader loader)

之前曾经说过,预设上在加载类别的时候,如果类别中有定义静态区块则会执行它,您可以使用 forName() 的第二个版本,将 initialize 设定为 false,如此在加载类别时并不会马上执行静态区块,而会在使用类别建立对象时才执行静态区块,为了印证,您可以先设计一个测试类别。

范例 16.6 TestClass2.java

1
2
3
4
5
6
7
package onlyfun.caterpillar;

public class TestClass2 {
static {
System.out.println("[执行静态区块]");
}
}

范例 16.6 中只定义了静态区块显示一段讯息,以观察静态区块何时被执行,您可以设计范例 16.7 使用第一个版本的 forName() 方法。

范例 16.7 ForNameDemoV1.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package onlyfun.caterpillar;

public class ForNameDemoV1 {
public static void main(String[] args) {
try {
System.out.println("载入TestClass2");
Class c = Class.forName("onlyfun.caterpillar.TestClass2");

System.out.println("使用TestClass2宣告参考名称");
TestClass2 test = null;

System.out.println("使用TestClass2建立对象");
test = new TestClass2();
}
catch(ClassNotFoundException e) {
System.out.println("找不到指定的类别");
}
}
}

执行结果如下:

载入TestClass2
[执行静态区块]
使用TestClass2宣告参考名称
使用TestClass2建立对象

从执行结果中可以看到,第一个版本的 forName() 方法在加载类别之后,默认会马上执行静态区块,来看看范例 16.8 中使用第二个版本的 forName() 方法会是如何。

范例 16.8 ForNameDemoV2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package onlyfun.caterpillar;

public class ForNameDemoV2 {
public static void main(String[] args) {
try {
System.out.println("载入TestClass2");
Class c = Class.forName(
"onlyfun.caterpillar.TestClass2",
false, // 加载类别时不执行静态方法
Thread.currentThread().getContextClassLoader());

System.out.println("使用TestClass2宣告参考名称");
TestClass2 test = null;

System.out.println("使用TestClass2建立对象");
test = new TestClass2();
}
catch(ClassNotFoundException e) {
System.out.println("找不到指定的类别");
}
}
}

执行结果如下:

载入TestClass2
使用TestClass2宣告参考名称
使用TestClass2建立对象
[执行静态区块]

由于使用第二个版本的 forName() 方法时,设定 initialize 为 false,所以加载类别时并不会马上执行静态区块,而会在使用类别建立对象时才去执行静态区块,第二个版本的 forName() 方法会需要一个类别加载器(Class loader),范例中所使用的是主线程的类别加载器,16.1.4 还会详细介绍 Java 中的类别加载器机制。

16.1.3 从 Class 中获取信息

Class 对象表示所加载的类别,取得 Class 对象之后,您就可以取得与类别相关联的信息,像是套件(package)(别忘了 package 也是类别名称的一部份)、建构方法、方法成员、数据成员等的讯息,而每一个讯息,也会有相应的类别型态,例如套件的对应型态是 java.lang.Package,建构方法的对应型态是 java.lang.reflect.Constructor,方法成员的对应型态是 java.lang.reflect.Method,数据成员的对应型态是 java.lang.reflect.Field 等。

来看个简单的示范,范例 16.9 可以让您取得所指定类别上的套件名称。

范例 16.9 ClassInfoDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package onlyfun.caterpillar;

public class ClassInfoDemo {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
Package p = c.getPackage();
System.out.println(p.getName());
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("没有指定类别");
}
catch(ClassNotFoundException e) {
System.out.println("找不到指定类别");
}
}
}

执行结果:

java onlyfun.caterpillar.ClassInfoDemo java.util.ArrayList
java.util

您可以分别取回 Field、Constructor、Method等对象,分别代表数据成员、建构方法与方法成员,范例16.10 简单的实作了取得类别基本信息的程序。

范例 16.10 SimpleClassViewer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package onlyfun.caterpillar;

import java.lang.reflect.*;

public class SimpleClassViewer {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
// 取得套件代表对象
Package p = c.getPackage();

System.out.printf("package %s;%n", p.getName());

// 取得型态修饰,像是class、interface
int m = c.getModifiers();

System.out.print(Modifier.toString(m) + " ");
// 如果是接口
if(Modifier.isInterface(m)) {
System.out.print("interface ");
}
else {
System.out.print("class ");
}

System.out.println(c.getName() + " {");

// 取得宣告的数据成员代表对象
Field[] fields = c.getDeclaredFields();
for(Field field : fields) {
// 显示权限修饰,像是public、protected、private
System.out.print("\t" +
Modifier.toString(field.getModifiers()));
// 显示型态名称
System.out.print(" " +
field.getType().getName() + " ");
// 显示数据成员名称
System.out.println(field.getName() + ";");
}

// 取得宣告的建构方法代表对象
Constructor[] constructors =
c.getDeclaredConstructors();
for(Constructor constructor : constructors) {
// 显示权限修饰,像是public、protected、private
System.out.print("\t" +
Modifier.toString(
constructor.getModifiers()));
// 显示建构方法名称
System.out.println(" " +
constructor.getName() + "();");
}
// 取得宣告的方法成员代表对象
Method[] methods = c.getDeclaredMethods();
for(Method method : methods) {
// 显示权限修饰,像是public、protected、private
System.out.print("\t" +
Modifier.toString(
method.getModifiers()));
// 显示返回值型态名称
System.out.print(" " +
method.getReturnType().getName() + " ");
// 显示方法名称
System.out.println(method.getName() + "();");
}
System.out.println("}");
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("没有指定类别");
}
catch(ClassNotFoundException e) {
System.out.println("找不到指定类别");
}
}
}

执行结果:

package java.util;
public class java.util.ArrayList {
        private static final long serialVersionUID;
        private transient [Ljava.lang.Object; elementData;
        private int size;
        public java.util.ArrayList();
        public java.util.ArrayList();
        public java.util.ArrayList();
        public boolean add();
        public void add();
        public java.lang.Object clone();
        public void clear();
        public boolean contains();
        public int indexOf();
        略...
}

一些类别查看器的实作原理基本上就是范例 16.10 所示范的,当然还可以取得更多的信息,您可以参考 Class 的在线 API 文件得到更多的讯息。

16.1.4 简介类别加载器

Java 在需要使用类别的时候,才会将类别加载,Java 的类别载入是由类别加载器(Class loader)来达到的。

当您在文本模式下执行 java XXX 指令后,java 执行程序会尝试找到 JRE 安装的所在目录,然后寻找 jvm.dll(默认是在JRE目录下bin\client目录中),接着启动 JVM 并进行初始化动作,接着产生 Bootstrap Loader,Bootstrap Loader 会加载 Extended Loader,并设定 Extended Loader 的 parent 为 Bootstrap Loader,接着 Bootstrap Loader 会加载 System Loader,并将 System Loader 的 parent 设定为 Extended Loader。

Bootstrap Loader 通常由 C 撰写而成;Extended Loader 是由 Java 所撰写而成,实际是对应于 sun.misc.Launcher\$ExtClassLoader(Launcher 中的内部类别);System Loader 是由 Java 撰写而成,实际对应于sun.misc. Launcher\$AppClassLoader(Launcher 中的内部类别)。

图 16.2 是 java 程序启动与加载类别的顺序图,也就是所谓的「类别加载器阶层架构」。

Java 类别加载器阶层架构

图 16.2 Java 类别加载器阶层架构

Bootstrap Loader 会搜寻系统参数 sun.boot.class.path 中指定位置的类别,默认是 JRE classes 下之 档案,或 lib 目录下 .jar 档案中(例如 rt.jar)的类别并加载,您可以使用 System.getProperty(“sun.boot.class.path”) 陈述来显示 sun.boot.class.path 中指定的路径,例如在我的计算机中显示的是以下的路径:

C:\Program Files\Java\jre1.5.0_03\lib\rt.jar;
C:\Program Files\Java\jre1.5.0_03\lib\i18n.jar;
C:\Program Files\Java\jre1.5.0_03\lib\sunrsasign.jar;
C:\Program Files\Java\jre1.5.0_03\lib\jsse.jar;
C:\Program Files\Java\jre1.5.0_03\lib\jce.jar;
C:\Program Files\Java\jre1.5.0_03\lib\charsets.jar;
C:\Program Files\Java\jre1.5.0_03\classes

Extended Loader(sun.misc.Launcher$ExtClassLoader)是由 Java 撰写而成,会搜寻系统参数 java.ext.dirs 中指定位置的类别,默认是 JRE 目录下的 lib\ext\classes 目录下的 .class 档案,或 lib\ext 目录下的 .jar 档案中(例如 rt.jar)的类别并加载,您可以使用 System.getProperty(“java.ext.dirs”) 陈述来显示 中指定的路径,例如在我的计算机中显示的是以下的路径:

C:\Program Files\Java\jre1.5.0_03\lib\ext

System Loader(sun.misc.Launcher$AppClassLoader)是由 Java 撰写而成,会搜寻系统参数 java.class.path 中指定位置的类别,也就是 Classpath 所指定的路径,默认是目前工作路径下的 .class 档案,您可以使用 System.getProperty(“java.class.path”) 陈述来显示 java.class.path 中指定的路径,在使用 java 执行程序时,您也可以加上 -cp 来覆盖原有的 Classpath 设定,例如:

java –cp ./classes SomeClass

Bootstrap Loader 会在 JVM 启动之后产生,之后它会加载 Extended Loader 并将其 parent 设为 Bootstrap Loader,然后 Bootstrap Loader 再加载 System Loader 并将其 parent 设定为 ExtClassLoader,接着 System Loader 开始加载您指定的类别,在加载类别时,每个类别加载器会先将加载类别的任务交由其 parent,如果 parent 找不到,才由自己负责加载,所以在加载类别时,会以 Bootstrap Loader→Extended Loader→System Loader 的顺序来寻找类别,如果都找不到,就会丢出 NoClassDefFoundError。

类别加载器在 Java 中是以 java.lang.ClassLoader 型态存在,每一个类别被载入后,都会有一个 Class 的实例来代表,而每个 Class 的实例都会记得自己是由哪个 ClassLoader 加载的,可以由 Class 的 getClassLoader() 取得加载该类别的 ClassLoader,而从 ClassLoader 的 getParent() 方法可以取得自己的 parent,图 16.3 显示了一个自定义的 SomeClass 实例与 Class、ClassLoader 及各 parent 的关系。

对象、Class、ClassLoader 与 parent 的关系

图 16.3 对象、Class、ClassLoader 与 parent 的关系

范例 16.11 示范了图 16.3 的一个实际例子。

范例 16.11 SomeClass.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package onlyfun.caterpillar;

public class SomeClass {
public static void main(String[] args) {
// 建立SomeClass实例
SomeClass some = new SomeClass();
// 取得SomeClass的Class实例
Class c = some.getClass();
// 取得ClassLoader
ClassLoader loader = c.getClassLoader();
System.out.println(loader);
// 取得父ClassLoader
System.out.println(loader.getParent());
// 再取得父ClassLoader
System.out.println(loader.getParent().getParent());
}
}

执行结果:

sun.misc.Launcher$AppClassLoader@82ba41
sun.misc.Launcher$ExtClassLoader@923e30
null

onlyfun.caterpillar.SomeClass 是个自定义类别,您在目前的工作目录下执行程序,首先 AppClassLoader 会将加载类别的任务交给 ExtClassLoader,而 ExtClassLoader 会将加载类别的任务交给 Bootstrap Loader,由于 Bootstrap Loader 在它的路径设定(sun.boot.class.path)下找不到类别,所以由 ExtClassLoader 来试着寻找,而 ExtClassLoader 在它的路径设定(java.ext.dirs)下也找不到类别,所以由 AppClassLoader 来试着寻找,AppClassLoader 最后在 Classpath(java.class.path)设定下找到指定的类别并加载。

在执行结果中可以看到,加载 SomeClass 的 ClassLoader 是 AppClassLoader,而 AppClassLoader 的 parent 是 ExtClassLoader,而 ExtClassLoader 的 parent 是 null,null 并不是表示 ExtClassLoader 没有设定 parent,而是因为 Bootstrap Loader 通常由 C 所撰写而成,在 Java 中并没有一个实际的类别来表示它,所以才会显示为 null。

如果把 SomeClass 的 .class 档案移至 JRE 目录下的 lib\ext\classes下(由于设定了套件,所以实际上 SomeClass.class 要放置在 JRE 目录下的 lib\ext\classes\onlyfun\caterpillar下),并重新(于任何目录下)执行程序,您会看到以下的讯息:

sun.misc.Launcher$ExtClassLoader@923e30
null
Exception in thread "main" java.lang.NullPointerException
        at onlyfun.caterpillar.SomeClass.main(SomeClass.java:15)

由于 SomeClass 这次可以在 ExtClassLoader 的设定路径下找到,所以会由 ExtClassLoader 来加载 SomeClass 类别,而 ExtClassLoader 的 parent 显示为 null,指的是它的 parent 是由 C 撰写而成的 Bootstrap Loader,因为没有实际的 Java 类别而表示为 null,所以再由 null 上尝试呼叫 getParent() 方法就会丢出 NullPointerException 例外。

如果再把 SomeClass 的 .class 档案移至 JRE 目录下的 classes 目录下(由于设定了套件,所以实际上 SomeClass.class 要放置在 JRE 目录下的 classes/onlyfun/caterpillar下),并重新(于任何目录下)执行程序,您会看到以下的讯息:

null
Exception in thread "main" java.lang.NullPointerException
        at onlyfun.caterpillar.SomeClass.main(SomeClass.java:13)

由于 SomeClass 这次可以在 Bootstrap Loader 的设定路径下找到,所以会由 Bootstrap Loader 来加载 SomeClass 类别,Bootstrap Loader 通常由 C 撰写而成,在 Java 中没有一个实际的类别来表示,所以显示为 null,因为表示为 null,所以再 由 null 上尝试呼叫 getParent() 方法就会丢出 NullPointerException 例外。

取得 ClassLoader 的实例之后,您可以使用它的 loadClass() 方法来加载类别,使用 loadClass() 方法加载别时,不会执行静态区块,静态区块的执行会等到真正使用类别来建立实例时,例如您可以改写范例 16.7 为范例 16.12。

范例 16.12 ForNameDemoV3.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package onlyfun.caterpillar;

public class ForNameDemoV3 {
public static void main(String[] args) {
try {
System.out.println("载入TestClass2");
ClassLoader loader = ForNameDemoV3.class.getClassLoader();
Class c = loader.loadClass("onlyfun.caterpillar.TestClass2");

System.out.println("使用TestClass2宣告参考名称");
TestClass2 test = null;

System.out.println("使用TestClass2建立对象");
test = new TestClass2();
}
catch(ClassNotFoundException e) {
System.out.println("找不到指定的类别");
}
}
}

从执行结果中可以看到,loadClass() 不会在加载类别时执行静态区块,而会在使用类别新建对象时才执行静态区块,结果如下所示:

载入TestClass2
使用TestClass2宣告参考名称
使用TestClass2建立对象
[执行静态区块]

16.1.5 使用自己的 ClassLoader

ExtClassLoader 与 AppClassLoader 都是 java.net.URLClassLoader 的子类别,您可以在使用 java 启动程序时,使用以下的指令来指定 ExtClassLoader 的搜寻路径:

java -Djava.ext.dirs=c:\workspace\ YourClass

可以在使用 java 启动程序时,使用 -classpath 或 -cp 来指定 AppClassLoader 的搜寻路径,也就是设定 Classpath:

java -classpath c:\workspace\ YourClass

ExtClassLoader 与 AppClassLoader 在程序启动后会在虚拟机中存在一份,您在程序运行过程中就无法再改变它的搜寻路径,如果在程序运行过程中,打算动态决定从其它的路径加载类别,就要产生新的类别加载器。

您可以使用 URLClassLoader 来产生新的类别加载器,它需要 java.net.URL 作为其参数来指定类别加载的搜寻路径,例如:

URL url = new URL("file:/d:/workspace/");
ClassLoader urlClassLoader =
                    new URLClassLoader(new URL[] {url});
Class c = urlClassLoader.loadClass("SomeClass");

由于 ClassLoader 是 Java SE 的标准API之一,可以在 rt.jar 中找到,因而会由 Bootstrap Loader 来载入 ClassLoader 类别,在新增了 ClassLoader 实例后,您可以使用它的 loadClass() 方法来指定要加载的类别名称,在新增 ClassLoader 时,会自动将新建的 ClassLoader 的 parent 设定为 AppClassLoader,并在每次加载类别时,先委托 parent 代为搜寻,所以上例中搜寻 SomeClass 类别时,会一路往上委托至 Bootstrap Loader 先开始搜寻,接着是 ExtClassLoader、AppClassLoader,如果都找不到,才使用新建的 ClassLoader 搜寻。

Java 的类别加载器阶层架构除了可以达到动态加载类别目的之外,还有着安全上的考虑,首先,因为每次寻找类别时都是委托 parent 开始寻找,所以除非有人可以侵入您的计算机,置换掉标准 Java SE API 与您自己安装的延伸套件,否则是不可能藉由撰写自己的类别加载器来载入恶意类别,以置换掉标准 Java SE API与您自己安装的延伸套件。

由于每次的类别载入是由子 ClassLoader 委托父 ClassLoader 先尝试加载,但父 lassLoader 看不到子 ClassLoader,所以同一阶层的子 ClassLoader 不会被误用,从而避免了加载错误类别的可能性,例如在图 16.4 中,您想从 YourClassLoader 来加载类别的话,类别加载器阶层不会看到 MaliciousClassLoader。

类别加载器阶层的安全设计

图 16.4 类别加载器阶层的安全设计

由同一个 ClassLoader 加载的类别档案,会只有一份Class实例,如果同一个类别档案是由两个不同的ClassLoader 载入,则会有两份不同的 Class 实例。注意这个说法,如果有两个不同的 ClassLoader 搜寻同一个类别,而在 parent 的 AppClassLoader 搜寻路径中就可以找到指定类别的话,则 Class 实例就只会有一个,因为两个不同的 ClassLoader 都是在委托父 ClassLoader 时找到该类别的,如果父 ClassLoader 找不到,而是由各自的 ClassLoader 搜寻到,则 Class 的实例会有两份。

范例 16.13 是个简单的示范,可用来测试加载路径与Class实例是否为同一对象。

范例 16.13 ClassLoaderDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package onlyfun.caterpillar;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class ClassLoaderDemo {
public static void main(String[] args) {
try {
// 测试路径
String classPath = args[0];
// 测试类别
String className = args[1];

URL url1 = new URL(classPath);
// 建立ClassLoader
ClassLoader loader1 =
new URLClassLoader(new URL[] {url1});
// 加载指定类别
Class c1 = loader1.loadClass(className);
// 显示类别描述
System.out.println(c1);

URL url2 = new URL(classPath);
ClassLoader loader2 =
new URLClassLoader(new URL[] {url2});
Class c2 = loader2.loadClass(className);

System.out.println(c2);

System.out.println("c1 与 c1 为同一实例?"
+ (c1 == c2));
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("没有指定类别加载路径与名称");
}
catch(MalformedURLException e) {
System.out.println("加载路径错误");
}
catch(ClassNotFoundException e) {
System.out.println("找不到指定的类别");
}
}
}

您可以任意设计一个类别,例如 TestClass,其中 classPath 可以输入不为 ExtClassLoader 或 AppClassLoader 的搜寻路径,例如 file:/d:/workspace/,这样同一个类别会分由两个 ClassLoader 载入,结果会有两份 Class 实例,则测试 c1 与 c2 是否为同一实例时,则结果会显示 false,一个执行结果如下:

java onlyfun.caterpillar.ClassLoaderDemo file:/d:/workspace/ TestClass
class TestClass
class TestClass
c1 与 c1 为同一实例?false

如果您在执行程序时,以 -cp 将 file:/d:/workspace/ 加入为 Classpath 的一部份,由于两个 ClassLoader 的 parent 都是 AppClassLoader,而 AppClassLoader 会在 Classpath 中找到指定的类别,所以最后会只有一个指定的类别之 Class 实例,则测试 c1 与 c2 是否为同一实例时,结果会显示 true,一个执行结果如下:

java -cp .;d:\workspace onlyfun.caterpillar.ClassLoaderDemo file:/d:/workspace/ TestClass
class TestClass
class TestClass
c1 与 c1 为同一实例?true

使用 -cp 指定 Classpath 时,会覆盖原有的 Classpath 定义,也就是连现行工作目录的路径也覆盖了,由于我的 ClassLoaderDemo 类别是在现行工作目录下,所以使用 -cp 时,也包括了现行工作目录,记得组合多个 Classpath 路径时,可以使用「;」。

16.2 使用反射生成与操作对象

使用反射机制,您可以于执行时期动态加载类别并生成对象,操作对象上的方法、改变类别成员的值,甚至连私用(private)成员的值也可以改变。

16.2.1 生成物件

您可以使用 Class 的 newInstance() 方法来实例化一个对象,实例化的对象是以 Object 型态传回,例如:

Class c = Class.forName(className);
Object obj = c.newInstance();

范例 16.14 是个简单的示范,您可以动态加载实现了 List 接口的类别。

范例 16.14 NewInstanceDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package onlyfun.caterpillar;

import java.util.*;

public class NewInstanceDemo {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
List list = (List) c.newInstance();

for(int i = 0; i < 5; i++) {
list.add("element " + i);
}

for(Object o: list.toArray()) {
System.out.println(o);
}
}
catch(ClassNotFoundException e) {
System.out.println("找不到指定的类别");
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

执行结果:

java onlyfun.caterpillar.NewInstanceDemo java.util.ArrayList
element 0
element 1
element 2
element 3
element 4

实际上如果想要使用反射来动态加载类别,通常是对对象的接口或类型都一无所知,也就无法像范例 16.14 中对 newInstance() 传回的对象进行接口转换动作,稍后会介绍如何以反射来呼叫方法以操作 newInstance() 所传回的对象。

如果加载的类别中具备无参数的建构方法,则可以无参数的 newInstance() 来建构一个不指定初始自变量的对象,如果您要在动态加载及生成对象时指定对象的初始化自变量,则要先指定参数型态、取得 Constructor 对象、使用 Constructor 的 newInstance() 并指定参数的接受值。

以一个例子来说明,首先定义一个 Student 类。

范例 16.15 Student.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package onlyfun.caterpillar;

public class Student {
private String name;
private int score;

public Student() {
name = "N/A";
}

public Student(String name, int score) {
this.name = name;
this.score = score;
}

public void setName(String name) {
this.name = name;
}

public void setScore(int score) {
this.score = score;
}

public String getName() {
return name;
}

public int getScore() {
return score;
}

public String toString() {
return name + ":" + score;
}
}

您可以用 Class.forName() 来加载 Student 类别,并使用第二个有参数的建构方法来建构 Student 实例,如范例 16.16 所示。

范例 16.16 NewInstanceDemo2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package onlyfun.caterpillar;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class NewInstanceDemo2 {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);

// 指定参数型态
Class[] params = new Class[2];
// 第一个参数是String
params[0] = String.class;
// 第二个参数是int
params[1] = Integer.TYPE;

// 取得对应参数列的建构方法
Constructor constructor =
c.getConstructor(params);

// 指定自变量内容
Object[] argObjs = new Object[2];
argObjs[0] = "caterpillar";
argObjs[1] = new Integer(90);

// 给定自变量并实例化
Object obj = constructor.newInstance(argObjs);
// 呼叫toString()来观看描述
System.out.println(obj);
} catch (ClassNotFoundException e) {
System.out.println("找不到类别");
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
System.out.println("没有所指定的方法");
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}

注意在指定基本型态时,要使用对应的包裹类别(Wrapper)并使用 .TYPE,例如指定 int 型态时,则使用 Integer.TYPE,如果要指定 Integer 型态的参数的话,才是使用 Integer.class,范例 16.16 会根据指定的自变量呼叫对应的建构方法,加载 onlyfun.caterpillar.Student 的执行结果如下:

java onlyfun.caterpillar.NewInstanceDemo2 onlyfun.caterpillar.Student
caterpillar:90

16.2.2 呼叫方法

使用反射可以取回类别上方法的对象代表,方法的对象代表是 java.lang.reflect.Method 的实例,您可以使用它的 invoke() 方法来动态呼叫指定的方法,例如呼叫范例 16.15 的 Student 类别上之 setName() 等方法,这边直接以范例 16.17 作为示范。

范例 16.17 InvokeMethodDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package onlyfun.caterpillar;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class InvokeMethodDemo {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
// 使用无参数建构方法建立对象
Object targetObj = c.newInstance();
// 设定参数型态
Class[] param1 = {String.class};
// 根据参数型态取回方法对象
Method setNameMethod = c.getMethod("setName", param1);
// 设定自变量值
Object[] argObjs1 = {"caterpillar"};
// 给定自变量呼叫指定对象之方法
setNameMethod.invoke(targetObj, argObjs1);


Class[] param2 = {Integer.TYPE};
Method setScoreMethod =
c.getMethod("setScore", param2);

Object[] argObjs2 = {new Integer(90)};
setScoreMethod.invoke(targetObj, argObjs2);
// 显示对象描述
System.out.println(targetObj);

} catch (ClassNotFoundException e) {
System.out.println("找不到类别");
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
System.out.println("没有这个方法");
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}

范例 16.17 可以指定加载 Student 类别并生成实例,接着可以动态呼叫 setName() 与 setScore() 方法,范例中参数型态与自变量值的设定与范例 16.16 是类似的,由于呼叫 setName() 与 setScore() 所给定的自变量是 “caterpillar” 与 90,所以执行的结果与范例 16.16 是相同的。

在很少的情况下,您会需要突破 Java 的存取限制来呼叫受保护的(protected)或私有(private)的方法(例如您拿到一个组件(Component),但您没法修改它的原始码来改变某个私有方法的权限,而您又一定要呼叫某个私有方法),这时候您可以使用反射机制来达到您的目的,一个存取私有方法的例子如下:

Method privateMethod =
            c.getDeclaredMethod("somePrivateMethod", new Class[0]);
privateMethod.setAccessible(true);
privateMethod.invoke(targetObj, argObjs);

使用反射来动态呼叫方法的实际例子之一是在 JavaBean 的设定,例如在 JSP/Servlet 中,可以根据使用者的请求名称与 JavaBean 的属性名称自动比对,将字符串请求值设定至指定的 JavaBean 上,并自动根据参数型态作型态转换(详细说明请见本章后网络索引)。范例 16.18 是个简单的示范,您可以给 CommandUtil 工具类别一个 Map 对象与类别名称,然后取得一个更新了值的实例,其中参数 Map 对象的「键」(Key)为要呼叫的 setter 方法名称(不包括set开头的名称,例如 setName() 方法的话,只要给定键为 name 即可),而「值」(Value)为要设定给 setter 的自变量。

范例 16.18 CommandUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package onlyfun.caterpillar;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

public class CommandUtil {
// 给定Map对象及要产生的Bean类别名称
// 可以取回已经设定完成的对象
public static Object getCommand(Map requestMap,
String commandClass)
throws Exception {
Class c = Class.forName(commandClass);
Object o = c.newInstance();

return updateCommand(requestMap, o);
}

// 使用reflection自动找出要更新的属性
public static Object updateCommand(
Map requestMap,
Object command)
throws Exception {
Method[] methods =
command.getClass().getDeclaredMethods();

for(int i = 0; i < methods.length; i++) {
// 略过private、protected成员
// 且找出必须是set开头的方法名称
if(!Modifier.isPrivate(methods[i].getModifiers()) &&
!Modifier.isProtected(methods[i].getModifiers()) &&
methods[i].getName().startsWith("set")) {
// 取得不包括set的名称
String name = methods[i].getName()
.substring(3)
.toLowerCase();
// 如果setter名称与键值相同
// 呼叫对应的setter并设定值
if(requestMap.containsKey(name)) {
String param = (String) requestMap.get(name);
Object[] values = findOutParamValues(
param, methods[i]);
methods[i].invoke(command, values);
}
}
}
return command;
}

// 转换为对应型态的值
private static Object[] findOutParamValues(
String param, Method method) {
Class[] params = method.getParameterTypes();
Object[] objs = new Object[params.length];

for(int i = 0; i < params.length; i++) {
if(params[i] == String.class) {
objs[i] = param;
}
else if(params[i] == Short.TYPE) {
short number = Short.parseShort(param);
objs[i] = new Short(number);
}
else if(params[i] == Integer.TYPE) {
int number = Integer.parseInt(param);
objs[i] = new Integer(number);
}
else if(params[i] == Long.TYPE) {
long number = Long.parseLong(param);
objs[i] = new Long(number);
}
else if(params[i] == Float.TYPE) {
float number = Float.parseFloat(param);
objs[i] = new Float(number);
}
else if(params[i] == Double.TYPE) {
double number = Double.parseDouble(param);
objs[i] = new Double(number);
}
else if(params[i] == Boolean.TYPE) {
boolean bool = Boolean.parseBoolean(param);
objs[i] = new Boolean(bool);
}
}
return objs;
}
}

CommandUtil 可以自动根据方法上的参数型态,将 Map 对象中的「值」对象转换为属性上的对应型态,目前它可以转换基本型态与 String 型态的属性,一个使用 CommandUtil 类别的例子如范例 16.19 所示。

范例 16.19 CommandUtilDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package onlyfun.caterpillar;

import java.util.*;

public class CommandUtilDemo {
public static void main(String[] args) throws Exception {
Map<String, String> request =
new HashMap<String, String>();
request.put("name", "caterpillar");
request.put("score", "90");
Object obj = CommandUtil.getCommand(request, args[0]);
System.out.println(obj);
}
}

您可以使用范例 16.19 来加载 Student 类别,使用 CommandUtil.getCommand() 方法可以返回一个设定好值的 Student 实例,虽然您设定给 request 的「值」是字符串型态,但 CommandUtil 会使用反射机制来自动转换为属性上的对应型态,一个执行的范例如下所示:

java onlyfun.caterpillar.CommandUtilDemo onlyfun.caterpillar.Student
caterpillar:90

良葛格的话匣子 不知道方法的名称如何呼叫?其实范例 16.17 就给出了答案,透过规范方法的命名方式,之后就可以透用反射机制加上方法名称的比对,以正确呼叫对应的方法。

16.2.3 修改成员值

尽管直接存取类别的数据成员(Field)是不被鼓励的,但您仍是可以直接存取公开的(public)数据成员,而您甚至也可以透过反射机制来存取私用数据成员,以一个实例来说明,首先撰写个 TestedField 类别。

范例 16.20 TestField.java

1
2
3
4
5
6
7
8
9
10
package onlyfun.caterpillar;

public class TestField {
public int testInt;
public String testString;

public String toString() {
return testInt + ":" + testString;
}
}

范例 16.21 则利用反射机制动态加载类别来存取数据成员。

范例 16.21 AssignFieldDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package onlyfun.caterpillar;

import java.lang.reflect.Field;

public class AssignFieldDemo {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
Object targetObj = c.newInstance();

Field testInt = c.getField("testInt");
testInt.setInt(targetObj, 99);

Field testString = c.getField("testString");
testString.set(targetObj, "caterpillar");

System.out.println(targetObj);
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("没有指定类别");
} catch (ClassNotFoundException e) {
System.out.println("找不到指定的类别");
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
System.out.println("找不到指定的数据成员");
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

您可以加载 TestField 类别来看看执行的结果,如下所示:

java onlyfun.caterpillar.AssignFieldDemo onlyfun.caterpillar.TestField
99:caterpillar

如果有必要的话,您也可以透过反射机制来存取私有的数据成员,例如:

Field privateField = c.getDeclaredField("privateField");
privateField.setAccessible(true);
privateField.setInt(targetObj, 99);

16.2.4 再看数组对象

在 Java 中数组也是一个对象,也会有一个 Class 实例来表示它,范例 16.22 显示几个基本型态以及 String 数组的类别名称描述。

范例 16.22 ArrayDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package onlyfun.caterpillar;

public class ArrayDemo {
public static void main(String[] args) {
short[] sArr = new short[5];
int[] iArr = new int[5];
long[] lArr = new long[5];
float[] fArr = new float[5];
double[] dArr = new double[5];
byte[] bArr = new byte[5];
boolean[] zArr = new boolean[5];
String[] strArr = new String[5];

System.out.println("short 数组类别:" + sArr.getClass());
System.out.println("int 数组类别:" + iArr.getClass());
System.out.println("long 数组类别:" + lArr.getClass());
System.out.println("float 数组类别:" + fArr.getClass());
System.out.println("double 数组类别:" + dArr.getClass());
System.out.println("byte 数组类别:" + bArr.getClass());
System.out.println("boolean 数组类别:" + zArr.getClass());
System.out.println("String 数组类别:" + strArr.getClass());
}
}

使用 toString() 来显示数组对象的类别名称描述时,会以 “class [“ 作为开始,之后跟随着一个型态表示字符,执行结果如下所示:

short 数组类别:class [S
int 数组类别:class [I
long 数组类别:class [J
float 数组类别:class [F
double 数组类别:class [D
byte 数组类别:class [B
boolean 数组类别:class [Z
String 数组类别:class [Ljava.lang.String;

要使用反射机制动态生成数组的话,可以使用 java.lang.reflect.Array 来协助,范例 16.23 简单的示范了如何生成 String 数组。

范例 16.23 NewArrayDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package onlyfun.caterpillar;

import java.lang.reflect.Array;

public class NewArrayDemo {
public static void main(String[] args) {
Class c = String.class;
Object objArr = Array.newInstance(c, 5);

for(int i = 0; i < 5; i++) {
Array.set(objArr, i, i+"");
}

for(int i = 0; i < 5; i++) {
System.out.print(Array.get(objArr, i) + " ");
}
System.out.println();

String[] strs = (String[]) objArr;
for(String s : strs) {
System.out.print(s + " ");
}
}
}

Array.newInstance() 的第一个参数是指定元素型态,而第二个参数是用来指定数组长度,执行结果如下:

0 1 2 3 4
0 1 2 3 4

Array.newInstance() 还有另一个版本,可用于建立二维数组,只要用一个表示二维数组的两个维度长度的 int 数组,传递给第二个参数,范例 16.24 是个简单示范。

范例 16.24 NewArrayDemo2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package onlyfun.caterpillar;

import java.lang.reflect.Array;

public class NewArrayDemo2 {
public static void main(String[] args) {
Class c = String.class;

// 打算建立一个3*4数组
int[] dim = new int[]{3, 4};
Object objArr = Array.newInstance(c, dim);

for(int i = 0; i < 3; i++) {
Object row = Array.get(objArr, i);
for(int j = 0; j < 4; j++) {
Array.set(row, j, "" + (i+1)*(j+1));
}
}

for(int i = 0; i < 3; i++) {
Object row = Array.get(objArr, i);
for(int j = 0; j < 4; j++) {
System.out.print(Array.get(row, j) + " ");
}
System.out.println();
}
}
}

执行结果如下:

1 2 3 4
2 4 6 8
3 6 9 12

如果想要得知数组元素的型态,可以在取得数组的 Class 实例之后,使用 Class 实例的 getComponentType() 方法,所取回的是元素的 Class 实例,例如:

int[] iArr = new int[5];
System.out.println(iArr.getClass().getComponentType());

16.2.5 Proxy 类别

Java 在 J2SE 1.3 之后加入 java.lang.reflect.Proxy 类别,可协助您实现动态代理功能,举个实际应用的例子,假设今天您打算开发一个 HelloSpeaker 类别,当中有一个 hello() 方法,而您想要在这个 hello() 呼叫前后加上记录(log)的功能,但又不想将记录的功能写到 HelloSpeaker 类别中,这时您可以使用 Proxy 类别来实现动态代理。
要实现动态代理,首先要定义所要代理的接口,范例 16.25 为定义了有 hello() 方法的 IHello 接口。

范例 16.25 IHello.java

1
2
3
4
5
package onlyfun.caterpillar;

public interface IHello {
public void hello(String name);
}

您的 HelloSpeaker 类别实现了 IHello 接口,如范例 16.26 所示。

范例 16.26 HelloSpeaker.java

1
2
3
4
5
6
7
package onlyfun.caterpillar;

public class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello, " + name);
}
}

您可以实作一个处理记录(log)的处理者(Handler),让处理者在呼叫 hello() 方法的前后进行记录的动作,一个处理者必须实现 java.lang.reflect.InvocationHandler 接口,InvocationHandler 有一个 invoke() 方法必须实现,范例 16.27 是个简单的实现。

范例 16.27 LogHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package onlyfun.caterpillar;

import java.util.logging.*;
import java.lang.reflect.*;

public class LogHandler implements InvocationHandler {
private Logger logger =
Logger.getLogger(this.getClass().getName());
private Object delegate;

// 绑定要代理的对象
public Object bind(Object delegate) {
this.delegate = delegate;
// 建立并传回代理对象
return Proxy.newProxyInstance(
delegate.getClass().getClassLoader(),
// 要被代理的接口
delegate.getClass().getInterfaces(),
this);
}

// 代理要呼叫的方法,并在其前后增加行为
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable {
Object result = null;
try {
logger.log(Level.INFO,
"method starts..." + method.getName());
result = method.invoke(delegate, args);
logger.log(Level.INFO,
"method ends..." + method.getName());
} catch (Exception e){
logger.log(Level.INFO, e.toString());
}
return result;
}
}

主要的概念是使用 Proxy.newProxyInstance() 方法建立一个代理对象,建立代理对象时必须告知所要代理的操作接口,之后您可以操作所建立的代理对象,在每次操作时会呼叫 InvocationHandler 的 invoke() 方法,invoke() 方法会传入被代理对象的方法名称与执行自变量,实际上要执行的方法交由 method.invoke(),您在 method.invoke() 前后加上记录动作,method.invoke() 传回的对象是实际方法执行过后的回传结果,先从范例 16.28 来看看一个执行的例子。

范例 16.28 ProxyDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package onlyfun.caterpillar;

public class ProxyDemo {
public static void main(String[] args) {
LogHandler handler = new LogHandler();
IHello speaker = new HelloSpeaker();

// 代理speaker的对象
IHello speakerProxy =
(IHello) handler.bind(speaker);

speakerProxy.hello("Justin");
}
}

执行结果如下:

2005/6/4 上午 09:39:11 onlyfun.caterpillar.LogHandler invoke
信息: method starts...hello
Hello, Justin
2005/6/4 上午 09:39:11 onlyfun.caterpillar.LogHandler invoke
信息: method ends...hello

透过代理机制,在不将记录动作写入为 HelloSpeaker 类别程序代码的情况下,您可以为其加入记录的功能,这并不是什么魔法,只不过是在 hello() 方法前后由代理对象 speakerProxy 先执行记录功能而已,而真正执行 hello() 方法时才使用 speaker 对象。

良葛格的话匣子 这边的例子是「Proxy 模式」的实现,您可以进一步参考:

  • Proxy 模式(一)
  • Proxy 模式(二)

16.3 接下来的主题

每一个章节的内容由浅至深,初学者该掌握的深度要到哪呢?在这个章节中,对于初学者我建议至少掌握以下几点内容:

  • 了解 Class 实例与类别的关系
  • 会使用 Class.forName() 加载类别并获得类别信息
  • 会使用 Class 建立实例
  • 会使用反射机制呼叫对象上的方法

下一个章节要来介绍 J2SE 5.0 中新增的 Annotation 功能,Annotation 是 J2SE 5.0 对 metadata 的支持,metadata 简单的说就是「数据的数据」(Data about data),您可以使用 Annotation 对程序代码作出一些说明,以利一些程序代码分析工具来使用这些信息。

Android 插件化开发

发表于 2018-07-11 | 分类于 develop
字数统计: 0 | 阅读时长 ≈ 1




  • Android博客周刊专题之#插件化开发#


    本期专栏目讨论插件化开发。插件化涉及的东西很多,所以我们需要多个维度去学习。大概分为5个部分:预备知识、入门、进阶、系列、类库。一步一步深入了解插件的原理。本专栏会不定时更新相关内容,请留意更新的消息。
    转自:http://www.androidblog.cn/index.php/Index/detail/id/16#



基础
1.Java 类加载器
类加载器(class loader)是 Java™中的一个很重要的概念。类加载器负责加载 Java 类的字节代码到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模式、加载类的具体过程和线程上下文类加载器等,接着介绍如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™中的应用。
2.反射原理

Java 提供的反射機制允許您於執行時期動態載入類別、檢視類別資訊、生成物件或操作生成的物件,要舉反射機制的一個應用實例,就是在整合式開發環境中所提供的方法提示或是類別檢視工具,另外像 JSP 中的 JavaBean 自動收集請求資訊也使用到反射,而一些軟體開發框架(Framework)也常見到反射機制的使用,以達到動態載入使用者自訂類別的目的。
3.代理模式及Java实现动态代理
定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。
入门

1.Android动态加载dex技术初探


Android使用Dalvik虚拟机加载可执行程序,所以不能直接加载基于class的jar,而是需要将class转化为dex字节码,从而执行代码。优化后的字节码文件可以存在一个.jar中,只要其内部存放的是.dex即可使用。2.Android插件化入门

开发者将插件代码封装成Jar或者APK。宿主下载或者从本地加载Jar或者APK到宿主中。将宿主调用插件中的算法或者Android特定的Class(如Activity)3.插件化开发—动态加载技术加载已安装和未安装的apk

动态加载技术就是使用类加载器加载相应的apk、dex、jar(必须含有dex文件),再通过反射获得该apk、dex、jar内部的资源(class、图片、color等等)进而供宿主app使用。4.Android动态加载技术三个关键问题详解

动态加载技术(也叫插件化技术)在技术驱动型的公司中扮演着相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和CPU占用,还可以实现热插拔,即在不发布新版本的情况下更新某些模块。
进阶
1.携程Android App插件化和动态加载实践

携程Android App的插件化和动态加载框架已上线半年,经历了初期的探索和持续的打磨优化,新框架和工程配置经受住了生产实践的考验。本文将详细介绍Android平台插件式开发和动态加载技术的原理和实现细节,回顾携程Android App的架构演化过程,期望我们的经验能帮助到更多的Android工程师。2.动态加载APK原理分享

被加载的apk称之为插件,因为机制类似于生物学的”寄生”,加载了插件的应用也被称为宿主。
往往不是所有的apk都可作为插件被加载,往往需要遵循一定的”开发规范”,还需要插件项目引入某种api类库,业界通常都是这么做的。3.Android插件化的一种实现

Android的插件化已经是老生常谈的话题了,插件化的好处有很多:解除代码耦合,插件支持热插拔,静默升级,从根本上解决65K属性和方法的bug等等。下面给大家介绍一下我们正在用的差价化框架。本片主要以类图的方式向大家介绍插件话框架的实现。4.蘑菇街 App 的组件化之路



随着我街业务的蓬勃发展,产品和运营随时上新功能新活动的需求越来越强烈,经常可以听到“有个功能我想周x上,行不行”。行么?当然是不行啦,上新功能得发新版本啊,到时候费时费力打乱开发节奏不说,覆盖率也是个问题。5.DynamicLoadApk 源码解析



DynamicLoadApk 是一个开源的 Android 插件化框架。插件化的优点包括:(1) 模块解耦,(2) 动态升级,(3) 高效并行开发(编译速度更快) (4) 按需加载,内存占用更低等等DynamicLoadApk 提供了 3 种开发方式,让开发者在无需理解其工作原理的情况下快速的集成插件化功能。6.Android apk动态加载机制的研究



问题是这样的:我们知道,apk必须安装才能运行,如果不安装要是也能运行该多好啊,事实上,这不是完全不可能的,尽管它比较难实现。在理论层面上,我们可以通过一个宿主程序来运行一些未安装的apk,当然,实践层面上也能实现,不过这对未安装的apk有要求。我们的想法是这样的,首先要明白apk未安装是不能被直接调起来.7.美团Android DEX自动拆包及动态加载简介



作为一个android开发者,在开发应用时,随着业务规模发展到一定程度,不断地加入新功能、添加新的类库,代码在急剧的膨胀,相应的apk包的大小也急剧增加, 那么终有一天,你会不幸遇到这个错误.
8.途牛原创|途牛Android App的插件实现



途牛的插件化是基于dynamic-load-apk(github)实现的。定义了宿主和插件的通信方式,使得两者能够互起对方的页面,调用彼此的功能。同时对activity的启动方式singletask等进行了模式实现,并增加了对Service的支持等。总之使得插件开发最大限度的保持着原有的Android开发习惯。9. Android apk资源加载和activity生命周期管理



博主分析了Android中apk的动态加载机制,并在文章的最后指出需要解决的两个复杂问题:资源的访问和activity生命周期的管理,而本文将会分析这两个复杂问题的解决方法。10.APK动态加载框架(DL)解析



首先要说的是动态加载技术(或者说插件化)在技术驱动型的公司中扮演这相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和cpu占用,还可以实现热插拔,即在不发布新版本的情况下更新某些模块。

系列

1.Kaedea—Android动态加载技术 简单易懂的介绍



我们很早开始就在Android项目中采用了动态加载技术,主要目的是为了达到让用户不用重新安装APK就能升级应用的功能(特别是 SDK项目),这样一来不但可以大大提高应用新版本的覆盖率,也减少了服务器对旧版本接口兼容的压力,同时如果也可以快速修复一些线上的BUG。2.Kaedea—Android动态加载基础 ClassLoader的工作机制



早期使用过Eclipse等Java编写的软件的同学可能比较熟悉,Eclipse可以加载许多第三方的插件(或者叫扩展),这就是动态加载。这些插件大多是一些Jar包,而使用插件其实就是动态加载Jar包里的Class进行工作。3.Kaedea—Android动态加载补充 加载SD卡的SO库



Android中JNI的使用其实就包含了动态加载,APP运行时动态加载.so库并通过JNI调用其封装好的方法。后者一般是使用NDK工具从C/C++代码编译而成,运行在Native层,效率会比执行在虚拟机的Java代码高很多,所以Android中经常通过动态加载.so库来完成一些对性能比较有需求的工作(比如T9搜索、或者Bitmap的解码、图片高斯模糊处理等)。4.Kaedea—Android动态加载入门 简单加载模式



Java程序中,JVM虚拟机是通过类加载器ClassLoader加载.jar文件里面的类的。Android也类似,不过Android用的是Dalvik/ART虚拟机,不是JVM,也不能直接加载.jar文件,而是加载dex文件。5.Kaedea—Android动态加载进阶 代理Activity模式



简单模式中,使用ClassLoader加载外部的Dex或Apk文件,可以加载一些本地APP不存在的类,从而执行一些新的代码逻辑。但是使用这种方法却不能直接启动插件里的Activity。6.Kaedea—Android动态加载黑科技 动态创建Activity模式



还记得我们在代理Activity模式里谈到启动插件APK里的Activity的两个难题吗,由于插件里的Activity没在主项目的Manifest里面注册,所以无法经历系统Framework层级的一系列初始化过程,最终导致获得的Activity实例并没有生命周期和无法使用res资源。7.尼古拉斯—插件开发基础篇:动态加载技术解读



在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势。本文对网上Android动态加载jar的资料进行梳理和实践在这里与大家一起分享,试图改善频繁升级这一弊病。8.尼古拉斯—插件开发开篇:类加载器分析



这篇文章主要介绍了Android中主要的两个类加载器:PathClassLoader和DexClassLoader,他们的区别,联系,用法等问题,以及我们在制作插件的过程中会遇到哪些常见的问题。这篇文章也是后续两篇文章的基础,因为如果不了解这两个类的话,我们将无法进行后续的操作。9.尼古拉斯—插件开发中篇:资源加载问题(换肤原理解析)



这篇文章主要通过现在一些应用自带的换肤技术的解读来看看,在开发插件的过程中如何解决一些资源加载上的问题,这个问题为何要单独拿出来解释,就是因为他涉及的知识很多,也是后面一篇文章的基础,我们在需要加载插件中的资源文件的时候。10.尼古拉斯—插件开发终极篇:动态加载Activity(免安装运行程序)



这篇文章主要是讲解了如何加载插件中的Activity。从而实现免安装运行程序,同时这篇文章也是对前三篇文章知识的综合使用。下载很多应用都会使用到插件技术,因为包的大小和一些功能的优先级来决定哪些模块可以制作成插件。11.Weishu—Android插件化原理解析——概要



类的加载可以使用Java的ClassLoader机制,但是对于Android来说,并不是说类加载进来就可以用了,很多组件都是有“生命”的;因此对于这些有血有肉的类,必须给它们注入活力,也就是所谓的组件生命周期管理.12.Weishu—Android插件化原理解析——Hook机制之动态代理



使用代理机制进行API Hook进而达到方法增强是框架的常用手段,比如J2EE框架Spring通过动态代理优雅地实现了AOP编程,极大地提升了Web开发效率;同样,插件框架也广泛使用了代理机制来增强系统API从而达到插件化的目的.13.Weishu—Android插件化原理解析——Hook机制之Binder Hook



Android系统通过Binder机制给应用程序提供了一系列的系统服务,诸如ActivityManagerService,ClipboardManager, AudioManager等;这些广泛存在系统服务给应用程序提供了诸如任务管理,音频,视频等异常强大的功能。14.Weishu—Android 插件化原理解析——Hook机制之AMS&PMS



在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook;插件框架通过AOP实现了插件使用和开发的透明性。在讲述DroidPlugin如何实现四大组件的插件化之前,有必要说明一下它对AMS以及PMS的Hook方式。15.Weishu—Android 插件化原理解析——Activity生命周期管理



之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在Android系统上运行起来?
16.Weishu—Android 插件化原理解析——插件加载机制



上文 Activity生命周期管理 中我们地完成了『启动没有在AndroidManifest.xml中显式声明的Activity』的任务;通过Hook AMS和拦截ActivityThread中H类对于组件调度我们成功地绕过了AndroidMAnifest.xml的限制。
17.Weishu—Android插件化原理解析——广播的管理



在Activity生命周期管理 以及 插件加载机制 中我们详细讲述了插件化过程中对于Activity组件的处理方式,为了实现Activity的插件化我们付出了相当多的努力;那么Android系统的其他组件,比如BroadcastReceiver,Service还有ContentProvider,它们又该如何处理呢?18.Weishu—Android 插件化原理解析——Service的插件化



在 Activity生命周期管理 以及 广播的管理 中我们详细探讨了Android系统中的Activity、BroadcastReceiver组件的工作原理以及它们的插件化方案,相信读者已经对Android Framework和插件化技术有了一定的了解;

类库

1.DroidPlugin



是360手机助手在Android系统上实现了一种新的插件机制2.Android-Plugin-Framework



此项目是Android插件开发框架完整源码及示例。用来通过动态加载的方式在宿主程序中运行插件APK。3.Small



世界那么大,组件那么小。Small,做最轻巧的跨平台插件化框架。里面有很详细的文档4.dynamic-load-apk



Android 使用动态加载框架DL进行插件化开发5.AndroidDynamicLoader



Android 动态加载框架,他不是用代理 Activity 的方式实现而是用 Fragment 以及 Schema 的方式实现6.DynamicAPK



实现Android App多apk插件化和动态加载,支持资源分包和热修复.携程App的插件化和动态加载框架.7.ACDD


非代理Android动态部署框架8.android-pluginmgr


不需要插件规范的apk动态加载框架。
参考视频

1.DroidPlugin的实现原理及其应用



Droid Plugin是360手机助手在2015年初研发的一个全新的基于Android平台的插件机制.2.android插件化及动态部署



阿里技术沙龙第十六期《android插件化及动态部署》视频

Github Top100

发表于 2018-07-11 | 分类于 develop
字数统计: 4,057 | 阅读时长 ≈ 15

(转) GitHub Top 100 简介

主要对当前 GitHub 排名前 100 的项目做一个简单的简介, 方便初学者快速了解到当前 Objective-C 在 GitHub 的情况.

项目名称 项目信息
1. AFNetworking 作者是 NSHipster 的博主, iOS 开发界的大神级人物, 毕业于卡内基·梅隆大学, 开源了许多牛逼的项目, 这个便是其中之一, AFNetworking 采用 NSURLConnection + NSOperation, 主要方便与服务端 API 进行数据交换, 操作简单, 功能强大, 现在许多人都用它取代 ASIHTTPRequest
2. GPUImage 一款强大的图片滤镜工具, 支持自定义滤镜, 可用来实时处理图片和视频流, 作者是 SonoPlot 公司的 CTO, 在很小的时候便开始接触编程, 他在 SO 上面的回答也有很多值得阅读, GPUImage 这个项目从 2012 年开始, 使用 OpenGL 图形程序接口编写, 性能非常好, 现在很多 iOS 程序员都用它来实现 iOS 的模糊效果
3. SDWebImage 作者 Olivier Poitrey 是 Dailymotion 的 CTO, 拥有多个不错的开源项目, 此项目常用于对从 Web 端接受到的图片进行缓存, 是 UIImageView 的扩展, 应用起来比较简单
4. RestKit 主要用于 iOS 上网络通信, 允许与 RESTful Web 服务交互, 常用于处理 API, 解析 JSON, 映射响应对象等操作, 简单易用, 方便你把所有精力都放在对数据的操作上
5. ReactiveCocoa 由 GitHub 工程师们开发的一个应用于 iOS 和 OS X 开发的函数响应式编程新框架, Matt 称其为 “An open source project that exemplifies this brave new era for Objective-C”, 也有人说它是 Cocoa 的未来, 具体可看唐巧写的这篇文章
6. three20 由 Facebook iOS 客户端衍生出的一款 iPhone 框架, 内置许多丰富的功能, 有丰富的界面, 对底层的操作便捷, 为开发者省下了很多时间, 但现在已经停止了更新, 一个 PR 把代码删得干干净净, 不要好奇去点开 Files changed, 我点开后该页面直接卡死, three20 当中的一位作者创建了 Nimbus, 算是 three20 的一个替代品
7. MBProgressHUD 作者 Matej Bukovinski 是一位全栈工程师, UI/UX 设计师, 此项目是一款提示框第三方库, 帮助开发者快速应用到项目中)
8. MagicalRecord 作者是 Coursera 的 iOS 工程师, 该项目创作灵感来自于 Ruby on Rails 的 Active Record, 主要为方便操作 CoreData 而生, 帮助清除 CoreData 引用的代码, 协助方便 CoreData 的工作
9. FMDB 一个对 SQLite 进行封装的库, 使用起来方便, 简单
10. Mantle 作者是 GitHub 的员工, 文档写的很清楚: Mantle makes it easy to write a simple model layer for your Cocoa or Cocoa Touch application, 主要用来将 JSON 数据模型化为 Model 对象, 唱吧在前段时间也改用 Mantle 了.
11. FlatUIKit 收集了很多扁平化 UI 的 iOS 组件, 方便使用
12. ASIHTTPRequest 一个轻量级的 iOS 网络通信类库, 基于 CFNetwork 框架开发, 但现在已经停止更新, 多数开发者改用 AFNetworking 替代)
13. FastImageCache Path 公司出品的 iOS 库, 作者 Mallory Paine 是苹果前员工, 此类库适用于在滚动时快速显示图像, 高速持久是其最大的特点
14. Masonry 一个轻量级的布局框架, 同时支持 iOS 和 Mac OS X, 语法优雅, 帮助开发者快速适配不同分辨率的 iOS 设备
15. Shimmer Facebook 推出的一款具有闪烁效果的第三方控件, 供它旗下一款名为 Paper 的应用使用, 安装使用整个过程都十分简单
16. SVProgressHUD 又一款轻量级的 iOS 第三方控件, 用于显示任务加载时的动画, 非常轻便, 容易使用
17. Slate 一款窗口管理应用程序, 但在两年前就已经停止更新了
18. JSONKit 主要用于解析 JSON, 适用于 iOS6 以下环境, 自从 iOS5 开始 Apple 官方给出了 NSJSONSerialization API, 自此大家都用官方的了
19. Nimbus 作者 Jeff 曾为 Facebook, Google 做过不少好东西, 也是 three20 的成员之一, three20 停更后, 他创造出这个框架来代替 three20, 文档齐全
20. CocoaLumberjack 这是 Mac 和 iOS 的一款强大的日志框架, 配置简单, 多线程, 提供更高级的 log 功能, 可用于代替默认的 NSLog 语句
21. Facebook SDK for iOS Facebook 官方的 iOS SDK, 方便开发者集成 Facebook 的一些功能到自己的 iOS APP 里面
22. AsyncDisplayKit Facebook 开源的一款 iOS UI 框架, Paper 用的就是该框架, 另外框架还用到了 Facebook 早期开源 Pop 动画引擎
23. Alcatraz Alcatraz 是一款管理 Xcode 插件、模版以及颜色配置的工具, 可以集成到 Xcode 的图形界面中, 安装删除都是几条命令的事, 很方便, 支持自己开发插件并上传
24. ViewDeck 一款开源的 iOS 活动面板组件, 还原 Path 2.0 的侧滑效果, 作者因为时间关系在两年前停止对其更新
25. JSQMessagesViewController 优雅的 iOS 消息类库, 常用于聊天应用中, 可定制性高
26. FLEX 这是 Flipboard 官方发布的一组专门用于 iOS 开发的应用内调试工具, 开发者无需将其连接到 LLDB/Xcode 或其他远程调试服务器,支持直接在 App 中运行
27. Xctool 是 Facebook 开源的一个命令行工具,用来替代苹果的 XcodeBuild 工具, 极大的方便了 iOS 的构建和测试, 输出错误信息也比较友好, 受到许多 iOS 开发者的称赞, 经常与其搭配使用的还有 OCUnit, Travis CI, OCLint 等测试工具
28. OpenEmu 超强的游戏模拟器, 做游戏开发必备, 官网做得也很不错
29. iCarousel 作者是英国 Charcoal Design 公司的创始人, 开源领域的贡献颇为卓著, 这个项目就是其中之一, 这是一款可以在 iOS 上实现旋转木马视图切换效果的第三方控件, 并提供多种切换效果
30. RESideMenu 作者 Roman Efimov 是雅虎的 iOS 工程师, 这个项目实现了 iOS 上的菜单侧滑效果, 创意来源于 Dribbble, 该项目支持 iOS8
321 PNChart 作者周楷雯是 90 后, 秒视的创始人, 该项目是一个带动画效果的图表控件, 简约易用, 受到不少开发者喜爱
31.2PonyDebugger 由 Square 公司推出的一款优秀的 iOS 应用网络调试工具, 用户可以实时看到应用程序的网络请求, 也可以对 iOS 应用程序的核心数据栈进行远程调试
33. JVFloatLabeledTextField 作者是 Thumb Labs 的联合创始人, JVFloatLabeledTextField 是 UITextField 的子类, 主要实现输入框标签浮动效果, 创作灵感来自 Dribbble, 已出现多个移植版本
34. SWTableViewCell UITableViewCell 的子类, 实现了左右滑动显示信息视图并调出按钮
35. AwesomeMenu 作者是一位中国人, 该项目主要是使用 CoreAnimation 还原了 Path menu 的动画效果
36. Reachability Reachablity 是用于检测 iOS 设备网络环境的库
37. VVDocumenter-Xcode 作者是王巍国内著名的 iOS 开发者, 人称喵神, 目前在日本 LINE 公司工作, 该项目帮助开发者轻松的生成注释文档, 节省了不少工作量, 赞
38. The Physical Web 由 Chrome 团队主导的一个项目, 意在用 URL 连接世界, 方便用户接受数据, 目前尚处在实验阶段
39. NewsBlur 作者独自一个人 Samuel Clay 做出来的一款名为 NewsBlur 的新闻阅读器, 很多人都称其为 Google Reader 的替代品, 这是它的源码
40. Cocos2D-SpriteBuilder 一个可用于在 iOS, Mac 和 Android 上制作 2D 游戏或其它图形/交互应用的框架, 之前的项目名称为 Cocos Swift, 目前该项目在 GitHub 上更新较为频繁
41. TTTAttributedLabel UILabel 的替代品, 使 iOS 上的 Label 功能更加丰富, 可支持链接植入等功能
42. CocoaAsyncSocket 一个功能强大、简单易用的异步 socket 通讯类库, 支持 TCP 和 UDP 协议, 可用于 Mac 和 iOS 设备上, 作者 Robbie Hanson 是 Deusty 的首席软件工程师
43. TapkuLibrary 作者是 Devin Ross, 这是在 iOS 上一款功能强大的 UI 效果类库, 可以实现多种酷炫的效果, 目前仍在更新中
44. Canvas 无需编码实现牛逼的动画效果的库, 连设计师都可以快速上手
45. SocketRocket Square 公司开源的一个 WebSocket 客户端, 稳定并且易用, 做实时应用常会用到, 受广大开发者喜爱
46. ECSlidingViewController 一个视图控制器容器, 将子视图处理成两层, 通过滑动来处理层的切换, 创作灵感来自 Facebook 和 Path的 App, 作者是 Cleveland 的员工
47. Json Framework 用于解析 JSON 数据的一个框架, 但是在 iOS5 以上版本大多数人都选择使用 NSJSONSerialization 来解析 JSON, 该项目现在在 GitHub 上也几乎没怎么更新了
48. Tweaks Facebook 开源的一款工具, 旨在帮助 iOS 开发者更快的迭代应用, 方便用户动态的调整参数, 是的, Paper 这个项目也用到了
49. realm-cocoa Realm-Cocoa 是 Realm 公司推出一款移动端数据库, 可以运行在手机、平板和可穿戴设备之上, 其目标是取代 CoreData 和 SQLite 数据库
50. BlocksKit 一个开源的与 Cocoa 紧密集合的基础性框架
51. Appirater 一款用于提醒用户给你的 App 打分的工具
52. KIF Square 出品的一个开源的用户界面测试框架, 极大的简化了 iOS 开发者的 UI 测试流程
53. SlackTextViewController Slack 推出的一款具有文字输入框高度自适应, 自动输入, 复制单元格内容等功能的解决方案
54. JazzHands IFTTT 开源的一个简单易用的关键帧基础动画框架, 可通过手势、scroll views, KVO, ReactiveCocoa 等方式来控制动画
55. Bolts-iOS Bolts 是一个 Parse 和 Facebook 在内部使用的底层库, 方便移动开发
56. Spectacle 一款易用的 OS X 窗口分屏操作快捷键工具, 这是其源代码
57. nui 方便样式化 iOS 应用中的 UI 元素, 可在短时间内样式化整个应用, 类 CSS 原理
58. Induction Induction 是一款用于理解数据关系的管理工具, 这是其程序代码
59. JSONModel 一个能迅速解析服务器返回的 Json 数据的库, 方便数据的类型转换
60. DTCoreText 一个开源的 iOS 富文本组件, 它可以解析 HTML 与 CSS 并最终用 CoreText 绘制出来, 通常用于在一些需要显示富文本的场景下代替低性能的 UIWebView
61. Popping 基于 Facebook Pop 引擎的 iOS 动画库, 集合了很多动画效果
62. TSMessages 一个用来弹出显示警告和通知的轻量级库, 样式丰富, 简单易用
63. KVOController 一个简单安全的 KVO(Key-value Observing, 键-值 观察)工具, 提供简单方便、线程安全的API, Facebook 的开源项目之一
64. MWPhotoBrowser 一款简单的 iOS 照片浏览控件
65. MMDrawerController 一个轻量级, 易于使用的侧边抽屉导航 iOS 控件
66. QuickDialog 用于快速创建复杂的 iOS 表单, 自定义了 UITableViewCell, TableView 的样式
67. SVPullToRefresh 一款只需一行代码便可集成上拉刷新和下拉加载的组件
68. cheddar-ios Cheddar 是一款简单易用的日程管理软件, 这是其早期版本的开源代码, 该项目已停止维护
69. XVim 一款在 Xcode 上实现了 Vim 功能的插件
70. EGOTableViewPullRefresh 一款提供下拉刷新的控件, 最后更新时间是一年前
71. iOS-boilerplate iOS 应用程序的基础模板, 使用该模板可以省掉许多项目初始编码的工作, 内置非常多丰富的功能, 现已经停止维护
72. JASidePanels 一个 UIViewController 容器, 灵感来自 Facebook 和 Path 2.0 应用的菜单, 实现了左右侧滑的操作
73. FormatterKit 收集了很多构思优秀的 NSFormatter 子类
74. MSDynamicsDrawerViewController 实现了具有动态弹性效果的抽屉式侧边导航栏, 效果丰富, 可定制性强
75. idev-recipes iDevRecipes 博客的代码, 演示如何实现一些有趣的控件, 该项目在两年前(2013)停止了更新
76. XMPPFramework 一个基于 RFC-3920 实现, 支持多线程和线程保护, 同时通用于所有的 iOS 和 Mac OS 开发设备的通信框架.
77. MacGap1 一款可以将 HTML/CSS/JS 网络应用打包成原生 Mac App 的工具
78. FXBlurView iOS 模糊背景类库, 可以方便的根据底层显示的状态生成模糊效果
79. iOS7-Sampler 整合演示了多个具有 iOS7 新特性的的项目, 提供了非常多的例子参考
80. PromiseKit 提供强大的 iOS 开发异步功能, 是 Promises 的实现, 受到广大开发者的追捧
81. Origami 此为 Facebook 推出的 Quartz Composer 的一个开源插件, 由其设计团队花费了 9 个月打造而成, 目的是为方便设计师快速构建原型, 以零代码完成复杂动画的合成和测试, 堪称神器
82. NSLogger 一款强大的日志分析工具, 具有大窗口查看 Log, 自定义日志等级等功能
83. KSImageNamed-Xcode 一款对 UIImage 的 imageNamed 提供自动补全功能的插件, 非常方便
84. PureLayout 一个简单却强大的 AutoLayout API 库, 兼容了 Objective-C 和 Swift, 扩展了 UIView/NSView, NSArray, 和 NSLayoutConstraint
85. AppleDoc 一款 Objective-C 文档生成工具, 生成的文档风格保持与 Apple 官方的一致, 极大的方便了 Xcode 识别自己写的 API 文档, 安装也是十分的简单
86. iTerm2 iTerm2 被不少程序员称赞为 Mac 下最好用的终端, 这是其源代码, 配合 oh-my-zsh 使用效果更佳
87. Kiwi 一个行为驱动开发测试框架, 适用于 iOS 平台, 旨在为开发者提供一个简单配置便可使用的 BDD 库
88. terminal-notifier 一款命令行工具, 用来给 Mac OS X 用户发送通知
89. MacDown Mac OS X 下的一款开源的 Markdown 编辑器, 创意来自与 Mou, 使用 brew cask 即可完成安装
90. TwUI Twitter 开源的一个支持硬件加速的 Mac 的 UI 框架, 最后一次的更新时间是在 3 年前(2012)
91. PaperFold for iOS 实现了类似折纸效果的视图切换, 可从不同方向进行切换, 该项目已经许久未更新
92. Reader 一款开源的 iOS PDF 阅读器, 附带书签, 列纲要等功能
93. WebViewJavascriptBridge 一个方便使用 Objective-C 与 JavaScript 进行通信的第三方库, 支持消息发送, 接收, 消息处理器的注册与调用以及设置消息处理的回调
94. iOS8-Sampler iOSX-Sampler 系列之一, 整合演示了多个具有 iOS8 新特性的的项目, 提供了非常多的例子参考
95. CocoaHTTPServer 一个用于 Mac OS X 或 iOS 应用的轻量级、可嵌入的HTTP 服务器框架, 方便开发者在应用中嵌入一个 HTTP 服务器
96. Kod Mac OS X 上一款专为程序员打造的编辑器, 这是其开源代码, 可惜的是作者在 2011 年停止了维护
97. TPKeyboardAvoiding 下拉键盘在 iOS 移动文本字段的通用解决方案, 能够自动处理键盘弹出后出现遮挡到文本输入框的问题
98. MKNetworkKit 一个轻量级网络请求框架, 完全基于 ARC, 仅有两个类, 具有自主操作多个网络请求, 更加准确的显示网络活动指标等优点
99. PKRevealController 一个 iOS 平台上的视图控制器集合, 通过展现多个视图控制器来进行控制器之间的切换. 设置简单, 高度灵活
00. AQGridView 一个命令行工具, 通过项目里的 .xcdatamodel 文件, 可以为每个 entity 生成两个类, 方便 CoreData 的使用

Android ack 解决

发表于 2018-07-11 | 分类于 Android
字数统计: 400 | 阅读时长 ≈ 2

当adb出现如下异常时:可以考虑如下做法。

1
2
3
4
5
C:\Users\zhaohao1>adb remount
* daemon not running. starting it now on port 5037 *
ADB server didn't ACK
* failed to start daemon *
error: cannot connect to daemon

解决办法:

方法一:
(1)查看任务管理器,关闭所有adb.exe,或者运行->cmd,在命令窗口输入adb kill-server
(2)重启eclipse即可
方法二, :
如果方法一不行,就查看下自己电脑是否启动了如:91手机助手,360手机 豌豆荚等先把他关闭,重新连接试试,再不行就卸载这些软件应该可以解决。因为这些软件可能占用了android手机跟eclipse连接启动adb的端口
方法三:
5037端口被占用的话,也会出现如下提示……
于是乎,真像是找到了救命稻草一般,先在命令行中输入如下指令,查看5037这个端口现在是被谁占用了:

1
netstat -a -o 5037

然后得到如下的一个结果,还真被占用了哦:

1
2
TCP    127.0.0.1:5037         USER-20161115XS:0      LISTENING       7608
TCP 127.0.0.1:5037 USER-20161115XS:64426 CLOSE_WAIT 7608

C:\Users\zhaohao1>netstat -ano | findstr "5037"
 TCP    127.0.0.1:5037         0.0.0.0:0              LISTENING       4436
 TCP    127.0.0.1:5037         127.0.0.1:49343        TIME_WAIT       0

那这个4792在我机器上,到底是个什么进程呢,好的,接着输入以下命令,把真凶找出来:

1
tasklist /fi "pid eq 4792"

然后,额……我郁闷了……是存在adb进程占用端口:

1
2
3
映像名称                       PID 会话名              会话#       内存使用
========================= ======== ================
adb.exe 7608 Console 1 9,740 K

Hello Hexo

发表于 2018-07-07
字数统计: 78 | 阅读时长 ≈ 1

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Android 拉起通知设置界面

发表于 2018-07-07 | 分类于 Android
字数统计: 272 | 阅读时长 ≈ 1

4.4以下并没有提过从app跳转到应用通知设置页面的Action,可考虑跳转到应用详情页面,下面是直接跳转到应用通知设置的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Intent intent = new Intent();
intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
intent.putExtra("app_package", getActivity().getPackageName());
intent.putExtra("app_uid", getActivity().getApplicationInfo().uid);
startActivity(intent);
} else if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse("package:" + getActivity().getPackageName()));
startActivity(intent);
}

跳转到应用详情页面的代码如下:

1
2
3
4
5
6
7
8
9
10
11
Intent localIntent = new Intent();
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 9) {
localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
localIntent.setData(Uri.fromParts("package", getActivity().getPackageName(), null));
} else if (Build.VERSION.SDK_INT <= 8) {
localIntent.setAction(Intent.ACTION_VIEW);
localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
localIntent.putExtra("com.android.settings.ApplicationPkgName", getActivity().getPackageName());
}
startActivity(localIntent);

对于检测app是否开启通知,同样是针对4.4以上的系统才有效:

1
2
NotificationManagerCompat manager = NotificationManagerCompat.from(App.getInstance().getContext());
boolean isOpened = manager.areNotificationsEnabled();

4.4以下调用该方法并不会出错,只是全部返回true,默认开启状态

markdown 语法

发表于 2018-07-05 | 分类于 blog
字数统计: 2,242 | 阅读时长 ≈ 8

Markdown 语法,参考CMD Markdown


我们理解您需要更便捷更高效的工具记录思想,整理笔记、知识,并将其中承载的价值传播给他人,Cmd Markdown 是我们给出的答案 —— 我们为记录思想和分享知识提供更专业的工具。 您可以使用 Cmd Markdown:

  • 整理知识,学习笔记
  • 发布日记,杂文,所见所想
  • 撰写发布技术文稿(代码支持)
  • 撰写发布学术论文(LaTeX 公式支持)

cmd-markdown-logo

除了您现在看到的这个 Cmd Markdown 在线版本,您还可以前往以下网址下载:

Windows/Mac/Linux 全平台客户端

请保留此份 Cmd Markdown 的欢迎稿兼使用说明,如需撰写新稿件,点击顶部工具栏右侧的 新文稿 或者使用快捷键 Ctrl+Alt+N。


什么是 Markdown

Markdown 是一种方便记忆、书写的纯文本标记语言,用户可以使用这些标记符号以最小的输入代价生成极富表现力的文档:譬如您正在阅读的这份文档。它使用简单的符号标记不同的标题,分割不同的段落,粗体 或者 斜体 某些文字,更棒的是,它还可以

1. 制作一份待办事宜 Todo 列表

  • 支持以 PDF 格式导出文稿
  • 改进 Cmd 渲染算法,使用局部渲染技术提高渲染效率
  • 新增 Todo 列表功能
  • 修复 LaTex 公式渲染问题
  • 新增 LaTex 公式编号功能

2. 书写一个质能守恒公式[^LaTeX]

$$E=mc^2$$

3. 高亮一段代码[^code]

1
2
3
4
5
6
7
@requires_authorization
class SomeClass:
pass

if __name__ == '__main__':
# A comment
print 'hello world'

4. 高效绘制 流程图

1
2
3
4
5
6
7
8
st=>start: Start
op=>operation: Your Operation
cond=>condition: Yes or No?
e=>end

st->op->cond
cond(yes)->e
cond(no)->op

5. 高效绘制 序列图

1
2
3
Alice->Bob: Hello Bob, how are you?
Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!

6. 高效绘制 甘特图

1
2
3
4
5
6
7
8
9
10
11
12
13
title 项目开发流程
section 项目确定
需求分析 :a1, 2016-06-22, 3d
可行性报告 :after a1, 5d
概念验证 : 5d
section 项目实施
概要设计 :2016-07-05 , 5d
详细设计 :2016-07-08, 10d
编码 :2016-07-15, 10d
测试 :2016-07-22, 5d
section 发布验收
发布: 2d
验收: 3d

7. 绘制表格

项目 价格 数量
计算机 \$1600 5
手机 \$12 12
管线 \$1 234

8. 更详细语法说明

想要查看更详细的语法说明,可以参考我们准备的 Cmd Markdown 简明语法手册,进阶用户可以参考 Cmd Markdown 高阶语法手册 了解更多高级功能。

总而言之,不同于其它 所见即所得 的编辑器:你只需使用键盘专注于书写文本内容,就可以生成印刷级的排版格式,省却在键盘和工具栏之间来回切换,调整内容和格式的麻烦。Markdown 在流畅的书写和印刷级的阅读体验之间找到了平衡。 目前它已经成为世界上最大的技术分享网站 GitHub 和 技术问答网站 StackOverFlow 的御用书写格式。


什么是 Cmd Markdown

您可以使用很多工具书写 Markdown,但是 Cmd Markdown 是这个星球上我们已知的、最好的 Markdown 工具——没有之一 :)因为深信文字的力量,所以我们和你一样,对流畅书写,分享思想和知识,以及阅读体验有极致的追求,我们把对于这些诉求的回应整合在 Cmd Markdown,并且一次,两次,三次,乃至无数次地提升这个工具的体验,最终将它演化成一个 编辑/发布/阅读 Markdown 的在线平台——您可以在任何地方,任何系统/设备上管理这里的文字。

1. 实时同步预览

我们将 Cmd Markdown 的主界面一分为二,左边为编辑区,右边为预览区,在编辑区的操作会实时地渲染到预览区方便查看最终的版面效果,并且如果你在其中一个区拖动滚动条,我们有一个巧妙的算法把另一个区的滚动条同步到等价的位置,超酷!

2. 编辑工具栏

也许您还是一个 Markdown 语法的新手,在您完全熟悉它之前,我们在 编辑区 的顶部放置了一个如下图所示的工具栏,您可以使用鼠标在工具栏上调整格式,不过我们仍旧鼓励你使用键盘标记格式,提高书写的流畅度。

tool-editor

3. 编辑模式

完全心无旁骛的方式编辑文字:点击 编辑工具栏 最右侧的拉伸按钮或者按下 Ctrl + M,将 Cmd Markdown 切换到独立的编辑模式,这是一个极度简洁的写作环境,所有可能会引起分心的元素都已经被挪除,超清爽!

4. 实时的云端文稿

为了保障数据安全,Cmd Markdown 会将您每一次击键的内容保存至云端,同时在 编辑工具栏 的最右侧提示 已保存 的字样。无需担心浏览器崩溃,机器掉电或者地震,海啸——在编辑的过程中随时关闭浏览器或者机器,下一次回到 Cmd Markdown 的时候继续写作。

5. 离线模式

在网络环境不稳定的情况下记录文字一样很安全!在您写作的时候,如果电脑突然失去网络连接,Cmd Markdown 会智能切换至离线模式,将您后续键入的文字保存在本地,直到网络恢复再将他们传送至云端,即使在网络恢复前关闭浏览器或者电脑,一样没有问题,等到下次开启 Cmd Markdown 的时候,她会提醒您将离线保存的文字传送至云端。简而言之,我们尽最大的努力保障您文字的安全。

6. 管理工具栏

为了便于管理您的文稿,在 预览区 的顶部放置了如下所示的 管理工具栏:

tool-manager

通过管理工具栏可以:

发布:将当前的文稿生成固定链接,在网络上发布,分享
新建:开始撰写一篇新的文稿
删除:删除当前的文稿
导出:将当前的文稿转化为 Markdown 文本或者 Html 格式,并导出到本地
列表:所有新增和过往的文稿都可以在这里查看、操作
模式:切换 普通/Vim/Emacs 编辑模式

7. 阅读工具栏

tool-manager

通过 预览区 右上角的 阅读工具栏,可以查看当前文稿的目录并增强阅读体验。

工具栏上的五个图标依次为:

目录:快速导航当前文稿的目录结构以跳转到感兴趣的段落
视图:互换左边编辑区和右边预览区的位置
主题:内置了黑白两种模式的主题,试试 黑色主题,超炫!
阅读:心无旁骛的阅读模式提供超一流的阅读体验
全屏:简洁,简洁,再简洁,一个完全沉浸式的写作和阅读环境

8. 阅读模式

在 阅读工具栏 点击 或者按下 Ctrl+Alt+M 随即进入独立的阅读模式界面,我们在版面渲染上的每一个细节:字体,字号,行间距,前背景色都倾注了大量的时间,努力提升阅读的体验和品质。

9. 标签、分类和搜索

在编辑区任意行首位置输入以下格式的文字可以标签当前文档:

标签: 未分类

标签以后的文稿在【文件列表】(Ctrl+Alt+F)里会按照标签分类,用户可以同时使用键盘或者鼠标浏览查看,或者在【文件列表】的搜索文本框内搜索标题关键字过滤文稿,如下图所示:

file-list

10. 文稿发布和分享

在您使用 Cmd Markdown 记录,创作,整理,阅读文稿的同时,我们不仅希望它是一个有力的工具,更希望您的思想和知识通过这个平台,连同优质的阅读体验,将他们分享给有相同志趣的人,进而鼓励更多的人来到这里记录分享他们的思想和知识,尝试点击 (Ctrl+Alt+P) 发布这份文档给好友吧!


再一次感谢您花费时间阅读这份欢迎稿,点击 (Ctrl+Alt+N) 开始撰写新的文稿吧!祝您在这里记录、阅读、分享愉快!

作者 @ghosert
2016 年 07月 07日

[^LaTeX]: 支持 LaTeX 编辑显示支持,例如:$\sum_{i=1}^n a_i=0$, 访问 MathJax 参考更多使用方法。

[^code]: 代码高亮功能支持包括 Java, Python, JavaScript 在内的,四十一种主流编程语言。

Hexo 入门

发表于 2018-07-05 | 分类于 blog
字数统计: 349 | 阅读时长 ≈ 2

hexo的下载和安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
常用命令:
hexo new post “博客名” #创建
hexo d -g #发布
hexo s --debug #测试界面

hexo new "postName" #新建文章
hexo new page "pageName" #新建页面
hexo generate #生成静态页面至public目录
hexo server #开启预览访问端口(默认端口4000,'ctrl + c'关闭server)
hexo deploy #部署到GitHub
hexo help # 查看帮助
hexo version #查看Hexo的版本

清除缓存的方法:
- 执行命令:hexo clean
- 然后可以生成静态博客并在本地预览:hexo g & hexo s

1、利用 npm 命令即可安装。在任意位置点击鼠标右键,选择Git Bash输入命令:npm install -g hexo
2、创建文件夹(我的是在E盘创建的Hexo),然后在Hexo文件下,右键运行Git Bash,输入命令:hexo init在_config.yml,进行基础配置

部分报错
1.找不到git部署 ERROR Deployer not found: git
解决方法
npm install hexo-deployer-git –save
3.部署类型设置git
hexo 3.0 部署类型不再是github,_config.yml 中修改

Deployment

Docs: http://hexo.io/docs/deployment.html

deploy:
type: git
repository: git@.github.com:/***.github.io.git
branch: master

  1. xcodebuild
    xcode-select: error: tool ‘xcodebuild’ requires Xcode, but active developer directory ‘/Library/Developer/CommandLineTools’ is a command line tools instance
    npm install bcrypt
  2. RSS不显示
    安装RSS插件
    npm install hexo-generator-feed –save
    开启RSS功能
    编辑hexo/_config.yml,添加如下代码:
123
Horace Zhao

Horace Zhao

宁静以致远

21 日志
6 分类
8 标签
RSS
E-Mail Github CSDN 知乎
Links
  • Github
  • StackOverflow
  • Segmentfault
© 2018 — 2019 Horace Zhao
本站访客数:
|
由 Hexo 强力驱动
|
博客全站共47.5k字