zoukankan      html  css  js  c++  java
  • 《安卓网络编程》之第六篇 Android中的WIFI和蓝牙

    关于WIFI就不多介绍啦,直接来个段子吧。

    问:“WiFi对人体有伤害么?”

    答:“不清楚,反正没有WiFi我就浑身不舒服。

    比较重要的一点就是WifiManager  wm=(WifiManager)Android_Wifi.this.getSystemService(Context.WIFI_SERVICE);

    关闭打开搜索都可以通过调用wm的相关方法实现。可能要开发wifi万能钥匙那一类的程序才用到这个吧,普通应用程序主要就识别是wifi网络还是移动网络。不多讲

    代码参考博客:http://blog.csdn.net/sbvfhp/article/details/7007090

    重点蓝牙:

     一:什么是蓝牙

        1:Bluetooth是目前使用最广泛的无线通讯协议,近距离无线通讯的标准。传说瑞典有个国王特别爱吃蓝莓导致自己的牙齿天天都是蓝色的,在他执政期间这位国王非常善于交际,能说会到,和邻国的搞得关系非常好,这个Bluetooth的发明者觉得蓝牙它的作用就是在近距离沟通周围的设备,跟这个国王很类似,于是起名叫蓝牙。

        2:主要针对短距离设备通讯(10米)

        3:无线耳机,无线鼠标,无线键盘

        

    蓝牙标志

     二:蓝牙工作流程图

        首先两个设备上都要有蓝牙设备或者专业一点叫蓝牙适配器,以手机和电脑为例我画了如下流程图。其次在手机上进行扫描,扫描周围蓝蓝牙设备,先找到手机附近的电脑,然后给它发出一个信号需要进行蓝牙的配对,再次返回一个信号说明手机和电脑已经配对成功了,最后配对成功后可以进行文件传输了。这是一个最基本的一个流程。

      三:蓝牙开发相关类

      在Android手机中开发蓝牙程序时,离不开几个类:

    BluetoothSocket:close()  connect()  getInputStream()......

    BluetoothServerSocket : accept()

    BlutoothAdapter :代表本地的蓝牙适配器设备,通过此类可以让用户能执行基本的蓝牙任务。

    BluetoothClass: 代表了一个描述设备通用特性和功能的蓝牙类。比如一个蓝牙类会指定如电话、计算机或耳机的通用设备类型。

    BluetoothClass.Service: 

    BluetoothClass.Device:

    BluetoothClass.Device.Major: 定义了主要设备类的常量

    其中与蓝牙相关的最重要的两个API

       1:BuletoothAdapter

      这个类的对象代表了本地的蓝牙适配器,相当于蓝牙工作流程图中的手机里的蓝牙适配器,也就是说比如这个应用程序是运行在手机上,那么手机上的蓝牙适配器就是本地蓝牙适配器。

       2:BuletoothDevice

       这个类的对象代表了远程的蓝牙设备,相当于蓝牙工作流程图中的计算机里的蓝牙适配器,也就是说比如这个应用程序是运行在手机上,那么BuletoothDevice代表了你要连接的远程的那个设备上面的蓝牙适配器。

     四:蓝牙开发步骤:

    此部分转载于 http://zhouyunan2010.iteye.com/blog/1186021

    从查找蓝牙设备到能够相互通信要经过几个基本步骤(本机做为服务器): 
    1.设置权限 
    在manifest中配置 

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

    2.启动蓝牙 
    首先要查看本机是否支持蓝牙,获取BluetoothAdapter蓝牙适配器对象 

    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if(mBluetoothAdapter == null){
            //表明此手机不支持蓝牙
            return;
    }
    if(!mBluetoothAdapter.isEnabled()){    //蓝牙未开启,则开启蓝牙
                Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
    }
    //......
    public void onActivityResult(int requestCode, int resultCode, Intent data){
           if(requestCode == REQUEST_ENABLE_BT){
                  if(requestCode == RESULT_OK){
                       //蓝牙已经开启 
                  }
           }
    }

    3。发现蓝牙设备 
    这里可以细分为几个方面 
    (1)使本机蓝牙处于可见(即处于易被搜索到状态),便于其他设备发现本机蓝牙 

    //使本机蓝牙在300秒内可被搜索
    private void ensureDiscoverable() {
            if (mBluetoothAdapter.getScanMode() !=
                BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
                Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
                discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
                startActivity(discoverableIntent);
            }
    }

    (2)查找已经配对的蓝牙设备,即以前已经配对过的设备 

            Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
            if (pairedDevices.size() > 0) {
                findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
                for (BluetoothDevice device : pairedDevices) {
                    //device.getName() +" "+ device.getAddress());
                }
            } else {
                mPairedDevicesArrayAdapter.add("没有找到已匹对的设备");
            }

    (3)通过mBluetoothAdapter.startDiscovery();搜索设备,要获得此搜索的结果需要注册 
    一个BroadcastReceiver来获取。先注册再获取信息,然后处理 

    //注册,当一个设备被发现时调用onReceive
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
            this.registerReceiver(mReceiver, filter);
    
    //当搜索结束后调用onReceive
    filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            this.registerReceiver(mReceiver, filter);
    //.......
    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if(BluetoothDevice.ACTION_FOUND.equals(action)){
                     BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                      // 已经配对的则跳过
                     if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                          mNewDevicesArrayAdapter.add(device.getName() + "
    " + device.getAddress());  //保存设备地址与名字
                     }
                }else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {  //搜索结束
                    if (mNewDevicesArrayAdapter.getCount() == 0) {
                        mNewDevicesArrayAdapter.add("没有搜索到设备");
                    }
                }
    
            }
    };

    4.建立连接 
    查找到设备 后,则需要建立本机与其他设备之间的连接。 
    一般用本机搜索其他蓝牙设备时,本机可以作为一个服务端,接收其他设备的连接。 
    启动一个服务器端的线程,死循环等待客户端的连接,这与ServerSocket极为相似。 
    这个线程在准备连接之前启动 

    //UUID可以看做一个端口号
    private static final UUID MY_UUID =
            UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
       //像一个服务器一样时刻监听是否有连接建立
        private class AcceptThread extends Thread{
            private BluetoothServerSocket serverSocket;
            
            public AcceptThread(boolean secure){
                BluetoothServerSocket temp = null;
                try {
                    temp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(
                                NAME_INSECURE, MY_UUID);
                } catch (IOException e) {
                      Log.e("app", "listen() failed", e);
                }
                serverSocket = temp;
            }
            
            public void run(){
                BluetoothSocket socket=null;
                while(true){
                    try {
                        socket = serverSocket.accept();
                    } catch (IOException e) {
                         Log.e("app", "accept() failed", e);
                         break;
                    }
                }
                if(socket!=null){
                    //此时可以新建一个数据交换线程,把此socket传进去
                }
            }
            
            //取消监听
            public void cancel(){    
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    Log.e("app", "Socket Type" + socketType + "close() of server failed", e);
                }
            }
    
    }
    View Code

    5.交换数据

    搜索到设备后可以获取设备的地址,通过此地址获取一个BluetoothDeviced对象,可以看做客户端,通过此对象device.createRfcommSocketToServiceRecord(MY_UUID);同一个UUID可与服务器建立连接获取另一个socket对象,由此服务端与客户端各有一个socket对象,此时 
    他们可以互相交换数据了。 
    创立客户端socket可建立线程 

        //另一个设备去连接本机,相当于客户端
        private class ConnectThread extends Thread{
            private BluetoothSocket socket;
            private BluetoothDevice device;
            public ConnectThread(BluetoothDevice device,boolean secure){
                this.device = device;
                BluetoothSocket tmp = null;
                try {
                    tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
                } catch (IOException e) {
                     Log.e("app", "create() failed", e);
                }
            }
            
            public void run(){
                mBluetoothAdapter.cancelDiscovery();    //取消设备查找
                try {
                    socket.connect();
                } catch (IOException e) {
                    try {
                        socket.close();
                    } catch (IOException e1) {
                         Log.e("app", "unable to close() "+
                                    " socket during connection failure", e1);
                    }
                    connetionFailed();    //连接失败
                    return;
                }
                //此时可以新建一个数据交换线程,把此socket传进去
            }
            
              public void cancel() {
                  try {
                      socket.close();
                  } catch (IOException e) {
                      Log.e("app", "close() of connect  socket failed", e);
                  }
              }
        }
    View Code

    6.建立数据通信线程,进行读取数据 

    //建立连接后,进行数据通信的线程
        private class ConnectedThread extends Thread{
            private BluetoothSocket socket;
            private InputStream inStream;
            private OutputStream outStream;
            
            public ConnectedThread(BluetoothSocket socket){
                
                this.socket = socket;
                try {
                    //获得输入输出流
                    inStream = socket.getInputStream();
                    outStream = socket.getOutputStream();
                } catch (IOException e) {
                    Log.e("app", "temp sockets not created", e);
                }
            }
            
            public void run(){
                byte[] buff = new byte[1024];
                int len=0;
                //读数据需不断监听,写不需要
                while(true){
                    try {
                        len = inStream.read(buff);
                        //把读取到的数据发送给UI进行显示
                        Message msg = handler.obtainMessage(BluetoothChat.MESSAGE_READ,
                                len, -1, buff);
                        msg.sendToTarget();
                    } catch (IOException e) {
                        Log.e("app", "disconnected", e);
                        connectionLost();    //失去连接
                        start();    //重新启动服务器
                        break;
                    }
                }
            }
            
            
            public void write(byte[] buffer) {
                try {
                    outStream.write(buffer);
    
                    // Share the sent message back to the UI Activity
                    handler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)
                            .sendToTarget();
                } catch (IOException e) {
                    Log.e("app", "Exception during write", e);
                }
            }
    
            public void cancel() {
                try {
                    socket.close();
                } catch (IOException e) {
                    Log.e("app", "close() of connect socket failed", e);
                }
            }
        }

    到这里,蓝牙通信的基本操作已经全部完成。 

    五:蓝牙聊天室开发

    源码参考 http://download.csdn.net/detail/mr_raptor/8033951#comment  

    此demo界面优美,实现基本功能,但不能保存聊天记录,黑屏自动断线,在此基础做出修改。

         相关知识:

         在EditText中插入表情图片 (CharacterStyle&SpannableString)

     源码分析:  

    1.  Android中pendingIntent的深入理解 

    Utils.java

     1 package cn.com.farsgiht.bluetoothdemo.utils;
     2 
     3 import android.app.Activity;
     4 import android.app.Notification;
     5 import android.app.NotificationManager;
     6 import android.app.PendingIntent;
     7 import android.content.Context;
     8 import android.content.Intent;
     9 import cn.com.farsgiht.bluetoothdemo.R;
    10 
    11 public class Utils {
    12     public static final int NOTIFY_ID1 = 1001;
    13     
    14     public static void notifyMessage(Context context, String msg, Activity activity){
    15         //Notification builder; 
    16         PendingIntent contentIntent = null; 
    17         NotificationManager nm;
    18         // 发送通知需要用到NotificationManager对象 
    19         nm = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
    20         // 消息对象
    21         Intent notificationIntent = new Intent(context, activity.getClass());
    22         // PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)
    23         // 用来获得一个挂起的PendingIntent,让该Intent去启动新的Activity来处理通知
    24         contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); 
    25 
    26         
    27         //定义通知栏展现的内容信息
    28         int icon = R.drawable.icon;
    29         long when = System.currentTimeMillis();
    30         Notification notification = new Notification(icon, msg, when);
    31         notification.defaults |= Notification.DEFAULT_VIBRATE;
    32         notification.defaults |= Notification.DEFAULT_SOUND; // 调用系统自带声音  
    33         notification.flags |= Notification.FLAG_AUTO_CANCEL; // 点击清除按钮或点击通知后会自动消失
    34         notification.defaults |= Notification.DEFAULT_LIGHTS;
    35         notification.vibrate = new long[]{300, 500};
    36         notification.setLatestEventInfo(context, "BluetoothChat", msg, contentIntent);
    37        /* // 定制我们要在状态栏显示的通知样式
    38         builder = new Notification(context);
    39         builder.setContentIntent(contentIntent)
    40              .setSmallIcon(R.drawable.ic_launcher)//设置状态栏里面的图标(小图标)                     .setLargeIcon(BitmapFactory.decodeResource(res, R.drawable.i5))//下拉下拉列表里面的图标(大图标)        .setTicker("this is bitch!") //设置状态栏的显示的信息
    41              .setWhen(System.currentTimeMillis())//设置时间发生时间
    42              .setAutoCancel(true)//设置可以清除
    43              .setContentTitle("This is ContentTitle")//设置下拉列表里的标题
    44              .setContentText("this is ContentText");//设置上下文内容
    45         */        
    46         // 获得刚才创建的通知对象
    47         // Notification notification = builder.getNotification();//获取一个Notification
    48          
    49          // 通过NotificationManger来发送通知消息
    50          // 参数1通知的ID,参数2发送哪个通知
    51          nm.notify(NOTIFY_ID1, notification);
    52     }
    53 }
    View Code

    通知栏相关代码

         2. PageIndicatorView.java

     1 package cn.com.farsgiht.bluetoothdemo.UI;
     2 
     3 import cn.com.farsgiht.bluetoothdemo.R;
     4 import android.content.Context;
     5 import android.graphics.Bitmap;
     6 import android.graphics.BitmapFactory;
     7 import android.widget.ImageView;
     8 import android.widget.LinearLayout;
     9 
    10 public class PageIndicatorView extends ImageView {
    11     private final String TAG = "PageIndicatorView";
    12     private LinearLayout mPageIndicLayout;
    13 
    14     public PageIndicatorView(Context context) {
    15         super(context);
    16         setSelectedView(false);
    17     }
    18     
    19     public void setSelectedView(boolean selected){
    20         Bitmap bitmap;
    21         if(selected){
    22             bitmap = BitmapFactory.decodeResource(getResources(),
    23                 R.drawable.page_select);
    24         }else{
    25             bitmap = BitmapFactory.decodeResource(getResources(),
    26                     R.drawable.page_item);
    27         }
    28         this.setImageBitmap(bitmap);
    29     }
    30 }
    View Code

            表情页页面指示器

      3. DrawerHScrollView.java

      1 package cn.com.farsgiht.bluetoothdemo.UI;
      2 
      3 import java.util.Hashtable;
      4 
      5 import android.content.Context;
      6 import android.util.AttributeSet;
      7 import android.util.Log;
      8 import android.view.Gravity;
      9 import android.view.ViewGroup;
     10 import android.view.ViewParent;
     11 import android.widget.HorizontalScrollView;
     12 import android.widget.LinearLayout;
     13 
     14 public class DrawerHScrollView extends HorizontalScrollView {
     15     private static final String TAG = "DrawerHScrollView";
     16     
     17     private int currentPage = 0;
     18     private int totalPages = 1;
     19     private static Hashtable<Integer, Integer> positionLeftTopOfPages = new Hashtable();
     20     private LinearLayout mPageIndicLayout;
     21     private Context mContext;
     22 
     23     public DrawerHScrollView(Context context) {
     24         super(context);
     25         this.mContext = context;
     26     }
     27 
     28     public DrawerHScrollView(Context context, AttributeSet attrs) {
     29         super(context, attrs);
     30         this.mContext = context;
     31     }
     32 
     33     public DrawerHScrollView(Context context, AttributeSet attrs, int defStyle) {
     34         super(context, attrs, defStyle);
     35         this.mContext = context;
     36     }
     37     
     38     public void cleanup(){
     39         currentPage = 0;
     40         totalPages = 1;
     41         if(positionLeftTopOfPages != null){
     42             positionLeftTopOfPages.clear();
     43         }
     44     }
     45     
     46     public void setParameters(int totalPages, int currentPage, int scrollDisX, int space) {
     47         Log.d(TAG, "~~~~~setParameters totalPages:"+totalPages +",currentPage:"+ currentPage +",scrollDisX:"+scrollDisX);
     48         this.totalPages = totalPages;
     49         this.currentPage = currentPage;
     50         positionLeftTopOfPages.clear();
     51         for (int i = 0;i < totalPages;i++){
     52             int posx = (scrollDisX) * i - space;
     53             positionLeftTopOfPages.put(i, posx);
     54             Log.d(TAG, "~~~~~setParameters i:"+i +",posx:"+posx);
     55         }
     56         smoothScrollTo(0, 0);
     57         setPageIndicLayout();
     58         if(mPageIndicLayout != null){
     59             updateDrawerPageLayout(totalPages, currentPage);
     60         }
     61     }
     62     
     63     public void setPageIndicLayout(){
     64         // 添加表情多页图标布局
     65         mPageIndicLayout = new LinearLayout(mContext);
     66         mPageIndicLayout.setGravity(Gravity.CENTER);
     67         ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
     68         mPageIndicLayout.setLayoutParams(params);
     69         ViewParent parent = this.getParent();
     70         if(parent instanceof LinearLayout){
     71             LinearLayout layout = (LinearLayout)parent;
     72             layout.addView(mPageIndicLayout, new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
     73         }
     74     }
     75 
     76     @Override
     77     public void fling(int velocityX) {
     78         Log.v(TAG, "-->fling velocityX:"+velocityX);
     79         boolean change_flag = false;
     80         if (velocityX > 0 && (currentPage < totalPages - 1)){
     81             currentPage++;
     82             change_flag = true;
     83         } else if (velocityX < 0 && (currentPage > 0)){
     84             currentPage--;
     85             change_flag = true;
     86         }
     87         if (change_flag){
     88             int postionTo = (Integer)positionLeftTopOfPages.get(new Integer(currentPage)).intValue();
     89             Log.v(TAG, "------smoothScrollTo posx:"+postionTo);
     90             smoothScrollTo(postionTo, 0);
     91             updateDrawerPageLayout(totalPages, currentPage);
     92         }
     93         //super.fling(velocityX);
     94     }
     95     
     96     public void updateDrawerPageLayout(int total_pages, int sel_page) {
     97         Log.e(TAG, "~~~updateBooksPageLayout total_pages:" + total_pages
     98                 + ",sel_page:" + sel_page);
     99         mPageIndicLayout.removeAllViews();
    100         if (total_pages <= 0 || sel_page < 0 || sel_page >= total_pages) {
    101             Log.e(TAG, "total_pages or sel_page is outofrange.");
    102             return;
    103         }
    104         for (int i = 0; i < total_pages; i++) {
    105             if (i != 0) {
    106                 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
    107                         LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    108                 params.setMargins(5, 0, 0, 0);
    109                 mPageIndicLayout.addView(new PageIndicatorView(mContext), params);
    110             } else {
    111                 mPageIndicLayout.addView(new PageIndicatorView(mContext));
    112             }
    113         }
    114         PageIndicatorView selItem = (PageIndicatorView) mPageIndicLayout
    115                 .getChildAt(sel_page);
    116         selItem.setSelectedView(true);
    117     }
    118 }
    View Code

        定制的表情页面,继承HorizontalScrollView,支持水平滑动。需要说明的是页面指示器的布局是通过代码实现的,而不是在xml文件中

      4. ChatListViewAdapter.java

        消息适配器。发送的消息存放在mDatalist中

               在activity_chat.xml设置android:listSelector="#00FFFFFF"可以避免点击消息时出现的矩形框。

               重写的getView中实现自己发送的消息显示在右边,别人发送的消息显示在左边。

        TouchListener实现触摸消息变成密码形态,并在onclick方法中再次调用。我想大概是因为触摸和点击本来就不好区分吧。同时在点击事件里不要忘了设置消息显示的形态,因为listview在重绘时需要考虑这一点。

        Adapter的作用就是ListView界面与数据之间的桥梁,当列表里的每一项显示到页面时,都会调用Adapter的getView方法返回一个View。 


    优化的思路两种: 

    1. View的重用 
        View的每次创建是比较耗时的,因此对于getview方法传入的convertView应充分利用 != null的判断 

    2.ViewHolder的应用 

    View的findViewById()方法也是比较耗时的,因此需要考虑只调用一次,ViewHolder就是一个持有者的类,他里面一般没有方法,只有属性,作用就是一个临时的储存器,把你getView方法中每次返回的View存起来,可以下次再用。这样做的好处就是不必每次都到布局文件中去拿到你的View,提高了效率。

       5. Task.java定义了一些常量

       6. TaskService.java  在Service中新开线程和直接新开线程的区别与意义

         前面写到的蓝牙通信的几步走也是在这里面实现的

    分为几个线程:

    AcceptThread 等待客户端连接线程 

    ConnectThread(BluetoothDevice device) 作为客户端连接指定的蓝牙设备线程

    ConnectedThread(BluetoothSocket socket)  在客户端,使用一个单独的BluetoothSocket类去初始化一个外接连接和管理该连接,发送消息也在这个线程里面

    TaskThread总线程和mTaskList一起协调上面三个线程的运行工作。

    要搞清TaskServic的原理,理清两个变量很重要:mServiceHandler  mActivityHandler

    mServiceHandler:监控着连接状态的变化,如断线,连接成功,连接中

    mActivityHandler:监控着信息状态的变化,起到通知主Activity的作用。如:发消息,收消息。

    7. SoundEffect.java 

        音效相关的类,实现了一个OnLoadCompleteListener接口,还有一个play() 方法,决定播放哪一首歌曲。

    8. DataProtocol 和 Message

      DataProtocol 是对不同的消息类型进行打包和解包

      Message 定义了消息的几个参数

    9. SelectDevice  DownloadActivity   ChatActivity 是应用程序里面的三个Activity

         保存和恢复activity的状态数据

     自己修改说明:

     ChatActivity加入再按一次退出功能,更加人性化。加入消息记录功能。

    消息记录实现概述:当在服务中开始收发发送消息时,就通过mActivityHandler通知ChatActivity,完成数据库读写操作。

    ChatActivity.java:

      1 package cn.com.farsgiht.bluetoothdemo;
      2 
      3 import java.text.SimpleDateFormat;
      4 import java.util.ArrayList;
      5 import java.util.HashMap;
      6 import android.app.Activity;
      7 import android.app.AlertDialog;
      8 import android.bluetooth.BluetoothAdapter;
      9 import android.bluetooth.BluetoothDevice;
     10 import android.content.ContentValues;
     11 import android.content.Context;
     12 import android.content.DialogInterface;
     13 import android.content.DialogInterface.OnClickListener;
     14 import android.content.Intent;
     15 import android.database.sqlite.SQLiteDatabase;
     16 import android.graphics.Bitmap;
     17 import android.graphics.BitmapFactory;
     18 import android.os.Bundle;
     19 import android.os.Handler;
     20 import android.os.Message;
     21 import android.text.Spannable;
     22 import android.text.SpannableString;
     23 import android.text.style.ImageSpan;
     24 import android.util.DisplayMetrics;
     25 import android.util.Log;
     26 import android.view.Display;
     27 import android.view.Gravity;
     28 import android.view.KeyEvent;
     29 import android.view.LayoutInflater;
     30 import android.view.Menu;
     31 import android.view.MenuInflater;
     32 import android.view.MenuItem;
     33 import android.view.View;
     34 import android.view.ViewGroup;
     35 import android.view.inputmethod.InputMethodManager;
     36 import android.widget.AdapterView;
     37 import android.widget.AdapterView.OnItemClickListener;
     38 import android.widget.Button;
     39 import android.widget.EditText;
     40 import android.widget.GridView;
     41 import android.widget.ImageView;
     42 import android.widget.LinearLayout;
     43 import android.widget.LinearLayout.LayoutParams;
     44 import android.widget.ListView;
     45 import android.widget.SimpleAdapter;
     46 import android.widget.Toast;
     47 import cn.com.farsgiht.bluetoothdemo.UI.ChatListViewAdapter;
     48 import cn.com.farsgiht.bluetoothdemo.UI.DrawerHScrollView;
     49 import cn.com.farsgiht.bluetoothdemo.sound.SoundEffect;
     50 import cn.com.farsgiht.bluetoothdemo.task.Task;
     51 import cn.com.farsgiht.bluetoothdemo.task.TaskService;
     52 import cn.com.farsgiht.bluetoothdemo.utils.Utils;
     53 
     54 public class ChatActivity extends Activity implements View.OnClickListener{
     55     private final String TAG = "ChatActivity";
     56     public static int sAliveCount = 0;
     57     public static final String EXTRA_MESSAGER = "cn.com.farsgiht.bluetoothdemo.BUNDLE";
     58     public static final String DEVICE_NAME = "device_name";
     59     // 蓝牙状态变量
     60     private static int sBTState = -1;
     61     
     62     private final int REQUES_BT_ENABLE_CODE = 123;
     63     private final int REQUES_SELECT_BT_CODE = 222;
     64     
     65     private ListView mList;
     66     private EditText mInput;
     67     private Button mSendBtn;
     68     private ImageView mEmoButton;
     69     private GridView mGridView;
     70     private boolean isUpdate = false;
     71     private BluetoothDevice mRemoteDevice;
     72     
     73     private LinearLayout mRootLayout, mChatLayout;
     74     
     75     private View mEmoView;
     76     private boolean isShowEmo = false;
     77     private boolean isHaspressed = false;
     78     private int mScrollHeight;
     79     
     80     private DrawerHScrollView mScrollView;
     81     private ChatListViewAdapter mAdapter2;
     82     private ArrayList<HashMap<String, Object>> mChatContent2 = new ArrayList<HashMap<String, Object>>();
     83     private BluetoothAdapter mBluetoothAdapter;
     84     
     85     private ArrayList<HashMap<String, Object>> mEmoList = new ArrayList<HashMap<String, Object>>();
     86      // 已连接设备的名字
     87     private String mConnectedDeviceName = null;
     88     
     89     private Handler mHandler = new Handler(){
     90         @Override
     91         public void handleMessage(Message msg) {
     92             //设置聊天信息的时间
     93             SimpleDateFormat df0 = new SimpleDateFormat("MM-dd HH:mm:ss");
     94             String pdate=df0.format(System.currentTimeMillis()).toString();            
     95             switch(msg.what){
     96             case -1:
     97                 showToast("没有连接其它用户,点击"Menu"扫描并选择周国用户");
     98                 SoundEffect.getInstance(ChatActivity.this).play(SoundEffect.SOUND_ERR);
     99                 break;
    100             case Task.TASK_SEND_MSG:
    101 //               showToast(msg.obj.toString());
    102                String writeMessage = msg.obj.toString();
    103                if(writeMessage!=null && isHaspressed)
    104                {
    105                    //将发送的信息插入到数据库
    106                    ContentValues values=new ContentValues();
    107                    values.put("name", "我");
    108                    values.put("pdate",pdate);
    109                    values.put("informations", writeMessage);
    110                    //创建数据库
    111                    DatabaseHelper insertdbHelper=new DatabaseHelper(ChatActivity.this,"zhsf_db");
    112                    SQLiteDatabase insertdb=insertdbHelper.getWritableDatabase();
    113                    insertdb.insert("info", null, values);
    114                }
    115                 if(sAliveCount <= 0){
    116                     Utils.notifyMessage(ChatActivity.this, msg.obj.toString(), ChatActivity.this);
    117                 }              
    118                 break;    
    119             case Task.TASK_RECV_MSG:    
    120                String readMessage =((HashMap<String,Object>) msg.obj).get(ChatListViewAdapter.KEY_TEXT).toString();
    121                mConnectedDeviceName = ((HashMap<String,Object>) msg.obj).get(ChatListViewAdapter.KEY_NAME).toString(); 
    122                if(readMessage!=null)
    123                {
    124                    //将接受的信息插入到数据库
    125                    ContentValues values2=new ContentValues();
    126                    values2.put("name", mConnectedDeviceName);
    127                    values2.put("pdate",pdate);
    128                    values2.put("informations", readMessage);
    129                    DatabaseHelper insertdbHelper2=new DatabaseHelper(ChatActivity.this,"zhsf_db");
    130                    SQLiteDatabase insertdb2=insertdbHelper2.getWritableDatabase();
    131                    insertdb2.insert("info", null, values2);         
    132                }
    133                 
    134                 if(msg.obj == null)
    135                     return;
    136                 if(msg.obj instanceof HashMap<?, ?>){
    137                     showTargetMessage((HashMap<String, Object>) msg.obj);                    
    138                 }        
    139                 if(sAliveCount <= 0){
    140                     Utils.notifyMessage(ChatActivity.this, "您有未读取消息", ChatActivity.this);
    141                 }                              
    142                 break;
    143             case Task.TASK_GET_REMOTE_STATE:
    144                 setTitle((String)msg.obj);
    145                 if(sAliveCount <= 0){
    146                     if(isBTStateChanged(msg.arg1) && msg.arg1 != TaskService.BT_STAT_WAIT)
    147                         Utils.notifyMessage(ChatActivity.this, (String)msg.obj, ChatActivity.this);
    148                 }
    149                 break;
    150     
    151             }
    152         }
    153     };
    154     
    155     @Override
    156     protected void onCreate(Bundle savedInstanceState) {
    157         super.onCreate(savedInstanceState);
    158         setContentView(R.layout.activity_chat);
    159         
    160         // 获得蓝牙管理器
    161         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 
    162         if (mBluetoothAdapter == null) {
    163             Log.e(TAG, "Your device is not support Bluetooth!");
    164             Toast.makeText(this, "该设备没有蓝牙设备", Toast.LENGTH_LONG).show();
    165             return;
    166         }
    167         
    168         mRootLayout = (LinearLayout) findViewById(R.id.root);
    169         mChatLayout = (LinearLayout) findViewById(R.id.topPanel);
    170         mList = (ListView) findViewById(R.id.listView1);
    171     
    172         mAdapter2 = new ChatListViewAdapter(this, mChatContent2);
    173         
    174         mList.setAdapter(mAdapter2);
    175 
    176         // 初始化表情
    177         mEmoView = initEmoView();
    178         
    179         mInput = (EditText) findViewById(R.id.inputEdit);
    180         mInput.setOnClickListener(new android.view.View.OnClickListener() {
    181             @Override
    182             public void onClick(View v) {
    183                 // 点击输入框后,隐藏表情,显示输入法
    184                 InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 
    185                 imm.showSoftInput(mInput, 0);
    186                 showEmoPanel(false);
    187             }
    188         });
    189         
    190         mSendBtn = (Button) findViewById(R.id.sendBtn);
    191         mEmoButton = (ImageView) findViewById(R.id.emotionBtn);
    192         
    193         mSendBtn.setOnClickListener(this);
    194         mEmoButton.setOnClickListener(this);
    195         
    196         //---------------------------------------------------------------------
    197         // 打开蓝牙设备
    198         if (!mBluetoothAdapter.isEnabled()) {
    199             Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    200             startActivityForResult(enableBtIntent, REQUES_BT_ENABLE_CODE); 
    201         }else{
    202             // 默认设备作为服务端
    203             startServiceAsServer();
    204         }
    205         //---------------------------------------------------------------------
    206     }
    207     
    208     private View initEmoView(){
    209         if(mEmoView == null){
    210             LayoutInflater inflater = getLayoutInflater();
    211             mEmoView = inflater.inflate(R.layout.emo_layout, null);
    212             
    213             mScrollView = (DrawerHScrollView) mEmoView.findViewById(R.id.scrollView);
    214             mGridView = (GridView) mEmoView.findViewById(R.id.gridView);
    215             mGridView.setOnItemClickListener(new OnItemClickListener() {
    216                 @Override
    217                 public void onItemClick(AdapterView<?> parent, View view,
    218                         int position, long id) {
    219                     // 在android中要显示图片信息,必须使用Bitmap位图的对象来装载  
    220                     Bitmap bitmap = BitmapFactory.decodeResource(getResources(), (Integer) mEmoList.get(position).get("img")); 
    221                     ImageSpan imageSpan = new ImageSpan(ChatActivity.this, bitmap);  
    222                     SpannableString spannableString = new SpannableString((String) mEmoList.get(position).get("text"));//face就是图片的前缀名  
    223                     spannableString.setSpan(imageSpan, 0, 8,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);  
    224                     mInput.append(spannableString);
    225                     System.out.println("mInput:"+mInput.getText());
    226                 }
    227             });
    228 
    229             mScrollHeight = setScrollGridView(mScrollView, mGridView, 3);
    230             System.out.println("mScrollHeight:" + mScrollHeight);
    231         }
    232         return mEmoView;
    233     }
    234     
    235 
    236     
    237     private void startServiceAsServer(){
    238         TaskService.start(this, mHandler);
    239         TaskService.newTask(new Task(mHandler, Task.TASK_START_ACCEPT, null));
    240         SoundEffect.getInstance(this).play(SoundEffect.SOUND_PLAY);
    241     }
    242     
    243     @Override
    244     protected void onResume() {
    245         sAliveCount++;
    246         super.onResume();
    247     }
    248 
    249     @Override
    250     protected void onPause() {
    251         sAliveCount--;
    252         super.onPause();
    253     }
    254     
    255     @Override
    256     protected void onDestroy() {
    257         super.onDestroy();
    258         // 关闭蓝牙
    259         if(mBluetoothAdapter.isEnabled())
    260             mBluetoothAdapter.disable();
    261         // 停止服务
    262         TaskService.stop(this);
    263     }
    264     
    265     
    266     @Override
    267     public void onClick(View v) {
    268         if(v == mSendBtn){
    269             String msg = mInput.getText().toString().trim();
    270             TaskService.newTask(new Task(mHandler, Task.TASK_GET_REMOTE_STATE, null));//通过点击按钮触发相应线程的启动,比较巧妙,值得学习
    271             if(msg.length() == 0){
    272                 showToast("聊天内容为空");
    273                 SoundEffect.getInstance(ChatActivity.this).play(SoundEffect.SOUND_ERR);
    274                 return;
    275             }
    276             
    277             //------ DEUBG ------ 
    278             TaskService.newTask(new Task(mHandler, Task.TASK_SEND_MSG, new Object[]{msg}));
    279             showOwnMessage(msg);//立马显示自己发送的消息,所以在handler里面就没有再做处理
    280             isHaspressed = true;//数据库可以开始记录消息啦
    281             mInput.setText("");
    282         }else if(v == mEmoButton){
    283             System.out.println("Emo btn clicked");
    284             // 关闭输入法
    285             InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 
    286             imm.hideSoftInputFromWindow(mInput.getWindowToken(),0);
    287             if(isShowEmo){
    288                 showEmoPanel(false);
    289             }else{
    290                 showEmoPanel(true);
    291             }
    292         }
    293     }
    294     
    295     private void showEmoPanel(boolean show){
    296         if(show && !isShowEmo){
    297             mEmoView.setVisibility(View.VISIBLE);
    298             mEmoButton.setImageResource(R.drawable.emo_collapse);
    299             ViewGroup.LayoutParams params = mChatLayout.getLayoutParams();
    300             params.height = mChatLayout.getHeight() - mScrollHeight;
    301             mChatLayout.setLayoutParams(params);
    302             isShowEmo = true;
    303         }else if(!show && isShowEmo){
    304             mEmoView.setVisibility(View.GONE);
    305             mEmoButton.setImageResource(R.drawable.emo_bkg);
    306             ViewGroup.LayoutParams params = mChatLayout.getLayoutParams();
    307             params.height = mChatLayout.getHeight() + mScrollHeight;
    308             mChatLayout.setLayoutParams(params);
    309             isShowEmo = false;
    310         }
    311         if(!isUpdate && show){
    312             LinearLayout.LayoutParams para = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
    313             mRootLayout.addView(mEmoView, para);
    314             isUpdate = true;
    315         }
    316     }
    317     
    318     
    319     
    320     private boolean isBTStateChanged(int now){
    321         if(sBTState != now){
    322             sBTState = now;
    323             return true;
    324         }else{
    325             return false;
    326         }
    327     }
    328     
    329     /**
    330      * 显示对方信息
    331      * @param data
    332      */
    333     private void showTargetMessage(HashMap<String, Object> data){
    334         SimpleDateFormat df1 = new SimpleDateFormat("E MM月dd日   HH:mm ");
    335         data.put(ChatListViewAdapter.KEY_DATE, df1.format(System.currentTimeMillis()).toString());
    336         data.put(ChatListViewAdapter.KEY_SHOW_MSG, true);
    337         mChatContent2.add(data);
    338         mAdapter2.notifyDataSetChanged();
    339         SoundEffect.getInstance(ChatActivity.this).play(SoundEffect.SOUND_RECV);
    340     }
    341     
    342     /**
    343      * 显示自己信息
    344      * @param data
    345      */
    346     private void showOwnMessage(String msg){
    347         HashMap<String, Object> map = new HashMap<String, Object>();
    348         map.put(ChatListViewAdapter.KEY_ROLE, ChatListViewAdapter.ROLE_OWN);//哪个角色的消息
    349         map.put(ChatListViewAdapter.KEY_NAME, mBluetoothAdapter.getName());
    350         map.put(ChatListViewAdapter.KEY_TEXT, msg);
    351         SimpleDateFormat df2 = new SimpleDateFormat("E MM月dd日  HH:mm ");
    352         map.put(ChatListViewAdapter.KEY_DATE, df2.format(System.currentTimeMillis()).toString());
    353         map.put(ChatListViewAdapter.KEY_SHOW_MSG, true);
    354         mChatContent2.add(map);
    355         mAdapter2.notifyDataSetChanged();
    356         SoundEffect.getInstance(ChatActivity.this).play(SoundEffect.SOUND_SEND);
    357     }
    358 
    359     @Override
    360     public boolean onCreateOptionsMenu(Menu menu) {
    361          MenuInflater inflater = getMenuInflater();
    362          inflater.inflate(R.menu.option_menu, menu);
    363          return true;
    364     }
    365 
    366     @Override
    367     public boolean onOptionsItemSelected(MenuItem item) {
    368           switch (item.getItemId()) {
    369             case R.id.scan:
    370                 startActivityForResult(new Intent(this, SelectDevice.class), REQUES_SELECT_BT_CODE);
    371                 break;
    372             case R.id.discoverable:
    373                 // 调用设置用户名方法
    374                 AlertDialog.Builder dlg = new AlertDialog.Builder(this);
    375                 final EditText devNameEdit = new EditText(this);
    376                 dlg.setView(devNameEdit);
    377                 dlg.setTitle("请输入用户名");
    378                 dlg.setPositiveButton("设置", new OnClickListener() {
    379                     public void onClick(DialogInterface dialog, int which) {
    380                         if(devNameEdit.getText().toString().length() != 0)
    381                             mBluetoothAdapter.setName(devNameEdit.getText().toString());
    382                     }
    383                 });
    384                 dlg.create();
    385                 dlg.show();
    386                 return true;
    387             case R.id.record:
    388                 Intent recordIntent = new Intent(ChatActivity.this, RecordListActivity.class);
    389                 startActivity(recordIntent);
    390                 return true;
    391             case R.id.exit:
    392                 Intent aboutIntent = new Intent(ChatActivity.this, DownloadActivity.class);
    393                 startActivity(aboutIntent);
    394                 return true;
    395             }
    396             return false;
    397     }
    398 
    399     @Override
    400     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    401         if(requestCode == REQUES_BT_ENABLE_CODE && resultCode == RESULT_OK){
    402             startServiceAsServer();
    403         }else if(requestCode == REQUES_SELECT_BT_CODE && resultCode == RESULT_OK){
    404             mRemoteDevice = data.getParcelableExtra("DEVICE");
    405             if(mRemoteDevice == null)
    406                 return;
    407             TaskService.newTask(new Task(mHandler, Task.TASK_START_CONN_THREAD, new Object[]{mRemoteDevice}));
    408         }
    409         super.onActivityResult(requestCode, resultCode, data);
    410     }
    411     
    412     private void showToast(String msg){
    413         Toast tst = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
    414         tst.setGravity(Gravity.CENTER | Gravity.TOP, 0, 240);
    415         tst.show();
    416     }
    417     
    418     
    419     // 设置表情的多页滚动显示控件
    420     public int setScrollGridView(DrawerHScrollView scrollView, GridView gridView, 
    421             int lines) {
    422         
    423         DisplayMetrics dm = new DisplayMetrics();
    424         getWindowManager().getDefaultDisplay().getMetrics(dm); 
    425         Display display = getWindowManager().getDefaultDisplay();
    426         System.out.println("Width:" + display.getWidth());
    427         System.out.println("Height:" + display.getHeight());
    428 
    429         
    430         int scrollWid = display.getWidth();
    431         int scrollHei;
    432         System.out.println("scrollWid:" + scrollWid);
    433         if (scrollWid <= 0 ){
    434             Log.d(TAG, "scrollWid or scrollHei is less than 0");
    435             return 0;
    436         }
    437          
    438           
    439         float density  = dm.density;      // 屏幕密度(像素比例:0.75/1.0/1.5/2.0)
    440         
    441         int readlViewWidht = 56;
    442         // 图片都放在了Hdpi中,所以计算出图片的像素独立宽度
    443         int viewWidth = (int) (readlViewWidht * density / 1.5);
    444         int viewHeight = viewWidth;
    445         System.out.println("viewWidth:" + viewWidth + " viewHeight:" + viewHeight);
    446         
    447         int numColsPage = scrollWid / viewWidth;
    448         int spaceing = (scrollWid - viewWidth * numColsPage)/(numColsPage);
    449         System.out.println("Space:" + spaceing);
    450 
    451 
    452         SimpleAdapter adapter = getEmoAdapter();
    453         int pages = adapter.getCount() / (numColsPage * lines);
    454         
    455         if (pages * numColsPage * lines < adapter.getCount()){
    456             pages++;
    457         }
    458 
    459         System.out.println("pages:" + pages);
    460         
    461         scrollHei = lines * viewHeight + spaceing * (lines + 1);
    462         
    463         LayoutParams params = new LayoutParams(pages * scrollWid, LayoutParams.WRAP_CONTENT);
    464         gridView.setLayoutParams(params);
    465         gridView.setColumnWidth(viewWidth);
    466         gridView.setHorizontalSpacing(spaceing);
    467         gridView.setVerticalSpacing(spaceing);
    468         gridView.setStretchMode(GridView.NO_STRETCH);
    469         gridView.setNumColumns(numColsPage * pages);
    470 
    471         //adapter = new DrawerListAdapter(this, colWid, colHei);
    472         //listener = new DrawerItemClickListener();
    473         gridView.setAdapter(adapter);
    474         //mGridView.setOnItemClickListener(listener);
    475 
    476         scrollView.setParameters(pages, 0, scrollWid, spaceing);
    477         //updateDrawerPageLayout(pageNum, 0);
    478         // 表情区域还要加上分布显示区
    479         int pageNumHei = (int) (18 * density); 
    480         return scrollHei + pageNumHei;
    481     }
    482     
    483         
    484     private SimpleAdapter getEmoAdapter(){
    485            HashMap<String, Object> map = new HashMap<String, Object>();
    486            map.put("img", R.drawable.emo001);
    487            map.put("text", "<emo001>");
    488            mEmoList.add(map);
    489            map = new HashMap<String, Object>();
    490            map.put("img", R.drawable.emo002);
    491            map.put("text", "<emo002>");
    492            mEmoList.add(map);
    493            map = new HashMap<String, Object>();
    494            map.put("img", R.drawable.emo003);
    495            map.put("text", "<emo003>");
    496            mEmoList.add(map);
    497            map = new HashMap<String, Object>();
    498            map.put("img", R.drawable.emo004);
    499            map.put("text", "<emo004>");
    500            mEmoList.add(map);
    501            map = new HashMap<String, Object>();
    502            map.put("img", R.drawable.emo005);
    503            map.put("text", "<emo005>");
    504            mEmoList.add(map);
    505            map = new HashMap<String, Object>();
    506            map.put("img", R.drawable.emo006);
    507            map.put("text", "<emo006>");
    508            mEmoList.add(map);
    509            map = new HashMap<String, Object>();
    510            map.put("img", R.drawable.emo007);
    511            map.put("text", "<emo007>");
    512            mEmoList.add(map);
    513            map = new HashMap<String, Object>();
    514            map.put("img", R.drawable.emo008);
    515            map.put("text", "<emo008>");
    516            mEmoList.add(map);
    517            map = new HashMap<String, Object>();
    518            map.put("img", R.drawable.emo009);
    519            map.put("text", "<emo009>");
    520            mEmoList.add(map);
    521            map = new HashMap<String, Object>();
    522            map.put("img", R.drawable.emo010);
    523            map.put("text", "<emo010>");
    524            mEmoList.add(map);
    525            map = new HashMap<String, Object>();
    526            map.put("img", R.drawable.emo011);
    527            map.put("text", "<emo011>");
    528            mEmoList.add(map);
    529            map = new HashMap<String, Object>();
    530            map.put("img", R.drawable.emo012);
    531            map.put("text", "<emo012>");
    532            mEmoList.add(map);
    533            map = new HashMap<String, Object>();
    534            map.put("img", R.drawable.emo013);
    535            map.put("text", "<emo013>");
    536            mEmoList.add(map);
    537            map = new HashMap<String, Object>();
    538            map.put("img", R.drawable.emo014);
    539            map.put("text", "<emo014>");
    540            mEmoList.add(map);
    541            map = new HashMap<String, Object>();
    542            map.put("img", R.drawable.emo015);
    543            map.put("text", "<emo015>");
    544            mEmoList.add(map);
    545            map = new HashMap<String, Object>();
    546            map.put("img", R.drawable.emo016);
    547            map.put("text", "<emo016>");
    548            mEmoList.add(map);
    549            map = new HashMap<String, Object>();
    550            map.put("img", R.drawable.emo017);
    551            map.put("text", "<emo017>");
    552            mEmoList.add(map);
    553            map = new HashMap<String, Object>();
    554            map.put("img", R.drawable.emo018);
    555            map.put("text", "<emo018>");
    556            mEmoList.add(map);
    557            map = new HashMap<String, Object>();
    558            map.put("img", R.drawable.emo019);
    559            map.put("text", "<emo019>");
    560            mEmoList.add(map);
    561            map = new HashMap<String, Object>();
    562            map.put("img", R.drawable.emo020);
    563            map.put("text", "<emo020>");
    564            mEmoList.add(map);
    565            map = new HashMap<String, Object>();
    566            map.put("img", R.drawable.emo021);
    567            map.put("text", "<emo021>");
    568            mEmoList.add(map);
    569            map = new HashMap<String, Object>();
    570            map.put("img", R.drawable.emo022);
    571            map.put("text", "<emo022>");
    572            mEmoList.add(map);
    573            map = new HashMap<String, Object>();
    574            map.put("img", R.drawable.emo023);
    575            map.put("text", "<emo023>");
    576            mEmoList.add(map);
    577            map = new HashMap<String, Object>();
    578            map.put("img", R.drawable.emo024);
    579            map.put("text", "<emo024>");
    580            mEmoList.add(map);
    581            map = new HashMap<String, Object>();
    582            map.put("img", R.drawable.emo025);
    583            map.put("text", "<emo025>");
    584            mEmoList.add(map);
    585            map = new HashMap<String, Object>();
    586            map.put("img", R.drawable.emo026);
    587            map.put("text", "<emo026>");
    588            mEmoList.add(map);
    589            map = new HashMap<String, Object>();
    590            map.put("img", R.drawable.emo027);
    591            map.put("text", "<emo027>");
    592            mEmoList.add(map);
    593            map = new HashMap<String, Object>();
    594            map.put("img", R.drawable.emo028);
    595            map.put("text", "<emo028>");
    596            mEmoList.add(map);
    597            map = new HashMap<String, Object>();
    598            map.put("img", R.drawable.emo029);
    599            map.put("text", "<emo029>");
    600            mEmoList.add(map);
    601            map = new HashMap<String, Object>();
    602            map.put("img", R.drawable.emo030);
    603            map.put("text", "<emo030>");
    604            mEmoList.add(map);
    605            map = new HashMap<String, Object>();
    606            map.put("img", R.drawable.emo031);
    607            map.put("text", "<emo031>");
    608            mEmoList.add(map);
    609            map = new HashMap<String, Object>();
    610            map.put("img", R.drawable.emo032);
    611            map.put("text", "<emo032>");
    612            mEmoList.add(map);
    613            map = new HashMap<String, Object>();
    614            map.put("img", R.drawable.emo033);
    615            map.put("text", "<emo033>");
    616            mEmoList.add(map);
    617            map = new HashMap<String, Object>();
    618            map.put("img", R.drawable.emo034);
    619            map.put("text", "<emo034>");
    620            mEmoList.add(map);
    621            map = new HashMap<String, Object>();
    622            map.put("img", R.drawable.emo035);
    623            map.put("text", "<emo035>");
    624            mEmoList.add(map);
    625            map = new HashMap<String, Object>();
    626            map.put("img", R.drawable.emo036);
    627            map.put("text", "<emo036>");
    628            mEmoList.add(map);
    629            map = new HashMap<String, Object>();
    630            map.put("img", R.drawable.emo037);
    631            map.put("text", "<emo037>");
    632            mEmoList.add(map);
    633            map = new HashMap<String, Object>();
    634            map.put("img", R.drawable.emo038);
    635            map.put("text", "<emo038>");
    636            mEmoList.add(map);
    637            map = new HashMap<String, Object>();
    638            map.put("img", R.drawable.emo039);
    639            map.put("text", "<emo039>");
    640            mEmoList.add(map);
    641            map = new HashMap<String, Object>();
    642            map.put("img", R.drawable.emo040);
    643            map.put("text", "<emo040>");
    644            mEmoList.add(map);
    645            map = new HashMap<String, Object>();
    646            map.put("img", R.drawable.emo041);
    647            map.put("text", "<emo041>");
    648            mEmoList.add(map);
    649            map = new HashMap<String, Object>();
    650            map.put("img", R.drawable.emo042);
    651            map.put("text", "<emo042>");
    652            mEmoList.add(map);
    653            map = new HashMap<String, Object>();
    654            map.put("img", R.drawable.emo043);
    655            map.put("text", "<emo043>");
    656            mEmoList.add(map);
    657            map = new HashMap<String, Object>();
    658            map.put("img", R.drawable.emo044);
    659            map.put("text", "<emo044>");
    660            mEmoList.add(map);
    661            map = new HashMap<String, Object>();
    662            map.put("img", R.drawable.emo045);
    663            map.put("text", "<emo045>");
    664            mEmoList.add(map);
    665            map = new HashMap<String, Object>();
    666            map.put("img", R.drawable.emo046);
    667            map.put("text", "<emo046>");
    668            mEmoList.add(map);
    669            map = new HashMap<String, Object>();
    670            map.put("img", R.drawable.emo047);
    671            map.put("text", "<emo047>");
    672            mEmoList.add(map);
    673            map = new HashMap<String, Object>();
    674            map.put("img", R.drawable.emo048);
    675            map.put("text", "<emo048>");
    676            mEmoList.add(map);
    677            map = new HashMap<String, Object>();
    678            map.put("img", R.drawable.emo049);
    679            map.put("text", "<emo049>");
    680            mEmoList.add(map);
    681            map = new HashMap<String, Object>();
    682            map.put("img", R.drawable.emo050);
    683            map.put("text", "<emo050>");
    684            mEmoList.add(map);
    685            map = new HashMap<String, Object>();
    686            map.put("img", R.drawable.emo051);
    687            map.put("text", "<emo051>");
    688            mEmoList.add(map);
    689            map = new HashMap<String, Object>();
    690            map.put("img", R.drawable.emo052);
    691            map.put("text", "<emo052>");
    692            mEmoList.add(map);
    693            map = new HashMap<String, Object>();
    694            map.put("img", R.drawable.emo053);
    695            map.put("text", "<emo053>");
    696            mEmoList.add(map);
    697            map = new HashMap<String, Object>();
    698            map.put("img", R.drawable.emo054);
    699            map.put("text", "<emo054>");
    700            mEmoList.add(map);
    701            map = new HashMap<String, Object>();
    702            map.put("img", R.drawable.emo055);
    703            map.put("text", "<emo055>");
    704            mEmoList.add(map);
    705            map = new HashMap<String, Object>();
    706            map.put("img", R.drawable.emo056);
    707            map.put("text", "<emo056>");
    708            mEmoList.add(map);
    709            map = new HashMap<String, Object>();
    710            map.put("img", R.drawable.emo057);
    711            map.put("text", "<emo057>");
    712            mEmoList.add(map);
    713            map = new HashMap<String, Object>();
    714            map.put("img", R.drawable.emo058);
    715            map.put("text", "<emo058>");
    716            mEmoList.add(map);
    717            map = new HashMap<String, Object>();
    718            map.put("img", R.drawable.emo059);
    719            map.put("text", "<emo059>");
    720            mEmoList.add(map);
    721            map = new HashMap<String, Object>();
    722            map.put("img", R.drawable.emo060);
    723            map.put("text", "<emo060>");
    724            mEmoList.add(map);
    725            
    726            /**
    727             * 上述添加表情效率高,但是代码太冗余,下面的方式代码简单,但是效率较低
    728             */
    729            /*
    730            HashMap<String, Integer> map;
    731            for(int i = 0; i < 100; i++){
    732                map = new HashMap<String, Integer>();
    733                Field field=R.drawable.class.getDeclaredField("image"+i);  
    734                int resourceId=Integer.parseInt(field.get(null).toString());
    735                map.put("img", resourceId);
    736                mEmoList.add(map);
    737            }
    738            */
    739         return new SimpleAdapter(this, mEmoList, R.layout.grid_view_item, 
    740                     new String[]{"img"}, new int[]{R.id.imageView});
    741     }
    742     
    743     
    744     long waitTime = 2000;
    745     long touchTime = 0;
    746     @Override
    747     public boolean onKeyDown(int keyCode, KeyEvent event) {
    748         if(event.getAction() == KeyEvent.ACTION_DOWN && KeyEvent.KEYCODE_BACK == keyCode) {
    749             long currentTime = System.currentTimeMillis();
    750             if((currentTime-touchTime)>=waitTime) {
    751                 Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show();
    752                 touchTime = currentTime;
    753             }else {
    754                 finish();
    755             }
    756             return true;
    757         }
    758         return super.onKeyDown(keyCode, event);
    759     }
    760     
    761 }
    View Code

    TaskService.java:

      1 package cn.com.farsgiht.bluetoothdemo.task;
      2 
      3 import java.io.BufferedInputStream;
      4 import java.io.BufferedOutputStream;
      5 import java.io.IOException;
      6 import java.io.InputStream;
      7 import java.io.OutputStream;
      8 import java.io.UnsupportedEncodingException;
      9 import java.util.ArrayList;
     10 import java.util.Calendar;
     11 import java.util.HashMap;
     12 import java.util.UUID;
     13 import android.app.Service;
     14 import android.bluetooth.BluetoothAdapter;
     15 import android.bluetooth.BluetoothDevice;
     16 import android.bluetooth.BluetoothServerSocket;
     17 import android.bluetooth.BluetoothSocket;
     18 import android.content.ContentValues;
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.database.sqlite.SQLiteDatabase;
     22 import android.os.Bundle;
     23 import android.os.Handler;
     24 import android.os.IBinder;
     25 import android.util.Log;
     26 import android.widget.ArrayAdapter;
     27 import android.widget.Toast;
     28 import cn.com.farsgiht.bluetoothdemo.ChatActivity;
     29 import cn.com.farsgiht.bluetoothdemo.UI.ChatListViewAdapter;
     30 import cn.com.farsgiht.bluetoothdemo.protocol.DataProtocol;
     31 import cn.com.farsgiht.bluetoothdemo.protocol.Message;
     32 import cn.com.farsgiht.bluetoothdemo.sound.SoundEffect;
     33 
     34 /**
     35  * 任务处理服务
     36  * @author Administrator
     37  */
     38 public class TaskService extends Service {
     39     public static final int BT_STAT_WAIT = 0;
     40     public static final int BT_STAT_CONN = 1;
     41     public static final int BT_STAT_ONLINE = 2;
     42     public static final int BT_STAT_UNKNOWN = 3;
     43     
     44  
     45     public static final String DEVICE_NAME = "device_name";
     46     
     47     private final String TAG = "TaskService";
     48     private TaskThread mThread;
     49 
     50     private BluetoothAdapter mBluetoothAdapter;
     51     
     52     private AcceptThread mAcceptThread;
     53     private ConnectThread mConnectThread;
     54     private ConnectedThread mCommThread;
     55     
     56     private boolean isServerMode = true;
     57 
     58     private static Handler mActivityHandler;
     59 
     60     // 任务队列
     61     private static ArrayList<Task> mTaskList = new ArrayList<Task>();
     62 
     63     private Handler mServiceHandler = new Handler() {
     64         @Override
     65         public void handleMessage(android.os.Message msg) {        
     66             switch (msg.what) {
     67             case Task.TASK_GET_REMOTE_STATE:
     68                 android.os.Message activityMsg = mActivityHandler.obtainMessage();
     69                 activityMsg.what = msg.what;
     70                 if (mAcceptThread != null && mAcceptThread.isAlive()) {
     71                     activityMsg.obj = "等待连接...";
     72                     activityMsg.arg1 = BT_STAT_WAIT;
     73                 } else if (mCommThread != null && mCommThread.isAlive()) {
     74                     activityMsg.obj = mCommThread.getRemoteName() + "[在线]";
     75                     activityMsg.arg1 = BT_STAT_ONLINE;
     76                 } else if (mConnectThread != null && mConnectThread.isAlive()) {
     77                     SoundEffect.getInstance(TaskService.this).play(3);
     78                     activityMsg.obj = "正在连接:"
     79                             + mConnectThread.getDevice().getName();
     80                     activityMsg.arg1 = BT_STAT_CONN;
     81                 } else {
     82                     activityMsg.obj = "未知状态";
     83                     activityMsg.arg1 = BT_STAT_UNKNOWN;
     84                     SoundEffect.getInstance(TaskService.this).play(2);
     85                     // 重新等待连接
     86                     mAcceptThread = new AcceptThread();
     87                     mAcceptThread.start();
     88                     isServerMode = true;
     89                 }
     90 
     91                 mActivityHandler.sendMessage(activityMsg);
     92                 break;
     93             default:
     94                 break;
     95             }
     96             super.handleMessage(msg);
     97         }
     98     };
     99     
    100     
    101     
    102     @Override
    103     public void onCreate() {
    104         super.onCreate();
    105         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    106         if (mBluetoothAdapter == null) {
    107             Log.e(TAG, "Your device is not support Bluetooth!");
    108             return;
    109         }
    110         mThread = new TaskThread();
    111         mThread.start();
    112     }
    113 
    114 
    115     public static void start(Context c, Handler handler){
    116         mActivityHandler = handler;
    117         Intent intent = new Intent(c, TaskService.class);
    118         c.startService(intent);
    119     }
    120     
    121     public static void stop(Context c){
    122         Intent intent = new Intent(c, TaskService.class);
    123         c.stopService(intent);
    124     }
    125     
    126 
    127 
    128     public static void newTask(Task target) {
    129         synchronized (mTaskList) {
    130             mTaskList.add(target);
    131         }
    132     }
    133 
    134     private class TaskThread extends Thread {
    135         private boolean isRun = true;
    136         private int mCount = 0;
    137 
    138         public void cancel() {
    139             isRun = false;
    140         }
    141 
    142         @Override
    143         public void run() {
    144             Task task;
    145             while (isRun) {
    146 
    147                 // 有任务
    148                 if (mTaskList.size() > 0) {
    149                     synchronized (mTaskList) {
    150                         // 获得任务
    151                         task = mTaskList.get(0);
    152                         doTask(task);
    153                     }
    154                 } else {
    155                     try {
    156                         Thread.sleep(200);
    157                         mCount++;
    158                     } catch (InterruptedException e) {
    159                     }
    160                     // 每过10秒钟进行一次状态检查
    161                     if (mCount >= 50) {
    162                         mCount = 0;
    163                         // 检查远程设备状态
    164                         android.os.Message handlerMsg = mServiceHandler
    165                                 .obtainMessage();
    166                         handlerMsg.what = Task.TASK_GET_REMOTE_STATE;
    167                         mServiceHandler.sendMessage(handlerMsg);
    168                     }
    169                 }
    170             }
    171         }
    172 
    173     }
    174 //对应三个线程,其中mCommThread是在mConnectThread的run()方法中new出来的
    175     private void doTask(Task task) {
    176         switch (task.getTaskID()) {
    177         case Task.TASK_START_ACCEPT:
    178             mAcceptThread = new AcceptThread();
    179             mAcceptThread.start();
    180             isServerMode = true;
    181             break;
    182         case Task.TASK_START_CONN_THREAD:
    183             if (task.mParams == null || task.mParams.length == 0) {
    184                 break;
    185             }
    186             BluetoothDevice remote = (BluetoothDevice) task.mParams[0];
    187             mConnectThread = new ConnectThread(remote);
    188             mConnectThread.start();
    189             isServerMode = false;
    190             break;
    191         case Task.TASK_SEND_MSG:
    192             boolean sucess = false;
    193             if (mCommThread == null || !mCommThread.isAlive()
    194                     || task.mParams == null || task.mParams.length == 0) {
    195                 Log.e(TAG, "mCommThread or task.mParams null");
    196             }else{
    197                 byte[] msg = null;
    198                 try {
    199                     
    200                     msg = DataProtocol.packMsg((String) task.mParams[0]);
    201                     sucess = mCommThread.write(msg);
    202      
    203                 } catch (UnsupportedEncodingException e) {
    204                     sucess = false;
    205                 }
    206             }
    207             if (!sucess) {
    208                 android.os.Message returnMsg = mActivityHandler.obtainMessage();
    209                 returnMsg.what = Task.TASK_SEND_MSG_FAIL;
    210                 returnMsg.obj = "消息发送失败";
    211                 mActivityHandler.sendMessage(returnMsg);
    212             }
    213             break;
    214         }
    215 
    216         // 移除任务
    217         mTaskList.remove(task);//每次保证任务列表里面只有一个任务,task = mTaskList.get(0);
    218     }
    219 
    220     @Override
    221     public void onDestroy() {
    222         super.onDestroy();
    223         mThread.cancel();
    224     }
    225 
    226     private final String UUID_STR = "00001101-0000-1000-8000-00805F9B34FB";
    227 
    228     /**
    229      * 等待客户端连接线程
    230      * 
    231      * @author Administrator
    232      */
    233     private class AcceptThread extends Thread {
    234         private final BluetoothServerSocket mmServerSocket;
    235         private boolean isCancel = false;
    236 
    237         public AcceptThread() {
    238             Log.d(TAG, "AcceptThread");
    239             BluetoothServerSocket tmp = null;
    240             try {
    241                 tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(
    242                         "MT_Chat_Room", UUID.fromString(UUID_STR));
    243             } catch (IOException e) {
    244             }
    245             mmServerSocket = tmp;
    246         }
    247 
    248         public void run() {
    249             BluetoothSocket socket = null;
    250             while (true) {
    251                 try {
    252                     // 阻塞等待
    253                     socket = mmServerSocket.accept();
    254                 } catch (IOException e) {
    255                     if (!isCancel) {
    256                         try {
    257                             mmServerSocket.close();
    258                         } catch (IOException e1) {
    259                         }
    260                         mAcceptThread = new AcceptThread();
    261                         mAcceptThread.start();
    262                         isServerMode = true;
    263                     }
    264                     break;
    265                 }
    266                 if (socket != null) {
    267                     manageConnectedSocket(socket);
    268                     try {
    269                         mmServerSocket.close();
    270                     } catch (IOException e) {
    271                     }
    272                     mAcceptThread = null;
    273                     break;
    274                 }
    275             }
    276         }
    277 
    278         public void cancel() {
    279             try {
    280                 Log.d(TAG, "AcceptThread canceled");
    281                 isCancel = true;
    282                 isServerMode = false;
    283                 mmServerSocket.close();
    284                 mAcceptThread = null;
    285                 if (mCommThread != null && mCommThread.isAlive()) {
    286                     mCommThread.cancel();
    287                 }
    288             } catch (IOException e) {
    289             }
    290         }
    291     }
    292 
    293     /**
    294      * 作为客户端连接指定的蓝牙设备线程
    295      * 
    296      * @author Administrator
    297      */
    298     private class ConnectThread extends Thread {
    299         private final BluetoothSocket mmSocket;
    300         private final BluetoothDevice mmDevice;
    301 
    302         public ConnectThread(BluetoothDevice device) {
    303 
    304             Log.d(TAG, "ConnectThread");
    305 
    306             if (mAcceptThread != null && mAcceptThread.isAlive()) {
    307                 mAcceptThread.cancel();
    308             }
    309 
    310             if (mCommThread != null && mCommThread.isAlive()) {
    311                 mCommThread.cancel();
    312             }
    313 
    314             // Use a temporary object that is later assigned to mmSocket,
    315             // because mmSocket is final
    316             BluetoothSocket tmp = null;
    317             mmDevice = device;
    318             try {
    319                 tmp = device.createRfcommSocketToServiceRecord(UUID
    320                         .fromString(UUID_STR));
    321             } catch (IOException e) {
    322                 Log.d(TAG, "createRfcommSocketToServiceRecord error!");
    323             }
    324 
    325             mmSocket = tmp;
    326         }
    327 
    328         public BluetoothDevice getDevice() {
    329             return mmDevice;
    330         }
    331 
    332         public void run() {
    333             // Cancel discovery because it will slow down the connection
    334             mBluetoothAdapter.cancelDiscovery();
    335             try {
    336                 // Connect the device through the socket. This will block
    337                 // until it succeeds or throws an exception
    338                 mmSocket.connect();
    339             } catch (IOException connectException) {
    340                 // Unable to connect; close the socket and get out
    341                 Log.e(TAG, "Connect server failed");
    342                 try {
    343                     mmSocket.close();
    344                 } catch (IOException closeException) {
    345                 }
    346                 mAcceptThread = new AcceptThread();
    347                 mAcceptThread.start();
    348                 isServerMode = true;
    349                 return;
    350             } // Do work to manage the connection (in a separate thread)
    351             manageConnectedSocket(mmSocket);
    352         }
    353 
    354         public void cancel() {
    355             try {
    356                 mmSocket.close();
    357             } catch (IOException e) {
    358             }
    359             mConnectThread = null;
    360         }
    361     }
    362 
    363     private void manageConnectedSocket(BluetoothSocket socket) {
    364         // 启动子线程来维持连接
    365         mCommThread = new ConnectedThread(socket);
    366         mCommThread.start();
    367     }
    368 
    369     private class ConnectedThread extends Thread {
    370         private final BluetoothSocket mmSocket;
    371         private final InputStream mmInStream;
    372         private final OutputStream mmOutStream;
    373         private BufferedOutputStream mmBos;
    374         private byte[] buffer;
    375 
    376         public ConnectedThread(BluetoothSocket socket) {
    377             Log.d(TAG, "ConnectedThread");
    378             mmSocket = socket;
    379             InputStream tmpIn = null;
    380             OutputStream tmpOut = null;
    381             try {
    382                 tmpIn = socket.getInputStream();
    383                 tmpOut = socket.getOutputStream();
    384             } catch (IOException e) {
    385             }
    386             mmInStream = tmpIn;
    387             mmOutStream = tmpOut;
    388             mmBos = new BufferedOutputStream(mmOutStream);
    389         }
    390 
    391         public OutputStream getOutputStream() {
    392             return mmOutStream;
    393         }
    394 
    395         public boolean write(byte[] msg) {
    396             if (msg == null)
    397                 return false;
    398             try {
    399                 mmBos.write(msg);
    400                 mmBos.flush();
    401                 
    402                 mActivityHandler.obtainMessage(Task.TASK_SEND_MSG, -1, -1, new String(msg)).sendToTarget();
    403                 System.out.println("Write:" + msg);
    404             } catch (IOException e) {
    405                 return false;
    406             }
    407             return true;
    408         }
    409 
    410         public String getRemoteName() {
    411             return mmSocket.getRemoteDevice().getName();
    412         }
    413 
    414         
    415         
    416         
    417         public void cancel() {
    418             try {
    419                 mmSocket.close();
    420             } catch (IOException e) {
    421             }
    422             mCommThread = null;
    423         }
    424 
    425         public void run() {
    426             try {
    427                 write(DataProtocol.packMsg(mBluetoothAdapter.getName()
    428                         + "已经上线
    "));//获取本地蓝牙适配器的蓝牙名称,这一条消息默认发送出去啦,
    429                                       //但消息记录里面不应该有这条消息,消息记录里面记录按过发送键的消息
    430             } catch (UnsupportedEncodingException e2) {
    431             }
    432             int size;
    433             Message msg;
    434             android.os.Message handlerMsg;
    435             buffer = new byte[1024];
    436 
    437             BufferedInputStream bis = new BufferedInputStream(mmInStream);
    438             // BufferedReader br = new BufferedReader(new
    439             // InputStreamReader(mmInStream));
    440             HashMap<String, Object> data;
    441             while (true) {
    442                 try {
    443                     size = bis.read(buffer);
    444                     msg = DataProtocol.unpackData(buffer);
    445                     if (msg == null)
    446                         continue;
    447 
    448                     if (mActivityHandler == null) {
    449                         return;
    450                     }
    451 
    452                     msg.remoteDevName = mmSocket.getRemoteDevice().getName();//得到对方设备的名字
    453                     if (msg.type == DataProtocol.TYPE_FILE) {
    454                         // 文件接收处理忽略
    455 
    456                     } else if (msg.type == DataProtocol.TYPE_MSG) {
    457                         data = new HashMap<String, Object>();
    458                         System.out.println("Read data.");
    459                         data.put(ChatListViewAdapter.KEY_ROLE,
    460                                 ChatListViewAdapter.ROLE_TARGET);
    461                         data.put(ChatListViewAdapter.KEY_NAME,
    462                                 msg.remoteDevName);
    463                         data.put(ChatListViewAdapter.KEY_TEXT, msg.msg);
    464                         
    465                         // 通过Activity更新到UI上
    466                         handlerMsg = mActivityHandler.obtainMessage();
    467                         handlerMsg.what = Task.TASK_RECV_MSG;
    468                         handlerMsg.obj = data;
    469                         mActivityHandler.sendMessage(handlerMsg);
    470                     }
    471                 } catch (IOException e) {
    472                     try {
    473                         mmSocket.close();
    474                     } catch (IOException e1) {
    475                     }
    476                     mCommThread = null;
    477                     if (isServerMode) {
    478                         // 检查远程设备状态
    479                         handlerMsg = mServiceHandler.obtainMessage();
    480                         handlerMsg.what = Task.TASK_GET_REMOTE_STATE;
    481                         mServiceHandler.sendMessage(handlerMsg);
    482                         SoundEffect.getInstance(TaskService.this).play(2);
    483                         mAcceptThread = new AcceptThread();
    484                         mAcceptThread.start();
    485                     }
    486                     break;
    487                 }
    488             }
    489         }
    490     }
    491 
    492     // ================================================================
    493 
    494     @Override
    495     public IBinder onBind(Intent intent) {
    496         // TODO Auto-generated method stub
    497         return null;
    498     }
    499 
    500 }
    View Code 

    实现效果:

                 

     

     源码传送门

     

  • 相关阅读:
    题目1449:确定比赛名次
    题目1005:Graduate Admission
    HDU 4786 Fibonacci Tree
    FZU Problem 2136 取糖果
    iOS 递归锁
    iOS xcode问题集结
    iOS 芝麻认证开发(跳转本地的支付宝进行认证开发)
    iOS导出ipa包时四个选项的意义
    两排按钮循环
    Masony 常用方法
  • 原文地址:https://www.cnblogs.com/hixin/p/4366481.html
Copyright © 2011-2022 走看看