一,通信结构
对于软件的注册,登录,用户信息管理,通讯录等功能模块,客户端与服务器的通信结构为C/S结构,使用HTTP协议进行数据交互。
而对于聊天模块,客户端与服务器的通信结构为类P2P结构(其实还是C/S,不过服务器作为中转站而已),使用Socket(套接字)实现服务器对客户端的消息推送功能。
二,RSA加解密
为了提高数据传输的安全性,针对HTTP协议的通信方式,将客户端提交的参数通过1024位长的公钥进行加密,服务器接收到数据后,使用256位长的私钥进行解密。公钥和私钥都是通过OpenSSL预先生成,保存在字符串中的。
密钥生成步骤:
1. 安装OpenSSL。可以在 http://www.openssl.org/source/ 下载,直接安装。
2. 打开密令提示符,进入到你想存放密钥文件的文件夹。比如F:\key目录
3.生成密钥。在命令提示符输入下面的命令:
openssl genrsa -out private_key.pem 1024 (生成1024位长度的ASCII编码的私钥)
openssl rsa -in private_key.pem -out public_key.pem -pubout (通过私钥来生成对应的公钥)
openssl pkcs8 -topk8 -in private_key.pem -out pkcs8_private_key.pem -nocrypt (将私钥进行PKCS#8编码,这样才能使用。-nocrypt,不采用二次加密)
4.打开F:\key目录的public_key.pem和pkcs8_private_key.pem,提取
-----BEGIN PUBLIC KEY-----和
-----END PUBLIC KEY-----之间的字符串,就可以分别得到
公私钥。
Java中使用RSA加密(需要添加bcprov-jdk15on-150.jar和sun.misc.BASE64Decoder.jar)
客户端加密
public class EncryptUtils {
/** RSA加密的公钥 ,与服务端的私钥对应*/
private static final String publicKeyStr = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0dLTXOCN7VMM4MbbxkVIwIRmEjzlKJSbd97uoWiwt3z/Q1u4DQNfTVdeLtDIEFeYUz1/mJPltMdlUDB8/YO2MfHnvipk4DC+C7mZ5DgP/5Qtglvl6alPTL2yhZNpJ5MCJQNvYk7l5A1lDwSwFKkFmBl2vHeGY76C/Y62ofeZYRwIDAQAB";
private static char[] HEX_CHAR = { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
/**
* RSA加密
*/
public static String GetRsaEncrypt(String src) {
if (src == null)
return "";
String enData = null;
try {
Cipher cipher = Cipher.getInstance("RSA",
new BouncyCastleProvider());
RSAPublicKey pubKey = GetPublicKey(publicKeyStr);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] output = cipher.doFinal(src.getBytes("UTF-8"));
enData = ByteToString(output);
} catch (Exception e) {
e.printStackTrace();
}
return enData;
}
/**将加密后的byte数组转换为String*/
private static String ByteToString(byte[] data) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < data.length; i++) {
// 取出字节的高四位 作为索引得到相应的十六进制标识符 注意无符号右移
stringBuilder.append(HEX_CHAR[(data[i] & 0xf0) >>> 4]);
// 取出字节的低四位 作为索引得到相应的十六进制标识符
stringBuilder.append(HEX_CHAR[(data[i] & 0x0f)]);
}
return stringBuilder.toString();
}
/**
* 将String型私钥转换为RSAPublicKey
*
* @param publicKeyStr
* 公钥数据字符串
*/
private static RSAPublicKey GetPublicKey(String publicKeyStr) {
RSAPublicKey pubKey = null;
try {
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] buffer = base64Decoder.decodeBuffer(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
pubKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (Exception e) {
e.printStackTrace();
}
return pubKey;
}
}
服务器解密
public class EncryptUtils {
/** 服务器端RSA解密的私钥 ,与客户端的公钥对应*/
private static final String privateKeyStr = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALR0tNc4I3tUwzgxtvGRUjAhGYSPOUolJt33u6haLC3fP9DW7gNA19NV14u0MgQV5hTPX+Yk+W0x2VQMHz9g7Yx8ee+KmTgML4LuZnkOA//lC2CW+XpqU9MvbKFk2knkwIlA29iTuXkDWUPBLAUqQWYGXa8d4ZjvoL9jrah95lhHAgMBAAECgYEAqpnjFc0HDmP2I7wsXniqoMHKJB5bZRN2iUbZ7LFDLyLua/umDQFSiYOQQY1b86zYVjgvS58NCASml+TV7c8vA5care9HqpQX+1YdBUB1Hw+zloIwJD5894123wumRpbHBK1vkgvX3cKz0K+lNHfnXoov49DLvDeTCtBb9GQQX2ECQQDv1avuF4DlbVavIuTNZFw5YntZM13E2M+JMx84XGODvncuDl8C/TpW+ZsaaKqU7caXtWl2p5ip4UZPYLwyRh65AkEAwJ51buJ5qZhA7kD6is4RC4P7F2LwGkhKrpXxlzHsPeW8m9if62E7AH3B8MGicIVeQjJwxSrsiI3wYEHDlIyu/wJAN7ZzEgPztVgI4vZAIFZH9iyiar478hZLX5u4jOcpVtlP5isAdzlL7Bhfp2rY9W+mymch8KZOGGh0ZMwb67HOQQJAcOw04mXpd3CoGEWF3FxEh+C/Eo3RP0dEaSfEs6Pz4LHPqfoMfvzIj1gqm8+ZQKgfg2V40U6BzuiPlI7Zbzwu1wJAMbq668GPCzMgc0LLImkGTaOPcmjPYUbAYXa4k/90M3sX0t6s9u0kl9NfotSpF9M3AdbFSdKWXoY8XScOkhnpQQ==";
/**
* RSA解密
*
* 用于将客户端上传的,经过RSA加密的参数进行解密
*
* @param src
* @return 解密后的数据
*/
public static String GetRsaDecrypt(String src) {
String deData = null;
try {
Cipher cipher = Cipher.getInstance("RSA",
new BouncyCastleProvider());
RSAPrivateKey priKey = GetPrivateKey(privateKeyStr);
cipher.init(Cipher.DECRYPT_MODE, priKey);
byte[] output = cipher.doFinal(StringToByte(src));
deData = new String(output, "utf-8");// 此处如果不指定编码格式,则会产生中文乱码
} catch (Exception e) {
e.printStackTrace();
}
return deData;
}
/**
* 将String型私钥转换为RSAPrivateKey
*
* @param privateKeyStr
* @return
*/
private static RSAPrivateKey GetPrivateKey(String privateKeyStr) {
RSAPrivateKey priKey = null;
try {
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] buffer = base64Decoder.decodeBuffer(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
priKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
} catch (Exception e) {
e.printStackTrace();
}
return priKey;
}
/**将解密数据转换为byte数组*/
private static byte[] StringToByte(String str) {
int len = str.length();
byte[] data = new byte[len / 2];
char[] ch = str.toCharArray();
String tmp;
int i = 0, j = 0;
while (i < len) {
//两个char组合成一个byte
tmp = String.valueOf(ch[i++]);
//高位
int high = Integer.parseInt(tmp, 16);
tmp = String.valueOf(ch[i++]);
//低位
int low = Integer.parseInt(tmp, 16);
data[j] = (byte) ((high << 4) + low);
j++;
}
return data;
}
}
详情可以看:http://blog.csdn.net/chaijunkun/article/details/7275632
三,Log4j日志输出
Log4j用于在服务器端输出日志。
首先需要添加commons-logging.jar和log4j.jar,然后在src根目录下创建一个log4j.properties文件。
使用时,只需要Logger logger = Logger.getLogger(String name); 就可以通过logger输出debug,info,error等级别的日志。不过,如果你在log4j.properties文件中设置的日志输出的最低级别为info,则不会输出debug级别的日志。
详情可以看:http://kdboy.iteye.com/blog/208851
四,低版本支持ActionBar
Android3.0版本后,才支持ActionBar。如果要在低版本中支持ActionBar,需要用到android-support-v7-appcompat项目。这个项目在Android官方SDK的sdk\extras\android\support\v7目录也可以找到,不过有一个很奇怪的地方,我用官方的appcompat项目实现下拉菜单时,其背景图片的边框是透明的,如下面左图。所以我将这个图片替换成下面的右图(将appcompat项目drawable-hdpi,drawable-mdpi,drawable-xhdpi三个目录下的abc_menu_dropdown_panel_holo_light.9.png替换即可)。
在上一篇文章中讲到了如何导入和使用appcompat这个项目,接下来是以MainActivity为例,讲述在项目中如何使用兼容版本的ActionBar。
1.在AndroidManifest.xml文件中,需要在MainActivity的activity标签中添加主题。
android:name=".MainActivity"
android:theme="@style/Theme.AppCompat.Light"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="portrait">
2.让MainActivity继承ActionBarActivity,通过getSupportActionBar()获得ActionBar对象。
3.通过ActionBar的addTab为其添加选项卡。
4.通过onCreateOptionsMenu添加Menu项,onOptionsItemSelected为Menu项增加下拉子菜单,onMenuItemClick监听每个子菜单的点击。由于android-support-v7-appcompat在Android3.0以下不支持Overflow按钮,如果要使用Overflow按钮,可以通过PopupMenu来实现。
在实现代码的过程中,有一点要特别注意!import ActionBar相关类的时候,要看清楚,导入的必须是appcompat项目中的类,如android.support.v7.app.ActionBar,而不是android.app.ActionBar。这是因为我们工程的target platform是3.0以上版本,因此Android sdk包含有ActionBar相关的类。但是我们需要支持的最低版本是低于3.0的,比如部署到2.3的真机上,这时Android sdk并不含有ActionBar相关的类,需要appcompat
的支持。(这里还需要区分target platform和minSdkVersion的含义。target platform:编译项目的Android版本。minSdkVersion:支持的最低版本)
具体代码如下,
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBar.Tab;
import android.support.v7.app.ActionBar.TabListener;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.PopupMenu.OnMenuItemClickListener;
public class MainActivity extends ActionBarActivity implements OnMenuItemClickListener{
private Context mContext;
private PopupMenu mAddMenu;
private PopupMenu mManageMenu;
private ActionBar mActionBar;
private ChatFragment mChatFragment;
private ContactsFragment mContactsFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mContext = this;
mChatFragment = new ChatFragment();
mContactsFragment = new ContactsFragment();
setActionBar();
}
private void setActionBar()
{
mActionBar = getSupportActionBar();
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// mActionBar.setDisplayShowTitleEnabled(false);//隐藏ActionBar的title
TabListener listener = new MyTabListener();
mActionBar.addTab(mActionBar.newTab().setIcon(R.drawable.main_tab_chat).setText(R.string.fragment_chat).setTabListener(listener));
mActionBar.addTab(mActionBar.newTab().setIcon(R.drawable.main_tab_contacts).setText
(R.string.fragment_contacts).setTabListener(listener));
}
/**添加ActionBar的Menu*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}
/**添加Menu的下拉子菜单*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_manage:
if(mManageMenu == null)
{
mManageMenu = new PopupMenu(mContext,
findViewById(R.id.action_manage));
mManageMenu.inflate(R.menu.main_item_manage);
mManageMenu.setOnMenuItemClickListener(this);
}
mManageMenu.show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**监听子菜单的点击事件*/
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.menu_userInfo:
//TODO ...
break;
case R.id.menu_setting:
//TODO ...
break;
}
return false;
}
/**TabListener实现类*/
class MyTabListener implements TabListener{
public void onTabSelected(Tab tab, FragmentTransaction arg1) {
switch (tab.getPosition()) {
case 0://聊天界面
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_container, mChatFragment).commit();
break;
case 1://通讯录界面
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_container, mContactsFragment).commit();
break;
}
}
public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
}
public void onTabUnselected(Tab arg0, FragmentTransaction arg1) {
}
}
}
最后实现的MainActivity界面
首页
Android聊天软件的开发