我的学习笔记


  • 首页

  • 归档

  • 标签

UDP穿透NAT实现p2p

发表于 2017-07-14

什么是NAT

NAT(Network Address Translation),网络地址转换。是1994年提出的。当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址),但现在又想和因特网上的主机通信(并不需要加密)时,可使用NAT方法。
。装有NAT软件的路由器叫做NAT路由器,它至少有一个有效的外部全球IP地址。这样,所有使用本地地址的主机在和外界通信时,都要在NAT路由器上将其本地地址转换成全球IP地址,才能和因特网连接。
同样还有NAPT,网络地址、端口转换。现在一般穿透规则就是通过获取ip、端口来实现的,重点是实现穿透,所以以下统称NAT即可。

为什么要穿透NAT

NAT可以有效的减缓全球IP枯竭的问题。但是同时NAT也屏蔽了内部设备。

比如在局域网A下有一个内网IP地址为192.168.1.155的设备A,现在一个设备B想要访问这个设备A,根据设备B所在网络可分为一下情况。

  • 设备B同在局域网A中:
    设备B可直接通过设备内网ip 192.168.1.155 地址访问设备A;
  • 设备B在外网:
    设备B无法通过设备A内网访问设备A;
  • 设备B在不同局域网A的局域网B中:
    设备B在通过ip 192.168.1.155 访问的时候可能访问到局域网B下内网IP也为192.168.1.155的设备C,也可能找不到该ip,但是无法访问设备A。

这就是为什么在内网建立一个tcp或者udp服务,内网客户端可以通过ip和端口号直接通信,而外网却无法访问该内网建立的服务。
要实现p2p(Peer to Peer),首先我们的要知道客户端ip和端口号,但是通过局域网路由器的NAT转换,生成的外网ip和端口我们无法预知,这样我们就无法建立p2p连接。

NAT转换

如果你还是不明白NAT为什么屏蔽了内部设备,接下来举个NAT转换例子就明白了。
例:
内网机器A ip(192.168.1.188) 端口(9999) - 访问外网目标主机B ip(220.233.28.42) 端口(8888):

1.数据包

目的主机:220.233.28.42
目的端口:8888

源主机:192.168.1.188
源端口:9999 (用户自定义或随机)

2.地址转换

目的主机:220.233.28.42
目的端口:8888

源主机:123.206.41.242 (NAT转换,为路由器外网ip)
源端口:17309 (NAT转换)

3.记录地址映射(包含指向)

192.168.1.188:9999 <–> 123.206.41.242:17309

4.外网主机B向内网主机A返回响应消息

目的主机:123.206.41.242
目的端口:17309

源主机:220.233.28.42
源端口:8888

5.NAT查找地址映像并转换

目的主机:192.168.1.188
目的端口:9999

源主机:220.233.28.42
源端口:8888

通过地址转换,主机A的内网地址被映射之后我们是无法预知的,而且我们无法通过主机A的内外网地址直接访问A,所以NAT屏蔽了主机A。

通过例子可以看到,当主机A访问外网主机B时,通过NAT随机分配一个(外网ip为路由器外网ip)端口,这样就把内网地址映射成了一个唯一的外网地址。然后外网主机B响应主机A时,主机B不直接访问主机A,而是通过NAT转换后的地址访问路由器,路由器就会通过映射把数据分配给内网主机A。这也就是NAT穿透的原理。

NAT穿透原理

通过双方所在网络环境不同可分为一下模式:

  • 一个在局域网,另一个在外网
  • 都在不同的局域网
  • 都在相同的局域网

上一章节已经提到了一种穿透方式,即:局域网-外网的访问模式,因为主机B在外网,ip端口确定,主机A可以直接通过B的外网地址访问B,外网主机B首先接受到了主机A的数据包,便可以知道主机A经过NAT转化后的外网地址,然后就可以进行相互通信。
但是如果双方都在不同的局域网,互相都不知道自己的外网地址怎么办。
这种情况就需要利用一个拥有唯一IP的中间服务器S,因为S ip固定并且已知,就让两个设备都向S发送数据包,S就可以的知道两个设备的公网ip,在设备p2p通信之前先去服务器查找对方的ip、端口,就可以实现通信。
两个设备同时处在同一个局域网下,可以不通过NAT,直接用内网ip进行通信,这是最节省带宽的方式。当然,也可以通过第二种,通过S服务器来得到外网ip端口,这种情况外网的IP是相同的(同一个路由器),只是分配的端口不同。

发送数据的方式利用UDP,虽然UDP不可靠,但是UDP可以轻松实现穿透。通过浏览各大博客,都没有找到通过TCP实现穿透的项目,甚至有人说TCP几乎不可能实现穿透(博主初学,还望指点)。

使用过UDP的肯定知道,首先创建一个UDP监听端口,然后可以通过这个端口发送和接收数据包。NAT会把这个监听端口映射为外网ip和端口。我们只需要通过端口发送数据包给服务器就可以让服务器拿到这个端口信息,然后可以让其他客户机通过这个端口的信息来发送数据给该端口。

在这里NAPT对UDP的端口映射(session)还有一定的规则:

  • A.源地址(内网ip)不相同,忽略其他因素,则session不同。
  • B.源地址相同,源端口不同,忽略其他因素,则session不同。
  • C.源地址相同,源端口相同,目标地址不同,对于不同的NAPT,session可能不同。(一般大部分是相同的,不同的无法进行穿透)
  • D.源地址相同,源端口相同,目标地址相同,任何端口,session一定相同。

session并不是长期存在的,不同的路由器session储存时间不同,短的有的几十秒,长的可能有的几分种。要想维持这个session可以通过心跳包来维持,比如10秒向服务器发送一个心跳包。

NAT穿透UDP具体实现

本来是打算把我的java代码黏贴出来的,后来想了想,我写的只是其中一种实现方式,不同语言也有不同的实现方式,我就来说说实现的步骤,就不再写代码了。也挺简单的。

  1. 客户端建立一个UDP监听端口
  2. 客户端做一个心跳包向服务器发送数据包
  3. 服务器接收到心跳包后,储存客户端的ip、端口信息

当客户端要进行p2p通信的时候

  1. 发送方服务器查询接收方ip、端口
  2. 数据包定向ip、端口发送数据包
  3. 完成通信

注意 重点

到此,你以为结束了? 那就大错特错了。
到这里你会发现,接收方(以下统称A)可能拿不到数据包,这种情况出现在接收方在局域网内(需要穿透NAT)。
这是为什么?
这是因为NAT丢弃了你这个来源不明的包,根本没有分发给接收设备A。
为什么叫来源不明呢,这是因为首先A的UDP端口给服务器发数据包,A的NAT创建了一个session,这样A再接收到服务器的数据时,会查找这个映射(A<—>服务器),这个映射就储存在这个session里。但是发送方(B)向A的NAT发送数据(这是在B的NAT建立对A的映射),没有指向B的映射,所以数据包被A的NAT丢弃。
那怎么解决呢,其实让A也向B发一个数据包就好了,这样A的NAT会建立一个对B的session,这样再收到B的数据NAT就可以查找到对应的映射了。

之前提到的NAPT对UDP的session映射,源地址相同,源端口相同,目标地址不同,对于不同的NAPT,session可能不同。这条规则是依据不同的NAT的。Symmetric NAPT会导致session不同,Cone NAPT则是相同的。对于p2p只要一方使用的是Symmetric NAPT就会导致无法穿透。具体可自行百度。

自定义FlowLayout

发表于 2017-06-06

自定义FlowLayout

自定义ViewGroup具体要实现以下几个步骤。

  • onMeasure 在onMeasure里实现对子view的测量,并且根据子view的测量结果决定FlowLayout的大小。主要测量ViewGroup在AS_MOST模式下的大小。
  • onLayout 在这里对子view进行布局,决定自view的位置。

对于本例,FlowLayout为流式布局。当前行剩余空间大于子View占用的大小时,子View往后排布;当前行剩余空间不能够容纳子View时,子View换行。依照该规则进行测量和布局。

FlowLayout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
public class FlowLayout extends ViewGroup {
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;//测量后FlowLayout的宽度
int height = 0;//测量后FlowLayout的高度
int lineWidth = 0;//行当前宽度
int lineHeight = 0;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);//宽度的mode
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);//宽度的大小
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
//拿到childView
View child = getChildAt(i);
//测量childView
measureChild(child, widthMeasureSpec, heightMeasureSpec);
//拿到childView的LayoutParams,只支持Margin
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//childView占用的宽度
int childWidth = lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
int childHeight = lp.topMargin + lp.bottomMargin + child.getMeasuredHeight();
//换行逻辑,不换行累加宽度,最大行宽为FlowLayout的宽度
//换行累加高度
if (childWidth + lineWidth <= widthSize) {
lineWidth += childWidth;
lineHeight = Math.max(childHeight, lineHeight);
width = Math.max(lineWidth,width);
} else {
//满足换行条件,重置lineWidth为换行之后的这一个View的宽度,累加高度
height += lineHeight;
lineHeight = childHeight;
lineWidth = childWidth;
width = Math.max(width, childWidth);
}
//处理最后一个view,累加高度
if (i == childCount - 1) {
height += lineHeight;
}
}
//设置FlowLayout的大小,主要考虑AS_MOST(wrap_content)模式的大小
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
heightMode == MeasureSpec.EXACTLY ? heightSize : height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
int height = 0;
for (int i = 0;i<childCount;i++){
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWitch = lp.leftMargin+lp.rightMargin+child.getMeasuredWidth();
int childHeght = lp.topMargin+lp.bottomMargin+child.getMeasuredHeight();
int mL = lp.leftMargin;
int mT = lp.topMargin;
int mR = lp.leftMargin+child.getMeasuredWidth();
int mB = lp.topMargin+child.getMeasuredHeight();
if (lineWidth+childWitch<=width){
//不换行,view往后边排布
child.layout(mL+lineWidth,mT+height,mR+lineWidth,mB+height);
//累加lineWidth
lineWidth+=childWitch;
//lineHeight为该行view最大高度
lineHeight = Math.max(lineHeight,childHeght);
}else {
//换行,重置lineWidth,累加lineHeight
lineWidth = 0;
height+=lineHeight;
//layout这个view
child.layout(mL+lineWidth,mT+height,mR+lineWidth,mB+height);
//换行,行宽为当前view占用的宽度
lineWidth=childWitch;
}
}
}
/**
* 根据XML文件的设置的属性,返回一个支持Margin的LayoutParams
* @param attrs
* @return
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}

然后给文字定义一个好看的背景 drawable/shape_drawable:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp"/>
<solid android:color="#ffffff"/>
<stroke
android:width="1dp"
android:dashWidth="10dp"
android:dashGap="4dp"
android:color="#158441"/>
</shape>

xml:

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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="1">
<com.ljk.FlowLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/shape_drawable"
android:padding="10dp"
android:text="Android"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/shape_drawable"
android:padding="10dp"
android:text="Switch"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/shape_drawable"
android:padding="10dp"
android:text="Java"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/shape_drawable"
android:padding="10dp"
android:text="Kotlin"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/shape_drawable"
android:padding="10dp"
android:text="C++"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/shape_drawable"
android:padding="10dp"
android:text="C#"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/shape_drawable"
android:padding="10dp"
android:text="软件工程师"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/shape_drawable"
android:padding="10dp"
android:text="App架构师"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/shape_drawable"
android:padding="10dp"
android:text="UI设计"/>
</com.ljk.FlowLayout>
</LinearLayout>

效果:

Android WebView和JavaScript交互

发表于 2017-05-28

Android WebView和JavaScript交互

现在移动应用几乎都是 Hybrid App(混合模式移动应用)是指介于web-app、native-app这两者之间的app,兼具“Native App良好用户交互体验的优势”和“Web App跨平台开发的优势”。
而hybrid的实现关键在于打通Java和JavaScript之间的交互。
在Android开发中我们是使用WebView控件来加载HTML页面的,WebView默认为我们提供了让Java和HTML页面中JavaScript脚本交互的能力。

1 Java调用JavaScript

java调用JavaScript非常简单,只需要执行下面代码即可。

1
mWebView.loadUrl("javascript:methodName(parameterValues)");

不过其中还需要注意几个细节,我们先来看个例子,然后再介绍需要注意的地方。

IDE:Android Studio

先写一个H5的页面test.html,把它放在了assets资源文件下

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE HTML>
<head>
<title>java调用JavaScript</title>
</head>
<!--提供给java调用的方法-->
<script type="text/javascript"/>
function alertMessage(message) {
alert(message)
}
</script>
</HTML>

xml布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/id_linearlayout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/id_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="alertMessage"/>
<WebView
android:id="@+id/id_webview"
android:layout_width="match_parent"
android:layout_height="match_parent"></WebView>
</LinearLayout>

Activity代码:

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
public class MyWebView extends AppCompatActivity {
private WebView mWebView;
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_web_view);
mWebView = (WebView) findViewById(R.id.id_webview);
mButton = (Button) findViewById(R.id.id_button);
loadURL();
}
private void loadURL() {
mWebView.setWebViewClient(new WebViewClient());//此方法可以在webview中打开链接而不会跳转到外部浏览器
mWebView.setWebChromeClient(new WebChromeClient());//各种内容的渲染需要使用webviewChromClient去实现,比如alert()方法
mWebView.loadUrl("file:///android_asset/test.html");
}
private void initEvents() {
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mWebView.loadUrl("javascript:alertMessage(\""+"Message"+"\")");//注意转义字符
}
});
}
//重写onKeyDown,当浏览网页,WebView可以后退时执行后退操作。
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) {
mWebView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
}

需要注意的是setWebViewClient(new WebViewClient())和setWebChromeClient(new WebChromeClient())方法。

  • setWebViewClient(new WebViewClient())可以在webview中打开链接而不会跳转到外部浏览器
  • setWebChromeClient(new WebChromeClient())方法可以给WebView设置一个WebChromeClient,各种内容的渲染需要使用webviewChromClient去实现,比如alert()方法,如果这个方法没写不会有alert()方法的效果。


点击按钮之后效果

2 JavaScript调用Java

WebView提供了一个名为WebSettings的工具类实现让Webview中的JavaScript脚本调用android应用的Java方法,实现方式很简单。

  • 调用与WebView关联的WebSettings实例的setJavaScriptEnabled方法使能调用的功能
  • 调用WebView的addJavaScriptInterface方法将应用中的Java对象暴露给JavaScript
  • 在JavaScript脚本中调用暴露的Java对象的方法

增加H5页面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE HTML>
<head>
<title>h5</title>
</head>
<!--调用java方法-->
<body>
<input type="button" value=" 打印Log" onclick=" javaObject.printLog('printLog');">
<input type="button" value=" 显示Toast" onclick=" javaObject.showToast();">
</body>
<!--提供给java调用的方法-->
<script type="text/javascript"/>
function alertMessage(message) {
alert(message)
}
</script>
</HTML>

我们新建提供给JavaScript调用的Java类javaObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class javaObject {
private Context context;
private final String TAG = "javaObjectLog";
public javaObject(Context context) {
this.context = context;
}
public void printLog(String mes){
Log.i(TAG, mes);
}
public void showToast(){
Toast.makeText(context, "Toast", Toast.LENGTH_SHORT).show();
}
}

增加Activity代码

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
public class MyWebView extends AppCompatActivity {
private WebView mWebView;
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_web_view);
mWebView = (WebView) findViewById(R.id.id_webview);
mButton = (Button) findViewById(R.id.id_button);
loadURL();
initEvents();
}
private void initEvents() {
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mWebView.loadUrl("javascript:alertMessage(\""+"Message"+"\")");
}
});
}
private void loadURL() {
mWebView.setWebViewClient(new WebViewClient());//此方法可以在webview中打开链接而不会跳转到外部浏览器
mWebView.setWebChromeClient(new WebChromeClient());//各种内容的渲染需要使用webviewChromClient去实现,比如alert()方法
WebSettings webSettings = mWebView.getSettings();//
webSettings.setJavaScriptEnabled(true);//
mWebView.loadUrl("file:///android_asset/test.html");
mWebView.addJavascriptInterface(new javaObject(this), "javaObject");//把javaObject类对象暴露给JavaScript
}
//重写onKeyDown,当浏览网页,WebView可以后退时执行后退操作。
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) {
mWebView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
}

这种方法是官方提供的,但是却存在极大地安全隐患,所以从Android 4.2开始,Google就修复了这个漏洞,所以如果你测试机是4.2以上你会发现并没有成功调用暴露的方法。我们想要安全的使用上面的方法,唯一需要改动的就是对暴露给JavaScript调用的方法添加@JavascriptInterface注解。修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class javaObject {
private Context context;
private final String TAG = "javaObjectLog";
public javaObject(Context context) {
this.context = context;
}
@JavascriptInterface
public void printLog(String mes){
Log.i(TAG, mes);
}
@JavascriptInterface
public void showToast(){
Toast.makeText(context, "Toast", Toast.LENGTH_SHORT).show();
}
}

输出结果


Kotlin初探

发表于 2017-05-23

Kotlin语言

在上周末召开的谷歌IO大会上,谷歌宣布:将Kotlin语言作为安卓开发的官方编程语言。

Kotlin由JetBrains公司开发一个基于 JVM 的新的编程语言,它的最大特点之一就是与Java 100%互通,并具备诸多Java尚不支持的新特性。在这里就不在多说,要想了解更多戳下面链接:https://www.kotlincn.net/

Kotlin语言会不会取代Java呢

将Kotlin语言作为安卓开发的官方编程语言这个消息宣布后,好多人认为Kotlin将会取代Java在安卓编程上的应用。对于这个说法我是不太赞同的,Kotlin从来就没有打算取代java,而且Kotlin是基于JVM的。Kotlin在安卓上的推出,只能说是给开发者提供了更加适合安卓的开发语言,因为Kotlin比起Java更加简介、安全。对于要不要入手Kotlin的问题,其实我觉得个人开心就好,不过既然谷歌已经决定Kotlin已经成为安卓的开发语言,无论如何都是要学学的,至于最后要用什么语言开发完全看个人爱好。

Kotlin for Android

Kotlin插件的安装

要使用Kotlin编写安卓程序只需要在Android Studio里面安装Kotlin插件即可。

第一种方法:如果你是稳定版的AS

依次打开:Android Studio > Preferences > Plugins,然后选择『Browse repositories』,在搜索框中搜索Kotlin。
选择『Kotlin』点击安装,安装完成之后,重启Android Studio。

Kotlin插件安装成功后,执行new操作,会发现弹窗多出了两个Kotlin相关选项:

1)“KotlinFile/Class”:一般Kotlin类
2)“Kotlin Activity”:Activity类

第一次创建Kotlin类的时候IDE会提示配置Kotlin

配置完成后,会在build.gradle中生成下面代码

1
2
3
4
5
6
7
8
9
10
buildscript {
ext.kotlin_version = '1.0.4'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
...
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
...
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

配置完成后

Kotlin有一个很实用的操作,就是将Java源代码转化为Kotlin代码:

执行code–> Convert Java File to Kotlin File

会把java代码转化成Kotlin代码。
这是转换后Activity的代码

1
2
3
4
5
6
7
8
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

第二种方法:官方下载AS 3.0尝鲜版,AS 3.0默认配置了Kotlin插件

网址:https://developer.android.com/studio/preview/index.html?utm_source=android-studio

从这个网站可以下载AS 3.0 尝鲜版,尝鲜版AS可以与你以前安装的稳定版AS共同运行,下载的压缩包解压就能直接使用。
在这之前你要保证你已经翻墙了,如果你没有翻墙可以找我要压缩包。

安装之后好多人都遇到这个错误,这是因为Kotlin的版本错误导致的

把build.gradle里面的ext.kotlin_version = ‘1.1.2-3’改为ext.kotlin_version = ‘1.1.2-4’同步一下就行了

新建Kotlin安卓程序

如果你不是用的3.0,那么你就直接把你的java程序利用代码转换转换为Kotlin就行了。
只介绍一下AS 3.0的使用方法

新建过程跟以前是一样一样的,并没有太大差别,只有这个步骤,勾选上Kotlin支持

新建之后的项目默认是有的就是Kotlin

具体的Kotlin使用方法戳官方链接:https://www.kotlincn.net/docs/reference/android-overview.html

Kotlin有一个很实用的功能

Kotlin可以省略findViewById的方式绑定控件,可以直接用控件的id来操作控件。

要使用这个功能,首先在app:build.gradle里面添加 apply plugin: ‘kotlin-android-extensions’

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
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
...
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile 'com.android.support:appcompat-v7:25.3.0'
testCompile 'junit:junit:4.12'
compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4'
}

然后在你的Activity添加
import kotlinx.android.synthetic.main.你绑定的Layout.*
然后你就可以在Activity直接使用id来操作控件了

对象的序列化和Socket简单使用

发表于 2017-05-17

对象的序列化和Socket简单使用

对象序列化就是把对象转化为字节序列的过程,相对的反序列化就是把字节序列转化为对象的过程,是把对象转化成容易传输的流的方法,一般是生成传输的字节流或者是IO文件(长期存储)。

阅读全文 »

ImageLoader的实现(2)-ImageLoader的具体实现

发表于 2017-05-16

ImageLoader的实现(2)-ImageLoader的具体实现

1.1 ImageLoaderde介绍

一个优秀的ImageLoader应该具备以下功能:

  • 图片的同步加载;
  • 图片的异步加载;
  • 图片压缩;
  • 内存缓存;
  • 磁盘缓存;
  • 网络拉取。
阅读全文 »

ImageLoader的实现(1) LruCache和DiakLruCache

发表于 2017-05-15

ImageLoader的实现(1)-LruCache和DiakLruCache

参考文献《Android开发艺术探索》

LRU(Least Recently Used),LRU是近期最少使用算法

阅读全文 »

打造优美列表布局——进阶篇

发表于 2017-04-05

打造优美列表布局——进阶篇

比起ListView,RecyclerView可以自定义LayoutManager来实现不同的页面布局。

阅读全文 »

打造优美列表布局——基础篇

发表于 2017-04-04

打造优美列表布局——基础篇

最近学习使用第三方LayoutManager实现了优美的图书列表,记录下我的学习历程,同时写给学弟学习使用。

阅读全文 »

Android跨程序共享数据,探究内容提供器(进阶篇)

发表于 2017-03-16

Android跨程序共享数据,探究内容提供器(进阶篇)

上一章讲到怎么使用ContentResolver访问系统提供的数据接口读取联系人信息,那么系统程序是怎样对外建立并开放这些数据接口的呢?好多时候我们自己应用程序也需要数据共享的功能,以便于其他程序访问我们的数据,那么我们该怎么实现我们自己的数据接口,接下来让我们来一起学习。

阅读全文 »
12
LiuJiaKuo

LiuJiaKuo

学习总结 思考感悟 知识管理

13 日志
4 标签
© 2017 LiuJiaKuo
由 Hexo 强力驱动
主题 - NexT.Muse