• 基于xmpp openfire smack开发之Android客户端开发[3]


    在上两篇文章中,我们依次介绍openfire部署以及smack常用API的使用,这一节中我们着力介绍如何基于asmack开发一个Android的客户端,本篇的重点在实践,讲解和原理环节,大家可以参考前两篇的文章

    基于xmpp openfire smack开发之openfire介绍和部署[1]

    基于xmpp openfire smack开发之smack类库介绍和使用[2]

    1.源码结构介绍

    activity包下存放一些android页面交互相关的控制程序,还有一个些公共帮助类

    db包为sqlite的工具类封装,这里做了一些自定义的改造,稍微仿Spring的JdbcTemplate结构,使用起来更加方便一点

    manager包留下主要是一些管理组件,包括联系人管理,消息管理,提醒管理,离线消息管理,用户管理,xmpp连接管理

    model包中都是一些对象模型,传输介质

    service中存放一些android后台的核心服务,主要包括聊天服务,联系人服务,系统消息服务,重连接服务

    task包中存放一些耗时的异步操作

    util中存放一些常用的工具类

    view中一些和android的UI相关的显示控件

    anim中存放一些动画元素的配置

    layout是布局页面

    menu是地步菜单布局页面

    values中存放一些字符,颜色,样式,参数的配置信息

    其中strings.xml中,保存的缺省配置为gtalk的服务器信息,大家如果有谷歌gtalk的账号可以直接登录,否则需要更改这里的配置才可以使用其他的xmpp服务器

    [html] view plaincopy
     
    1. <!-- 缺省的服务器配置 -->   
    2.   <integer name="xmpp_port">5222</integer>   
    3.   <string name="xmpp_host">talk.google.com</string>   
    4.   <string name="xmpp_service_name">gmail.com</string>  
    5.   <bool name="is_remember">true</bool>  
    6.   <bool name="is_autologin">false</bool>  
    7.   <bool name="is_novisible">false</bool>   


    AndroidManifest.xml为android功能清单的配置文件,我们这里开放的权限并不多

    [html] view plaincopy
     
    1.     <!-- 访问Internet -->  
    2. <uses-permission android:name="android.permission.INTERNET" />  
    3. <!--- 访问网络状态 -->  
    4.     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  
    5.     <!-- 往SDCard写入数据权限 -->  
    6.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  
    7.    <span style="WHITE-SPACE: pre">  </span><!-- 在SDCard中创建与删除文件权限 -->  
    8.    <span style="WHITE-SPACE: pre">  </span><uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>  
    9.    <span style="WHITE-SPACE: pre">  </span><!-- 往SDCard写入数据权限 -->  
    10.    <span style="WHITE-SPACE: pre">  </span><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  

    2.核心类介绍

    1.ActivitySupport类
    [java] view plaincopy
     
    1. package csdn.shimiso.eim.activity;  
    2.   
    3. import android.app.Activity;  
    4. import android.app.AlertDialog;  
    5. import android.app.Notification;  
    6. import android.app.NotificationManager;  
    7. import android.app.PendingIntent;  
    8. import android.app.ProgressDialog;  
    9. import android.content.Context;  
    10. import android.content.DialogInterface;  
    11. import android.content.Intent;  
    12. import android.content.SharedPreferences;  
    13. import android.location.LocationManager;  
    14. import android.net.ConnectivityManager;  
    15. import android.net.NetworkInfo;  
    16. import android.os.Bundle;  
    17. import android.os.Environment;  
    18. import android.provider.Settings;  
    19. import android.view.inputmethod.InputMethodManager;  
    20. import android.widget.Toast;  
    21. import csdn.shimiso.eim.R;  
    22. import csdn.shimiso.eim.comm.Constant;  
    23. import csdn.shimiso.eim.model.LoginConfig;  
    24. import csdn.shimiso.eim.service.IMChatService;  
    25. import csdn.shimiso.eim.service.IMContactService;  
    26. import csdn.shimiso.eim.service.IMSystemMsgService;  
    27. import csdn.shimiso.eim.service.ReConnectService;  
    28.   
    29. /** 
    30.  * Actity 工具支持类 
    31.  *  
    32.  * @author shimiso 
    33.  *  
    34.  */  
    35. public class ActivitySupport extends Activity implements IActivitySupport {  
    36.   
    37.     protected Context context = null;  
    38.     protected SharedPreferences preferences;  
    39.     protected EimApplication eimApplication;  
    40.     protected ProgressDialog pg = null;  
    41.     protected NotificationManager notificationManager;  
    42.   
    43.     @Override  
    44.     protected void onCreate(Bundle savedInstanceState) {  
    45.         super.onCreate(savedInstanceState);  
    46.         context = this;  
    47.         preferences = getSharedPreferences(Constant.LOGIN_SET, 0);  
    48.         notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);  
    49.         pg = new ProgressDialog(context);  
    50.         eimApplication = (EimApplication) getApplication();  
    51.         eimApplication.addActivity(this);  
    52.     }  
    53.   
    54.     @Override  
    55.     protected void onStart() {  
    56.         super.onStart();  
    57.     }  
    58.   
    59.     @Override  
    60.     protected void onResume() {  
    61.         super.onResume();  
    62.     }  
    63.   
    64.     @Override  
    65.     protected void onPause() {  
    66.         super.onPause();  
    67.     }  
    68.   
    69.     @Override  
    70.     protected void onStop() {  
    71.         super.onStop();  
    72.     }  
    73.   
    74.     @Override  
    75.     public void onDestroy() {  
    76.         super.onDestroy();  
    77.     }  
    78.   
    79.     @Override  
    80.     public ProgressDialog getProgressDialog() {  
    81.         return pg;  
    82.     }  
    83.   
    84.     @Override  
    85.     public void startService() {  
    86.         // 好友联系人服务  
    87.         Intent server = new Intent(context, IMContactService.class);  
    88.         context.startService(server);  
    89.         // 聊天服务  
    90.         Intent chatServer = new Intent(context, IMChatService.class);  
    91.         context.startService(chatServer);  
    92.         // 自动恢复连接服务  
    93.         Intent reConnectService = new Intent(context, ReConnectService.class);  
    94.         context.startService(reConnectService);  
    95.         // 系统消息连接服务  
    96.         Intent imSystemMsgService = new Intent(context,  
    97.                 IMSystemMsgService.class);  
    98.         context.startService(imSystemMsgService);  
    99.     }  
    100.   
    101.     /** 
    102.      *  
    103.      * 销毁服务. 
    104.      *  
    105.      * @author shimiso 
    106.      * @update 2012-5-16 下午12:16:08 
    107.      */  
    108.     @Override  
    109.     public void stopService() {  
    110.         // 好友联系人服务  
    111.         Intent server = new Intent(context, IMContactService.class);  
    112.         context.stopService(server);  
    113.         // 聊天服务  
    114.         Intent chatServer = new Intent(context, IMChatService.class);  
    115.         context.stopService(chatServer);  
    116.   
    117.         // 自动恢复连接服务  
    118.         Intent reConnectService = new Intent(context, ReConnectService.class);  
    119.         context.stopService(reConnectService);  
    120.   
    121.         // 系统消息连接服务  
    122.         Intent imSystemMsgService = new Intent(context,  
    123.                 IMSystemMsgService.class);  
    124.         context.stopService(imSystemMsgService);  
    125.     }  
    126.   
    127.     @Override  
    128.     public void isExit() {  
    129.         new AlertDialog.Builder(context).setTitle("确定退出吗?")  
    130.                 .setNeutralButton("确定", new DialogInterface.OnClickListener() {  
    131.                     @Override  
    132.                     public void onClick(DialogInterface dialog, int which) {  
    133.                         stopService();  
    134.                         eimApplication.exit();  
    135.                     }  
    136.                 })  
    137.                 .setNegativeButton("取消", new DialogInterface.OnClickListener() {  
    138.                     @Override  
    139.                     public void onClick(DialogInterface dialog, int which) {  
    140.                         dialog.cancel();  
    141.                     }  
    142.                 }).show();  
    143.     }  
    144.   
    145.     @Override  
    146.     public boolean hasInternetConnected() {  
    147.         ConnectivityManager manager = (ConnectivityManager) context  
    148.                 .getSystemService(context.CONNECTIVITY_SERVICE);  
    149.         if (manager != null) {  
    150.             NetworkInfo network = manager.getActiveNetworkInfo();  
    151.             if (network != null && network.isConnectedOrConnecting()) {  
    152.                 return true;  
    153.             }  
    154.         }  
    155.         return false;  
    156.     }  
    157.   
    158.     @Override  
    159.     public boolean validateInternet() {  
    160.         ConnectivityManager manager = (ConnectivityManager) context  
    161.                 .getSystemService(context.CONNECTIVITY_SERVICE);  
    162.         if (manager == null) {  
    163.             openWirelessSet();  
    164.             return false;  
    165.         } else {  
    166.             NetworkInfo[] info = manager.getAllNetworkInfo();  
    167.             if (info != null) {  
    168.                 for (int i = 0; i < info.length; i++) {  
    169.                     if (info[i].getState() == NetworkInfo.State.CONNECTED) {  
    170.                         return true;  
    171.                     }  
    172.                 }  
    173.             }  
    174.         }  
    175.         openWirelessSet();  
    176.         return false;  
    177.     }  
    178.   
    179.     @Override  
    180.     public boolean hasLocationGPS() {  
    181.         LocationManager manager = (LocationManager) context  
    182.                 .getSystemService(context.LOCATION_SERVICE);  
    183.         if (manager  
    184.                 .isProviderEnabled(android.location.LocationManager.GPS_PROVIDER)) {  
    185.             return true;  
    186.         } else {  
    187.             return false;  
    188.         }  
    189.     }  
    190.   
    191.     @Override  
    192.     public boolean hasLocationNetWork() {  
    193.         LocationManager manager = (LocationManager) context  
    194.                 .getSystemService(context.LOCATION_SERVICE);  
    195.         if (manager  
    196.                 .isProviderEnabled(android.location.LocationManager.NETWORK_PROVIDER)) {  
    197.             return true;  
    198.         } else {  
    199.             return false;  
    200.         }  
    201.     }  
    202.   
    203.     @Override  
    204.     public void checkMemoryCard() {  
    205.         if (!Environment.MEDIA_MOUNTED.equals(Environment  
    206.                 .getExternalStorageState())) {  
    207.             new AlertDialog.Builder(context)  
    208.                     .setTitle(R.string.prompt)  
    209.                     .setMessage("请检查内存卡")  
    210.                     .setPositiveButton(R.string.menu_settings,  
    211.                             new DialogInterface.OnClickListener() {  
    212.                                 @Override  
    213.                                 public void onClick(DialogInterface dialog,  
    214.                                         int which) {  
    215.                                     dialog.cancel();  
    216.                                     Intent intent = new Intent(  
    217.                                             Settings.ACTION_SETTINGS);  
    218.                                     context.startActivity(intent);  
    219.                                 }  
    220.                             })  
    221.                     .setNegativeButton("退出",  
    222.                             new DialogInterface.OnClickListener() {  
    223.                                 @Override  
    224.                                 public void onClick(DialogInterface dialog,  
    225.                                         int which) {  
    226.                                     dialog.cancel();  
    227.                                     eimApplication.exit();  
    228.                                 }  
    229.                             }).create().show();  
    230.         }  
    231.     }  
    232.   
    233.     public void openWirelessSet() {  
    234.         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);  
    235.         dialogBuilder  
    236.                 .setTitle(R.string.prompt)  
    237.                 .setMessage(context.getString(R.string.check_connection))  
    238.                 .setPositiveButton(R.string.menu_settings,  
    239.                         new DialogInterface.OnClickListener() {  
    240.                             @Override  
    241.                             public void onClick(DialogInterface dialog,  
    242.                                     int which) {  
    243.                                 dialog.cancel();  
    244.                                 Intent intent = new Intent(  
    245.                                         Settings.ACTION_WIRELESS_SETTINGS);  
    246.                                 context.startActivity(intent);  
    247.                             }  
    248.                         })  
    249.                 .setNegativeButton(R.string.close,  
    250.                         new DialogInterface.OnClickListener() {  
    251.                             @Override  
    252.                             public void onClick(DialogInterface dialog,  
    253.                                     int whichButton) {  
    254.                                 dialog.cancel();  
    255.                             }  
    256.                         });  
    257.         dialogBuilder.show();  
    258.     }  
    259.   
    260.     /** 
    261.      *  
    262.      * 显示toast 
    263.      *  
    264.      * @param text 
    265.      * @param longint 
    266.      * @author shimiso 
    267.      * @update 2012-6-28 下午3:46:18 
    268.      */  
    269.     public void showToast(String text, int longint) {  
    270.         Toast.makeText(context, text, longint).show();  
    271.     }  
    272.   
    273.     @Override  
    274.     public void showToast(String text) {  
    275.         Toast.makeText(context, text, Toast.LENGTH_SHORT).show();  
    276.     }  
    277.   
    278.     /** 
    279.      *  
    280.      * 关闭键盘事件 
    281.      *  
    282.      * @author shimiso 
    283.      * @update 2012-7-4 下午2:34:34 
    284.      */  
    285.     public void closeInput() {  
    286.         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);  
    287.         if (inputMethodManager != null && this.getCurrentFocus() != null) {  
    288.             inputMethodManager.hideSoftInputFromWindow(this.getCurrentFocus()  
    289.                     .getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);  
    290.         }  
    291.     }  
    292.   
    293.     /** 
    294.      *  
    295.      * 发出Notification的method. 
    296.      *  
    297.      * @param iconId 
    298.      *            图标 
    299.      * @param contentTitle 
    300.      *            标题 
    301.      * @param contentText 
    302.      *            你内容 
    303.      * @param activity 
    304.      * @author shimiso 
    305.      * @update 2012-5-14 下午12:01:55 
    306.      */  
    307.     public void setNotiType(int iconId, String contentTitle,  
    308.             String contentText, Class activity, String from) {  
    309.         /* 
    310.          * 创建新的Intent,作为点击Notification留言条时, 会运行的Activity 
    311.          */  
    312.         Intent notifyIntent = new Intent(this, activity);  
    313.         notifyIntent.putExtra("to", from);  
    314.         // notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
    315.   
    316.         /* 创建PendingIntent作为设置递延运行的Activity */  
    317.         PendingIntent appIntent = PendingIntent.getActivity(this, 0,  
    318.                 notifyIntent, 0);  
    319.   
    320.         /* 创建Notication,并设置相关参数 */  
    321.         Notification myNoti = new Notification();  
    322.         // 点击自动消失  
    323.         myNoti.flags = Notification.FLAG_AUTO_CANCEL;  
    324.         /* 设置statusbar显示的icon */  
    325.         myNoti.icon = iconId;  
    326.         /* 设置statusbar显示的文字信息 */  
    327.         myNoti.tickerText = contentTitle;  
    328.         /* 设置notification发生时同时发出默认声音 */  
    329.         myNoti.defaults = Notification.DEFAULT_SOUND;  
    330.         /* 设置Notification留言条的参数 */  
    331.         myNoti.setLatestEventInfo(this, contentTitle, contentText, appIntent);  
    332.         /* 送出Notification */  
    333.         notificationManager.notify(0, myNoti);  
    334.     }  
    335.   
    336.     @Override  
    337.     public Context getContext() {  
    338.         return context;  
    339.     }  
    340.   
    341.     @Override  
    342.     public SharedPreferences getLoginUserSharedPre() {  
    343.         return preferences;  
    344.     }  
    345.   
    346.     @Override  
    347.     public void saveLoginConfig(LoginConfig loginConfig) {  
    348.         preferences.edit()  
    349.                 .putString(Constant.XMPP_HOST, loginConfig.getXmppHost())  
    350.                 .commit();  
    351.         preferences.edit()  
    352.                 .putInt(Constant.XMPP_PORT, loginConfig.getXmppPort()).commit();  
    353.         preferences  
    354.                 .edit()  
    355.                 .putString(Constant.XMPP_SEIVICE_NAME,  
    356.                         loginConfig.getXmppServiceName()).commit();  
    357.         preferences.edit()  
    358.                 .putString(Constant.USERNAME, loginConfig.getUsername())  
    359.                 .commit();  
    360.         preferences.edit()  
    361.                 .putString(Constant.PASSWORD, loginConfig.getPassword())  
    362.                 .commit();  
    363.         preferences.edit()  
    364.                 .putBoolean(Constant.IS_AUTOLOGIN, loginConfig.isAutoLogin())  
    365.                 .commit();  
    366.         preferences.edit()  
    367.                 .putBoolean(Constant.IS_NOVISIBLE, loginConfig.isNovisible())  
    368.                 .commit();  
    369.         preferences.edit()  
    370.                 .putBoolean(Constant.IS_REMEMBER, loginConfig.isRemember())  
    371.                 .commit();  
    372.         preferences.edit()  
    373.                 .putBoolean(Constant.IS_ONLINE, loginConfig.isOnline())  
    374.                 .commit();  
    375.         preferences.edit()  
    376.                 .putBoolean(Constant.IS_FIRSTSTART, loginConfig.isFirstStart())  
    377.                 .commit();  
    378.     }  
    379.   
    380.     @Override  
    381.     public LoginConfig getLoginConfig() {  
    382.         LoginConfig loginConfig = new LoginConfig();  
    383.         String a = preferences.getString(Constant.XMPP_HOST, null);  
    384.         String b = getResources().getString(R.string.xmpp_host);  
    385.         loginConfig.setXmppHost(preferences.getString(Constant.XMPP_HOST,  
    386.                 getResources().getString(R.string.xmpp_host)));  
    387.         loginConfig.setXmppPort(preferences.getInt(Constant.XMPP_PORT,  
    388.                 getResources().getInteger(R.integer.xmpp_port)));  
    389.         loginConfig.setUsername(preferences.getString(Constant.USERNAME, null));  
    390.         loginConfig.setPassword(preferences.getString(Constant.PASSWORD, null));  
    391.         loginConfig.setXmppServiceName(preferences.getString(  
    392.                 Constant.XMPP_SEIVICE_NAME,  
    393.                 getResources().getString(R.string.xmpp_service_name)));  
    394.         loginConfig.setAutoLogin(preferences.getBoolean(Constant.IS_AUTOLOGIN,  
    395.                 getResources().getBoolean(R.bool.is_autologin)));  
    396.         loginConfig.setNovisible(preferences.getBoolean(Constant.IS_NOVISIBLE,  
    397.                 getResources().getBoolean(R.bool.is_novisible)));  
    398.         loginConfig.setRemember(preferences.getBoolean(Constant.IS_REMEMBER,  
    399.                 getResources().getBoolean(R.bool.is_remember)));  
    400.         loginConfig.setFirstStart(preferences.getBoolean(  
    401.                 Constant.IS_FIRSTSTART, true));  
    402.         return loginConfig;  
    403.     }  
    404.   
    405.     @Override  
    406.     public boolean getUserOnlineState() {  
    407.         // preferences = getSharedPreferences(Constant.LOGIN_SET,0);  
    408.         return preferences.getBoolean(Constant.IS_ONLINE, true);  
    409.     }  
    410.   
    411.     @Override  
    412.     public void setUserOnlineState(boolean isOnline) {  
    413.         // preferences = getSharedPreferences(Constant.LOGIN_SET,0);  
    414.         preferences.edit().putBoolean(Constant.IS_ONLINE, isOnline).commit();  
    415.   
    416.     }  
    417.   
    418.     @Override  
    419.     public EimApplication getEimApplication() {  
    420.         return eimApplication;  
    421.     }  
    422. }  

    大家写android程序会发现,不同的activity之间经常需要调用一些公共的资源,这里的资源不仅包括android自身的,还有我们自己的管理服务类,甚至相互之间传递一些参数,这里我仿照struts2的设计,提炼出一个ActivitySupport类,同时抽取一个接口,让所有的Activity都集成这个类,因为有了接口,我们便可以采用回调模式,非常方便的传递数据和使用公共的资源,这种好处相信大家使用之后都能有深刻的体会,通过接口回调传递参数和相互调用的方式无疑是最优雅的,spring和hibernate源码中曾经大量使用这种结构。
     
    2.SQLiteTemplate类
    [java] view plaincopy
     
    1. package csdn.shimiso.eim.db;  
    2.   
    3. import java.util.ArrayList;  
    4. import java.util.List;  
    5.   
    6. import android.content.ContentValues;  
    7. import android.database.Cursor;  
    8. import android.database.sqlite.SQLiteDatabase;  
    9.   
    10. /** 
    11.  * SQLite数据库模板工具类 
    12.  *  
    13.  * 该类提供了数据库操作常用的增删改查,以及各种复杂条件匹配,分页,排序等操作 
    14.  *  
    15.  * @see SQLiteDatabase 
    16.  */  
    17. public class SQLiteTemplate {  
    18.     /** 
    19.      * Default Primary key 
    20.      */  
    21.     protected String mPrimaryKey = "_id";  
    22.   
    23.     /** 
    24.      * DBManager 
    25.      */  
    26.     private DBManager dBManager;  
    27.     /** 
    28.      * 是否为一个事务 
    29.      */  
    30.     private boolean isTransaction = false;  
    31.     /** 
    32.      * 数据库连接 
    33.      */  
    34.     private SQLiteDatabase dataBase = null;  
    35.   
    36.     private SQLiteTemplate() {  
    37.     }  
    38.   
    39.     private SQLiteTemplate(DBManager dBManager, boolean isTransaction) {  
    40.         this.dBManager = dBManager;  
    41.         this.isTransaction = isTransaction;  
    42.     }  
    43.   
    44.     /** 
    45.      * isTransaction 是否属于一个事务 注:一旦isTransaction设为true 
    46.      * 所有的SQLiteTemplate方法都不会自动关闭资源,需在事务成功后手动关闭 
    47.      *  
    48.      * @return 
    49.      */  
    50.     public static SQLiteTemplate getInstance(DBManager dBManager,  
    51.             boolean isTransaction) {  
    52.         return new SQLiteTemplate(dBManager, isTransaction);  
    53.     }  
    54.   
    55.     /** 
    56.      * 执行一条sql语句 
    57.      *  
    58.      * @param name 
    59.      * @param tel 
    60.      */  
    61.     public void execSQL(String sql) {  
    62.         try {  
    63.             dataBase = dBManager.openDatabase();  
    64.             dataBase.execSQL(sql);  
    65.         } catch (Exception e) {  
    66.             e.printStackTrace();  
    67.         } finally {  
    68.             if (!isTransaction) {  
    69.                 closeDatabase(null);  
    70.             }  
    71.         }  
    72.     }  
    73.   
    74.     /** 
    75.      * 执行一条sql语句 
    76.      *  
    77.      * @param name 
    78.      * @param tel 
    79.      */  
    80.     public void execSQL(String sql, Object[] bindArgs) {  
    81.         try {  
    82.             dataBase = dBManager.openDatabase();  
    83.             dataBase.execSQL(sql, bindArgs);  
    84.         } catch (Exception e) {  
    85.             e.printStackTrace();  
    86.         } finally {  
    87.             if (!isTransaction) {  
    88.                 closeDatabase(null);  
    89.             }  
    90.         }  
    91.     }  
    92.   
    93.     /** 
    94.      * 向数据库表中插入一条数据 
    95.      *  
    96.      * @param table 
    97.      *            表名 
    98.      * @param content 
    99.      *            字段值 
    100.      */  
    101.     public long insert(String table, ContentValues content) {  
    102.         try {  
    103.             dataBase = dBManager.openDatabase();  
    104.             // insert方法第一参数:数据库表名,第二个参数如果CONTENT为空时则向表中插入一个NULL,第三个参数为插入的内容  
    105.             return dataBase.insert(table, null, content);  
    106.         } catch (Exception e) {  
    107.             e.printStackTrace();  
    108.         } finally {  
    109.             if (!isTransaction) {  
    110.                 closeDatabase(null);  
    111.             }  
    112.         }  
    113.         return 0;  
    114.     }  
    115.   
    116.     /** 
    117.      * 批量删除指定主键数据 
    118.      *  
    119.      * @param ids 
    120.      */  
    121.     public void deleteByIds(String table, Object... primaryKeys) {  
    122.         try {  
    123.             if (primaryKeys.length > 0) {  
    124.                 StringBuilder sb = new StringBuilder();  
    125.                 for (@SuppressWarnings("unused")  
    126.                 Object id : primaryKeys) {  
    127.                     sb.append("?").append(",");  
    128.                 }  
    129.                 sb.deleteCharAt(sb.length() - 1);  
    130.                 dataBase = dBManager.openDatabase();  
    131.                 dataBase.execSQL("delete from " + table + " where "  
    132.                         + mPrimaryKey + " in(" + sb + ")",  
    133.                         (Object[]) primaryKeys);  
    134.             }  
    135.         } catch (Exception e) {  
    136.             e.printStackTrace();  
    137.         } finally {  
    138.             if (!isTransaction) {  
    139.                 closeDatabase(null);  
    140.             }  
    141.         }  
    142.     }  
    143.   
    144.     /** 
    145.      * 根据某一个字段和值删除一行数据, 如 name="jack" 
    146.      *  
    147.      * @param table 
    148.      * @param field 
    149.      * @param value 
    150.      * @return 返回值大于0表示删除成功 
    151.      */  
    152.     public int deleteByField(String table, String field, String value) {  
    153.         try {  
    154.             dataBase = dBManager.openDatabase();  
    155.             return dataBase.delete(table, field + "=?", new String[] { value });  
    156.         } catch (Exception e) {  
    157.             e.printStackTrace();  
    158.         } finally {  
    159.             if (!isTransaction) {  
    160.                 closeDatabase(null);  
    161.             }  
    162.         }  
    163.         return 0;  
    164.     }  
    165.   
    166.     /** 
    167.      * 根据条件删除数据 
    168.      *  
    169.      * @param table 
    170.      *            表名 
    171.      * @param whereClause 
    172.      *            查询语句 参数采用? 
    173.      * @param whereArgs 
    174.      *            参数值 
    175.      * @return 返回值大于0表示删除成功 
    176.      */  
    177.     public int deleteByCondition(String table, String whereClause,  
    178.             String[] whereArgs) {  
    179.         try {  
    180.             dataBase = dBManager.openDatabase();  
    181.             return dataBase.delete(table, whereClause, whereArgs);  
    182.         } catch (Exception e) {  
    183.             e.printStackTrace();  
    184.         } finally {  
    185.             if (!isTransaction) {  
    186.                 closeDatabase(null);  
    187.             }  
    188.         }  
    189.         return 0;  
    190.     }  
    191.   
    192.     /** 
    193.      * 根据主键删除一行数据 
    194.      *  
    195.      * @param table 
    196.      * @param id 
    197.      * @return 返回值大于0表示删除成功 
    198.      */  
    199.     public int deleteById(String table, String id) {  
    200.         try {  
    201.             dataBase = dBManager.openDatabase();  
    202.             return deleteByField(table, mPrimaryKey, id);  
    203.         } catch (Exception e) {  
    204.             e.printStackTrace();  
    205.         } finally {  
    206.             if (!isTransaction) {  
    207.                 closeDatabase(null);  
    208.             }  
    209.         }  
    210.         return 0;  
    211.     }  
    212.   
    213.     /** 
    214.      * 根据主键更新一行数据 
    215.      *  
    216.      * @param table 
    217.      * @param id 
    218.      * @param values 
    219.      * @return 返回值大于0表示更新成功 
    220.      */  
    221.     public int updateById(String table, String id, ContentValues values) {  
    222.         try {  
    223.             dataBase = dBManager.openDatabase();  
    224.             return dataBase.update(table, values, mPrimaryKey + "=?",  
    225.                     new String[] { id });  
    226.         } catch (Exception e) {  
    227.             e.printStackTrace();  
    228.         } finally {  
    229.             if (!isTransaction) {  
    230.                 closeDatabase(null);  
    231.             }  
    232.         }  
    233.         return 0;  
    234.     }  
    235.   
    236.     /** 
    237.      * 更新数据 
    238.      *  
    239.      * @param table 
    240.      * @param values 
    241.      * @param whereClause 
    242.      * @param whereArgs 
    243.      * @return 返回值大于0表示更新成功 
    244.      */  
    245.     public int update(String table, ContentValues values, String whereClause,  
    246.             String[] whereArgs) {  
    247.         try {  
    248.             dataBase = dBManager.openDatabase();  
    249.             return dataBase.update(table, values, whereClause, whereArgs);  
    250.         } catch (Exception e) {  
    251.             e.printStackTrace();  
    252.         } finally {  
    253.             if (!isTransaction) {  
    254.                 closeDatabase(null);  
    255.             }  
    256.         }  
    257.         return 0;  
    258.     }  
    259.   
    260.     /** 
    261.      * 根据主键查看某条数据是否存在 
    262.      *  
    263.      * @param table 
    264.      * @param id 
    265.      * @return 
    266.      */  
    267.     public Boolean isExistsById(String table, String id) {  
    268.         try {  
    269.             dataBase = dBManager.openDatabase();  
    270.             return isExistsByField(table, mPrimaryKey, id);  
    271.         } catch (Exception e) {  
    272.             e.printStackTrace();  
    273.         } finally {  
    274.             if (!isTransaction) {  
    275.                 closeDatabase(null);  
    276.             }  
    277.         }  
    278.         return null;  
    279.     }  
    280.   
    281.     /** 
    282.      * 根据某字段/值查看某条数据是否存在 
    283.      *  
    284.      * @param status 
    285.      * @return 
    286.      */  
    287.     public Boolean isExistsByField(String table, String field, String value) {  
    288.         StringBuilder sql = new StringBuilder();  
    289.         sql.append("SELECT COUNT(*) FROM ").append(table).append(" WHERE ")  
    290.                 .append(field).append(" =?");  
    291.         try {  
    292.             dataBase = dBManager.openDatabase();  
    293.             return isExistsBySQL(sql.toString(), new String[] { value });  
    294.         } catch (Exception e) {  
    295.             e.printStackTrace();  
    296.         } finally {  
    297.             if (!isTransaction) {  
    298.                 closeDatabase(null);  
    299.             }  
    300.         }  
    301.         return null;  
    302.     }  
    303.   
    304.     /** 
    305.      * 使用SQL语句查看某条数据是否存在 
    306.      *  
    307.      * @param sql 
    308.      * @param selectionArgs 
    309.      * @return 
    310.      */  
    311.     public Boolean isExistsBySQL(String sql, String[] selectionArgs) {  
    312.         Cursor cursor = null;  
    313.         try {  
    314.             dataBase = dBManager.openDatabase();  
    315.             cursor = dataBase.rawQuery(sql, selectionArgs);  
    316.             if (cursor.moveToFirst()) {  
    317.                 return (cursor.getInt(0) > 0);  
    318.             } else {  
    319.                 return false;  
    320.             }  
    321.         } catch (Exception e) {  
    322.             e.printStackTrace();  
    323.         } finally {  
    324.             if (!isTransaction) {  
    325.                 closeDatabase(cursor);  
    326.             }  
    327.         }  
    328.         return null;  
    329.     }  
    330.   
    331.     /** 
    332.      * 查询一条数据 
    333.      *  
    334.      * @param rowMapper 
    335.      * @param sql 
    336.      * @param args 
    337.      * @return 
    338.      */  
    339.     public <T> T queryForObject(RowMapper<T> rowMapper, String sql,  
    340.             String[] args) {  
    341.         Cursor cursor = null;  
    342.         T object = null;  
    343.         try {  
    344.             dataBase = dBManager.openDatabase();  
    345.             cursor = dataBase.rawQuery(sql, args);  
    346.             if (cursor.moveToFirst()) {  
    347.                 object = rowMapper.mapRow(cursor, cursor.getCount());  
    348.             }  
    349.         } finally {  
    350.             if (!isTransaction) {  
    351.                 closeDatabase(cursor);  
    352.             }  
    353.         }  
    354.         return object;  
    355.   
    356.     }  
    357.   
    358.     /** 
    359.      * 查询 
    360.      *  
    361.      * @param rowMapper 
    362.      * @param sql 
    363.      * @param startResult 
    364.      *            开始索引 注:第一条记录索引为0 
    365.      * @param maxResult 
    366.      *            步长 
    367.      * @return 
    368.      */  
    369.     public <T> List<T> queryForList(RowMapper<T> rowMapper, String sql,  
    370.             String[] selectionArgs) {  
    371.         Cursor cursor = null;  
    372.         List<T> list = null;  
    373.         try {  
    374.             dataBase = dBManager.openDatabase();  
    375.             cursor = dataBase.rawQuery(sql, selectionArgs);  
    376.             list = new ArrayList<T>();  
    377.             while (cursor.moveToNext()) {  
    378.                 list.add(rowMapper.mapRow(cursor, cursor.getPosition()));  
    379.             }  
    380.         } finally {  
    381.             if (!isTransaction) {  
    382.                 closeDatabase(cursor);  
    383.             }  
    384.         }  
    385.         return list;  
    386.     }  
    387.   
    388.     /** 
    389.      * 分页查询 
    390.      *  
    391.      * @param rowMapper 
    392.      * @param sql 
    393.      * @param startResult 
    394.      *            开始索引 注:第一条记录索引为0 
    395.      * @param maxResult 
    396.      *            步长 
    397.      * @return 
    398.      */  
    399.     public <T> List<T> queryForList(RowMapper<T> rowMapper, String sql,  
    400.             int startResult, int maxResult) {  
    401.         Cursor cursor = null;  
    402.         List<T> list = null;  
    403.         try {  
    404.             dataBase = dBManager.openDatabase();  
    405.             cursor = dataBase.rawQuery(sql + " limit ?,?", new String[] {  
    406.                     String.valueOf(startResult), String.valueOf(maxResult) });  
    407.             list = new ArrayList<T>();  
    408.             while (cursor.moveToNext()) {  
    409.                 list.add(rowMapper.mapRow(cursor, cursor.getPosition()));  
    410.             }  
    411.         } finally {  
    412.             if (!isTransaction) {  
    413.                 closeDatabase(cursor);  
    414.             }  
    415.         }  
    416.         return list;  
    417.     }  
    418.   
    419.     /** 
    420.      * 获取记录数 
    421.      *  
    422.      * @return 
    423.      */  
    424.     public Integer getCount(String sql, String[] args) {  
    425.         Cursor cursor = null;  
    426.         try {  
    427.             dataBase = dBManager.openDatabase();  
    428.             cursor = dataBase.rawQuery("select count(*) from (" + sql + ")",  
    429.                     args);  
    430.             if (cursor.moveToNext()) {  
    431.                 return cursor.getInt(0);  
    432.             }  
    433.         } catch (Exception e) {  
    434.             e.printStackTrace();  
    435.         } finally {  
    436.             if (!isTransaction) {  
    437.                 closeDatabase(cursor);  
    438.             }  
    439.         }  
    440.         return 0;  
    441.     }  
    442.   
    443.     /** 
    444.      * 分页查询 
    445.      *  
    446.      * @param rowMapper 
    447.      * @param table 
    448.      *            检索的表 
    449.      * @param columns 
    450.      *            由需要返回列的列名所组成的字符串数组,传入null会返回所有的列。 
    451.      * @param selection 
    452.      *            查询条件子句,相当于select语句where关键字后面的部分,在条件子句允许使用占位符"?" 
    453.      * @param selectionArgs 
    454.      *            对应于selection语句中占位符的值,值在数组中的位置与占位符在语句中的位置必须一致,否则就会有异常 
    455.      * @param groupBy 
    456.      *            对结果集进行分组的group by语句(不包括GROUP BY关键字)。传入null将不对结果集进行分组 
    457.      * @param having 
    458.      *            对查询后的结果集进行过滤,传入null则不过滤 
    459.      * @param orderBy 
    460.      *            对结果集进行排序的order by语句(不包括ORDER BY关键字)。传入null将对结果集使用默认的排序 
    461.      * @param limit 
    462.      *            指定偏移量和获取的记录数,相当于select语句limit关键字后面的部分,如果为null则返回所有行 
    463.      * @return 
    464.      */  
    465.     public <T> List<T> queryForList(RowMapper<T> rowMapper, String table,  
    466.             String[] columns, String selection, String[] selectionArgs,  
    467.             String groupBy, String having, String orderBy, String limit) {  
    468.         List<T> list = null;  
    469.         Cursor cursor = null;  
    470.         try {  
    471.             dataBase = dBManager.openDatabase();  
    472.             cursor = dataBase.query(table, columns, selection, selectionArgs,  
    473.                     groupBy, having, orderBy, limit);  
    474.             list = new ArrayList<T>();  
    475.             while (cursor.moveToNext()) {  
    476.                 list.add(rowMapper.mapRow(cursor, cursor.getPosition()));  
    477.             }  
    478.         } finally {  
    479.             if (!isTransaction) {  
    480.                 closeDatabase(cursor);  
    481.             }  
    482.         }  
    483.         return list;  
    484.     }  
    485.   
    486.     /** 
    487.      * Get Primary Key 
    488.      *  
    489.      * @return 
    490.      */  
    491.     public String getPrimaryKey() {  
    492.         return mPrimaryKey;  
    493.     }  
    494.   
    495.     /** 
    496.      * Set Primary Key 
    497.      *  
    498.      * @param primaryKey 
    499.      */  
    500.     public void setPrimaryKey(String primaryKey) {  
    501.         this.mPrimaryKey = primaryKey;  
    502.     }  
    503.   
    504.     /** 
    505.      *  
    506.      * @author shimiso 
    507.      *  
    508.      * @param <T> 
    509.      */  
    510.     public interface RowMapper<T> {  
    511.         /** 
    512.          *  
    513.          * @param cursor 
    514.          *            游标 
    515.          * @param index 
    516.          *            下标索引 
    517.          * @return 
    518.          */  
    519.         public T mapRow(Cursor cursor, int index);  
    520.     }  
    521.   
    522.     /** 
    523.      * 关闭数据库 
    524.      */  
    525.     public void closeDatabase(Cursor cursor) {  
    526.         if (null != dataBase) {  
    527.             dataBase.close();  
    528.         }  
    529.         if (null != cursor) {  
    530.             cursor.close();  
    531.         }  
    532.     }  
    533. }  
     
    我们希望在android操作数据库是优雅的一种方式,这里不必关注事务,也不用担心分页,更不用为了封装传递对象烦恼,总之一切就像面向对象那样,简单,模板类的出现正是解决这个问题,虽然它看上去可能不是那么完美有待提高,这里我封装了很多sqlite常用的工具,大家可以借鉴使用。
     
    3.XmppConnectionManager管理类
    [java] view plaincopy
     
    1. package csdn.shimiso.eim.manager;  
    2.   
    3. import org.jivesoftware.smack.Connection;  
    4. import org.jivesoftware.smack.ConnectionConfiguration;  
    5. import org.jivesoftware.smack.Roster;  
    6. import org.jivesoftware.smack.XMPPConnection;  
    7. import org.jivesoftware.smack.provider.ProviderManager;  
    8. import org.jivesoftware.smackx.GroupChatInvitation;  
    9. import org.jivesoftware.smackx.PrivateDataManager;  
    10. import org.jivesoftware.smackx.packet.ChatStateExtension;  
    11. import org.jivesoftware.smackx.packet.LastActivity;  
    12. import org.jivesoftware.smackx.packet.OfflineMessageInfo;  
    13. import org.jivesoftware.smackx.packet.OfflineMessageRequest;  
    14. import org.jivesoftware.smackx.packet.SharedGroupsInfo;  
    15. import org.jivesoftware.smackx.provider.DataFormProvider;  
    16. import org.jivesoftware.smackx.provider.DelayInformationProvider;  
    17. import org.jivesoftware.smackx.provider.DiscoverInfoProvider;  
    18. import org.jivesoftware.smackx.provider.DiscoverItemsProvider;  
    19. import org.jivesoftware.smackx.provider.MUCAdminProvider;  
    20. import org.jivesoftware.smackx.provider.MUCOwnerProvider;  
    21. import org.jivesoftware.smackx.provider.MUCUserProvider;  
    22. import org.jivesoftware.smackx.provider.MessageEventProvider;  
    23. import org.jivesoftware.smackx.provider.MultipleAddressesProvider;  
    24. import org.jivesoftware.smackx.provider.RosterExchangeProvider;  
    25. import org.jivesoftware.smackx.provider.StreamInitiationProvider;  
    26. import org.jivesoftware.smackx.provider.VCardProvider;  
    27. import org.jivesoftware.smackx.provider.XHTMLExtensionProvider;  
    28. import org.jivesoftware.smackx.search.UserSearch;  
    29.   
    30. import csdn.shimiso.eim.model.LoginConfig;  
    31.   
    32. /** 
    33.  *  
    34.  * XMPP服务器连接工具类. 
    35.  *  
    36.  * @author shimiso 
    37.  */  
    38. public class XmppConnectionManager {  
    39.     private XMPPConnection connection;  
    40.     private static ConnectionConfiguration connectionConfig;  
    41.     private static XmppConnectionManager xmppConnectionManager;  
    42.   
    43.     private XmppConnectionManager() {  
    44.   
    45.     }  
    46.   
    47.     public static XmppConnectionManager getInstance() {  
    48.         if (xmppConnectionManager == null) {  
    49.             xmppConnectionManager = new XmppConnectionManager();  
    50.         }  
    51.         return xmppConnectionManager;  
    52.     }  
    53.   
    54.     // init  
    55.     public XMPPConnection init(LoginConfig loginConfig) {  
    56.         Connection.DEBUG_ENABLED = false;  
    57.         ProviderManager pm = ProviderManager.getInstance();  
    58.         configure(pm);  
    59.   
    60.         connectionConfig = new ConnectionConfiguration(  
    61.                 loginConfig.getXmppHost(), loginConfig.getXmppPort(),  
    62.                 loginConfig.getXmppServiceName());  
    63.         connectionConfig.setSASLAuthenticationEnabled(false);// 不使用SASL验证,设置为false  
    64.         connectionConfig  
    65.                 .setSecurityMode(ConnectionConfiguration.SecurityMode.enabled);  
    66.         // 允许自动连接  
    67.         connectionConfig.setReconnectionAllowed(false);  
    68.         // 允许登陆成功后更新在线状态  
    69.         connectionConfig.setSendPresence(true);  
    70.         // 收到好友邀请后manual表示需要经过同意,accept_all表示不经同意自动为好友  
    71.         Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);  
    72.         connection = new XMPPConnection(connectionConfig);  
    73.         return connection;  
    74.     }  
    75.   
    76.     /** 
    77.      *  
    78.      * 返回一个有效的xmpp连接,如果无效则返回空. 
    79.      *  
    80.      * @return 
    81.      * @author shimiso 
    82.      * @update 2012-7-4 下午6:54:31 
    83.      */  
    84.     public XMPPConnection getConnection() {  
    85.         if (connection == null) {  
    86.             throw new RuntimeException("请先初始化XMPPConnection连接");  
    87.         }  
    88.         return connection;  
    89.     }  
    90.   
    91.     /** 
    92.      *  
    93.      * 销毁xmpp连接. 
    94.      *  
    95.      * @author shimiso 
    96.      * @update 2012-7-4 下午6:55:03 
    97.      */  
    98.     public void disconnect() {  
    99.         if (connection != null) {  
    100.             connection.disconnect();  
    101.         }  
    102.     }  
    103.   
    104.     public void configure(ProviderManager pm) {  
    105.   
    106.         // Private Data Storage  
    107.         pm.addIQProvider("query", "jabber:iq:private",  
    108.                 new PrivateDataManager.PrivateDataIQProvider());  
    109.   
    110.         // Time  
    111.         try {  
    112.             pm.addIQProvider("query", "jabber:iq:time",  
    113.                     Class.forName("org.jivesoftware.smackx.packet.Time"));  
    114.         } catch (ClassNotFoundException e) {  
    115.         }  
    116.   
    117.         // XHTML  
    118.         pm.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im",  
    119.                 new XHTMLExtensionProvider());  
    120.   
    121.         // Roster Exchange  
    122.         pm.addExtensionProvider("x", "jabber:x:roster",  
    123.                 new RosterExchangeProvider());  
    124.         // Message Events  
    125.         pm.addExtensionProvider("x", "jabber:x:event",  
    126.                 new MessageEventProvider());  
    127.         // Chat State  
    128.         pm.addExtensionProvider("active",  
    129.                 "http://jabber.org/protocol/chatstates",  
    130.                 new ChatStateExtension.Provider());  
    131.         pm.addExtensionProvider("composing",  
    132.                 "http://jabber.org/protocol/chatstates",  
    133.                 new ChatStateExtension.Provider());  
    134.         pm.addExtensionProvider("paused",  
    135.                 "http://jabber.org/protocol/chatstates",  
    136.                 new ChatStateExtension.Provider());  
    137.         pm.addExtensionProvider("inactive",  
    138.                 "http://jabber.org/protocol/chatstates",  
    139.                 new ChatStateExtension.Provider());  
    140.         pm.addExtensionProvider("gone",  
    141.                 "http://jabber.org/protocol/chatstates",  
    142.                 new ChatStateExtension.Provider());  
    143.   
    144.         // FileTransfer  
    145.         pm.addIQProvider("si", "http://jabber.org/protocol/si",  
    146.                 new StreamInitiationProvider());  
    147.   
    148.         // Group Chat Invitations  
    149.         pm.addExtensionProvider("x", "jabber:x:conference",  
    150.                 new GroupChatInvitation.Provider());  
    151.         // Service Discovery # Items  
    152.         pm.addIQProvider("query", "http://jabber.org/protocol/disco#items",  
    153.                 new DiscoverItemsProvider());  
    154.         // Service Discovery # Info  
    155.         pm.addIQProvider("query", "http://jabber.org/protocol/disco#info",  
    156.                 new DiscoverInfoProvider());  
    157.         // Data Forms  
    158.         pm.addExtensionProvider("x", "jabber:x:data", new DataFormProvider());  
    159.         // MUC User  
    160.         pm.addExtensionProvider("x", "http://jabber.org/protocol/muc#user",  
    161.                 new MUCUserProvider());  
    162.         // MUC Admin  
    163.         pm.addIQProvider("query", "http://jabber.org/protocol/muc#admin",  
    164.                 new MUCAdminProvider());  
    165.         // MUC Owner  
    166.         pm.addIQProvider("query", "http://jabber.org/protocol/muc#owner",  
    167.                 new MUCOwnerProvider());  
    168.         // Delayed Delivery  
    169.         pm.addExtensionProvider("x", "jabber:x:delay",  
    170.                 new DelayInformationProvider());  
    171.         // Version  
    172.         try {  
    173.             pm.addIQProvider("query", "jabber:iq:version",  
    174.                     Class.forName("org.jivesoftware.smackx.packet.Version"));  
    175.         } catch (ClassNotFoundException e) {  
    176.         }  
    177.         // VCard  
    178.         pm.addIQProvider("vCard", "vcard-temp", new VCardProvider());  
    179.         // Offline Message Requests  
    180.         pm.addIQProvider("offline", "http://jabber.org/protocol/offline",  
    181.                 new OfflineMessageRequest.Provider());  
    182.         // Offline Message Indicator  
    183.         pm.addExtensionProvider("offline",  
    184.                 "http://jabber.org/protocol/offline",  
    185.                 new OfflineMessageInfo.Provider());  
    186.         // Last Activity  
    187.         pm.addIQProvider("query", "jabber:iq:last", new LastActivity.Provider());  
    188.         // User Search  
    189.         pm.addIQProvider("query", "jabber:iq:search", new UserSearch.Provider());  
    190.         // SharedGroupsInfo  
    191.         pm.addIQProvider("sharedgroup",  
    192.                 "http://www.jivesoftware.org/protocol/sharedgroup",  
    193.                 new SharedGroupsInfo.Provider());  
    194.         // JEP-33: Extended Stanza Addressing  
    195.         pm.addExtensionProvider("addresses",  
    196.                 "http://jabber.org/protocol/address",  
    197.                 new MultipleAddressesProvider());  
    198.   
    199.     }  
    200. }  

    这个类是xmpp连接的管理类,如果大家使用smack的api对这个应该不会陌生,asmack对xmpp连接的管理,与smack的差别不大,但是部分细微区别也有,我们在使用中如果遇到问题,还要多加注意,我们这里将其设计成单例,毕竟重复创建连接是个非常消耗的过程。
     

    3.演示效果

       
     
    很像QQ吧,没错,这是2012年版本qq的安卓界面,只是界面元素一样,实现方式大不相同,下面简单列一下这个客户端实现的功能:
    1.聊天
    2.离线消息
    3.添加,删除好友
    4.添加,移动好友分组
    5.设置昵称
    6.监控好友状态
    7.网络断开系统自动重连接
    8.收到添加好友请求消息处理
    9.收到系统广播消息处理
    10.查看历史聊天记录
    11.消息弹出提醒,和小气泡
    ....
    因为时间关系不是很完美,主要用于学习研究,欢迎大家给我提bug和改进意见。
     

    4.源码下载

    分数比较大,不是为了坑大家,是怕有伸手党出现,拿了源码出去招摇撞骗,请尊重作者原创!
     
     
     
  • 相关阅读:
    【Repost】Comparision of B-Tree and LSM Tree
    字符串经典算法 Knuth-Morris-Pratt Boyer-Moore AC(Aho-Corasick)算法 后缀自动机
    【Leetcode 913】【Hard】Cat and Mouse 博弈论
    【转】初探计算机视觉的三个源头、兼谈人工智能
    MySQL--06(索引)
    MySQL--05(子查询&视图)
    MySQL--04(聚合函数&表连接查询)
    MySQL--03(增删改查&主键)
    MySQL--02
    MySQL--01
  • 原文地址:https://www.cnblogs.com/adjk/p/5126031.html
Copyright © 2020-2023  润新知