今天的主題要來寫關於在Android手機上實現一個以UDP/TCP技術實現在區域網路內實現聊天的技術
而重點我想就如同標題麻...就是TCP/UDP技術
那何謂TCP/UDP技術呢?
首先雖然我把兩個寫在一起,但是他們是不一樣的東西哦( ・ὢ・ )
然後本部落格有寫關於TCP/UDP技術的相關範例都在這裡了
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊上篇-UDP伺服器端與客戶端實現
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊中篇-TCP伺服器端實現
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊下篇-TCP客戶端實現
歡迎參考抄襲โ๏∀๏ใ
雖然就結果來看根本是一樣的
TCP是英文Transmission Control Protocol的縮寫
繁中翻譯傳輸控制協定
UDP則是英文User Datagram Protocol的縮寫
繁中翻譯使用者資料報協定
然後至於他們的相關原理...維基都有我懶得講了XDDDDDD
相關參考這篇好了,這篇講得頗詳細的
->https://ithelp.ithome.com.tw/articles/10205476
反正針對這兩個東西我只做一個結論
UDP傳輸速度快,但容易掉包
TCP傳輸慢,但是傳輸內容可靠穩定
就這樣,還想知道什麼三次握手四次揮手的自己去找文章โ๏∀๏ใ
好啦,那回到我們的重點
今天我下的標題是"在Android上實現TCP/UDP區域網路通訊(上)"
我打算這個系列分成三篇文章來寫
而這篇的主要內容是以UDP技術實現兩台Android手機之間的資料傳輸
之後的兩篇分別是TCP的客戶端與TCP的Server端在Android APP上的實現
那麼,來看一下今天的功能吧
Emmmm...畫面有點模糊
不過應該還看得到在做什麼啦
總之,右邊的APP就是我們在本篇文章要寫的APP
右邊的則是我在Google Play載下來別人寫的APP
在此->https://play.google.com/store/apps/details?id=com.sandersoft.udpmonitor&hl=zh_TW&gl=US
最後,這邊是本次APP的Github
->https://github.com/thumbb13555/UDP_TCP_ConnectDemo/tree/master
開始吧(・ωー)~☆
1. 介紹&加入權限
1.1 介紹專案結構
本次專案為了節省我Github的空間,所以我直接把UDP跟TCP的功能都放在一個APP
怎麼樣?很有誠意對吧(-‿◦)
來看一下專案結構吧
身為一個二合一的APP,所以主畫面(MainActivity.java)便是選擇UDP或是TCP的頁面而已
當然,這次的文章我們是選擇UDP得這個畫面
而在檔案中還有一個CommendFun.java
裡面只放了一個用來取得手機所連線到之WIfi之IP而已
我直接PO一下吧
public class CommendFun { @SuppressLint("DefaultLocale") public static String getLocalIP(Context context) { WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(WIFI_SERVICE); assert wifiManager != null; WifiInfo info = wifiManager.getConnectionInfo(); int ipAddress = info.getIpAddress(); return String.format("%d.%d.%d.%d" , ipAddress & 0xff , ipAddress >> 8 & 0xff , ipAddress >> 16 & 0xff , ipAddress >> 24 & 0xff); } }
再重申一次,本篇文章只有要講到UDP那個資料夾內的內容
TCP的內容要到下篇文章囉~
1.2 加入權限
本次的專案因為涉及到網路使用以及Wifi使用
因此必須加入相關權限
請至AndroidManifest.xml的部分
加入以下粉底白字兩行
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jetec.udp_tcp_connectdemo"> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.INTERNET"/> <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=".TCP.TCPActivity"></activity> <activity android:name=".UDP.UDPActivity" /> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
網路的話不用特別問使用者,因此準備工作到這篇囉
接下來畫介面
2. 畫介面
本次介面長這副德性
直接PO,沒什麼好說的
activity_u_d_p.xml
<?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=".UDP.UDPActivity"> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.08" /> <TextView android:id="@+id/textView_IP" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="本機IP: " android:textColor="@android:color/background_dark" android:textSize="18sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/guideline2" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ToggleButton android:id="@+id/toggleButton_ReceiveSwitch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textOff="開啟監聽" android:textOn="關閉監聽" app:layout_constraintBottom_toTopOf="@+id/guideline6" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/editText_Port" app:layout_constraintTop_toTopOf="@+id/guideline2" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.3" /> <EditText android:id="@+id/editText_ReceiveMessage" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" android:focusable="false" android:focusableInTouchMode="false" android:gravity="top" android:minLines="20" android:inputType="textMultiLine" app:layout_constraintBottom_toTopOf="@+id/guideline4" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView3" app:layout_constraintVertical_bias="0.0" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.9" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:text="收到的消息" android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textStyle="bold" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline3" /> <EditText android:id="@+id/editText_Input" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:ems="10" android:hint="請輸入內容" android:inputType="textPersonName" android:text="Hello! I'm samsung" app:layout_constraintBottom_toBottomOf="@+id/button_Send" app:layout_constraintEnd_toStartOf="@+id/button_clear" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/button_Send" /> <EditText android:id="@+id/editText_Port" android:layout_width="0dp" android:layout_height="0dp" android:ems="10" android:hint="Port" android:inputType="number" android:text="8888" app:layout_constraintBottom_toBottomOf="@+id/toggleButton_ReceiveSwitch" app:layout_constraintEnd_toStartOf="@+id/guideline5" app:layout_constraintStart_toEndOf="@+id/textView4" app:layout_constraintTop_toTopOf="@+id/toggleButton_ReceiveSwitch" /> <Button android:id="@+id/button_Send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="發送" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline4" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.7" /> <EditText android:id="@+id/editText_RemoteIp" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:ems="10" android:inputType="numberDecimal" android:text="192.168.50.5" app:layout_constraintBottom_toTopOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@+id/guideline5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView5" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.17" /> <TextView android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="本機Port: " android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textSize="18sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/guideline6" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline2" /> <TextView android:id="@+id/textView5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:text="Remote Ip:" android:textAppearance="@style/TextAppearance.AppCompat.Body1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline6" /> <TextView android:id="@+id/textView6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="Remote Port: " android:textAppearance="@style/TextAppearance.AppCompat.Body1" app:layout_constraintStart_toStartOf="@+id/guideline5" app:layout_constraintTop_toTopOf="@+id/guideline6" /> <EditText android:id="@+id/editText_RemotePort" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:ems="10" android:inputType="textPersonName" android:text="8888" app:layout_constraintBottom_toTopOf="@+id/guideline3" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline5" app:layout_constraintTop_toBottomOf="@+id/textView6" /> <Button android:id="@+id/button_clear" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="清除" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/button_Send" app:layout_constraintTop_toTopOf="@+id/guideline4" /> </androidx.constraintlayout.widget.ConstraintLayout>
原則上直接複製即可( ゚Д゚)b
3. 撰寫UDP主要功能
接下來要撰寫的是UDP功能的主要部分,也就是UDP.java這個檔案
基本上我也是維持我一慣的風格,你可以直接把以下內容全部複製打包帶走(・ωー)~☆
總之先全部給你,再來一段一段程式分析
class UDP implements Runnable { public static final String TAG = "MyUDP"; public static final String RECEIVE_ACTION = "GetUDPReceive"; public static final String RECEIVE_STRING = "ReceiveString"; public static final String RECEIVE_BYTES = "ReceiveBytes"; private int port = 8888; private String ServerIp; private boolean isOpen; private static DatagramSocket ds = null; private Context context; /**切換伺服器監聽狀態*/ public void changeServerStatus(boolean isOpen) { this.isOpen = isOpen; if (!isOpen) { ds.close(); Log.e(TAG, "UDP-Server已關閉"); } } //切換Port public void setPort(int port){ this.port = port; } /**初始化建構子*/ public UDP(String ServerIp,Context context) { this.context = context; this.ServerIp = ServerIp; this.isOpen = true; } /**發送訊息*/ public void send(String string, String remoteIp, int remotePort) throws IOException { Log.d(TAG, "客户端IP:" + remoteIp + ":" + remotePort); InetAddress inetAddress = InetAddress.getByName(remoteIp); DatagramSocket datagramSocket = new DatagramSocket(); DatagramPacket dpSend = new DatagramPacket(string.getBytes(), string.getBytes().length, inetAddress, remotePort); datagramSocket.send(dpSend); } /**監聽執行緒*/ @Override public void run() { /**在本機上開啟Server監聽*/ InetSocketAddress inetSocketAddress = new InetSocketAddress(ServerIp, port); try { ds = new DatagramSocket(inetSocketAddress); Log.e(TAG, "UDP-Server已啟動"); } catch (SocketException e) { Log.e(TAG, "啟動失敗,原因: " + e.getMessage()); e.printStackTrace(); } //預備一組byteArray來放入回傳得到的值(PS.回傳為格式為byte[],本範例將值轉為字串了) byte[] msgRcv = new byte[1024]; DatagramPacket dpRcv = new DatagramPacket(msgRcv, msgRcv.length); //建立while迴圈持續監聽來訪的數值 while (isOpen) { Log.e(TAG, "UDP-Server監聽資訊中.."); try { //執行緒將會在此打住等待有值出現 ds.receive(dpRcv); String string = new String(dpRcv.getData(), dpRcv.getOffset(), dpRcv.getLength()); Log.d(TAG, "UDP-Server收到資料: " + string); /**以Intent的方式建立廣播,將得到的值傳至主要Activity*/ Intent intent = new Intent(); intent.setAction(RECEIVE_ACTION); intent.putExtra(RECEIVE_STRING,string); intent.putExtra(RECEIVE_BYTES, dpRcv.getData()); context.sendBroadcast(intent); } catch (IOException e) { e.printStackTrace(); } } } }
Okay來吧
首先,這整個Class都使用了Runnable方法
Runnable方法基本是跟著執行緒一起使用的
不懂執行緒的話歡迎參考我這篇
->碼農日常-『Android studio』Android背景執行之Thread、Handler及AsyncTask的基礎用法
也就是說,這個Class內run的部分在背景執行緒跑的,不能在這邊寫更動介面的程式噢!
先講這裡提供的方法
三個方法如下
/**切換伺服器監聽狀態*/ public void changeServerStatus(boolean isOpen) { this.isOpen = isOpen; if (!isOpen) { ds.close(); Log.e(TAG, "UDP-Server已關閉"); } } //切換Port public void setPort(int port){ this.port = port; } /**發送訊息*/ public void send(String string, String remoteIp, int remotePort) throws IOException { Log.d(TAG, "客户端IP:" + remoteIp + ":" + remotePort); InetAddress inetAddress = InetAddress.getByName(remoteIp); DatagramSocket datagramSocket = new DatagramSocket(); DatagramPacket dpSend = new DatagramPacket(string.getBytes(), string.getBytes().length, inetAddress, remotePort); datagramSocket.send(dpSend); }
關於關閉Server監聽的部分,我把它寫在changeServerStatus()內了
所以如果傳入為false時,就會把監聽關掉
再來是初始化建構子,在這
/**初始化建構子*/ public UDP(String ServerIp,Context context) { this.context = context; this.ServerIp = ServerIp; this.isOpen = true; }
原則上主要要拿到這支手機連現在到IP,還有Context(上下文)
還有就是打開監聽
最後是RUN的內容
/**監聽執行緒*/ @Override public void run() { /**在本機上開啟Server監聽*/ InetSocketAddress inetSocketAddress = new InetSocketAddress(ServerIp, port); try { ds = new DatagramSocket(inetSocketAddress); Log.e(TAG, "UDP-Server已啟動"); } catch (SocketException e) { Log.e(TAG, "啟動失敗,原因: " + e.getMessage()); e.printStackTrace(); } //預備一組byteArray來放入回傳得到的值(PS.回傳為格式為byte[],本範例將值轉為字串了) byte[] msgRcv = new byte[1024]; DatagramPacket dpRcv = new DatagramPacket(msgRcv, msgRcv.length); //建立while迴圈持續監聽來訪的數值 while (isOpen) { Log.e(TAG, "UDP-Server監聽資訊中.."); try { //執行緒將會在此打住等待有值出現 ds.receive(dpRcv); String string = new String(dpRcv.getData(), dpRcv.getOffset(), dpRcv.getLength()); Log.d(TAG, "UDP-Server收到資料: " + string); /**以Intent的方式建立廣播,將得到的值傳至主要Activity*/ Intent intent = new Intent(); intent.setAction(RECEIVE_ACTION); intent.putExtra(RECEIVE_STRING,string); intent.putExtra(RECEIVE_BYTES, dpRcv.getData()); context.sendBroadcast(intent); } catch (IOException e) { e.printStackTrace(); } } }
基本上大致分成三段
第一段為啟動Server的部分
/**在本機上開啟Server監聽*/ InetSocketAddress inetSocketAddress = new InetSocketAddress(ServerIp, port); try { ds = new DatagramSocket(inetSocketAddress); Log.e(TAG, "UDP-Server已啟動"); } catch (SocketException e) { Log.e(TAG, "啟動失敗,原因: " + e.getMessage()); e.printStackTrace(); }
第二段則為接收資料的部分
//預備一組byteArray來放入回傳得到的值(PS.回傳為格式為byte[],本範例將值轉為字串了) byte[] msgRcv = new byte[1024]; DatagramPacket dpRcv = new DatagramPacket(msgRcv, msgRcv.length); //建立while迴圈持續監聽來訪的數值 while (isOpen) { Log.e(TAG, "UDP-Server監聽資訊中.."); try { //執行緒將會在此打住等待有值出現 ds.receive(dpRcv); String string = new String(dpRcv.getData(), dpRcv.getOffset(), dpRcv.getLength()); Log.d(TAG, "UDP-Server收到資料: " + string); . . . } catch (IOException e) { e.printStackTrace(); } }
最後,就是把資料傳出去了
while (isOpen) { Log.e(TAG, "UDP-Server監聽資訊中.."); try { . . . /**以Intent的方式建立廣播,將得到的值傳至主要Activity*/ Intent intent = new Intent(); intent.setAction(RECEIVE_ACTION); intent.putExtra(RECEIVE_STRING,string); intent.putExtra(RECEIVE_BYTES, dpRcv.getData()); context.sendBroadcast(intent); } catch (IOException e) { e.printStackTrace(); } }
以上就是全部重點程式,接下來就是使用這塊"模組"了
4. 撰寫主要程式
接下來的是對我來說最難解釋的部分( ´_ゝ`)
這次的內容的重點是UDP傳輸技術本身
但是問題是這項技術要成為一個完整的APP的話必須要有很多UI控制項
而這些UI又會干擾要講解的內容本身...
好吧,我盡力講
總之,還好程式不算長,已經盡力縮短了
所以一樣全PO,再來標記號
public class UDPActivity extends AppCompatActivity { EditText edRemoteIp, edLocalPort, edReceiveMessage, edInputBox, edRemotePort; MyBroadcast myBroadcast = new MyBroadcast(); StringBuffer stringBuffer = new StringBuffer(); ExecutorService exec = Executors.newCachedThreadPool(); UDP udpServer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_u_d_p); //設置基礎UI setBaseUI(); //設置監UDP功能 setReceiveSwitch(); //設置發送資料功能 setSendFunction(); //註冊廣播器,使回傳能夠從其他類別內傳回此Activity IntentFilter intentFilter = new IntentFilter(UDP.RECEIVE_ACTION); registerReceiver(myBroadcast, intentFilter); } private void setBaseUI() { TextView tvLocalIp = findViewById(R.id.textView_IP); //注意:此處有調用CommendFun.java的內容以取得本機IP tvLocalIp.setText("本機IP: " + getLocalIP(this)); //清除所有儲存過的資訊 Button btClear = findViewById(R.id.button_clear); btClear.setOnClickListener(v -> { stringBuffer.delete(0,stringBuffer.length()); edReceiveMessage.setText(stringBuffer); }); edRemoteIp = findViewById(R.id.editText_RemoteIp); edRemotePort = findViewById(R.id.editText_RemotePort); edLocalPort = findViewById(R.id.editText_Port); edReceiveMessage = findViewById(R.id.editText_ReceiveMessage); edInputBox = findViewById(R.id.editText_Input); } private void setReceiveSwitch() { ToggleButton btSwitch = findViewById(R.id.toggleButton_ReceiveSwitch); //初始化UDP伺服器 //注意:此處有調用CommendFun.java的內容以取得本機IP udpServer = new UDP(getLocalIP(this),this); btSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { if (isChecked) { /**開啟UDP伺服器監聽*/ int port = Integer.parseInt(edLocalPort.getText().toString()); udpServer.setPort(port); udpServer.changeServerStatus(true); exec.execute(udpServer); edLocalPort.setEnabled(false); } else { /**關閉UDP伺服器監聽*/ udpServer.changeServerStatus(false); edLocalPort.setEnabled(true); } }); } private void setSendFunction() { Button btSend = findViewById(R.id.button_Send); /**發送UDP訊息至指定的IP*/ btSend.setOnClickListener(v -> { String msg = edInputBox.getText().toString(); String remoteIp = edRemoteIp.getText().toString(); int port = Integer.parseInt(edRemotePort.getText().toString()); if (msg.length() == 0) return; stringBuffer.append("發送: ").append(msg).append("\n"); edReceiveMessage.setText(stringBuffer); //調用UDP.java中的方法,送出資料 //本範例用lambda表達式,原貌在下面註解 exec.execute(()->{ try { udpServer.send(msg, remoteIp, port); } catch (IOException e) { e.printStackTrace(); } }); // exec.execute(new Runnable() { // @Override // public void run() { // try { // udpServer.send(msg, remoteIp, port); // } catch (IOException e) { // e.printStackTrace(); // } // } // }); }); } private class MyBroadcast extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String mAction = intent.getAction(); assert mAction != null; switch (mAction) { /**接收來自UDP回傳之訊息*/ case UDP.RECEIVE_ACTION: String msg = intent.getStringExtra(UDP.RECEIVE_STRING); byte[] bytes = intent.getByteArrayExtra(UDP.RECEIVE_BYTES); stringBuffer.append("收到: ").append(msg).append("\n"); edReceiveMessage.setText(stringBuffer); break; } } } @Override protected void onDestroy() { super.onDestroy(); //取消監聽廣播 unregisterReceiver(myBroadcast); } }
OK,總共有四個顏色的記號
分別是
藍底白字:接收回傳廣播的部分
粉底白字: 開啟Server
綠底白字: 關閉Server
紫底白字: 送出資料
而在本篇,我使程式進入到背景執行緒的方法是用這個
ExecutorService exec = Executors.newCachedThreadPool();
然後在搭配這樣使用
exec.execute(udpServer);
基本上,雖然好像沒頭沒尾
不過需要注意的部分就是如此
整個過程就是
1. 寫好模組
2. 用
就這樣(´◑ω◐`)
但是大家的盲點其實就是不會用模組,或寫不出來而已
所以我標注完模組用在哪邊後,我想應該就相對容易完成了
其他的部分就都是跟UI比較相關的,強烈建議載下我的Github研究研究囉
喔對了~如果有幫助記得幫我點Github星星外加底下拍手五下謝謝XDDDD
今天是2021的1/2號XD
我之前在要放假的時候我就想著既然難得放了連假就來幹一票大的(咦?←難度比較高的意思..
這篇文章本身不會打很久,不過我寫那隻範例程式卻把我的1/1號給吃掉了(゜ロ゜)
程式本身是不會難,可是問題是要怎麼把這麼多東西塞進一個畫面就很傷腦筋
不過好處是今天寫完後下週跟下下週我就不用煩惱要寫什麼了啦顆顆顆
2020去年是一個全世界都過得不太好的一年
不過對我洨洨攻城屍來說我頂多就是因為公司業績差TM去年沒領到中秋獎金跟端午獎金就是了
其實還蠻幹的!!( ゚Д゚)凸
哎算了,反正都過了
祈禱今年獎金可以稍微多一點點吧..
總之雖然這篇文章看著看著是很有壓力的一篇文章
不過還是祝各位新年快樂,寫出很牛逼的程式發大財
如果可以幫我在下面多多拍手,就可以幫碼農我發大財囉ɷ◡ɷ
新年新希望 新年快樂喔
爺爺也是新年快樂噢!!
2021新年快樂!彩魚又來亂了 不是啦~是來打招呼的啦!哈哈~多來你這裡走走,看能不能哪一天無師自通那些看不懂像宇宙密碼的東西(那是不可能的事 嘻嘻~)
哈哈,雖然lag了很久,不過2021新年快樂:D 我想..或許不會看得懂那些密碼,不過有時候還是會出現一些妳看得懂的文章吧xD
好羨慕懂這些複雜東西的人😂 新年快樂呀 幫你鼓掌
哈哈哈沒甚麼好羨慕的 都是用肝指數跟髮根換來的 新年快樂~謝謝鼓掌!!
新年快樂! 希望我今年可以順順利利 然後可以突然程式大爆發(?)並且找到一份真心喜歡的好工作(丟硬幣
可以的可以的~ 常來幫我灌水程式就會大爆發!!(不要瞎掰好嗎 轉職轉起來RRRRRRR!!!!我也打算今年做滿兩年來換工作了XD
其實我是個畢業沒多久的菜逼八 目前在這間公司待了半年多 我個人沒有很喜歡這間公司跟工作內容 (大致上的問題就是我沒有前後輩,所有工作都要靠自己,連主管都不太會幫我這樣)(而且沒有前人的基礎,要我從零開始完成一個APP)<--被老闆騙,他一開始明明跟我說公司的APP是完整的,我只需要接手(? 剛畢業那時候我的程式真的弱到爆炸,一直瘋狂惡補(以前上課不好好學的下場),要不是為了領畢業生補助,我一開始大概就撐不下去了XDDD 但我對程式這方面真的沒什麼天賦XD再加上天天寫程式真的很鬱悶(? 所以我還在猶豫下次轉職還要不要走這一行 可是不走這一行我也不知道要幹嘛(? 總而言之就是個被社會現實瘋狂洗禮的廢物畢業生(? 好吧廢話太多了XDDD 希望我2021可以找到人生方向,不管要不要走這行,至少有個讓我喜歡的工作,不要天天都這麼痛苦 另外也希望我辭職之後不會失業太久(雙手合十再次許願 (完全把這裡當許願池)
哈哈哈~我一直以為你工作有一段時間了ㄟ 說到靠自己的部分其實我也是一樣啦,我們公司大概就是一個技術一個坑的概念 很多時候老闆所認知的跟第一線開發員所認知的都不一樣,也算是通病了吧 而且某些意義上接手他人的APP反而痛苦,而且也未必能學到很多東西 我入職公司後,主管要我把公司之前的APP全部砍掉重練,導致我入職第一年就上架了4支APP 雖然超辛苦;不過相對的學到很多,也才能有這麼多靈感寫網誌 其實論基礎的話我想我也不會高你多少吧~我是私立科大電機系畢業的XDDDDD 我覺得程式之路的重點不是在寫程式,我們也不可能一輩子寫程式的,這畢竟算是年輕人的工作 或許在寫程式保住飯碗的過程中,試著去發跡第二、第三種能力;甚至思考創業基礎都會讓你更有動力去努力吧 總之就一起加油吧! 常有人來陪我聊天我也挺開心的~我不介意當許願池哦! 雖然我應該無法實現你的願望就是了,哈哈哈
祝您闔家2021全年健康愉悅
哈囉~謝謝你的祝福 近期天氣即將轉冷,請注意保暖喔:)
很厲害欸你 這種東西我都看不懂
不會啦! 人各有所長麻是不是XD 謝謝你的來訪留言:D
我們有新村民加入了 !
我有看到~~我立刻去拜訪
昨天看到台灣大哥的app新聞 下一秒就想到你是寫程式的XD (恐怖ㄜ~)
看來我陰魂不散!! (咦? 我覺得有Bug還好,可是Bug被媒體報導就會覺得很想做一些很不應該做的事情... 這就是工程師阿...(跪
對XDDDD老闆的想法永遠跟我們不一樣XDDD 每次溝通的時候常常會各種黑人問號XD 我雖然就讀的大學有“國立”兩個字,但我的基礎其實很爛,都畢業了連java都不太會寫XDDD 我當初會賽到國立也是多虧了所謂的“多元入學”,讓我這個智商不足的人也擠進國立的行列(? (而且老實講國立大學的教育不見得比較好,像我們系上有一半的教授就很雷,你上他的課就是完全不知道他在衝三小XDDDD)(當然也有可能是我才疏學淺無法參透那門課的精髓 所以我其實就是典型的成績到哪賽到哪就讀哪(?)的人,也因此我念了大學才發現我其實並不喜歡這個科系,但我又沒有轉職(?)的勇氣(畢竟好不容易擠進學費便宜的國立大學)(對我會努力擠進國立就是因為學費便宜)(此處應有刪除線) 總而言之就是我實在不是程式這塊料,當初我朋友聽說我的工作是寫APP他們的表情都很驚訝(可見我平常程式有多爛 所以當我發現APP要重寫的時候簡直晴天霹靂,這也導致我一路走來都在抄作業XDDD 我常常在想要是哪天遇到某個功能沒人用愛發電借作業讓我抄怎麼辦???(皺眉 所以我現在天天都在掙扎XDDD 但還是會乖乖去上班研究程式 每天解BUG然後找問題 解不開就想辭職的事情 解開了就覺得自己好棒棒(? 天天都在自攻自受的過程中循環這樣(´∀`) 好拉一個不小心就打了這麼多,我也不知道重點在哪??? 最後總歸一句話 請大神繼續產出作業給我抄 本人我很需要大神們用愛發電的產物啊啊啊啊 (喔當然要是能有flutter就更棒了)(此處應有刪除線)
齁齁你的留言太有趣了~我不能上班時候回啊 國立老師有蠻多真的去養老的,其實教授們有時候也只是為了養家跟做研究而已 很多老師上課沒什麼熱情,但指導學生時兩眼放光的大有人在(我老師就是) 我自己從來沒覺得自己很適合寫程式XD 但至少不會排斥就是了 有時候我也會看看FB上的Android社團討論版,每次看辣些大神在討論的時候我都會懷疑人蔘QQ "我常常在想要是哪天遇到某個功能沒人用愛發電借作業讓我抄怎麼辦"我以前剛入行也懷疑過啊XD 有次我要做一個project,目標是要用手機的USB-OTG讀RS485,我看到後簡直傻了 你那句話真的很能形容我當下的心情啊啊啊啊啊啊 我身為邊緣人,有人陪我聊聊天我就很開心啦 無論回文有沒有重點~顆顆 這種感覺像是地方的馬鈴薯感到開心吧ㄏ 我還是會繼續產出作業der 至少這幾年應該都會 希望Tilly你可以有事沒事陪我聊天XD 惜馬鈴薯、得蕃薯葉(????) Flutter呀...真的看緣份了
艱苦喜樂相處共度危機,就是積善所得的福報!。
沒錯^^b
這麼久我沒上來了.... 但是 我還是看不懂 哈哈哈
哇!好久不見~ 我才是久沒去聽你講故事咧!
好難~~ 今天天氣太冷了,看了你的文章,整個真是暖心又暖肺 太久沒簽到了~2021年第一次留言
嗨雞蛋貓~ 暖心又暖肺,因為文章看得血壓變高了吧XDDD 對了~我前陣子在寫C#的時候有搜尋到你的文章內 雖然我似乎忘記留言,不過你的文章也幫助到我的工作了 灰常感謝!
你根本可以去教書啊
哈哈哈謝謝你~ 不瞞你說我以前真的有去高中教過書XD
我就說嘛, 厲害
( ̄∀ ̄)( ̄∀ ̄)( ̄∀ ̄)
新年快樂,日常攻城屍
雖然晚了,不過新年快樂
我專程來拍手了
我專門來感謝你了