Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

这是我的第一个在安卓平台上的应用程序

Screenshot_20210926_160659-473x1024

嘛,按照惯例,咱今儿再来分析分析代码

这样一个安卓应用是怎么做的?

这篇文章可以让对Java SE有一定了解的你快速进行Android开发以及带你避开我踩过的坑

环境

集成开发环境IDE使用的是Android Studio,使用的编程语言是Java,界面编写使用XML(可扩展标记语言)

界面

既然是一个app,那肯定需要一个UI界面叭,不然用户该如何进行操作呢?因此,首先咱们来介绍一下这个界面

在1.1 环境中,介绍了界面编写需要使用XML这一语言。在Android Studio新建了一个应用程序后,会自动生成一些基础的根目录文件,其中res\layout\activity_main.xml是应用程序的布局文件,我们在这个文件里即可制作程序界面,下面贴上代码解释(不要怕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
84
85
86
87
88
89
90
91
92
93
94
95
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- 以上全是Android Studio自动生成的,下面的代码由我们来编写 -->
<LinearLayout<!-- 这里是线性布局,可以理解为存放组件的一个盒子 -->
android:layout_width="match_parent"
android:layout_height="match_parent"<!-- 填充整个页面 -->
android:background="#FFFCCC"<!-- 背景色 -->
android:orientation="vertical"> <!-- 设置组件的排列方向为垂直向下排列 -->

<!-- 套娃使用LinearLayout也是可以的唷 -->
<LinearLayout
android:layout_weight="5"<!-- 这里是给我们套娃的一个LinearLayout的一个权重,在有多个存在权重的组件存在时,该数值越小,说明组件所占的比例越大(体积越大) -->
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<EditText<!-- 这是一个给我们输入东西的框框,对应的上图程序界面的改名文本框 -->
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/Name"<!-- 这里的id很重要哟,我们在正式写代码的时候,需要给对应ID的控件写代码,如果不写id的话,程序将定位不到我们这里写的组件,也就是说这个组件只能看不能用 -->
android:textSize="20dp"
android:layout_weight="1"
>

</EditText>
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="3"
android:text="确认改名"
android:id="@+id/clearScreen"
>
</Button>
</LinearLayout>
<LinearLayout
android:layout_weight="1"<!-- 存放消息框的布局组件,消息框框在程序界面中占比较大,因此权重给的比较大,为1 -->
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView<!-- 显示列表的一个组件 -->
android:layout_width="match_parent"
android:id="@+id/messageBoard"
android:layout_height="match_parent">

</ListView>
<!-- 后边也就没啥新奇的玩意儿了,堆代码吧! -->
</LinearLayout>
<LinearLayout
android:layout_weight="5"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="4"
android:text="在线人数"
android:id="@+id/onlineList"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/Text"
android:layout_weight="1"
>

</EditText>

</LinearLayout>
<LinearLayout
android:layout_weight="5"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="发送"
android:id="@+id/sendMessage"
>
</Button>
</LinearLayout>
</LinearLayout>


</LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

组件功能的实现

嗯,咱在布局文件里写了那么多的控件,但是实际上都是只能看不能用的。现在,我们需要给每个组件写上对应的事件监听器代码。

嘛,在MainActivity.java中,我们可以试着写一个定位到布局中的按钮的代码

1
public static Button sendMessage;

好哒!我们已经声明了一个按钮对象,不过这个对象是空的(null),接下来需要给这个对象存入按钮的具体实例。

在onCreate方法中,写上这样的代码

1
sendMessage = findViewById(R.id.sendMessage);

至此,我们已经把界面上的控件搬到我们的Java代码里面了。接下来,我们就可以用Java代码对该控件进行任何的操作、监听

还是以sendMessage这个按钮为例,咱给他搞个监听器

1
2
3
4
5
6
7
sendMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {//顾名思义,点击时触发

//按钮被点击了,这里是我们写的点击后触发的代码
}
});

ne,就是这样,我们给这个按钮安上了一个监听器,紧接着我们可以继续写代码。

来,咱把所有的控件都一起搬到Java代码里吧!

1
2
3
4
5
6
7
8
9
//下面是我们布局文件中涉及到的所有控件
public static Button sendMessage;//发送
public static Button confirm;//确认改名
public static Button onlineList;//在线人数
public static ListView messageBoard;//聊天信息
public static EditText Name;//改名的文本框
public static EditText Text;//输入消息的框框

public static ArrayAdapter<String> adapter;//用来初始化我们的ListView

好啦😃,所有组件都到齐了,接下来就是我们的网络通信环节。(没用到前面说的Socket工具类,稍微会有一点点麻烦)

切记!主线程里绝对不可以涉及到网络连接这类可能导致线程阻塞的活动,否则会导致程序闪退!

因此,为了实现网络通信,我们需要新开一个线程来负责网络连接(Socket连接到ServerSocket)。同时,我们需要在AndroidManifest.xml写入权限的一个申请,否则我们程序运行时也会因为没有权限连接互联网而卡退。

在AndroidManifest.xml的application标签之上写上这些吧!

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
    <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
class ConnectHandler extends Thread{
public static Socket s;
public ConnectHandler(){//标志性地留个空构造方法

}
@Override
public void run(){
try {
s = new Socket("49.234.97.49",5613);//咱ServerSocket部署的服务器IP和端口
s.setKeepAlive(true);
//感觉没毛用,在我之前的测试过程中,我发现Socket长时间连接不活动会自动断开连接,这个setKeepAlive我还没弄清楚能干啥,因为加上了这个好像也是会自动断开连接的。 正确的防止自动断开连接的方法就是发送心跳包,例如5秒钟发送一个数据给服务器来确保连接存活。
} catch (IOException e) {
e.printStackTrace();//直接抛异常,不要学我这样搞,会变得不幸
}
ThreadHelper threadHelper = new ThreadHelper(ConnectHandler.s);//另一个线程,用于接收消息,类名字起得毫无关系地说,这个类我放在下面说
threadHelper.start();
try{
ThreadHelper listener = new ThreadHelper(s);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream(),"UTF-8"));
listener.start();
writer.write(MainActivity.ID+"<手机端>进入茶馆,欢迎光临~\n");
//writer.write(MainActivity.ID+"调试机器正在调试\n");
writer.flush();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

接下来的代码有个要点:其他线程不能直接更新UI界面,需要使用post一类的操作

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
class ThreadHelper extends Thread{// 读取信息
public Socket s;
public ThreadHelper(Socket socket){
s=socket;
}

//emmmmmm,只能说是在磨磨唧唧地开几个线程,再磨磨唧唧地搞IO流吧...,要记住帅哥的网络通信秘诀😂
@Override
public void run(){
try {
while(true) {
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(),"UTF-8"));

MainActivity.temp = br.readLine();
MainActivity.messageBoard.post(new Runnable() {
@Override
public void run() {
// 更新UI
//MainActivity.messageBoard.setText(MainActivity.archiveMessage);
MainActivity.adapter.add(MainActivity.temp);
MainActivity.messageBoard.setAdapter(MainActivity.adapter);
MainActivity.messageBoard.getBottom();
}});
}
} catch (IOException e) {
MainActivity.archiveMessage = "[错误]原因:目标计算机已关闭或拒绝连接";//出现异常了一点处理也没有,别学我,会变得不幸。。。
//MainActivity.messageBoard.setText(MainActivity.archiveMessage);
}
}
}
class Sender extends Thread{//发送消息
@Override
public void run(){
try {
BufferedWriter writer;
writer = new BufferedWriter(new OutputStreamWriter(ConnectHandler.s.getOutputStream(),"UTF-8"));
if(MainActivity.mess != null){
writer.write(MainActivity.ID + ":"+MainActivity.mess+"\n");
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
}

}
}

对了,还有一个获取在线人数的按钮,这需要对服务器加一些更改,具体的思路是客户端发送一个#command/~~/~#getonline给服务端后,服务端就能根据ServerSocket下连接的Socket数量对发送这个指令的客户端进行一个回复。

代码总览

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
package com.huugh.myapplication;
//不要在主线程使用网络连接,切记!
import androidx.appcompat.app.AppCompatActivity;

import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Random;


public class MainActivity extends AppCompatActivity {
public static Button sendMessage;
public static Button confirm;
public static Button onlineList;
public static ListView messageBoard;
public static EditText Name;
public static EditText Text;
public static String archiveMessage;
public static String mess;
public static ArrayAdapter<String> adapter;
public static String temp;
public static String ID;
public static int streamID;
public static ArrayList<String> data = new ArrayList<>();
@Override
public boolean onKeyDown(int keyCode, KeyEvent key){
if(keyCode == KeyEvent.KEYCODE_HOME){
moveTaskToBack(false);
}
return super.onKeyDown(keyCode,key);
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
ConnectHandler.s.close();
}
catch (IOException e) {
e.printStackTrace();
}
}

public void playMessageSound(){
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
}
@Override
protected void onCreate(Bundle savedInstanceState) {

Random r = new Random();
ID = r.nextInt(32768)+"";
/* mSoundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
streamID = mSoundPool.load(this,R.raw.message, 1);*/
//MainActivity.mSoundPool.play(MainActivity.streamID, 10, 10, 1, 0, 2.0f);
//String[] permissions={Manifest.permission.INTERNET,Manifest.permission.ACCESS_NETWORK_STATE,Manifest.permission.CHANGE_NETWORK_STATE};
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Name = findViewById(R.id.Name);
sendMessage = findViewById(R.id.sendMessage);
onlineList = findViewById(R.id.onlineList);
confirm = findViewById(R.id.clearScreen);
messageBoard = findViewById(R.id.messageBoard);
messageBoard.setStackFromBottom(true);
messageBoard.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
Text = findViewById(R.id.Text);
Name.setText(ID);
ConnectHandler ConnectHandler = new ConnectHandler();
new Thread(ConnectHandler).start();
adapter = new ArrayAdapter<>(MainActivity.this,android.R.layout.simple_list_item_1,data);
confirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ID = Name.getText().toString();
}
});
onlineList.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mess = "#command/~~/~#getonline";
Sender sender = new Sender();
sender.start();
//MainActivity.mSoundPool.play(1, 10, 10, 1, 0, 1.0f);

}
});
sendMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(Text.getText()!=null){
mess = Text.getText().toString();
Sender sender = new Sender();
sender.start();
Text.setText("");
//MainActivity.mSoundPool.stop(1);

}

}
});
}
}
class ConnectHandler extends Thread{
public static Socket s;
public ConnectHandler(){

}
@Override
public void run(){
try {
s = new Socket("49.234.97.49",5613);
s.setKeepAlive(true);
} catch (IOException e) {
e.printStackTrace();
}
ThreadHelper threadHelper = new ThreadHelper(ConnectHandler.s);
threadHelper.start();
try{
ThreadHelper listener = new ThreadHelper(s);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream(),"UTF-8"));
listener.start();
writer.write(MainActivity.ID+"<手机端>进入茶馆,欢迎光临~\n");
//writer.write(MainActivity.ID+"调试机器正在调试\n");
writer.flush();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

class ThreadHelper extends Thread{
public Socket s;
public ThreadHelper(Socket socket){
s=socket;
}
@Override
public void run(){
try {
while(true) {
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(),"UTF-8"));

MainActivity.temp = br.readLine();


MainActivity.messageBoard.post(new Runnable() {
@Override
public void run() {
// 更新UI
//MainActivity.messageBoard.setText(MainActivity.archiveMessage);
MainActivity.adapter.add(MainActivity.temp);
MainActivity.messageBoard.setAdapter(MainActivity.adapter);
MainActivity.messageBoard.getBottom();
//MainActivity.mSoundPool.stop(1);
//MainActivity.mSoundPool.play(1, 10, 10, 1, 0, 1.0f);

}});

}
} catch (IOException e) {
MainActivity.archiveMessage = "[错误]原因:目标计算机已关闭或拒绝连接";
//MainActivity.messageBoard.setText(MainActivity.archiveMessage);
}

}
}
class Sender extends Thread{
@Override
public void run(){
try {
BufferedWriter writer;
writer = new BufferedWriter(new OutputStreamWriter(ConnectHandler.s.getOutputStream(),"UTF-8"));
if(MainActivity.mess != null){
writer.write(MainActivity.ID + ":"+MainActivity.mess+"\n");
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
}

}
}

activity_main.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
84
85
86
87
88
89
90
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFCCC"
android:orientation="vertical">
<LinearLayout
android:layout_weight="5"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/Name"
android:textSize="20dp"
android:layout_weight="1"
>

</EditText>
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="3"
android:text="确认改名"
android:id="@+id/clearScreen"
>
</Button>
</LinearLayout>
<LinearLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:id="@+id/messageBoard"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
<LinearLayout
android:layout_weight="5"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="4"
android:text="在线人数"
android:id="@+id/onlineList"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/Text"
android:layout_weight="1"
>

</EditText>

</LinearLayout>
<LinearLayout
android:layout_weight="5"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="发送"
android:id="@+id/sendMessage"
>
</Button>
</LinearLayout>
</LinearLayout>


</LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

AndroidManifest.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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.huugh.myapplication">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name=".AuthMain">

</activity>
</application>

</manifest>

小彩蛋

如果你对我的apk进行一个解包,你会发现里面有一个message.mp3,没错,这是我编配的一个收到消息的铃声,具体位置在res\raw\message.mp3,至于为啥没添加进APP呢?因为我遇到了到现在还没解决的bug——播放一次之后就不播放了QAQ

希望以后我解决了能添加进APP吧!

试听:

谱(透明背景):

铃声-1-724x1024

评论