2019-12-28 | UNLOCK

实训&安卓的期末复习

实训

Java调用C语言之JNI

JNI是什么?

JNI(Java Native Interface),是方便Java调用C、C++等Native代码所封装的一层接口,相当于一座桥梁。通过JNI可以操作一些Java无法完成的与系统相关的特性,尤其在图像和视频处理中大量用到。

NDK是什么?

NDK(Native Development Kit)是Google提供的一套工具,其中一个特性是提供了交叉编译。因为C或者C++不是跨平台的,但通过NDK配置生成的动态库却可以兼容各个平台。比如C在Windows平台编译后生成.exe文件,那么源码通过NDK编译后可以生成在安卓手机上运行的二进制文件.so。

原生代码

原生代码(即:Native Code):就是C/C++代码

下载NDK以及依赖

NDK:这套工具集允许Android 使用 C 和 C++ 代码。
CMake:一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果只计划使用 ndk-build,则不需要此组件。
LLDB:一种调试程序,Android Studio 使用它来调试原生代码。

配置环境变量

将javah.exe和ndk-build.cmd所在的路径添加到系统环境变量中去。
javah.exe系统自带,所在目录:C:\Program Files\Android\Android Studio\jre\bin
ndk-build.cmd是下载ndk之后才有的,所在目录:C:\sdk\ndk-bundle
配置之后的结果是在AS的Terminal中可以顺利运行两个命令。 (如果之前开启了CMD记得关掉重启)

在AS中使用ndk-build开发JNI示例

1、创建新项目JNIDemo
2、新建一个JNI类

1
2
3
public class JNITest {
public native static String getStringFromJNI();
}

3、在setting里关掉NDK Support,然后重启AS
4、在JNI类的所在文件夹里 app\src\main\java 调用javah命令生成*.h文件

命令行:javah -jni cn.maxma.jnidemo.JNITest
头文件:com_mercury_jnidemo_JNITest.h

5、实际项目最终可以不包含此头文件,不熟悉C的语法的开发人员,借助于该头文件可以知道JNI的相关语法,同时该文件不需要去做任何修改,只是借用一下生成后的方法名:
Java_cn_maxma_ndkdemo_JNITest_getStringFromJNI (JNIEnv *, jclass)
程序解释:
首先引入jni.h,里面包含了很多宏定义及调用本地方法的结构体。重点是方法名的格式。这里的JNIEXPORT和JNICALL都是jni.h中所定义的宏。JNIEnv *表示一个指向JNI环境的指针,可通过它来访问JNI提供的接口方法。jclass也是jni.h中定义好的,类型是jobject,实际上是一个不确定类型的指针,这里用来接收Java中的this。实际编写中一般只要遵循Java_包名_类名_方法名就好了。

实现JNI方法

像上面的头文件只是定义了方法,并没有实现,就像一个接口一样。这里就用C写一个简单的无参的JNI方法。
先创建一个jni目录,我直接在src的父目录下创建的,也可以在其他目录创建,因为最终只需要编译好的动态库。在jni目录下创建Android.mk和demo.c文件。

1
2
3
4
5
6
7
8
9
10
#include <jni.h>
jstring Java_top_luozetao_jnidemo_JNITest_getStringFromJNI(JNIEnv *env,jobject this)
{
return (*env)->NewStringUTF(env,"I am from jni libs lzt");
}

jint Java_top_luozetao_jnidemo_JNITest_add(JNIEnv *env,jobject this,int a,int b)
{
return a+b;
}

编写两个makefile

1)Android.mk
Android.mk是一个makefile配置文件,安卓大量采用makefile进行自动化编译。LOCAL_MODULE定义的名称就是编译好的so库名称,比如这里是jni-demo,最终生成的动态库名称就叫libjni-demo.so。 LOCAL_SRC_FILES表示参与编译的源文件名称,这里就是demo.c。

1
2
3
4
5
6
7
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := jni-demo
LOCAL_SRC_FILES := demo.c

include $(BUILD_SHARED_LIBRARY)

2)Application.mk
这时候NDK编译生成的动态库会有四个CPU平台:arm64-v8a、armeabi-v7a、x86、x86_64。如果创建Application.mk就可以指定要生成的CPU平台,语法也很简单:
APP_ABI := all
这样就会生成各个CPU平台下的动态库。

使用ndk-build编程生成.so库

使用NDK一定要先在build.gradle下要配置ndk-build的相关路径,这样在编写本地代码时才会有相关的提示功能,并且可以关联到相关的头文件。在build.gradle文件中,在Android{}当中增加,不能在外面添加:

1
2
3
4
5
externalNativeBuild {
ndkBuild {
path 'jni/Android.mk' //注意自己的路径
}
}

切回到jni目录的父目录下,在Terminal中运行ndk-build指令,就可以在和jni目录同级生成一个libs文件夹,里面存放相对应的平台的.so库。同时生成的还有一个中间临时的obj文件夹,和jni文件夹可以一起删除。
如果显示错误,那就是在上面build.gradle中没有写对Android.mk的文件路径。

调用so库

1、调用java的native方法
2、

1
2
3
static{
System.loadLibrary("jni-demo");
}

如果出现错误,大部分原因是路径、方法名、包名不对

系统编译过程

AS编译的时候会去根据D:\ma\NDKDemo3\app\build.gradle文件去进行,里面已经通过下面的关键语言指出了Android.mk文件:

1
2
3
4
5
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}

所以系统会按照Android.mk这个makefile文件的指示去自动编译:

1
2
LOCAL_MODULE := jni-demo
LOCAL_SRC_FILES := demo.c

编译的结果会放到与jni同一级目录,而不是在jni目录中:

libs/arm64-v8a/libjni-demo.so
libs/armeabi-v7a/libjni-demo.so
libs/x86/libjni-demo.so
libs/x86_64/libjni-demo.so

关于javah与生成的.h头文件

如果自己可以写出java方法对应的C语言函数名,就不需要javah那一步,因为生成的.h文件根本就没有参与后面的编译工作。

关于jni目录

创建jni目录也是为了归类,如果自己知晓存放位置,可以不需要创建新的jni目录。


交叉编译HelloWorld

什么是交叉编译器?为什么要交叉编译器?

交叉编译,简单地说,就是在一个平台上生成另一个平台上的可执行代码。这里需要注意的是所谓平台,实际上包含两个概念:体系结构(Architecture)、操作系统(Operating System)。同一个体系结构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。举例来说,我们常说的x86 Linux平台实际上是Intel x86体系结构和Linux for x86操作系统的统称;而x86 WinNT平台实际上是Intel x86体系结构和Windows NT for x86操作系统的简称。

有时是因为目的平台上不允许或不能够安装我们所需要的编译器,而我们又需要这个编译器的某些特征;有时是因为目的平台上的资源贫乏,无法运行我们所需要编译器;有时又是因为目的平台还没有建立,连操作系统都没有,根本谈不上运行什么编译器。综上,在嵌入式开发的时候我们就要使用交叉编译器。

操作例子

在linux下写了一个helloworld的程序test.c,然后在linux下用gcc test.c -o test编译成功后执行./test,test是运行在intel平台上。
error1: 然后把这个./test 应用程序复制到windows下,重命名为test.exe,会发现运行失败,提醒你不兼容。
error2: 再者把这个./test 应用程序复制到adb 安卓环境下,在6818上面运行,发现也是运行失败的。会提示not executable: 64-bit ELF file
所以我们就需要用交叉编译器去进行编译:

  1. 确认交叉编译器/usr/local/toolchain/toolchain-4.6.4目录存在
  2. 将交叉编译器目录加入到Linux的环境变量中:
  3. 执行如下指令 $export PATH=$PATH:/usr/local/toolchain/toolchain-4.6.4/bin/
  4. 编译:$arm-none-linux-gnueabi-gcc –static test.c –o test
  5. 然后再把他弄到6818上面运行,运行成功

交叉编译器,编译驱动程序驱动:Makefile后会生成*.ko文件,为驱动程序
arm-none-linux-gnueabi-gcc -static test.c -o led_test 生成后led_test是可执行程序
然后把这2个文件,push到6818的/sdcard/上,然后shell命令下将文件移动至/data文件夹里,改一下权限chmod 777 xxx·
加载驱动insmod xxx.ko,接着运行/led_test,功能就实现了。
完成了项目有helloWord、8个LED的控制、蜂鸣器、红外传感、温湿度等,都是在提供源代码的基础上同理交叉编译实现。


ARM华清远见教学主板如何烧写Android?

  1. 安装CH341SER.EXE驱动
  2. 连接串口,设置串口,putty成功
  3. 在putty窗口暂停系统,输入fastboot进入线刷阶段
  4. 连接USB线,Windows提示安装驱动,用“360手机助手”帮忙安装驱动,驱动安装成功
  5. 开始fastboot刷机(配置环境变量)
  6. 去Android系统镜像目录,开启CMD,依次刷入数据
    fastboot flash ubootpak ubootpak.bin (指令一)
    fastboot flash boot boot.img (指令二)
    fastboot flash system system.img (指令三)
    fastboot flash userdata userdata.img (指令四)
    fastboot flash cache cache.img (指令五)
  7. 重启系统,成功启动到安卓界面,用“360手机助手”,中途需要在开发板允许调试,烧写成功

安卓里Java是如何调用到C语言的?

先用System.loadLibrary("name")加载库然后进行以下操作。

  1. 在Java中声明Native方法(即需要调用的本地方法)
  2. 编译上述 Java源文件javac(得到 .class文件)
  3. 通过 javah 命令导出JNI的头文件(.h文件)
  4. 使用 Java需要交互的本地代码 实现在 Java中声明的Native方法 如 Java 需要与 C++ 交互,那么就用C++实现 Java的Native方法
    编译.so库文件
  1. 通过Java命令执行 Java程序,最终实现Java调用本地代码

JNI和NDK

JNI

JNI是 Java 调用 Native 语言的一种特性
JNI 是属于 Java 的,与 Android 无直接关系

JNI作用

扩展:JNI扩展了JVM能力,驱动开发,例如开发一个wifi驱动,可以将手机设置为无限路由;
高效: 本地代码效率高,游戏渲染,音频视频处理等方面使用JNI调用本地代码,C语言可以灵活操作内存;
复用: 在文件压缩算法 7zip开源代码库,机器视觉 OpenCV开放算法库等方面可以复用C平台上的代码,不必在开发一套完整的Java体系,避免重复发明轮子;
特殊: 产品的核心技术一般也采用JNI开发,不易破解;

NDK

定义:Native Development Kit,是 Android的一个工具开发包 NDK是属于 Android 的,与Java并无直接关系
作用:快速开发C、 C++的动态库,并自动将so和应用一起打包成 APK 即可通过 NDK在 Android中 使用 JNI与本地代码(如C、C++)交互
应用场景:在Android的场景下 使用JNI 即 Android开发的功能需要本地代码(C/C++)实现

他们的关系

JNI是实现的目的,NDK是在Android中实现JNI的手段,即在Android的开发环境中(AS),通过NDK从而实现JNI的功能

区别

JNI:JNI是一套编程接口,用来实现Java代码与本地的C/C++代码进行交互;
NDK: NDK是Google开发的一套开发和编译工具集,可以生成动态链接库,主要用于Android的JNI开发;

实现NDK

  1. 配置 Android NDK环境
  2. 关联 Andorid Studio项目 与 NDK
  3. 创建本地代码文件(即需要在 Android项目中调用的本地代码文件)
  4. 创建 Android.mk文件 & Application.mk文件
  5. 编译上述文件,生成.so库文件,并放入到工程文件中
  6. 在 Andoird Studio项目中使用 NDK实现 JNI 功能

安卓

Android UI线程模型

什么是进程、线程?

  1. 一个进程可以有多个线程,但至少有一个线程;而一个线程只能在一个进程的地址空间内活动。
  2. 资源分配给进程,同一个进程的所有线程共享该进程所有资源。
  3. CPU分配给线程,即真正在处理器运行的是线程。
  4. 线程在执行过程中需要协作同步,不同进程的线程间要利用消息通信的办法实现同步。

(1)进程
进程是程序的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间,至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。
(2)线程
线程是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
(3)联系
线程是进程的一部分,一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
线程和进程的区别总结:
1、进程是一段正在执行的程序,是资源分配的基本单元,而线程是CPU调度的基本单元。
2、进程间相互独立进程,进程之间不能共享资源,一个进程至少有一个线程,同一进程的各线程共享整个进程的资源(寄存器、堆栈、上下文)。
3、线程的创建和切换开销比进程小。

UI线程模型二原则

  1. UI线程不能进行耗时操作
  2. 不能用除了UI线程之外的其他线程进行UI操作,如果有会报以下的错误。
    Only the original thread that created a view hierarchy can touch its views.
  3. 简而言之,不要阻塞UI线程、不要在UI线程外操作UI组件。

intent跳转传值

MainActivity.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
//当前类继承父类AppCompatActivity
public class MainActivity extends AppCompatActivity {

public Button btn; //声明一个Button类型的变量btn
public TextView text; //声明一个TextView类型的变量text

@Override
//初始化操作
protected void onCreate(Bundle savedInstanceState) {
//调用父类的onCreate构造函数,保存当前MainActivity的状态信息
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); //初始化布局文件(activity_main.xml)

btn = findViewById(R.id.btn); //获取布局文件中id为btn
text = findViewById(R.id.text); //获取布局文件中id为text

//设置btn监听按钮事件
btn.setOnClickListener(new View.OnClickListener() {
@Override
//调用onClick方法
public void onClick(View view) {
//创建一个意图对象为MainActivity跳转到secondActivity
Intent intent = new Intent(MainActivity.this,secondActivity.class);
//传递键值为“name”的值为TextView类型的text变量中的值,并进行收尾去空
intent.putExtra("name", text.getText().toString().trim());
//显式启动新的Activity (intent)
startActivity(intent);
}
});
}

}

secondActivity.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
//当前类继承父类AppCompatActivity
public class secondActivity extends AppCompatActivity {

public Button close_btn; //声明一个Button类型的变量close_btn
public TextView textView; //声明一个TextView类型的变量textView

@Override
//初始化操作
protected void onCreate(Bundle savedInstanceState) {
//调用父类的onCreate构造函数,保存当前secondActivity的状态信息
super.onCreate(savedInstanceState);
//初始化布局文件(activity_second.xml)
setContentView(R.layout.activity_second);

close_btn = findViewById(R.id.close_btn); //获取布局文件中id为close_btn
textView = findViewById(R.id.textView); //获取布局文件中id为textView

Intent intent = getIntent(); //获取意图
//获取intent意图对象中"name"键中的值
String name = intent.getStringExtra("name");
textView.setText(name); //设置id为textView的值为传递过来的值

//设置close_btn监听按钮事件
close_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) { //调用onClick方法
finish(); //结束当前secondActivity
}
});
}
}

浏览器

打开网络权限

1
2
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />

MainActivity.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
public class MainActivity extends AppCompatActivity {

private WebView webView;
private Button button;
private EditText editText;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//检查网络连接
checkConnectivity();
//初始化控件
initViews();
}

//初始化控件
private void initViews(){
button = (Button) findViewById(R.id.button);
webView = (WebView) findViewById(R.id.webview);
editText = (EditText) findViewById(R.id.editText);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE))
.hideSoftInputFromWindow(
MainActivity.this.getCurrentFocus().getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
webView.loadUrl("http://www.szpt.edu.cn");
webView.setWebViewClient(new WebViewClient(){});
}
});
}

//检查网络连接
private void checkConnectivity(){
ConnectivityManager cm = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
//需要在AndroidMainifest添加<user-permission>权限即可
if (info == null){
Toast.makeText(this, "无网络连接", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, "当前网络连接为:"+info.getTypeName(),
Toast.LENGTH_SHORT).show();
}
}
}

Canvas 画图

将布局设置为画布

MainActivity.java

1
2
3
4
5
6
7
8
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
setContentView(new CanvasAndPaintTestView(this));
}
}

CanvasAndPaintTestView.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
public class CanvasAndPaintTestView extends View {
Paint paint,paint1;
public CanvasAndPaintTestView(Context context) {
super(context);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
paint1 = new Paint();

paint1.setAntiAlias(true);
paint1.setColor(Color.WHITE);
paint1.setStrokeWidth(5);
paint1.setStyle(Paint.Style.STROKE);
// paint.setAlpha(100);
}

@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas){
canvas.drawColor(Color.rgb(55, 55, 55));
canvas.drawText("SZPT", 10, 10, paint);
canvas.drawPoints(new float[]{10,20,20,30}, paint);
canvas.drawLine(10,40 ,50 ,50 , paint);
canvas.drawRect(10,100,110,200,paint );
RectF rectF = new RectF(10,250,160,400);
canvas.drawRoundRect(rectF, 20, 20, paint);
canvas.drawCircle(200, 500, 100, paint);
canvas.drawOval(rectF, paint);

paint1.setColor(Color.rgb(19, 23, 242));
canvas.drawCircle(200, 200, 50, paint1);

paint1.setColor(Color.rgb(0, 0, 0));
canvas.drawCircle(310, 200, 50, paint1);

paint1.setColor(Color.rgb(241, 15, 16));
canvas.drawCircle(420, 200, 50, paint1);

paint1.setColor(Color.rgb(237, 171, 23));
canvas.drawCircle(250, 250, 50, paint1);

paint1.setColor(Color.rgb(93, 218, 52));
canvas.drawCircle(360, 250, 50, paint1);

super.onDraw(canvas);
}
}

handler之实现进度条不同进度

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
public class MainActivity extends AppCompatActivity {
TextView tv,tv2;
SeekBar seekBar,seekBar2;
int i,j;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
switch (msg.what){
case 1:
tv.setText(String.valueOf(msg.arg1)+"%");
seekBar.setProgress(msg.arg1);
tv2.setText(String.valueOf(msg.arg2)+"%");
seekBar2.setProgress(msg.arg2);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
seekBar = findViewById(R.id.seekBar);
tv2 = findViewById(R.id.tv2);
seekBar2 = findViewById(R.id.seekBar2);

new Thread(new Runnable() {
@Override
public void run() {
i =0;
j = 0;
while(i<=100){
Message msg = Message.obtain();
msg.what=1;
msg.arg1=i;
msg.arg2=j;
handler.sendMessage(msg);
i++;

if(j<100){
j=i*2;
}

try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();

seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
if(b){
j = i = progress;
Message m = Message.obtain();
m.what = 1;
m.arg1 = i;
handler.sendMessage(m);
}
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});

seekBar2.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
if(b){
j = i = progress;
Message m = Message.obtain();
m.what = 1;
m.arg2 = j;
handler.sendMessage(m);
}
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
}

自定义字体

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

CustomTextView tv = findViewById(R.id.title);
tv.setText("我和我的祖国,一刻也不能分割!");
}
}

CustomTextView.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public CustomTextView(Context context) {
super(context);
Typeface typeface = Typeface.createFromAsset(this.getContext()
.getAssets(),"fonts/ygyxsziti2.0.ttf");
this.setTypeface(typeface);
}

public CustomTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
Typeface typeface = Typeface.createFromAsset(this.getContext()
.getAssets(),"fonts/ygyxsziti2.0.ttf");
this.setTypeface(typeface);
}

public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Typeface typeface = Typeface.createFromAsset(this.getContext()
.getAssets(),"fonts/ygyxsziti2.0.ttf");
this.setTypeface(typeface);
}

线程通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public class MainActivity extends AppCompatActivity {

TextView textView;
MyThread myThread;

Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);

textView.setText("arg1 = " + msg.arg1+"arg2 = " +msg.arg2
+"what = " +msg.what+"bundle = " +msg.getData().getString("name"));

}
};


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

myThread = new MyThread("thread1");
myThread.start();
textView = findViewById(R.id.tv);
}

//子线程发消息给主线程
public void click1(View view) {
myThread.state = true;

}

//主线程发消息给子线程
public void click2(View view) {
Message message = Message.obtain();
message.what = 2;
myThread.mhandler.sendMessage(message);
}

//子线程发消息给子线程
public void click3(View view) {
Message message = Message.obtain();
message.what = 3;
myThread.mhandler.sendMessage(message);
}

//新线程类
public class MyThread extends Thread{
Boolean state;
Handler mhandler;
private String name;

public MyThread(String name){
this.name = name;
state = false;
}

@Override
public void run() {
Looper.prepare();
mhandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 1:
System.out.println("主线程给线程"+name+"发消息……");
break;
case 2:
System.out.println("子线程1给子线程2"+name+"发消息……");
break;
case 3:
System.out.println("子线程2给子线程1"+name+"发消息……");
break;
}

}
};

while (true) {
if (state) {
System.out.println("Thread name " + name);
// textView.setText("my name is "+name);
String str = "Thread name:" + name;
Bundle bundle = new Bundle();
bundle.putString("name", str);
Message message = Message.obtain();
message.what = 1;
message.arg1 = 2;
message.arg2 = 3;

message.setData(bundle);
//将打包好的消息通过主线程的Handler发送给主线程
handler.sendMessage(message);
state = false;
}
}
}

@Override
public void destroy() {
super.destroy();
System.out.println(name + "is to be Destroyed!");
}
}
}

UI线程模型

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
public class MainActivity extends AppCompatActivity {
Button btn;
TextView tv1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv1 = findViewById(R.id.tv1);
btn = findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {

new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
while(count < 50){
Message m = Message.obtain();
m.what = 1;
m.arg1 = count;
handler.sendMessage(m);
count++;
System.out.println("count33 = "+count);
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
}
});
}

private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
switch (msg.what){
case 1:
tv1.setText(String.valueOf(msg.arg1)+"国庆节快乐");
}
}
};
}

大项目——天气预报APP

记得开启一些权限

1
2
3
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />

MainActivity.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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
public class MainActivity extends AppCompatActivity {
ListView listView;
ArrayList<WeatherInfo> weatherInfoArrayList;
WeatherAdapter adapter;
LinearLayout linearLayout;
Button btn1, btn2, btn3, btn4, btn5;
TextView pmLine,pmText;
//handler获取到数据后,进行显示天气。
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
showWeather(msg);
break;
}
}
};

//用于显示天气,从网上拉去的数据进行分析处理
private void showWeather(Message msg) {
weatherInfoArrayList = new ArrayList<WeatherInfo>(); //泛型数组
ArrayList<String> testlist = new ArrayList<String>();
String result = "";
String data = msg.obj.toString();
//1、去掉()
int start = data.indexOf("(") + 1;
int end = data.indexOf(")");
data = data.substring(start, end);

//2、Json处理
try {
JSONObject jsonObject = new JSONObject(data);
//获取的字符串键值 根据key获取value
String time = "更新时间:" + jsonObject.getString("update_time");

JSONObject wobj = jsonObject.getJSONObject("weatherinfo");

String city = wobj.getString("city");

String weather1 = wobj.getString("img_title_single");

String temp1 = "当前温度:" + wobj.getString("temp") + "℃";

String wind1 = "目前风力:" + wobj.getString("ws");
pmText.setText("目前PM2.5:" + wobj.getString("pm") +
",空气质量:" + wobj.getString("pm-level"));
String pmLevel = wobj.getString("pm-level");

String week2 = wobj.getString("week");
week2 += "(" + city + "-今天)";

switch (weather1) {
case "晴":
linearLayout.setBackgroundResource(R.mipmap.sunshine);
break;
case "多云":
linearLayout.setBackgroundResource(R.mipmap.cloud);
break;
case "中雨":
linearLayout.setBackgroundResource(R.mipmap.mr);
break;
case "小雪":
linearLayout.setBackgroundResource(R.mipmap.ssnow);
break;
}

switch (pmLevel) {
case "优":
pmLine.setBackgroundColor(Color.parseColor("#057748"));
break;
case "良":
pmLine.setBackgroundColor(Color.parseColor("#00e500"));
break;
case "轻度污染":
pmLine.setBackgroundColor(Color.parseColor("#fff143"));
break;
}

//调用WeatherInfo类,传值,并添加进weatherList里面
WeatherInfo info2 = new WeatherInfo(temp1, weather1, wind1,time, week2);
weatherInfoArrayList.add(info2);

//获取年月日和星期
SimpleDateFormat sf = new SimpleDateFormat("yyyy年MM月dd日");
SimpleDateFormat wk = new SimpleDateFormat("EEEE");

Calendar calendar = Calendar.getInstance();
String temp, weather, wind;
for (int i = 1; i <= 6; i++) {
temp = wobj.getString("temp" + i);
weather = wobj.getString("weather" + i);
wind = wobj.getString("wind" + i);

calendar.add(Calendar.DAY_OF_MONTH, 1);
String date = sf.format(calendar.getTime());
String week = wk.format(calendar.getTime());


WeatherInfo info = new WeatherInfo(temp, weather, wind, date, week);
weatherInfoArrayList.add(info);
testlist.add(weather);
}
for (int i = 0; i < weatherInfoArrayList.size(); i++) {
System.out.print(weatherInfoArrayList.get(i).getTemp() + ",");
System.out.print(weatherInfoArrayList.get(i).getWeather() + ",");
System.out.print(weatherInfoArrayList.get(i).getDate() + ",");
System.out.print(weatherInfoArrayList.get(i).getWeek() + ",");
System.out.println(weatherInfoArrayList.get(i).getWind());
}
adapter = new WeatherAdapter(getApplicationContext(), weatherInfoArrayList);
//添加一个string类型的适配器,布局用的是android封装好的布局
ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_list_item_1, testlist);
listView.setAdapter(adapter);
listView.setPadding(0, 10, 0, 10);
} catch (JSONException e) {
e.printStackTrace();
}
}


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView(); //初始化
}

public void initView() {
listView = findViewById(R.id.listview);
linearLayout = findViewById(R.id.bgg);
btn1 = findViewById(R.id.btn1);
btn2 = findViewById(R.id.btn2);
btn3 = findViewById(R.id.btn3);
btn4 = findViewById(R.id.btn4);
btn5 = findViewById(R.id.btn5);
pmLine = findViewById(R.id.pmLine);
pmText = findViewById(R.id.pmText);
search1(null); //默认显示深圳
}

public void ReSetBackground() {
btn1.setBackgroundColor(Color.parseColor("#80000000"));
btn2.setBackgroundColor(Color.parseColor("#80000000"));
btn3.setBackgroundColor(Color.parseColor("#80000000"));
btn4.setBackgroundColor(Color.parseColor("#80000000"));
btn5.setBackgroundColor(Color.parseColor("#80000000"));
}


public void search1(View view) { //深圳
getWeatherCityByCode("101280601");
ReSetBackground();
btn1.setBackgroundColor(Color.parseColor("#8FF66000"));

}

public void search2(View view) { //北京
getWeatherCityByCode("101010100");
ReSetBackground();
btn2.setBackgroundColor(Color.parseColor("#8FF66000"));


}

public void search3(View view) { //乌鲁木齐
getWeatherCityByCode("101130101");
ReSetBackground();
btn3.setBackgroundColor(Color.parseColor("#8FF66000"));

}

public void search4(View view) { //漠河
getWeatherCityByCode("101050703");
ReSetBackground();
btn4.setBackgroundColor(Color.parseColor("#8FF66000"));

}

public void search5(View view) { //三亚
getWeatherCityByCode("101310201");
ReSetBackground();
btn5.setBackgroundColor(Color.parseColor("#8FF66000"));

}


//开启新线程根据指定城市代码获取数据后向handle发送消息
private void getWeatherCityByCode(String code) {
final String CityCode = code;
new Thread(new Runnable() {
@Override
public void run() {
//发送消息给主线程
Message m = new Message();
m.what = 1;
m.obj = getDataFromInternet2(
"http://weather.123.duba.net/static/weather_info/", CityCode);
handler.sendMessage(m);
}

}).start();
}

//联网获取数据
private StringBuffer getDataFromInternet2(String urlString, String cityCode) {
try {
URL url = new URL(urlString + cityCode + ".html");
HttpURLConnection connection = (HttpURLConnection)
url.openConnection();
connection.connect();

InputStream is = connection.getInputStream();
InputStreamReader isReader = new InputStreamReader(is);
BufferedReader reader = new BufferedReader(isReader);

String line = null;
StringBuffer sb = new StringBuffer();
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
System.out.println(line);
}
reader.close();
isReader.close();
is.close();
connection.disconnect();

return sb;

} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

WeatherAdapter.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
ublic class WeatherAdapter extends BaseAdapter {

ArrayList<WeatherInfo> weatherList;
LayoutInflater inflater;

public WeatherAdapter(Context context, ArrayList<WeatherInfo> weatherList) {
inflater = LayoutInflater.from(context);
this.weatherList = weatherList;
}

@Override
public int getCount() {
return weatherList.size();
}

@Override
public Object getItem(int i) {
return null;
}

@Override
public long getItemId(int i) {
return 0;
}

@Override
public View getView(int position, View view, ViewGroup viewGroup) {
View myView = view;
ViewHolder holder;
if (myView == null) {
myView = inflater.inflate(R.layout.weather_list_item, null);
holder = new ViewHolder();

holder.temp = (TextView) myView.findViewById(R.id.tempView);
holder.weather = (TextView) myView.findViewById(R.id.weatherView);
holder.wind = (TextView) myView.findViewById(R.id.windView);
holder.week = (TextView) myView.findViewById(R.id.weekView);
holder.date = (TextView) myView.findViewById(R.id.dateView);

myView.setTag(holder);
} else {
holder = (ViewHolder) myView.getTag();
}

holder.temp.setText(weatherList.get(position).getTemp());
holder.weather.setText(weatherList.get(position).getWeather());
holder.wind.setText(weatherList.get(position).getWind());
holder.date.setText(weatherList.get(position).getDate());
holder.week.setText(weatherList.get(position).getWeek());
return myView;
}

class ViewHolder {
TextView temp;
TextView weather;
TextView wind;
TextView week;
TextView date;
}
}

WeatherInfo.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
public class WeatherInfo {
private String temp,weather,wind,date,week;

public WeatherInfo(String temp,String weather,
String wind,String date,String week){
this.temp = temp;
this.weather = weather;
this.wind = wind;
this.date = date;
this.week = week;
}

public String getTemp() {
return temp;
}

public void setTemp(String temp) {
this.temp = temp;
}

public String getWeek() {
return week;
}

public void setWeek(String week) {
this.week = week;
}

public String getWeather() {
return weather;
}

public void setWeather(String weather) {
this.weather = weather;
}

public String getWind() {
return wind;
}

public void setWind(String wind) {
this.wind = wind;
}

public String getDate() {
return date;
}

public void setDate(String date) {
this.date = date;
}
}

牛刀小试

listview是怎么渲染出来数据的?(简单的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainActivity extends AppCompatActivity {
private ListView mListView;
//字符串组合
private String [] data = {"item_One","item_Two","item_Three","item_Four",
"item_Five","item_Six","item_Seven","item_Eight","item_Nine","item_Ten"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//找到listview 组件
mListView = (ListView) findViewById(R.id.lv_id);
/**
* 通过ArrayAdapter 将数据和布局联系起来
* 参数1 当前上下文环境
* 参数2 当前引用的布局 一般系统默认
* 参数3 当前绑定的数据
* */
ArrayAdapter<String> array = new ArrayAdapter<String>(
this,android.R.layout.simple_list_item_1,data);
//将数据和布局 显示到列表
mListView.setAdapter(array);

}
}

loadLibrary?—— 是用来加载库的,System.loadLibrary()是我们在使用Java的JNI机制时,会用到的一个非常重要的函数,它的作用即是把实现了我们在Java code中声明的native方法的那个libraryload进来,或者load其他什么动态连接库。

setContentView?—— 是用来设置布局文件的

Linux编译出的驱动程序的文件是?—— *.ko

线程通信handler 子线程thread —— UI线程模型

为什么要使用线程通信?—— UI线程模型二原则