RustFisher

Android App, Java, Python

0%

Gradle for Android开始

Google在Gradle中的目标:能复用代码,创建构建变量,能配置和定制构建过程。

Gradle基础

Gradle构建脚本并不是用XML来写的,而是基于Groovy的一种(domain-specifc language)
DSL语言。这是一种运行在JVM上的动态语言。

如果要构建新的任务和插件,我们需要了解这门语言。

Projects and tasks

这是Gradle种最重要的两个概念。每个构建(build)至少包含一个project,每一个project包含
一个或多个task。每个build.gradle代表一个project。task被定义在这个构建脚本中。
一个task对象包含一列需要被执行的Action对象。一个Action对象就是一块被执行的代码,就像
Java中的方法。

当初始化构建进程时,Gradle收集build文件中的project和task对象。

构建的生命周期(The build lifecycle)

为简化构建过程,构建工具创造了一种工作流的动态模型DAG(Directed Acyclic Graph)。
这意味着所有的任务会一个接一个地执行,不会出现循环的情况。
一个任务一旦被执行就不会再被调用。没有依赖的任务永远是最优先执行的。
在配置过程中生成依赖关系。

一个Gradle构建过程有3个步骤:

  • 初始化:工程实例被创建时初始化。如果有多个模块,每个模块有自己的build.gradle文件,
    多个project被创建。
  • 配置:这一步执行build脚本,创建并配置每个project的task。
  • 执行:Gradle决定执行那些任务。根据当前目录和传入参数执行task。

build配置文件

build.gradle文件。配置build的地方。

1
2
3
4
5
6
7
8
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
}
}

repositories块中,指定JCenter作为依赖仓库。
这个脚本获取了Android构建工具。这个Android插件提供了构建和测试应用所需的功能。

插件被用来扩展Gradle构建脚本的功能。在project中使用插件,就可以定义属性和任务。

Gradle Wrapper初步

Gradle是一个开发中的工具。使用Gradle Wrapper可以避免一些问题,确保能构建顺利。
Gradle在Windows系统上提供了batch文件,在其他系统上提供了shell脚本。试图运行脚本时,会
自动检查并下载Gradle。但在我们的网络比较令人着急。可以尝试在网络上找资源。

比如我下载了一个gradle-2.14.1-all.zip,将其放到Android工程的gradle/wrapper下

1
2
3
4
5
gradle
`-- wrapper
|-- gradle-2.14.1-all.zip
|-- gradle-wrapper.jar
`-- gradle-wrapper.properties

然后修改gradle-wrapper.properties文件,把Url修改成
distributionUrl=gradle-2.14.1-all.zip

在Android Studio提供的Terminal中运行grawdlew,先unzipping,然后开始下载依赖文件。
这些文件在windows中默认存放到
C:\Users\UserName\.gradle\wrapper\dists\gradle-2.14.1-all,还是很占空间的。
此时你可以在项目下的命令行中使用grawdlew命令。比如查看版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
G:\rust_proj\NDKProj>gradlew -v

------------------------------------------------------------
Gradle 2.14.1
------------------------------------------------------------

Build time: 2016-07-18 06:38:37 UTC
Revision: d9e2113d9fb05a5caabba61798bdb8dfdca83719

Groovy: 2.4.4
Ant: Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM: 1.8.0_77 (Oracle Corporation 25.77-b03)
OS: Windows 7 6.1 amd64

如果在另一个Android项目下同样复制了gradle-2.14.1-all.zip,并且尝试运行gradlew,
C盘里相应目录下又会多一个文件夹。

获取Gradle Wrapper

打开Windows CMD,进入前面配置好的Android工程目录,同样可以运行gradlew。

此时我们的C盘里已经有gradle-2.14.1-all.zip了。找到gradle.bat的路径,将其添加到
电脑PATH中。这里添加到用户的环境变量中。

在G盘新建一个目录gradleTest,然后创建一个build.gradle文件;其中填写如下代码

1
2
3
task wrapper(type: Wrapper) {
gradleVersion = '2.4'
}

进入刚才的目录,在CMD中直接运行gradle

1
2
3
4
5
6
7
8
9
G:\gradleTest>gradle
:help
Welcome to Gradle 2.14.1.
To run a build, run gradle <task> ...
To see a list of available tasks, run gradle tasks
To see a list of command-line options, run gradle --help
To see more detail about a task, run gradle help --task <task>
BUILD SUCCESSFUL
Total time: 1.714 secs

此时目录下生成了一个.gradle目录

如果当前目录下没有build.gradle文件,gradle也会执行并生成.gradle目录。

我们来观察Android项目里Gradle Wrapper的情况

1
2
3
4
5
6
NDKProj/
├── gradlew
├── gradlew.bat
└── gradle/wrapper/
├── gradle-wrapper.jar
└── gradle-wrapper.properties

Gradle Wrapper包含3个部分:

  • MS可执行的gradlew.bat和Linux, Mac OS X可执行的gradlew
  • 脚本需要的Jar文件
  • 一个properties文件

在前面我们已经把properties文件修改成了这样:

1
2
3
4
5
6
#Mon Aug 29 19:26:36 CST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=gradle-2.14.1-all.zip

原distributionUrl如下:

1
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

这意味着我们可以使用不同的URL和Gradle。我们前面已经这么做了。

运行基本的构建任务(task)

进入Android工程目录下,用命令行执行gradlew
gradlew tasks会打印出任务列表;gradlew tasks --all打印出所有的任务

gradlew assembleDebug编译当前项目,创建一个debug版本的apk

gradlew clean清理当前项目的output

gradlew check运行所有的检查,通常是在真机或者模拟器上运行测试

gradlew build触发assemble 和 check

这些功能在Android Studio上都有相应按键

参考:Gradle for Android Kevin Pelgrims

QMainWindow继承自QWidget
QMainWindow相当于程序的主界面,内置了menu和toolBar。
使用 Qt Designer 可以很方便地添加menu选项。

对于较大型的界面,用Qt Designer比较方便。.ui文件就像Android中使用xml一样。
画出的ui文件可以用PyQt中的PyUIC转换成py文件。转换后的py文件中有一个class。
新建一个继承自QMainWindow的类,来调用生成的这个类。

主窗口关闭时,会调用closeEvent(self, *args, **kwargs),可复写这个方法,加上一些关闭时的操作。
比如终止子线程,关闭数据库接口,释放资源等等操作。

PyQt5 手写 QMainWindow 示例

Win7 PyCharm Python3.5.1 PyQt5

手写一个main window,主要使用了菜单栏、文本编辑框、工具栏和状态栏

1
2
3
4
5
|-- main.py
|-- res
| `-- sword.png
`-- ui
`-- app_main_window.py

main.py主文件

1
2
3
4
5
6
7
8
9
10
import sys

from PyQt5.QtWidgets import QApplication
from ui.app_main_window import AppMainWindow

if __name__ == '__main__':
app = QApplication(sys.argv)
window = AppMainWindow()
window.show()
sys.exit(app.exec_())

app_main_window.py窗口实现文件

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
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QTextEdit


class AppMainWindow(QMainWindow):
"""
菜单栏、文本编辑框、工具栏和状态栏
"""

def __init__(self):
super().__init__()
self.init_ui()

def init_ui(self):
# 菜单栏
self.statusBar().showMessage('Main window is ready')
self.setGeometry(500, 500, 450, 220)
self.setMinimumSize(150, 120)
self.setWindowTitle('MainWindow')

# 文本编辑框
text_edit = QTextEdit()
self.setCentralWidget(text_edit) # 填充剩下的位置

# 定义退出动作
exit_action = QAction(QIcon('res/sword.png'), 'Exit', self)
exit_action.setShortcut('Ctrl+Q')
exit_action.setStatusTip('Exit App') # 鼠标指向选项时在窗口状态栏出现的提示
# exit_action.triggered.connect(QCoreApplication.instance().quit)
exit_action.triggered.connect(self.close) # 关闭app

# 定义菜单栏,添加一个选项
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu('&File')
file_menu.addAction(exit_action)

# 定义工具栏,添加一个退出动作
toolbar = self.addToolBar('&Exit')
toolbar.addAction(exit_action)

有的时候PyCharm给的代码提示不完全。网上说PyCharm配合vim插件来使用能带来很好的体验。
生成的界面中,工具栏可以自由的拖动,可以放在上下左右4个地方。

同样的代码,可以很方便地移植到PyQt4中。

使用designer画出来的界面

Ubuntu

使用designer绘制好界面后,讲ui文件转换成py代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication
from ui_main_window import Ui_UAppMainWindow


class RustMainWindow(QMainWindow):
"""主界面类"""

def __init__(self):
super(RustMainWindow, self).__init__()
self.ma = Ui_UAppMainWindow() # designer画的界面
self.ma.setupUi(self)


if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = RustMainWindow()
main_window.show()
sys.exit(app.exec_())

复写__init__初始化方法时需要调用父类方法

PyQt4手写窗口代码

和上面那个功能类似。

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
import sys
from PyQt4.QtGui import QMainWindow, QTextEdit, QAction, QIcon, QApplication


class AppMainWindow(QMainWindow):
def __init__(self):
super(AppMainWindow, self).__init__()
self.init_ui()

def init_ui(self):
self.statusBar().showMessage('Main window is ready')
self.setGeometry(500, 500, 450, 220)
self.setMinimumSize(150, 120)
self.setWindowTitle('MainWindow')

text_edit = QTextEdit()
self.setCentralWidget(text_edit)

exit_action = QAction(QIcon('res/ic_s1.png'), 'Exit', self)
exit_action.setShortcut('Ctrl+Q')
exit_action.setStatusTip('Exit App')
exit_action.triggered.connect(self.close)

menu_bar = self.menuBar()
file_menu = menu_bar.addMenu('&File')
file_menu.addAction(exit_action)

toolbar = self.addToolBar('&Exit')
toolbar.addAction(exit_action)


if __name__ == '__main__':
app = QApplication(sys.argv)
window = AppMainWindow()
window.show()
sys.exit(app.exec_())

可以看出,PyQt4 和 5 的代码基本上是通用的。复写__init__的方法不同。

PyQt5.QtWidgets 示例

Win7 PyCharm Python3.5.1 PyQt5

主要文件:

1
2
3
4
5
|-- main.py
|-- res
| `-- fish.jpg
`-- ui
`-- app_widget.py

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
import sys

from PyQt5.QtWidgets import QApplication

from ui.app_widget import AppQWidget

if __name__ == '__main__':
app = QApplication(sys.argv)
w = AppQWidget()
w.show()

sys.exit(app.exec_())

app_main_window.py自定义了一个居中显示的窗口,关闭时弹确认框

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
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QPushButton, QDesktopWidget, QMessageBox


class AppQWidget(QWidget):
"""
A custom QWidget by Rust Fisher
"""

def __init__(self):
super().__init__()
self.init_ui()

def init_ui(self):
# self.setGeometry(300, 300, 400, 200) # 相当于move和resize
self.resize(300, 200)
self.move_to_center()
self.setWindowTitle('Demo1')
self.setWindowIcon(QIcon('res/fish.jpg'))

btn1 = QPushButton('Quit', self)
btn1.setToolTip('Click to quit')
btn1.resize(btn1.sizeHint())
btn1.move(200, 150)
btn1.clicked.connect(QCoreApplication.instance().quit) # cannot locate function connect

def closeEvent(self, event):
reply = QMessageBox.question(self, 'Message',
'Are you sure to quit now?',
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()

def move_to_center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center() # got center info here
qr.moveCenter(cp)
self.move(qr.topLeft()) # 应用窗口的左上方的点到qr矩形的左上方的点,因此居中显示在我们的屏幕上

Tips

多控件可以存在list中

存在一起,需要对整体操作时直接遍历列表

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
    # 同组的控件可以存在同一个list中
self.cb_list = [
self.ma.i2cCB,
self.ma.mipiCB,
self.ma.eepromCB,
self.ma.tem_sensorCB,
self.ma.lensCB,
self.ma.vcmCB,
self.ma.mirrorCB,
self.ma.mirrorCaliCB, ]

self.test_count_et_list = [
self.ma.i2cCountEt,
self.ma.mipiCountEt,
self.ma.eepromCountEt,
self.ma.tem_sensorCountEt,
self.ma.lensCountEt,
self.ma.vcmCountEt,
self.ma.mirrorCountEt,
self.ma.mirrorCaliCountEt,
]

# 需要操作某组控件时 直接遍历列表
def _click_test_item_cb(self):
""" Update [choose all checkbox] by all test item state """
choose_all = True
for cb in self.cb_list:
choose_all = choose_all & cb.isChecked()
self.ma.selecteAllCB.setChecked(choose_all)

QApplicationQWidget

QApplication是一个单例,在QWidget中可以通过QApplication.instance()获取到对象

实际上在实例化QApplication前就使用QtGui.QWidget()是会报错的

1
2
>>> QtGui.QWidget()
QWidget: Must construct a QApplication before a QPaintDevice

参考 How QApplication() and QWidget() objects are connected in PySide/PyQt?

在我们自定义的QMainWindow中,也可以直接获取到QApplication的实例。

1
2
3
4
5
6
7
class RustMainWindow(QMainWindow):
""" This is the main class """

def _trigger_english(self):
print "Change to English", QApplication.instance()

# Change to English <PyQt4.QtGui.QApplication object at 0x02ABE3A0>

注意widget持有外部对象引用的问题

如果在程序启动的地方将引用交给widget,退出时会造成应用无法关闭的问题(类似内存泄漏)。

1
2
3
4
5
6
if __name__ == '__main__':
app = QApplication(sys.argv)
# 这里把app交给了MainWindow,MainWindow关闭时是无法正常退出应用的
main_d = RustMainWindow(app) # 不建议这么做
main_d.show()
sys.exit(app.exec_())

安装和配置PyCharm
修改默认配置,修改config和system的路径,避免占据C盘太多的空间
将PyQt中的工具PyUIC安装到PyCharm中,使用更便捷(Windows和Ubuntu平台)

  • win7
  • Python3.5.1
  • PyQt5-5.6

PyCharm版本: JetBrains PyCharm Community Edition 2016.3.1(64)

安装路径: E:\IntelliJ IDEA Community Edition 2016.1.1

PyCharm默认配置

和Android Studio类似,可以自定义IDE的配置

在第一次启动前,找到bin\idea.properties,修改一下路径

1
2
3
idea.config.path=E:/IntelliJIDEAPath/config

idea.system.path=E:/IntelliJIDEAPath/system

启动后,可以发现config和system都在E:/IntelliJIDEAPath
此举是为了避免C盘挤爆

PyCharm工程配置

打开Settings

1
2
3
4
Build, Execution, Deployment
Console
Python Console
Python interpreter 选择 Python 3.5.1

假设有一个工程gui_app,同样要检查一下设置

1
2
3
Project: gui_app
Project interpreter
选择3.5.1 后面会显示路径

安装扩展工具

打开Settings > Tools > External Tools

选择新建+,或者编辑

安装QtDesigner

Name: QtDesigner

1
2
3
4
5
Tool Settings
Program: E:\Python351\Lib\site-packages\PyQt5\designer.exe
## 选择安装好的PyQt5\designer.exe

Working directory: $FileDir$

安装PyUIC

将designer生成的ui文件转为py文件的工具;这是Python自带的工具

Name: PyUIC

1
2
3
4
5
6
Tool Settings
Program: E:\Python351\python.exe
## 选择安装好的python.exe

Parameters: -m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
Working directory: $FileDir$

Ubuntu下PyCharm配置

将designer生成的ui文件转为py文件的工具
需要sudo apt-get install pyqt5-dev-tools
配置工具[Tool Settings]

1
2
3
Program: pyuic5
Parameters: -o $FileNameWithoutExtension$.py $FileNameWithoutExtension$.ui
Working directory: $FileDir$

在Windows和Ubuntu下安装PyQt5
需要先安装并配置好Python,Windows下需要配置环境变量。PyQt需要对应上Python版本。

Windows环境

用pip3安装PyQt5

先确认Python相关环境变量已经配置好,比如:

1
D:\python36;D:\python36\Scripts;D:\python36\libs;

然后运行pip3,参考 PyQt5 Download
1
pip3 install PyQt5

从网上下载相关文件并安装,等待过程比较长。

PyQt5-5.6-gpl exe 安装方式

Win7 Python3.5.1

PyQt5-5.6-gpl-Py3.5-Qt5.6.0-x64-2.exe (最新版本已经不再提供exe版本)

先安装Python3.5.1到 E:\Python351
再去官网下载PyQt5,翻墙后下载速度更快。双击安装,PyQt5会自动找到Python35的目录。
本例中PyQt5安装到 E:\Python351\Lib\site-packages\PyQt5

现在就可以使用PyQt5了。

>>> from PyQt5.QtWidgets import *

在命令行显示一个label试一下

1
2
3
4
5
6
>>> import sys
>>> from PyQt5 import QtWidgets
>>> app = QtWidgets.QApplication(sys.argv)
>>> label = QtWidgets.QLabel('Label')
>>> label.resize(150,100)
>>> label.show()

Ubuntu 16.04

Python3.5
直接安装

1
2
3
4
sudo apt-get install python3-dev
sudo apt-get install python3-pyqt5
sudo apt-get install qt5-default qttools5-dev-tools
designer # 启动designer

安装pyuic5

1
sudo apt-get install pyqt5-dev-tools

环境与工具

  • Python3
  • PyCharm CE

csv简介

什么是csv?csv是逗号分隔的一种文件格式。
例如下面这个csv格式的文本。

1
2
3
姓名,年龄,标签
Rust Fisher,18,Python
Tom Hanks,29,Java

这样的.csv文件可以用Excel或者WPS打开,会给出表格的形式。

要注意的是,csv是文本格式,并不是Excel的格式。我们用文本编辑器(例如xcode,记事本)是可以直接编辑它的。

读写csv文件

读文件时先产生str的列表,把最后的换行符删掉;然后一个个str转换成int

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## 读写csv文件
csv_file = 'datas.csv'

csv = open(csv_file,'w')
for i in range(1,20):
csv.write(str(i) + ',')
if i % 10 == 0:
csv.write('\n')
csv.close()

result = []
with open(csv_file,'r') as f:
for line in f:
linelist = line.split(',')
linelist.pop()# delete: \n
for index, item in enumerate(linelist):
result.append(int(item))
print('\nResult is \n' , result)

输出:

1
2
Result is
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

检查目录是否存在

若目标目录不存在,则新建一个目录

1
2
3
4
5
6
import os
json_dir = "../dir_json/2017-04/"
if not os.path.exists(json_dir):
print("json dir not found")
os.makedirs(json_dir)
print("Create dir " + json_dir)

写文件时指定格式

参考下面的代码,打开文件时指定utf8,转换成json时指定ensure_ascii=False

1
2
3
import json
json_file = open(json_dir + id + '.json', 'w', encoding='utf8')
json_file.write(json.dumps(data_dict, ensure_ascii=False))

避免写成的json文件乱码

函数 enumerate(iterable, start=0)

返回一个enumerate对象。iterable必须是一个句子,迭代器或者支持迭代的对象。

enumerate示例1:

1
2
3
4
5
6
7
8
>>> data = [1,2,3]
>>> for i, item in enumerate(data):
print(i,item)


0 1
1 2
2 3

示例2:
1
2
3
4
5
6
7
8
>>> line = 'one'
>>> for i, item in enumerate(line,4):
print(i,item)


4 o
5 n
6 e

参考: https://docs.python.org/3/library/functions.html?highlight=enumerate#enumerate

class int(x=0)

class int(x, base=10)
返回一个Integer对象。对于浮点数,会截取成整数。

1
2
3
4
5
6
7
8
9
10
11
>>> print(int('-100'),int('0'),int('3'))
-100 0 3
>>> int(7788)
7788
>>> int(7.98)
7
>>> int('2.33')
Traceback (most recent call last):
File "<pyshell#27>", line 1, in <module>
int('2.33')
ValueError: invalid literal for int() with base 10: '2.33'

读取binary文件

逐个byte读取,注意用b''来判断是否读到文件尾部

1
2
3
4
5
6
7
8
9
10
11
@staticmethod
def convert_bin_to_csv(bin_file_path, csv_file_path):
if not os.path.exists(bin_file_path):
print("Binary file is not exist! " + bin_file_path)
return
with open(bin_file_path, "rb") as bin_f:
cur_byte = bin_f.read(1)
while cur_byte != b'':
# Do stuff with byte.
print(int.from_bytes(cur_byte, byteorder='big', signed=True))
cur_byte = bin_f.read(1)

读取到的byte可以转换为int,参考文档

这里 cur_byte 类似于 b'\x08'

1
print(int.from_bytes(cur_byte, byteorder='big', signed=True))

从bin中读取数据并存入CSV文件中

先从bin中读取byte,规定好几个字节凑成1个数字。
按每行一个数字的格式写入CSV文件。

1
2
3
4
5
6
7
8
9
10
11
12
@staticmethod
def convert_bin_to_csv(bin_file_path, csv_file_path, byte_count=1, byte_order='big', digit_signed=True):
if not os.path.exists(bin_file_path):
print("Binary file is not exist! " + bin_file_path)
return
with open(csv_file_path, "w") as csv_f:
with open(bin_file_path, "rb") as bin_f:
cur_byte = bin_f.read(byte_count)
while cur_byte != b'':
csv_f.write(str(int.from_bytes(cur_byte, byteorder=byte_order, signed=digit_signed)) + ",\n")
cur_byte = bin_f.read(byte_count)

bin存储的数据格式一定要商量好。

  • 开发环境: win7 64,Android Studio 2.1

需要工具:NDK,Cygwin

使用adb查看手机CPU架构信息

将手机通过USB连接到电脑,adb shell进入手机根目录,执行cat /proc/cpuinfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
shell@hnCHE-H:/ $ cat /proc/cpuinfo
cat /proc/cpuinfo
Processor : AArch64 Processor rev 3 (aarch64)
processor : 0
processor : 1
processor : 2
processor : 3
processor : 4
processor : 5
processor : 6
processor : 7
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32
CPU implementer : 0x41
CPU architecture: AArch64
CPU variant : 0x0
CPU part : 0xd03
CPU revision : 3

Hardware : hi6210sft

可以看到手机处理器的信息

使用 SDK Manager 配置安装 NDK

添加系统环境变量 G:\SDK\ndk-bundle;G:\SDK\platform-tools

下载并安装Cygwin:https://cygwin.com/install.html

Cygwin 安装NDK需要的工具包(如果第一次安装时没有选择工具包,可以再次启动安装):
make, gcc, gdb, mingw64-x86_64-gcc, binutils
tools

配置G:\soft\Cygwin\home\Administrator\.bashrc,添加下面的指令,使用英文界面。

1
2
export LANG='en_US'
export LC_ALL='en_US.GBK'

配置text选项,在option里的text可设置。
可以在G:\soft\Cygwin\home\Administrator\.minttyrc中看到。

1
2
Locale=zh_CN
Charset=GBK

设置完字体后可以避免中文乱码。

配置 G:\soft\Cygwin\home\Administrator\.bash_profile

1
2
NDK=/cygdrive/G/SDK/ndk-bundle/ndk-build.cmd
export NDK

在Cygwin中查找NDK位置,可以看到在SDK目录里面

1
2
3
Administrator@rust-PC /cygdrive/g/soft/Cygwin/home/Administrator
$ echo $NDK
/cygdrive/G/SDK/ndk-bundle/ndk-build.cmd

操作示例NDK工程

JDK10已经不提供javah这个工具了,我们可以使用as支持c++的功能;详情见下文

生成一次试试。从github上获取android-ndk-android-mk,进入hello-jni工程。

1
2
3
Administrator@rust-PC /cygdrive/g/rust_proj/android-ndk-android-mk/hello-jni
$ ndk-build.cmd
# 输出很多信息

编译成功后,自动生成一个libs目录,编译生成的.so文件放在里面。

1
2
3
4
5
6
7
Administrator@rust-PC /cygdrive/g/rust_proj/NDKTest/app/src/main
$ ndk-build.cmd
[armeabi] Install : librust_ndk.so => libs/armeabi/librust_ndk.so
# 进入java目录,编译.h文件
Administrator@rust-PC /cygdrive/g/rust_proj/NDKTest/app/src/main/java
$ javah com.rustfisher.ndktest.HelloJNI
# 会生成一个.h文件

将它复制到jni文件夹下;这个就是JNI层的代码。

Ubuntu下javah报错。需要添加参数

1
javah -cp /home/rust/Android/Sdk/platforms/android-25/android.jar:. com.example.LibUtil

使用C/C++实现JNI

遇到错误: Error:Execution failed for task ‘:app:compileDebugNdk’.

Error: NDK integration is deprecated in the current plugin. Consider trying the new experimental plugin. For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental. Set “android.useDeprecatedNdk=true” in gradle.properties to continue using the current NDK integration.

解决办法:在app\build.gradle文件中添加

1
2
3
4
sourceSets.main {
jniLibs.srcDir 'src/main/libs'
jni.srcDirs = [] //disable automatic ndk-build call
}

文件有3种:接口文件.h; 实现文件.c,注意与前面的.h文件同名; .h.c生成的库文件.so

操作步骤小结

From Java to C/C++
Step 1 定义Java接口文件,里面定义好native方法。
Step 2 javah生成.h接口文件 。
Step 3 复制.h文件的文件名,编写C/C++文件。注意要实现.h中的接口。

NDK遇到的问题与注意事项

文件关联问题

写cpp源文件的时候,忘记include头文件。产生java.lang.UnsatisfiedLinkError: No implementation found for 之类的错误
stackoverflow上有关于Android NDK C++ JNI (no implementation found for native…)的问题。

NDK本地对象数量溢出问题 Local ref table overflow

NDK本地只允许持有512个本地对象,return后会销毁这些对象。必须注意,在循环中创建的本地对象要在使用后销毁掉。

1
env->DeleteLocalRef(local_ref);// local_ref 是本地创建的对象

调用Java方法时,注意指定返回值

env->CallBooleanMethod(resArrayList,arrayList_add, javaObject); ArrayList的add方法返回Boolean

参考:https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html

C++调用C方法

C++文件中,需要调用C里面的方法。如果未经任何处理,会出现无引用错误

1
error: undefined reference to '......

因此在C++文件中涉及到C方法,需要声明。
例如

1
2
3
4
5
6
7
8
#ifdef __cplusplus
extern "C" {
#include "c_file_header.h"
#ifdef __cplusplus
}
#endif
#endif
// ___ 结束声明

javah生成的JNI头文件中也有extern,可作为参考

NDK中使用logcat

配置:Cygwin, NDK 14.1…
可以在NDK中使用logcat,方便调试
需要在mk文件中添加

1
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog

代码中添加头文件,即可调用logcat的方法

1
2
3
4
#include <android/log.h>
#define LOG_TAG "rustApp"

__android_log_write(ANDROID_LOG_VERBOSE, LOG_TAG, "My Log");

此时编译出现了错误:

1
2
3
4
G:/SDK/ndk-bundle/build//../toolchains/x86_64-4.9/prebuilt/windows-x86_64/lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/bin\ld: warning: skipping incompatible G:/SDK/ndk-bundle/build//../platforms/android-21/arch-x86_64/usr/lib/libc.a while searching for c
G:/SDK/ndk-bundle/build//../toolchains/x86_64-4.9/prebuilt/windows-x86_64/lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/bin\ld: error: treating warnings as errors
clang++.exe: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [G:/openSourceProject/NDKAlgo/app/src/main/obj/local/x86_64/libNDKMan.so] Error 1

出现了error: treating warnings as errors
处理方法,在mk文件中添加LOCAL_DISABLE_FATAL_LINKER_WARNINGS=true
再次编译即可

我们可以使用宏定义简化打log的写法

1
2
3
4
5
6
7
8
9
#define LOG_TAG    "rustApp"
#define LOGV(...) __android_log_write(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN , LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOG_TAG, __VA_ARGS__)

// 调用
LOGV("This is my log");

Android Studio 3 为library module添加C++支持

as在新建project的时候可以选择支持C++,可以新建一个支持C++的项目来参考。
可以不用自己javah来生成头文件。

在工程中新建android library,将CMakeLists.txt添加到模块中。这里模块名是native-lib

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
cmake_minimum_required(VERSION 3.4.1)

add_library( # Sets the name of the library.
native-lib

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp
src/main/cpp/imagetool.cpp)

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log )

target_link_libraries( # Specifies the target library.
native-lib

# Links the target library to the log library
# included in the NDK.
${log-lib} )

修改模块的build.gradle

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
android {
compileSdkVersion 26
defaultConfig {
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// 添加
externalNativeBuild {
cmake {
cppFlags ""
}
}
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// 添加
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}

新建Java文件和C++文件,大致目录如下

1
2
3
4
5
6
7
8
9
10
11
main
|-- AndroidManifest.xml
|-- cpp
| |-- imagetool.cpp // cpp文件
| `-- native-lib.cpp
|-- java
| `-- com
| `-- example
| `-- myclib
| `-- ImageDetect.java // Java文件
`-- res

ImageDetect.java

1
2
3
4
5
6
7
8
9
10
11
12
public class ImageDetect {

static {
System.loadLibrary("native-lib");
}

public static native void inputPath(String path);

public static native String stringFromJNI();

public static native int getOne();
}

native-lib.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <jni.h>
#include <string>

extern "C" {

JNIEXPORT jstring
JNICALL
Java_com_example_myclib_ImageDetect_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "In myclib Hello from C++";
return env->NewStringUTF(hello.c_str());
}

JNIEXPORT jint
JNICALL
Java_com_example_myclib_ImageDetect_getOne(
JNIEnv *env,
jobject /* this */) {
return 9257;
}
}

imagetool.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <jni.h>
#include <string>

extern "C"
JNIEXPORT void

JNICALL
Java_com_example_myclib_ImageDetect_inputPath(
JNIEnv *env,
jobject /* this */jobj, jstring inputPath) {
// just test
return;
}

编译运行即可。

Ubuntu14.04

目的:想用awk来统计某个文本中单词出现的次数,并以一定的格式输出结构

通常,awk逐行处理文本。awk每接收文件的一行,然后执行相应的命令来处理。

用legal文件来做示例

1
2
3
4
5
6
7
8
$ cat /etc/legal

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

搜索统计单词“law”的个数

1
2
$ awk -F : '/law/{count++} END{print "the count is ",count}' /etc/legal
the count is 1

统计单词“the”的个数

1
2
$ awk -F : '/the/{count++} END{print "the count is ",count}' /etc/legal
the count is 3

找到指定单词,自定义变量count自增,最后输出语句和count值

命令sort,把各行按首字母排列顺序重新排列起来

  • sort -nr,每行都以数字开头,按数字从达到小,排列各行
  • uniq -c,统计各行出现的次数,并把次数打印在每行前端
  • awk参数 NF - 浏览记录的域的个数

综合起来,命令就是

1
2
awk -F' ' '{for(i=1;i<=NF;i=i+1){print $i}}' /etc/legal |
sort|uniq -c|sort -nr|awk -F' ' '{printf("%s %s\n",$2,$1)}'

统计/etc/legal中单词出现次数,并以“单词 次数”格式输出结果

grep 搜索,怎样排除某些目录?

使用 --exclude-dir 选项。

语法:

1
--exclude-dir=DIR

Exclude directories matching the pattern DIR from recursive searches.

单个目录示例

-R是表示启用正则

1
grep -E "http"  ./ -R --exclude-dir=.git

多个目录示例

1
grep -E "http"  . -R --exclude-dir={.git,res,bin}

多个文件示例

排除扩展名为 java 和 js 的文件

1
grep -E "http"  . -R --exclude=*.{java,js}

排除扩展名为 java,md~ 和 js 的文件

1
2
3
~/wd/rustNote/Linux_note$ grep -E DIR -R --exclude=*.{java,js,md~}
grep_note.md: --exclude-dir=DIR
grep_note.md: Exclude directories matching the pattern DIR from recursive searches.

排除扩展名为java, js 和 md~ 的文件

排除一些文件,并且显示行号-n

1
grep -n -E "org/webrtc/" . -R --exclude=*.{java,ninja,TOC,so,o,jar}

如何在 Linux 系统和类 Unix 的操作系统中使用带正则表达式的 grep 命令呢?

Linux 系统自带了支持拓展正则表达式的 GNU 版本 grep 工具。所有的 Linux 系统中默认安装的都是 GNU 版 grep 。
grep 命令被用来检索一台服务器或工作站上任何位置的文本信息。

快速了解正则表达式

如何匹配你要查找的内容?

正则表达式只不过是每个输入行匹配的模式。模式是一个字符序列。下面都是范例:

例如:^w1”、“w1|w2”、“[^ ]

/etc/passswd 中检索 vivekgrep vivek /etc/passwd

输出结果案例:

1
2
3
vivek:x:1000:1000:Vivek Gite,,,:/home/vivek:/bin/bash
vivekgite:x:1001:1001::/home/vivekgite:/bin/sh
gitevivek:x:1002:1002::/home/gitevivek:/bin/sh

在任何情况下都搜索 ‘vivek’ (即不区分大小):grep -i -w vivek /etc/passwd
不区分大小写地检索 ‘vivek’ 和 ‘raj’ : grep -E -i -w 'vivek|raj' /etc/passwd
在最后一个例子中,使用了扩展正则表达式的模式。

固定检索内容的位置:
你可以使用 ^ 和 $ 符号强制一个正则表达式分别匹配一行的开始或结束的位置。
下面的示例显示以 ‘vivek’ 开头的文本: grep ^vivek /etc/passwd
输出结果示例:

1
2
vivek:x:1000:1000:Vivek Gite,,,:/home/vivek:/bin/bash
vivekgite:x:1001:1001::/home/vivekgite:/bin/sh

#!开头的行

1
2
3
rust@rust-pc:~/note/Linux_note$ grep '^#!' *
organize_so_names.sh:#! /bin/sh
sort_ranking_solution.md:#! /bin/sh

你可以只显示以 vivek 开头的文本行。举例说就是不显示 vivekgite , vivekg 这样单词开头的。
grep -w ^vivek /etc/passwd

检索以 ‘foo’ 结尾的文本格式:grep 'foo$' FILENAME

你还可以用下面这样的方式搜索空白行:grep '^$' FILENAME

如何匹配具体字符?

匹配 ‘Vivek’ 或 ‘vivek’ :grep '[vV]ivek' FILENAME

或者可以这样:grep '[vV][iI][Vv][Ee][kK]' FILENAME

你可以匹配数字(例如匹配 vivek1 或 Vivek2 ):grep -w '[vV]ivek[0-9]' FILENAME

你可以匹配两位数(例如匹配 foo11 , foo12 ):grep 'foo[0-9][0-9]' FILENAME

不仅仅是数字,你可以匹配字母:grep '[A-Za-z]' FILENAME

显示所有包含 “w” 或 “n” 字母的文本行:grep [wn] FILENAME

在括号内的表达式中,在“ [: ”和“ :] ”中所附的字符类的名称:代表属于该类的所有字符的列表。标准字符类名称:

  • [:alnum:] – 字母数字字符
  • [:alpha:] – 字母顺序
  • [:blank:] – 空格和制表符
  • [:digit:] – 数字: ‘0 1 2 3 4 5 6 7 8 9’
  • [:lower:] – 小写字母:‘a b c d e f ’
  • [:space:] – 特殊字符:制表符,换行符,垂直制表符、换页,回车,和空间
  • [:upper:] – 大写字母:‘A B C D E F G H I J K L M N O P Q R S T U V W X Y Z’

在下面这个例子中,匹配所有大写字母:grep '[:upper:]' FILENAME

如何使用通配符?

你可以用 “.” 来代替单个字符。在下面的例子中,查询了所有以字母 “b” 开头、字母 “t” 结尾的三个字符的单词。
grep '\<b.t\>' FILENAME
在上面的例子中,
\< 在单词的开始位置匹配空格字符串
\> 在单词的结尾匹配空格字符串
检索并输出所有两个字母的结果:grep '^..$' FILENAME
检索并显示所有以 ‘.’ 和数字开头的结果:grep '^\.[0-9]' FILENAME
转义字符’.’
正则表达式查找 IP 地址 192.168.1.254 将不能获得预期的结果:grep '192.168.1.254' /etc/hosts
其中三个点都需要被转义:grep '192\.168\.1\.254' /etc/hosts

以下示例将只匹配一个地址:
egrep '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}' FILENAME

以下将不分大小写地匹配单词 Linux 或 Unix :egrep -i '^(linux|unix)' FILENAME

深入探索 grep 高级查找模式

如何检索一个具有以 ‘-‘ 开头的的模式?

使用 -e 选项搜索所有匹配 ‘–test–‘ 的结果。grep 会尝试把 ‘–test–‘ 作为一个选项解析:
grep -e '--test--' FILENAME

如何在grep中使用 OR 的逻辑运算 ?

grep -E 'word1|word2' FILENAME 或者 egrep 'word1|word2' FILENAME
或者可以这样做grep 'word1\|word2' FILENAME

如何在grep中使用 AND 的逻辑运算 ?

按照下面的语法显示所有包含了单词 ‘word1′ 和 ‘word2′ 的结果:
grep 'word1' FILENAME | grep 'word2'

或者可以这样:grep 'foo.*bar\|word3.*word4' FILENAME

如何测试序列?

你可以使用下面的语法测试一个字符在序列中的重复的次数:

  • {N}
  • {N,}
  • {min,max}

匹配包含两个字母 v 的字符串结果:egrep "v{2}" FILENAME

下面的例子中将检索文件内包含 “col” 和 “cool” 的字符串结果:egrep 'co{1,2}l' FILENAME

搜索patternpatern

1
2
3
rust@rust-pc:~/note/Linux_note$ egrep 'pat{1,2}ern' *
grep_note.md:Exclude directories matching the pattern DIR from recursive searches.
grep_note.md:grep_note.md: Exclude directories matching the pattern DIR from recursive searches.

下面的例子中将匹配至少含有3个字母 c 的结果:egrep 'c{3,}' FILENAME

下面的示例将匹配 “91-1234567890″ 格式的手机号码(即 “两位数字-十位数字”)
grep "[[:digit:]]\{2\}[ -]\?[[:digit:]]\{10\}" FILENAME

如何使 grep 的输出结果高亮标注?

使用下面例子的语法:grep --color regex FILENAME

如何使 grep 的输出只显示匹配的部分而不是整行?

使用下面例子的语法:grep -o regex FILENAME

统计行数

1
grep "" -r . | wc -l