xmpp版即时聊天

 

1.即时聊天简单介绍,socket通信

1.即时聊天的解决方案

* socket:
* xmpp:xmpp+openfire+asmack

2.环信--------即时通讯云领导者:http://www.easemob.com/

3.socket:套接字,连接需要`ip`和`端口`,分为tcp和udp两种形式

     代码

 

2.xmpp协议的介绍

 1.xmpp官网: http://xmpp.org/

 2.xmpp特点:

(1). 开放: XMPP协议是自由、开放、公开的,并且易于了解。 而且在客户端 、 服务器 、 组件 、 源码库等方面,都已经各自有多种实现。
(2). 标准: 互联网工程工作小组( IETF )已经将Jabber的核心XML流协议以XMPP之名,正式列为认可的实时通信及Presence技术。 而XMPP的技术规格已被定义在RFC 3920及RFC 3921 。 任何IM供应商在遵循XMPP协议下,都可与Google Talk实现连接。
(3). 证实可用: 第一个Jabber(现在XMPP)技术是Jeremie Miller在1998年开发的,现在已经相​​当稳定;数以百计的开发者为XMPP技术而努力。 今日的互联网上有数以万计的XMPP服务器运作着,并有数以百万计的人们使用XMPP实时传讯软件。
(4). 分散式: XMPP网络的架构和电子邮件十分相像;XMPP核心协议通信方式是先创建一个stream,XMPP以TCP传递XML数据流,没有中央主服务器。 任何人都可以运行自己的XMPP服务器,使个人及组织能够掌控他们的实时传讯体验。
(5). 安全: 任何XMPP协议的服务器可以独立于公众XMPP网络(例如在企业内部网络中),而使用SASL及TLS等技术的可靠安全性,已自带于核心XMPP技术规格中。
(6). 可扩展: XML 命名空间的威力可使任何人在核心协议的基础上建造定制化的功能;为了维持通透性,常见的扩展由XMPP标准基金会 。 弹性佳 XMPP除了可用在实时通信的应用程序,还能用在网络管理、内容供稿、协同工具、文件共享、游戏、远程系统监控等。
(7). 多样性: 用XMPP协议来建造及布署实时应用程序及服务的公司及开放源代码计划分布在各种领域;用XMPP技术开发软件,资源及支持的来源是多样的,使得使你不会陷于被“绑架”的困境。

3.xmpp相关中文文档和PPT--------------见资料

 

3.openfire服务器的安装

1.常见的术语

(1)xmpp:基于xml的可拓展协议.
(2)jabber:xmpp的前身.
(3)openfire:支持xmpp的开源服务器
(4)smack.jar:对xmpp协议封装.方便开发的jar包.
(5)spark.exe:基于xmpp的pc客户端;
(6)asmack.jar:smack.jar的精简版.**专门针对android端开发**

 

2.相关下载地址:

(1) asmack github:https://github.com/Flowdalic/asmack
(2)asmack下载地址1:http://asmack.freakempire.de/
(3)asmack下载地址2:http://code.google.com/p/asmack/downloads/list
(4)openfire下载地址:http://www.igniterealtime.org/downloads/index.jsp
(5)smack使用指南:http://www.igniterealtime.org/builds/smack/docs/latest/documentation/index.html

3.openfire的安装

(1). 官网`http://www.igniterealtime.org/`
(2). 安装包的类型
      1. exe安装包-->点击exe根据提示安装
      2. zip包解压版-->解压放到指定目录就可以了.
(3). 首次运行的配置
      1. 配置语言-->`中文简体`
      2. 配置数据库形式-->`内嵌数据库`
      3. 配置服务器名称-->`itheima`
      4. 配置管理员账号密码-->`admin admin`
(4). 创建用户
      1. admin admin
      2. hm1  111111

 

4. pc客户端安装

(1)spark的安装         -->xmpp客户端1
    * 直接下一步就可以完成
    * 运行
    * 1.**配置服务器ip**
    * 2.输入用户账号/密码

(2)如意通的安装       -->xmpp客户端2

 

4.通信模型,项目演示

1.xmpp版即使聊天的核心 :  其实就是熟悉asmack.jar里面的一些常见类.以及常见监听器;

 

5.创建工程.添加asmack.关联源码

1.smack.jar为PC端类

2.asmack.jar为Android端类

3.见smack文档-------见资料:Smack类库最好的学习资料.docx

4.工程搭建

(1)asmack.jar的下载,下载地址: http://asmack.freakempire.de/

(2)创建android工程.

(3)as添加jar包(放在目录下),另外添加依赖

(4)as关联源码(源码包)

 

6.完成splash界面的开发

1.application主题:style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"

2.Uiutils-------------ThreadUtils.java---线程工具类

import android.os.Handler;
 
public class ThreadUtils {
    /**
     * 子线程执行task
     */
    public static void runInThread(Runnable task) {
        new Thread(task).start();
    }

    /**
     * 创建一个主线程中handler
     */
    public static Handler mHandler = new Handler();

    /**
     * UI线程执行task
     */
    public static void runInUIThread(Runnable task) {
        mHandler.post(task);
    }
}


3.SplashActivity.java

public class SplashActivity extends ActionBarActivity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_splash);
		// 停留3s,进入登录界面

		ThreadUtils.runInThread(new Runnable() {
			@Override
			public void run() {
				// 休眠3s
				SystemClock.sleep(3000);

				// 进入主界面
				Intent intent = new Intent(SplashActivity.this, LoginActivity.class);
				startActivity(intent);
				finish();
			}
		});
	}
}


 

7.登录界面的完成

1.登陆界面布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingBottom="@dimen/activity_vertical_margin"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                tools:context="com.itheima.xmpp_20150807.activity.LoginActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:orientation="vertical">

        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:src="@mipmap/ic_launcher"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:orientation="horizontal">
            <!--账号-->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="账号: "
                android:textSize="24sp"/>
            <!--输入账号-->
            <EditText
                android:id="@+id/et_username"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="输入账号"
                android:text="admin"/>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:orientation="horizontal">
            <!--密码-->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="账号: "
                android:textSize="24sp"/>
            <!--输入密码-->
            <EditText
                android:id="@+id/et_password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="输入密码"
                android:password="true"
                android:text="admin"/>
        </LinearLayout>
        <!-- 登录按钮-->
        <Button
            android:id="@+id/btn_login"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="登录"/>
    </LinearLayout>

</RelativeLayout>


 

 

8.完成登录功能

 1.连接

  ThreadUtils.runInThread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 1.创建连接配置对象
                            ConnectionConfiguration config = new ConnectionConfiguration(HOST, PORT);

                            // 额外的配置(方面我们开发,上线的时候,可以改回来)
                            config.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled);// 明文传输
                            config.setDebuggerEnabled(true);// 开启调试模式,方便我们查看具体发送的内容

                            // 2.开始创建连接对象
                            XMPPConnection conn = new XMPPConnection(config);

                            // 开始连接
                            conn.connect();

                            // 连接成功了
                            // 3.开始登录
                            conn.login(userName, passWord);
                            // 已经成功成功
                            ToastUtils.showToastSafe(LoginActivity.this, "登录成功");

                            finish();
            

                        } catch (XMPPException e) {
                            e.printStackTrace();
                            ToastUtils.showToastSafe(LoginActivity.this, "登录失败");
                        }
						}
						}


 

 

9.登录成功跳转到主界面

1.ToastUtils--吐司工具类

public class ToastUtils {
    /**
     * 可以在子线程中弹出toast
     *
     * @param context
     * @param text
     */
    public static void showToastSafe(final Context context, final String text) {
        ThreadUtils.runInUIThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
            }
        });
    }
}


 

2.登陆完整代码

public class LoginActivity extends ActionBarActivity {

    public static final String HOST = "192.168.1.100";    // 主机ip
    public static final int PORT = 5222;            // 对应的端口号
    public static final String SERVICENAME = "itheima.com";
    private TextView mEtUserName;
    private TextView mEtPassWord;
    private Button mBtnLogin;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        initView();
        initListener();
    }

    private void initListener() {
        mBtnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String userName = mEtUserName.getText().toString();
                final String passWord = mEtPassWord.getText().toString();
                // 判断用户名是否为空
                if (TextUtils.isEmpty(userName)) {// 用户名为空
                    mEtUserName.setError("用户名不能为空");
                    return;
                }
                // 判断密码是否为空
                if (TextUtils.isEmpty(passWord)) {// 用户名为空
                    mEtPassWord.setError("密码不能为空");
                    return;
                }

                ThreadUtils.runInThread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 1.创建连接配置对象
                            ConnectionConfiguration config = new ConnectionConfiguration(HOST, PORT);

                            // 额外的配置(方面我们开发,上线的时候,可以改回来)
                            config.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled);// 明文传输
                            config.setDebuggerEnabled(true);// 开启调试模式,方便我们查看具体发送的内容

                            // 2.开始创建连接对象
                            XMPPConnection conn = new XMPPConnection(config);

                            // 开始连接
                            conn.connect();

                            // 连接成功了
                            // 3.开始登录
                            conn.login(userName, passWord);
                            // 已经成功成功
                            ToastUtils.showToastSafe(LoginActivity.this, "登录成功");

                            finish();
                            // 跳到主界面
                            Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                            startActivity(intent);

                            // 需要保存连接对象
                            IMService.conn = conn;

                            //启动IMService
                            Intent service = new Intent(LoginActivity.this, IMService.class);
                            startService(service);

                        } catch (XMPPException e) {
                            e.printStackTrace();
                            ToastUtils.showToastSafe(LoginActivity.this, "登录失败");
                        }
                    }
                });
            }
        });
    }

    private void initView() {
        mEtUserName = (TextView) findViewById(R.id.et_username);
        mEtPassWord = (TextView) findViewById(R.id.et_password);

        mBtnLogin = (Button) findViewById(R.id.btn_login);
    }
}


 

10.主界面完成,以及ButterKnife的引入

1.主界面布局 activity.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              tools:context="com.itheima.xmpp_20150807.activity.MainActivity">

    <TextView
        android:id="@+id/main_tv_title"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="#ff33b5e5"
        android:gravity="center"
        android:text="会话"
        android:textColor="#fff"
        android:textSize="24sp"
        />

    <android.support.v4.view.ViewPager
        android:id="@+id/main_viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        ></android.support.v4.view.ViewPager>

    <LinearLayout
        android:id="@+id/main_bottom"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:orientation="horizontal">
    </LinearLayout>
</LinearLayout>


2.ButtonKnife插件--------用于寻找布局中的控件

 

11.主界面完成_添加了2个Fragment

 1.主界面MainActivity

public class MainActivity extends ActionBarActivity {
	@InjectView(R.id.main_tv_title)
	TextView				mMainTvTitle;

	@InjectView(R.id.main_viewpager)
	ViewPager				mMainViewpager;

	@InjectView(R.id.main_bottom)
	LinearLayout			mMainBottom;

	// xutils viewutils 注解方式去找控件
	// viewutils httpUitls dbutils bitmaputils
	//

	private List<Fragment>	mFragments	= new ArrayList<Fragment>();
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ButterKnife.inject(this);
		initData();
	}
 
	private void initData() {
		// viewPager-->view-->pagerAdapter
		// viewPager-->fragment-->fragmentPagerAdapter-->fragment数量比较少
		// viewPager-->fragment-->fragmentStatePagerAdapter

		// 添加fragment到集合中
		mFragments.add(new SessionFragment());
		mFragments.add(new ContactsFragment());

		mMainViewpager.setAdapter(new MyPagerAdapter(getSupportFragmentManager()));
 
	}

	class MyPagerAdapter extends FragmentPagerAdapter {

		public MyPagerAdapter(FragmentManager fm) {
			super(fm);
		}

		@Override
		public Fragment getItem(int position) {
			return mFragments.get(position);
		}

		@Override
		public int getCount() {
			return 2;
		}
	}
}


 

12.ToolBarUtils创建底部按钮

1.使用插件自动生成状态选择器

<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/icon_meassage_pressed" android:state_pressed="true"/>
    <item android:drawable="@drawable/icon_meassage_pressed" android:state_selected="true"/>
    <item android:drawable="@drawable/icon_meassage_normal"/>
</selector>

 

2.底部为两个TextView

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:drawableTop="@drawable/selector_meassage"
    android:gravity="center"
    android:orientation="vertical"
    android:text="会话">

</TextView>


3.ToolBarUtil------自动为底部添加TextView

 

public class ToolBarUtil {

    private List<TextView> mTextViews = new ArrayList<TextView>();

    public void createToolBar(LinearLayout container, String[] toolBarTitleArr, int[] iconArr) {

        for (int i = 0; i < toolBarTitleArr.length; i++) {

            TextView tv = (TextView) View.inflate(container.getContext(), R.layout.inflate_toolbar_btn, null);
            tv.setText(toolBarTitleArr[i]);
            // 动态修改textView里面的drawableTop属性
            tv.setCompoundDrawablesWithIntrinsicBounds(0, iconArr[i], 0, 0);

            int width = 0;
            int height = LinearLayout.LayoutParams.MATCH_PARENT;
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width, height);
            //设置weight属性
            params.weight = 1;
            container.addView(tv, params);

            //保存textView到集合中
            mTextViews.add(tv);

            //设置点击事件
            final int finalI = i;
            tv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //不同模块之间传值需要用接口回调

                    //3.需要传值的地方.用接口对象调用接口方法
                    mOnToolBarClickListener.onToolBarClick(finalI);
                }
            });
        }
    }

    public void changeColor(int position) {
        //还原所有的颜色
        for (TextView tv : mTextViews) {
            tv.setSelected(false);
        }

        mTextViews.get(position).setSelected(true);//通过设置selected属性,控制为选中效果
    }
    //1.创建接口和接口方法
    public  interface  OnToolBarClickListener{
        void onToolBarClick(int position);
    }
    //2.定义接口变量
    OnToolBarClickListener mOnToolBarClickListener;

    //4.暴露一个公共的方法
    public void setOnToolBarClickListener(OnToolBarClickListener onToolBarClickListener) {
        mOnToolBarClickListener = onToolBarClickListener;
    }
}


4.MainActivity.java完整

public class MainActivity extends ActionBarActivity {
	@InjectView(R.id.main_tv_title)
	TextView				mMainTvTitle;

	@InjectView(R.id.main_viewpager)
	ViewPager				mMainViewpager;

	@InjectView(R.id.main_bottom)
	LinearLayout			mMainBottom;

	// xutils viewutils 注解方式去找控件
	// viewutils httpUitls dbutils bitmaputils
	//

	private List<Fragment>	mFragments	= new ArrayList<Fragment>();
	private ToolBarUtil		mToolBarUtil;
	private String[]		mToolBarTitleArr;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ButterKnife.inject(this);
		initData();
		initListener();
	}

	private void initListener() {
		mMainViewpager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
			@Override
			public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

			}

			@Override
			public void onPageSelected(int position) {
				// 修改颜色
				mToolBarUtil.changeColor(position);
				// 修改title
				mMainTvTitle.setText(mToolBarTitleArr[position]);
			}

			@Override
			public void onPageScrollStateChanged(int state) {

			}
		});

		mToolBarUtil.setOnToolBarClickListener(new ToolBarUtil.OnToolBarClickListener() {
			@Override
			public void onToolBarClick(int position) {
				mMainViewpager.setCurrentItem(position);
			}
		});
	}

	private void initData() {
		// viewPager-->view-->pagerAdapter
		// viewPager-->fragment-->fragmentPagerAdapter-->fragment数量比较少
		// viewPager-->fragment-->fragmentStatePagerAdapter

		// 添加fragment到集合中
		mFragments.add(new SessionFragment());
		mFragments.add(new ContactsFragment());

		mMainViewpager.setAdapter(new MyPagerAdapter(getSupportFragmentManager()));

		// 底部按钮
		mToolBarUtil = new ToolBarUtil();
		// 文字内容
		mToolBarTitleArr = new String[] { "会话", "联系人" };
		// 图标内容
		int[] iconArr = { R.drawable.selector_meassage, R.drawable.selector_selfinfo };

		mToolBarUtil.createToolBar(mMainBottom, mToolBarTitleArr, iconArr);

		// 设置默认选中会话
		mToolBarUtil.changeColor(0);
	}

	class MyPagerAdapter extends FragmentPagerAdapter {

		public MyPagerAdapter(FragmentManager fm) {
			super(fm);
		}

		@Override
		public Fragment getItem(int position) {
			return mFragments.get(position);
		}

		@Override
		public int getCount() {
			return 2;
		}
	}
}


 

14.联系人数据的获取

 1.ContactsFragment---------------联系人界面

 2.联系人

    每一个用户都有一个Roster列表,包含多个RosterEntry。
    在roster中每个用户用一个RosterEntry表示,它包括:
    一个XMPP地址(例如 livsun@z00189374)。
    一个您分配给用户的昵称(例如 "2b")。
    登陆所属的roster组列表。如果roster登陆不属于任何组,它将被称为“unfiled entry”。 在openfire中一个RosterEntry可以同时属于 多个分组。

 

3.获取联系人

    当用户通过一个连接登录服务器后,用户可以从服务器获取自己的Roster列表。
    Roster roster = conn.getRoster();
    获取Roster后可以通过调用roster.getEntries()方法来获取所有RosterEntry对象,返回类型为Collection<RosterEntry>。
    Collection<RosterEntry> rosterentrys = roster.getEntries();
    如果想获得分组信息,需要调用roster.getGroups(), 返回结果是一个Collection<RosterGroup>。可以在通过rosterGroup.getEntries()获取每个分组的成员。
    对于每个成员的状态信息,如是否在线,签名等,可通过roster.gerPresence(RosterEntry)获取。

 

 

4.在登陆的时候保存连接对象

(1)创建一个类保存连接对象  IMService

public class IMService extends Service {
	public static XMPPConnection	conn;
}


(2)在登陆时保存连接对象

 

// 需要保存连接对象
                            IMService.conn = conn;


(3)获取联系人 

  /**
   * JID=[ node”@” ] domain [ “/” resource ]
   我是hm4: hm4@itheima.com [Friends]   hm4@itheima.com  我是hm4  subscribe  none
   hm2: hm2@itheima.com [Friends]     hm2@itheima.com  hm2  null  both
   hm1: hm1@itheima.com [Friends]     hm1@itheima.com  hm1  null  both
   我是苍老师: hm3@itheima.com [Friends] hm3@itheima.com  我是苍老师  subscribe  none
   */

 

 

16.contactProvider写前回顾分析

1.联系人数据需要存放在数据库中,通过contactProvider暴露

2.为什么联系人数据需要通过contentProvider保存到数据库
   (1). 缓存联系人信息.不用每次从消息通道中获取-->本地缓存
   (2). 可以使用ContentObserver监听数据信息的改变,实时更新UI

 

3.实时更新UI

4.contentProvider回顾

(1) 数据存储
     五种,分别是:Shared Preferences、网络存储、文件存储、外储存储、SQLite
     共享数据的一种机制
(2) 优势
     ContentProvider为存储和获取数据`提供了统一的接口`。ContentProvide对数据进行封装,不用关心数据存储的细节。使用表的形式来组织数据。
     使用ContentProvider可以在不同的应用程序之间`共享数据`。 
     Android为常见的一些数据提供了默认的ContentProvider(包括音频、视频、图片和通讯录等)。
    也就是
    可以是外部应用访问自己的数据
    规范数据的访问(crud)-->add delete update query

 

5.Uri介绍

(1)表示这个ContentProvider所提供的数据
(2)格式:content://com.itheima.www/teacher/1
     标准前缀,用来说明一个Content Provider控制这些数据,无法改变的;"content://"
     URI 的标识,用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的类名。这个标识在 元素的 authorities属性中说明:一般是定义该ContentProvider的包.类的名称
    路径(path),通俗的讲就是你要操作的数据库中`表的名字`,或者你也可以自己定义,记得在使用的时候保持一致就可以了;"content://com.bing.provider.myprovider/tablename"
    如果URI中包含表示需要获取的记录的ID**;则就返回该id对应的数据,如果没有ID,就表示返回全部; "content://com.bing.provider.myprovider/tablename/#" #表示数据id。

 

6.Uri代表了要操作的数据,所以我们经常需要解析Uri,需要使用`UriMatcher类用于匹配Uri`

7.ContentUris类用于`操作Uri`路径后面的ID部分,它有1个比较实用的方法:withAppendedId(uri, id)用于为路径加上ID部分

8.通过ContentResolver得到数据

9.通过ContentObserver监听数据的改变

(1). 创建类继承contentObserver
(2). 重写onChange方法
(3). 注册内容观察者
(4). 反注册内容观察者
(5). 使用contentResolver发起数据的改变的信号

 

16.contactProvider完成

1.四大组件之一,需要在清单文件中配置

2.创建数据库

public class ContactOpenHelper extends SQLiteOpenHelper {
    public static final String T_CONTACT = "t_contact";

    public class ContactTable implements BaseColumns {//就是会默认给我们添加一列  _id
        public static final String ACCOUNT = "account";//账号
        public static final String NICKNAME = "nickname";//昵称
        public static final String AVATAR = "avatar";//头像
        public static final String PINYIN = "pinyin";//账号拼音
    }

    public ContactOpenHelper(Context context) {
        super(context, "contact.db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE " + T_CONTACT + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                ContactTable.ACCOUNT + " TEXT, " +
                ContactTable.NICKNAME + " TEXT, " +
                ContactTable.AVATAR + " TEXT, " +
                ContactTable.PINYIN + " TEXT);";
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}


 

3.内容提供者 ContactsProvider.java

public class ContactsProvider extends ContentProvider {
	// 主机地址的常量-->当前类的完整路径
	public static final String	AUTHORITIES	= ContactsProvider.class.getCanonicalName();			// 得到一个类的完整路径
	// 地址匹配对象
	static UriMatcher			mUriMatcher;

	// 对应联系人表的一个uri常量
	public static Uri			URI_CONTACT	= Uri.parse("content://" + AUTHORITIES + "/contact");

	public static final int		CONTACT		= 1;

	static {
		mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

		// 添加一个匹配的规则
		mUriMatcher.addURI(AUTHORITIES, "/contact", CONTACT);
		// content://com.itheima.xmpp_20150807.provider.ContactsProvider/contact-->CONTACT
	}

	private ContactOpenHelper	mHelper;

	@Override
	public boolean onCreate() {
		mHelper = new ContactOpenHelper(getContext());
		if (mHelper != null) {
			return true;
		}
		return false;
	}

	@Override
	public String getType(Uri uri) {
		return null;
	}

	/*--------------- crud  begin ---------------*/
	@Override
	public Uri insert(Uri uri, ContentValues values) {
		// 数据是存到sqlite-->创建db文件,建立表-->sqliteOpenHelper
		 
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		 
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
		 
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		 
	}

	/*--------------- curd  begin ---------------*/
}


 

17.contactProvider完成_2

1.contactProvoder对数据库进行增删改查

public class ContactsProvider extends ContentProvider {
	// 主机地址的常量-->当前类的完整路径
	public static final String	AUTHORITIES	= ContactsProvider.class.getCanonicalName();			// 得到一个类的完整路径
	// 地址匹配对象
	static UriMatcher			mUriMatcher;

	// 对应联系人表的一个uri常量
	public static Uri			URI_CONTACT	= Uri.parse("content://" + AUTHORITIES + "/contact");

	public static final int		CONTACT		= 1;

	static {
		mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

		// 添加一个匹配的规则
		mUriMatcher.addURI(AUTHORITIES, "/contact", CONTACT);
		// content://com.itheima.xmpp_20150807.provider.ContactsProvider/contact-->CONTACT
	}

	private ContactOpenHelper	mHelper;

	@Override
	public boolean onCreate() {
		mHelper = new ContactOpenHelper(getContext());
		if (mHelper != null) {
			return true;
		}
		return false;
	}

	@Override
	public String getType(Uri uri) {
		return null;
	}

	/*--------------- crud  begin ---------------*/
	@Override
	public Uri insert(Uri uri, ContentValues values) {
		// 数据是存到sqlite-->创建db文件,建立表-->sqliteOpenHelper
		int code = mUriMatcher.match(uri);
		switch (code) {
		case CONTACT:
			SQLiteDatabase db = mHelper.getWritableDatabase();
			// 新插入的id
			long id = db.insert(ContactOpenHelper.T_CONTACT, "", values);
			if (id != -1) {
				System.out.println("---------ContactsProvider-----insertSuccess--------------");
				// 拼接最新的uri
				// content://com.itheima.xmpp_20150807.provider.ContactsProvider/contact/id
				uri = ContentUris.withAppendedId(uri, id);
				// 通知ContentObserver数据改变了
				// getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT,"指定只有某一个observer可以收到");//
				getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT, null);// 为null就是所有都可以收到

			}
			break;

		default:
			break;
		}
		return uri;
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		int code = mUriMatcher.match(uri);
		int deleteCount = 0;
		switch (code) {
		case CONTACT:
			SQLiteDatabase db = mHelper.getWritableDatabase();
			// 影响的行数
			deleteCount = db.delete(ContactOpenHelper.T_CONTACT, selection, selectionArgs);
			if (deleteCount > 0) {
				System.out.println("---------ContactsProvider-----deleteSuccess--------------");
				// 通知ContentObserver数据改变了
				// getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT,"指定只有某一个observer可以收到");//
				getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT, null);// 为null就是所有都可以收到
			}
			break;

		default:
			break;
		}
		return deleteCount;
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
		int updateCount = 0;
		int code = mUriMatcher.match(uri);
		switch (code) {
		case CONTACT:
			SQLiteDatabase db = mHelper.getWritableDatabase();
			// 更新的记录总数
			updateCount = db.update(ContactOpenHelper.T_CONTACT, values, selection, selectionArgs);
			if (updateCount > 0) {
				System.out.println("---------ContactsProvider-----updateSuccess--------------");
				// 通知ContentObserver数据改变了
				// getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT,"指定只有某一个observer可以收到");//
				getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT, null);// 为null就是所有都可以收到
			}
			break;

		default:
			break;
		}
		return updateCount;
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		Cursor cursor = null;
		int code = mUriMatcher.match(uri);
		switch (code) {
		case CONTACT:
			SQLiteDatabase db = mHelper.getReadableDatabase();
			cursor = db.query(ContactOpenHelper.T_CONTACT, projection, selection, selectionArgs, null, null, sortOrder);
			System.out.println("---------ContactsProvider-----querySuccess--------------");
			break;

		default:
			break;
		}
		return cursor;
	}

	/*--------------- curd  begin ---------------*/
}


 

 

18.使用测试用例测试contactProvider

1.测试类

public class TestContactsProvider extends AndroidTestCase {

	public void testInsert() {
		/**
		 public static final String ACCOUNT = "account";//账号
		 public static final String NICKNAME = "nickname";//昵称
		 public static final String AVATAR = "avatar";//头像
		 public static final String PINYIN = "pinyin";//账号拼音
		 */
		ContentValues values = new ContentValues();
		values.put(ContactOpenHelper.ContactTable.ACCOUNT, "billy@itheima.com");
		values.put(ContactOpenHelper.ContactTable.NICKNAME, "老伍");
		values.put(ContactOpenHelper.ContactTable.AVATAR, "0");
		values.put(ContactOpenHelper.ContactTable.PINYIN, "laowu");
		getContext().getContentResolver().insert(ContactsProvider.URI_CONTACT, values);
	}

	public void testDelete() {
		getContext().getContentResolver().delete(ContactsProvider.URI_CONTACT,
				ContactOpenHelper.ContactTable.ACCOUNT + "=?", new String[] { "billy@itheima.com" });
	}

	public void testUpdate() {
		ContentValues values = new ContentValues();
		values.put(ContactOpenHelper.ContactTable.ACCOUNT, "billy@itheima.com");
		values.put(ContactOpenHelper.ContactTable.NICKNAME, "我是老伍");
		values.put(ContactOpenHelper.ContactTable.AVATAR, "0");
		values.put(ContactOpenHelper.ContactTable.PINYIN, "woshilaowu");
		getContext().getContentResolver().update(ContactsProvider.URI_CONTACT, values,
				ContactOpenHelper.ContactTable.ACCOUNT + "=?", new String[] { "billy@itheima.com" });
	}

	public void testQuery() {
		Cursor c = getContext().getContentResolver().query(ContactsProvider.URI_CONTACT, null, null, null, null);
		int columnCount = c.getColumnCount();// 一共多少列
		while (c.moveToNext()) {
			// 循环打印列
			for (int i = 0; i < columnCount; i++) {
				System.out.print(c.getString(i) + "    ");
			}
			System.out.println("");
		}
	}

	public void testPinyin() {
		// String pinyinString = PinyinHelper.convertToPinyinString("内容", "分隔符", 拼音的格式);
		String pinyinString = PinyinHelper.convertToPinyinString("黑马程序员", "", PinyinFormat.WITHOUT_TONE);
		System.out.println(pinyinString);
	}
}

 

 

19.jpinyin使用,同步联系人到contentProvider.使用cursorAdapter展示联系人

1.使用jar包把中文转换成拼音

2.ContactsProvider.java

public class ContactsProvider extends ContentProvider {
	// 主机地址的常量-->当前类的完整路径
	public static final String	AUTHORITIES	= ContactsProvider.class.getCanonicalName();			// 得到一个类的完整路径
	// 地址匹配对象
	static UriMatcher			mUriMatcher;

	// 对应联系人表的一个uri常量
	public static Uri			URI_CONTACT	= Uri.parse("content://" + AUTHORITIES + "/contact");

	public static final int		CONTACT		= 1;

	static {
		mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

		// 添加一个匹配的规则
		mUriMatcher.addURI(AUTHORITIES, "/contact", CONTACT);
		// content://com.itheima.xmpp_20150807.provider.ContactsProvider/contact-->CONTACT
	}

	private ContactOpenHelper	mHelper;

	@Override
	public boolean onCreate() {
		mHelper = new ContactOpenHelper(getContext());
		if (mHelper != null) {
			return true;
		}
		return false;
	}

	@Override
	public String getType(Uri uri) {
		return null;
	}

	/*--------------- crud  begin ---------------*/
	@Override
	public Uri insert(Uri uri, ContentValues values) {
		// 数据是存到sqlite-->创建db文件,建立表-->sqliteOpenHelper
		int code = mUriMatcher.match(uri);
		switch (code) {
		case CONTACT:
			SQLiteDatabase db = mHelper.getWritableDatabase();
			// 新插入的id
			long id = db.insert(ContactOpenHelper.T_CONTACT, "", values);
			if (id != -1) {
				System.out.println("---------ContactsProvider-----insertSuccess--------------");
				// 拼接最新的uri
				// content://com.itheima.xmpp_20150807.provider.ContactsProvider/contact/id
				uri = ContentUris.withAppendedId(uri, id);
				// 通知ContentObserver数据改变了
				// getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT,"指定只有某一个observer可以收到");//
				getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT, null);// 为null就是所有都可以收到

			}
			break;

		default:
			break;
		}
		return uri;
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		int code = mUriMatcher.match(uri);
		int deleteCount = 0;
		switch (code) {
		case CONTACT:
			SQLiteDatabase db = mHelper.getWritableDatabase();
			// 影响的行数
			deleteCount = db.delete(ContactOpenHelper.T_CONTACT, selection, selectionArgs);
			if (deleteCount > 0) {
				System.out.println("---------ContactsProvider-----deleteSuccess--------------");
				// 通知ContentObserver数据改变了
				// getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT,"指定只有某一个observer可以收到");//
				getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT, null);// 为null就是所有都可以收到
			}
			break;

		default:
			break;
		}
		return deleteCount;
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
		int updateCount = 0;
		int code = mUriMatcher.match(uri);
		switch (code) {
		case CONTACT:
			SQLiteDatabase db = mHelper.getWritableDatabase();
			// 更新的记录总数
			updateCount = db.update(ContactOpenHelper.T_CONTACT, values, selection, selectionArgs);
			if (updateCount > 0) {
				System.out.println("---------ContactsProvider-----updateSuccess--------------");
				// 通知ContentObserver数据改变了
				// getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT,"指定只有某一个observer可以收到");//
				getContext().getContentResolver().notifyChange(ContactsProvider.URI_CONTACT, null);// 为null就是所有都可以收到
			}
			break;

		default:
			break;
		}
		return updateCount;
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		Cursor cursor = null;
		int code = mUriMatcher.match(uri);
		switch (code) {
		case CONTACT:
			SQLiteDatabase db = mHelper.getReadableDatabase();
			cursor = db.query(ContactOpenHelper.T_CONTACT, projection, selection, selectionArgs, null, null, sortOrder);
			System.out.println("---------ContactsProvider-----querySuccess--------------");
			break;

		default:
			break;
		}
		return cursor;
	}

	/*--------------- curd  begin ---------------*/
}


 

20.listview修改了根布局

 

21.完成RosterListener

1.监听联系人的变化

		ThreadUtils.runInThread(new Runnable() {
			@Override
			public void run() {
				/*=============== 同步花名册 begin ===============*/
				System.out.println("--------------同步花名册 begin--------------");
				// 需要连接对象
				// 得到花名册对象
				mRoster = IMService.conn.getRoster();

				// 得到所有的联系人
				final Collection<RosterEntry> entries = mRoster.getEntries();

				// 监听联系人的改变
				mRosterListener = new MyRosterListener();
				mRoster.addRosterListener(mRosterListener);

				for (RosterEntry entry : entries) {
					saveOrUpdateEntry(entry);
				}
				System.out.println("--------------同步花名册 end--------------");
				/*=============== 同步花名册 end ===============*/
			}
		});

监听

	class MyRosterListener implements RosterListener {

		@Override
		public void entriesAdded(Collection<String> addresses) {// 联系人添加了
 
			}
		}

		@Override
		public void entriesUpdated(Collection<String> addresses) {// 联系人修改了
			System.out.println("--------------entriesUpdated--------------");
		 
		}

		@Override
		public void entriesDeleted(Collection<String> addresses) {// 联系人删除了
	 
		}

		@Override
		public void presenceChanged(Presence presence) {// 联系人状态改变
 
		}
	}


 


22.使用contentObserver时刻监听联系人的变化

1.如果数据库数据改变了,监听

class MyContentObserver extends ContentObserver {

		public MyContentObserver(Handler handler) {
			super(handler);
		}

		/**
		 * 如果数据库数据改变会在这个方法收到通知
		 */
		@Override
		public void onChange(boolean selfChange, Uri uri) {
			super.onChange(selfChange, uri);
			// 更新adapter或者刷新adapter
			setOrUpdateAdapter();
		}
	}


2.设置或者更新adapter

 

/**
	 * 设置或者更新adapter
	 */
	private void setOrUpdateAdapter() {
		// 判断adapter是否存在
		if (mAdapter != null) {
			// 刷新adapter就行了
			mAdapter.getCursor().requery();
			return;
		}
		ThreadUtils.runInThread(new Runnable() {
			@Override
			public void run() {
				// 对应查询记录
				final Cursor c =
						getActivity().getContentResolver().query(ContactsProvider.URI_CONTACT, null, null, null, null);

				// 假如没有数据的时候
				if (c.getCount() <= 0) {
					return;
				}
				// 设置adapter,然后显示数据
				ThreadUtils.runInUIThread(new Runnable() {
					@Override
					public void run() {
						/**
						 BaseAdapter    -->getView
						 |-CursorAdapter
						 */
						mAdapter = new CursorAdapter(getActivity(), c) {
							// 如果convertView==null,返回一个具体的根视图
							@Override
							public View newView(Context context, Cursor cursor, ViewGroup parent) {
								View view = View.inflate(context, R.layout.item_contact, null);
								return view;
							}

							// 设置数据显示数据
							@Override
							public void bindView(View view, Context context, Cursor cursor) {
								ImageView ivHead = (ImageView) view.findViewById(R.id.head);
								TextView tvAccount = (TextView) view.findViewById(R.id.account);
								TextView tvNickName = (TextView) view.findViewById(R.id.nickname);

								String acccount =
										cursor.getString(c.getColumnIndex(ContactOpenHelper.ContactTable.ACCOUNT));

								String nickName =
										cursor.getString(c.getColumnIndex(ContactOpenHelper.ContactTable.NICKNAME));

								tvAccount.setText(acccount);
								tvNickName.setText(nickName);
							}
						};

						mListView.setAdapter(mAdapter);
					}
				});
			}
		});
	}


3.监听数据库的变化

	/*=============== 监听数据库记录的改变 ===============*/

	MyContentObserver	mMyContentObserver	= new MyContentObserver(new Handler());

	/**注册监听*/
	public void registerContentObserver() {
		// content://xxxx/contact
		// content://xxxx/contact/i
		getActivity().getContentResolver().registerContentObserver(ContactsProvider.URI_CONTACT, true,
				mMyContentObserver);
	}

	public void unRegisterContentObserver() {
		getActivity().getContentResolver().unregisterContentObserver(mMyContentObserver);
	}

	/**反注册监听*/

	class MyContentObserver extends ContentObserver {

		public MyContentObserver(Handler handler) {
			super(handler);
		}

		/**
		 * 如果数据库数据改变会在这个方法收到通知
		 */
		@Override
		public void onChange(boolean selfChange, Uri uri) {
			super.onChange(selfChange, uri);
			// 更新adapter或者刷新adapter
			setOrUpdateAdapter();
		}
	}
}


 

4.断点调试

 

 

 23.roster同步和监听操作放到Service里面

1.目的

	@Override
	public void onDestroy() {
		// 按照常理,我们Fragment销毁了.那么我们就不应该去继续去监听
		// 但是,实际,我们是需要一直监听对应roster的改变
		// 所以,我们把联系人的监听和同步操作放到Service去
		unRegisterContentObserver();
		super.onDestroy();
	}


 

2.IMService.java

public class IMService extends Service {
	public static XMPPConnection	conn;

	private Roster					mRoster;
	private MyRosterListener		mRosterListener;

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onCreate() {
		System.out.println("--------------service onCreate--------------");
		ThreadUtils.runInThread(new Runnable() {
			@Override
			public void run() {
				/*=============== 同步花名册 begin ===============*/
				System.out.println("--------------同步花名册 begin--------------");
				// 需要连接对象
				// 得到花名册对象
				mRoster = IMService.conn.getRoster();

				// 得到所有的联系人
				final Collection<RosterEntry> entries = mRoster.getEntries();

				// 监听联系人的改变
				mRosterListener = new MyRosterListener();
				mRoster.addRosterListener(mRosterListener);

				for (RosterEntry entry : entries) {
					saveOrUpdateEntry(entry);
				}
				System.out.println("--------------同步花名册 end--------------");
				/*=============== 同步花名册 end ===============*/
			}
		});

		super.onCreate();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		System.out.println("--------------service onStartCommand--------------");
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public void onDestroy() {
		System.out.println("--------------service onDestroy--------------");
		// 移除rosterListener
		if (mRoster != null && mRosterListener != null) {
			mRoster.removeRosterListener(mRosterListener);
		}
		super.onDestroy();
	}

	class MyRosterListener implements RosterListener {

		@Override
		public void entriesAdded(Collection<String> addresses) {// 联系人添加了
			System.out.println("--------------entriesAdded--------------");
			// 对应更新数据库
			for (String address : addresses) {
				RosterEntry entry = mRoster.getEntry(address);
				// 要么更新,要么插入
				saveOrUpdateEntry(entry);
			}
		}

		@Override
		public void entriesUpdated(Collection<String> addresses) {// 联系人修改了
			System.out.println("--------------entriesUpdated--------------");
			// 对应更新数据库
			for (String address : addresses) {
				RosterEntry entry = mRoster.getEntry(address);
				// 要么更新,要么插入
				saveOrUpdateEntry(entry);
			}
		}

		@Override
		public void entriesDeleted(Collection<String> addresses) {// 联系人删除了
			System.out.println("--------------entriesDeleted--------------");
			// 对应更新数据库
			for (String account : addresses) {
				// 执行删除操作
				getContentResolver().delete(ContactsProvider.URI_CONTACT,
						ContactOpenHelper.ContactTable.ACCOUNT + "=?", new String[] { account });
			}

		}

		@Override
		public void presenceChanged(Presence presence) {// 联系人状态改变
			System.out.println("--------------presenceChanged--------------");
		}
	}

	private void saveOrUpdateEntry(RosterEntry entry) {
		ContentValues values = new ContentValues();
		String account = entry.getUser();

		// account = account.substring(0, account.indexOf("@")) + "@" + LoginActivity.SERVICENAME;

		// 处理昵称
		String nickname = entry.getName();
		if (nickname == null || "".equals(nickname)) {
			nickname = account.substring(0, account.indexOf("@"));// billy@itheima.com-->billy
		}

		values.put(ContactOpenHelper.ContactTable.ACCOUNT, account);
		values.put(ContactOpenHelper.ContactTable.NICKNAME, nickname);
		values.put(ContactOpenHelper.ContactTable.AVATAR, "0");
		values.put(ContactOpenHelper.ContactTable.PINYIN, PinyinUtil.getPinyin(account));

		// 先update,后插入-->重点
		int updateCount =
				getContentResolver().update(ContactsProvider.URI_CONTACT, values,
						ContactOpenHelper.ContactTable.ACCOUNT + "=?", new String[] { account });
		if (updateCount <= 0) {// 没有更新到任何记录
			getContentResolver().insert(ContactsProvider.URI_CONTACT, values);
		}
	}
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值