用sockets打造自己的Android聊天app(安卓篇)

翻译自http://www.androidhive.info/2014/10/android-building-group-chat-app-using-sockets-part-2/

在上一篇文章中我们介绍了web sockets,搭建好了web环境,这篇文章我们开始安卓app的开发。同web应用一样,有两个屏幕,第一个是输入名字,第二个就是显示和发送消息。OK,我们这次的开发环境依然是Eclipse IDE.

6 建立Android App

首先定义一下我们所用到的颜色res ⇒ values ⇒ colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><color name="actionbar">#3cb879</color><color name="body_background">#e8e8e8</color><color name="body_background_green">#82e783</color><color name="server_status_bar">#2b2b2b</color><color name="title_gray">#434343</color><color name="white">#ffffff</color><color name="bg_msg_you">#5eb964</color><color name="bg_msg_from">#e5e7eb</color><color name="msg_border_color">#a1a1a1</color><color name="bg_btn_join">#1e6258</color><color name="bg_msg_input">#e8e8e8</color><color name="text_msg_input">#626262</color><color name="lblFromName">#777777</color>
</resources>

再定义我们所用到的字符串res ⇒ values ⇒ strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><string name="app_name">WebMobileGroupChat</string><string name="title">(Android WebSockets Chat App)</string><string name="author_name">By Ravi Tamada</string><string name="author_url">www.androidhive.info</string><string name="enter_name">Enter your name</string><string name="btn_join">JOIN</string><string name="btn_send">Send</string></resources>

再增加样式文件res ⇒ values ⇒ styles.xml

<resources><style name="ChatAppTheme" parent="@android:style/Theme.Holo.Light"><item name="android:actionBarStyle">@style/MyActionBarTheme</item></style><style name="MyActionBarTheme" parent="@android:style/Widget.Holo.Light.ActionBar"><item name="android:background">@color/actionbar</item><item name="android:titleTextStyle">@style/TitleTextStyle</item></style><style name="TitleTextStyle" parent="android:TextAppearance.Holo.Widget.ActionBar.Title"><item name="android:textColor">@color/white</item></style></resources>

下面这个布局文件是第一个屏幕,让用户输入用户名:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/actionbar"android:orientation="vertical" ><ImageView
        android:id="@+id/imgLogo"android:layout_width="60dp"android:layout_height="60dp"android:layout_alignParentTop="true"android:layout_centerHorizontal="true"android:layout_gravity="center_horizontal"android:layout_marginBottom="10dp"android:layout_marginTop="60dp"android:src="@drawable/ic_launcher" /><TextView
        android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/imgLogo"android:layout_centerHorizontal="true"android:layout_marginTop="15dp"android:text="@string/title"android:textColor="@color/white"android:textSize="13dp" /><LinearLayout
        android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_centerInParent="true"android:gravity="center_horizontal"android:orientation="vertical"android:padding="20dp" ><TextView
            android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="10dp"android:layout_marginTop="15dp"android:text="@string/enter_name"android:textColor="@color/white"android:textSize="18dp" /><EditText
            android:id="@+id/name"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_marginBottom="10dp"android:layout_marginTop="10dp"android:background="@color/white"android:inputType="textCapWords"android:padding="10dp" /><Button
            android:id="@+id/btnJoin"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="40dp"android:background="@color/bg_btn_join"android:paddingLeft="25dp"android:paddingRight="25dp"android:text="@string/btn_join"android:textColor="@color/white" /></LinearLayout><!-- author info --><LinearLayout
        android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:layout_marginBottom="20dp"android:gravity="center_horizontal"android:orientation="vertical" ><TextView
            android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/author_name"android:textColor="@color/white"android:textSize="12dp" /><TextView
            android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/author_url"android:textColor="@color/white"android:textSize="12dp" /></LinearLayout></RelativeLayout>

下面是对应的Activity,很简单,就是传递数据
NameActivity.java


package info.androidhive.webgroupchat;import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;public class NameActivity extends Activity {private Button btnJoin;private EditText txtName;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_name);btnJoin = (Button) findViewById(R.id.btnJoin);txtName = (EditText) findViewById(R.id.name);// Hiding the action bargetActionBar().hide();btnJoin.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (txtName.getText().toString().trim().length() > 0) {String name = txtName.getText().toString().trim();Intent intent = new Intent(NameActivity.this,MainActivity.class);intent.putExtra("name", name);startActivity(intent);} else {Toast.makeText(getApplicationContext(),"Please enter your name", Toast.LENGTH_LONG).show();}}});}
}

不要忘记在清单文件中加上网络权限
在做完这些之后你的app应该是这样子

在实现sockets之前,我们还需要定义一些资源,用来显示聊天界面

下载background图片放到drawable目录下。

定义如下三个drawable文件,这些用作聊天的背景tile_bg.xml, bg_msg_from.xml and bg_msg_you.xml

tile_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"android:src="@drawable/bg_messages"android:tileMode="repeat" />
bg_msg_from.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle" ><!-- view background color --><solid android:color="@color/bg_msg_from" ></solid><corners android:radius="5dp" ></corners></shape>
bg_msg_you.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle" ><!-- view background color --><solid android:color="@color/bg_msg_you" ></solid><corners android:radius="5dp" ></corners></shape>

然后我们在定义两个布局文件,分别是聊天的条目(自己的和别人的)

list_item_message_left.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:paddingBottom="5dp"android:paddingTop="5dp"android:paddingLeft="10dp"><TextView
        android:id="@+id/lblMsgFrom"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="12dp"android:textColor="@color/lblFromName"android:textStyle="italic"android:padding="5dp"/><TextView
        android:id="@+id/txtMsg"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="16dp"android:layout_marginRight="80dp"android:textColor="@color/title_gray"android:paddingLeft="10dp"android:paddingRight="10dp"android:paddingTop="5dp"android:paddingBottom="5dp"android:background="@drawable/bg_msg_from"/></LinearLayout>
list_item_message_right.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="right"android:orientation="vertical"android:paddingBottom="5dp"android:paddingRight="10dp"android:paddingTop="5dp" ><TextView
        android:id="@+id/lblMsgFrom"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="5dp"android:textColor="@color/lblFromName"android:textSize="12dp"android:textStyle="italic" /><TextView
        android:id="@+id/txtMsg"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="80dp"android:background="@drawable/bg_msg_you"android:paddingBottom="5dp"android:paddingLeft="10dp"android:paddingRight="10dp"android:paddingTop="5dp"android:textColor="@color/white"android:textSize="16dp" /></LinearLayout>

接下来的这个布局文件就是我们聊天的主界面

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/tile_bg"android:orientation="vertical" ><ListView
        android:id="@+id/list_view_messages"android:layout_width="fill_parent"android:layout_height="0dp"android:layout_weight="1"android:background="@null"android:divider="@null"android:transcriptMode="alwaysScroll"android:stackFromBottom="true"></ListView><LinearLayout
        android:id="@+id/llMsgCompose"android:layout_width="fill_parent"android:layout_height="wrap_content"android:background="@color/white"android:orientation="horizontal"android:weightSum="3" ><EditText
            android:id="@+id/inputMsg"android:layout_width="0dp"android:layout_height="fill_parent"android:layout_weight="2"android:background="@color/bg_msg_input"android:textColor="@color/text_msg_input"android:paddingLeft="6dp"android:paddingRight="6dp"/><Button
            android:id="@+id/btnSend"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:background="@color/bg_btn_join"android:textColor="@color/white"android:text="@string/btn_send" /></LinearLayout></LinearLayout>

接下来是两个帮助类,第一个Utils类有两个功能,第一个是存储Session id,第二个就是把消息转换成一个JSON字符串,如下

Utils.java
package info.androidhive.webgroupchat.other;import org.json.JSONException;
import org.json.JSONObject;import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;public class Utils {private Context context;private SharedPreferences sharedPref;private static final String KEY_SHARED_PREF = "ANDROID_WEB_CHAT";private static final int KEY_MODE_PRIVATE = 0;private static final String KEY_SESSION_ID = "sessionId",FLAG_MESSAGE = "message";public Utils(Context context) {this.context = context;sharedPref = this.context.getSharedPreferences(KEY_SHARED_PREF,KEY_MODE_PRIVATE);}public void storeSessionId(String sessionId) {Editor editor = sharedPref.edit();editor.putString(KEY_SESSION_ID, sessionId);editor.commit();}public String getSessionId() {return sharedPref.getString(KEY_SESSION_ID, null);}public String getSendMessageJSON(String message) {String json = null;try {JSONObject jObj = new JSONObject();jObj.put("flag", FLAG_MESSAGE);jObj.put("sessionId", getSessionId());jObj.put("message", message);json = jObj.toString();} catch (JSONException e) {e.printStackTrace();}return json;}}

下面是JavaBean对象

Message.java
package info.androidhive.webgroupchat.other;public class Message {private String fromName, message;private boolean isSelf;public Message() {}public Message(String fromName, String message, boolean isSelf) {this.fromName = fromName;this.message = message;this.isSelf = isSelf;}public String getFromName() {return fromName;}public void setFromName(String fromName) {this.fromName = fromName;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public boolean isSelf() {return isSelf;}public void setSelf(boolean isSelf) {this.isSelf = isSelf;}}

下面这个类是配置对象

WsConfig.java
package info.androidhive.webgroupchat.other;public class WsConfig {public static final String URL_WEBSOCKET = "ws://192.168.0.102:8080/WebMobileGroupChatServer/chat?name=";
}

下面这个是ListView的适配器,主要就是判断是自己的消息还是别人的消息,在getView中分别填充

package info.androidhive.webgroupchat;import info.androidhive.webgroupchat.other.Message;import java.util.List;import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;public class MessagesListAdapter extends BaseAdapter {private Context context;private List<Message> messagesItems;public MessagesListAdapter(Context context, List<Message> navDrawerItems) {this.context = context;this.messagesItems = navDrawerItems;}@Overridepublic int getCount() {return messagesItems.size();}@Overridepublic Object getItem(int position) {return messagesItems.get(position);}@Overridepublic long getItemId(int position) {return position;}@SuppressLint("InflateParams")@Overridepublic View getView(int position, View convertView, ViewGroup parent) {/*** The following list not implemented reusable list items as list items* are showing incorrect data Add the solution if you have one* */Message m = messagesItems.get(position);LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);// Identifying the message ownerif (messagesItems.get(position).isSelf()) {// message belongs to you, so load the right aligned layoutconvertView = mInflater.inflate(R.layout.list_item_message_right,null);} else {// message belongs to other person, load the left aligned layoutconvertView = mInflater.inflate(R.layout.list_item_message_left,null);}TextView lblFrom = (TextView) convertView.findViewById(R.id.lblMsgFrom);TextView txtMsg = (TextView) convertView.findViewById(R.id.txtMsg);txtMsg.setText(m.getMessage());lblFrom.setText(m.getFromName());return convertView;}
}

下载android websockets library,感谢 Koush大神

将下载的代码导入到eclipse中,并且在自己的项目中引用它

开始最重要的了。。。。

同js代码作为sockets客户端类似,WebSocketClient 也有一些回调函数,onConnect, onMessage and onDisconnect.

parseMessage() 函数用作解析从server中获得的Json字符串

在parseMessage()方法中,json的目的有flag表示

当新的消息收到时,要调用adapter.notifyDataSetChanged() 方法去更新列表

sendMessageToServer()发送到服务器

playBeep() 播放声音

package info.androidhive.webgroupchat;import info.androidhive.webgroupchat.other.Message;
import info.androidhive.webgroupchat.other.Utils;
import info.androidhive.webgroupchat.other.WsConfig;import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;import org.json.JSONException;
import org.json.JSONObject;import android.app.Activity;
import android.content.Intent;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;import com.codebutler.android_websockets.WebSocketClient;public class MainActivity extends Activity {// LogCat tagprivate static final String TAG = MainActivity.class.getSimpleName();private Button btnSend;private EditText inputMsg;private WebSocketClient client;// Chat messages list adapterprivate MessagesListAdapter adapter;private List<Message> listMessages;private ListView listViewMessages;private Utils utils;// Client nameprivate String name = null;// JSON flags to identify the kind of JSON responseprivate static final String TAG_SELF = "self", TAG_NEW = "new",TAG_MESSAGE = "message", TAG_EXIT = "exit";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btnSend = (Button) findViewById(R.id.btnSend);inputMsg = (EditText) findViewById(R.id.inputMsg);listViewMessages = (ListView) findViewById(R.id.list_view_messages);utils = new Utils(getApplicationContext());// 从上一个屏幕获取姓名Intent i = getIntent();name = i.getStringExtra("name");btnSend.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// Sending message to web socket serversendMessageToServer(utils.getSendMessageJSON(inputMsg.getText().toString()));// Clearing the input filed once message was sentinputMsg.setText("");}});listMessages = new ArrayList<Message>();adapter = new MessagesListAdapter(this, listMessages);listViewMessages.setAdapter(adapter);/*** 创建web sockets客户端,有如下的回调函数* */client = new WebSocketClient(URI.create(WsConfig.URL_WEBSOCKET+ URLEncoder.encode(name)), new WebSocketClient.Listener() {@Overridepublic void onConnect() {}/*** 从服务端接受消息* */@Overridepublic void onMessage(String message) {Log.d(TAG, String.format("Got string message! %s", message));parseMessage(message);}@Overridepublic void onMessage(byte[] data) {Log.d(TAG, String.format("Got binary message! %s",bytesToHex(data)));// Message will be in JSON formatparseMessage(bytesToHex(data));}/*** 连接中断* */@Overridepublic void onDisconnect(int code, String reason) {String message = String.format(Locale.US,"Disconnected! Code: %d Reason: %s", code, reason);showToast(message);// clear the session id from shared preferencesutils.storeSessionId(null);}@Overridepublic void onError(Exception error) {Log.e(TAG, "Error! : " + error);showToast("Error! : " + error);}}, null);client.connect();}/*** 发送消息* */private void sendMessageToServer(String message) {if (client != null && client.isConnected()) {client.send(message);}}/*** 解析从服务端收到的json 消息的目的由flag字段所指定,flag=self,消息属于指定的人,* new:新人加入   *    到对话中,message:新的消息,exit:退出* ** * * */private void parseMessage(final String msg) {try {JSONObject jObj = new JSONObject(msg);// JSON node 'flag'String flag = jObj.getString("flag");// 如果是self,json中包含sessionId信息if (flag.equalsIgnoreCase(TAG_SELF)) {String sessionId = jObj.getString("sessionId");// Save the session id in shared preferencesutils.storeSessionId(sessionId);Log.e(TAG, "Your session id: " + utils.getSessionId());} else if (flag.equalsIgnoreCase(TAG_NEW)) {// If the flag is 'new', new person joined the roomString name = jObj.getString("name");String message = jObj.getString("message");// number of people onlineString onlineCount = jObj.getString("onlineCount");showToast(name + message + ". Currently " + onlineCount+ " people online!");} else if (flag.equalsIgnoreCase(TAG_MESSAGE)) {// if the flag is 'message', new message receivedString fromName = name;String message = jObj.getString("message");String sessionId = jObj.getString("sessionId");boolean isSelf = true;// Checking if the message was sent by youif (!sessionId.equals(utils.getSessionId())) {fromName = jObj.getString("name");isSelf = false;}Message m = new Message(fromName, message, isSelf);// 把消息加入到arraylist中appendMessage(m);} else if (flag.equalsIgnoreCase(TAG_EXIT)) {// If the flag is 'exit', somebody left the conversationString name = jObj.getString("name");String message = jObj.getString("message");showToast(name + message);}} catch (JSONException e) {e.printStackTrace();}}@Overrideprotected void onDestroy() {super.onDestroy();if(client != null & client.isConnected()){client.disconnect();}}/*** 把消息放到listView里* */private void appendMessage(final Message m) {runOnUiThread(new Runnable() {@Overridepublic void run() {listMessages.add(m);adapter.notifyDataSetChanged();// Playing device's notificationplayBeep();}});}private void showToast(final String message) {runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(), message,Toast.LENGTH_LONG).show();}});}/*** 播放默认的通知声音* */public void playBeep() {try {Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);Ringtone r = RingtoneManager.getRingtone(getApplicationContext(),notification);r.play();} catch (Exception e) {e.printStackTrace();}}final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();public static String bytesToHex(byte[] bytes) {char[] hexChars = new char[bytes.length * 2];for (int j = 0; j < bytes.length; j++) {int v = bytes[j] & 0xFF;hexChars[j * 2] = hexArray[v >>> 4];hexChars[j * 2 + 1] = hexArray[v & 0x0F];}return new String(hexChars);}}

最终结果应该是这样的

用sockets打造自己的Android聊天app(安卓篇)相关推荐

  1. 物联网平台搭建的全过程介绍(五)——基于阿里云物联网平台的Android聊天app源码

    本例程Android源码请点此处免费下载 物联网平台搭建的全过程介绍(四)两台设备之间通过云数据流转实现远程通信之Android studio例程中介绍了两台Android设备通过物联网平台进行通信的 ...

  2. 微信小程序android错误,app安卓端 跳转到微信小程序失败

    详细问题描述 (DCloud产品不会有明显的bug,所以你遇到的问题大都是在特定环境下才能重现的问题,请仔细描述你的环境和重现方式,否则DCloud很难排查解决你的问题) [内容] app安卓端跳转到 ...

  3. beats 耳机 android,Beats app安卓,Beats app安卓耳机管理预约 v2.3.5 - 游戏盒子下载站...

    Beats app安卓是一款十分好用的耳机管理类服务平台软件,这款软件能够直接对耳机设备进行电量的查看和音频的控制,一键连接帮你轻松管理,实用性是很强的,而且操作十分简单,帮你更快更好地管理自己的耳机 ...

  4. JAVA开发Android聊天APP,实现了类似QQ、微信的即时通讯功能

    视频学习地址 本内容接上篇文章 我的源码: 码云 github 大加有需要可以下载来参照一下. 文章目录 简介: 问题.报错 与视频中有差别的地方 视频的第十九节 视频的第38节,关于长按删除联系人, ...

  5. android 提醒app,安卓手机上提醒工作的软件有哪些?

    原标题:安卓手机上提醒工作的软件有哪些? 众所周知,在当前的职场中,大多数的办公人士每天都要处理很多繁琐的工作事项,所以忙碌已经成为他们的工作常态.但是即便如此,这些职场人士仍然无法摆脱加班的困扰,因 ...

  6. 在行 app android,在行app安卓版

    在行安卓版是安卓平台上一个类似咨询平台的APP应用,用户有任何问题都可以在这里找到答案,无论是工作方面.生活方面还是个人感情问题,快来试试吧! 官方介绍 在行app是一款非常专业的问题咨询平台,无论你 ...

  7. bookshelf app android,bookshelf app安卓

    介绍(2021-01-26) Use VitalSource Bookshelf® to download and access VitalSource e-books on your iPhone, ...

  8. android zuomian app,安卓手机中必备的五个黑科技App,没装的简直太可惜了

    原标题:安卓手机中必备的五个黑科技App,没装的简直太可惜了 方片收集 方片手机是一款资源丰富的收集资料的利器,它支持图片.视频.网站等多种手机资源,它的界面非常的简单,可以让人很好的静下心来阅读. ...

  9. android闹钟app,安卓手机闹钟软件谁最好?四款安卓闹钟软件横评

    小编今天为您带来四款安卓手机闹钟软件横评,希望可以为您参考,找到适合自己的闹钟软件. 安卓手机闹钟软件横评之软件介绍 评测环境: 评测手机:HTC G6 (Legend) 评测系统:Android 2 ...

最新文章

  1. 使用 EclEmma 来显示代码覆盖率
  2. 2017-2018-1 《信息安全系统设计基础》课下测试错题汇总
  3. 设计思维的要素:优化愿景(Vision)
  4. BitNami Redmine Stack
  5. Yii2.0 ActiveForm Input Fields
  6. 初学者python笔记(time模块、random模块功能分析)
  7. Postfix 故障记录
  8. Vuforia+Unity实现AR效果
  9. JAAS(Java 认证和授权服务)
  10. 共享该文件当前不能用户此计算机,局域网共享常见问题解决汇集
  11. 个税计算python版(2021最新版)
  12. ui和ux的区别_UX和UI设计之间有什么区别?
  13. 24点小游戏(C语言实现)
  14. 微商怎么找客源,新手做微商如何找客源的
  15. 批量执行ABAQUS的inp文件——整理
  16. BT TWS 听音乐无声问题
  17. python做按键精灵脚本_Python 实现按键精灵的功能,超简单详细(MAC版)
  18. 【读书笔记->数据分析】BDA教材《数据分析》书籍介绍
  19. Webix学习之template
  20. 软件工程:到底应该怎么理解软件工程

热门文章

  1. childnodes与children详解
  2. 马哥学习周总结第一周→linux简介、’文件系统及基础命令------李洋个人笔记。...
  3. input中blur失去焦点事件与点击事件冲突
  4. 【缓存】@Caching和@CacheConfig
  5. APP-iOS和Android的尺寸规范
  6. 计算器(可随意编辑)
  7. 提交工程到git的分支上
  8. android-sdk-windows版本下载
  9. Linux进程状态及其相互转换
  10. eval()函数的使用