根据 XMPP 的 XEP 标准协议规范,实现 avatar 头像传输与存储的功能主要有三种实现方式,分别对应于协议规范:

    + 【XEP-0153】vCard-BasedAvatars     http://xmpp.org/extensions/xep-0153.html

    + 【XEP-0084】UserAvatar             http://xmpp.org/extensions/xep-0084.html
   + 【XEP-0008】IQ-BasedAvatars        
http://xmpp.org/extensions/xep-0008.html

其中

    + XEP-0153是通过将 avatar 头像存储在 vcard 的 XML 报文中实现的,这个也是 openfire 和 spark 中支持的方式;

在 openfire 中的 vcard 的实现都在 org.jivesoftware.openfire.vcard 包中,其中:


       -
用户的 vcard 的存储实现在类 DefaultVCardProvider 中处理了 vcard 的查询,删除,更新,新增等 DB 操作;
       - 在VCardManager 中实现对 vcard 的缓存与管理(包括新增,删除,更新,以及查询);

这种实现方式比较直接,在服务端就是将用户的 vcard(XML格式)信息一起存储在表(ofVcard)中,示例:

<vCard  xmlns="vcard-temp">
 <N>
     <FAMILY/>
     <GIVEN/>
     <MIDDLE/>
 </N>
 <ORG>
     <ORGNAME/>
     <ORGUNIT/>
 </ORG>
 <FN/>
 <URL/>
 <TITLE/>
 <NICKNAME/>

<PHOTO><TYPE>p_w_picpath/jpeg</TYPE><BINVAL>/9j/4AAQSkZJRgABAQEAYABgAAD......YlFFFM4T//2Q==</BINVAL></PHOTO>

 <EMAIL>
     <HOME/>
     <INTERNET/>
     <PREF/>
     <USERID/>
 </EMAIL>
 <TEL><PAGER/><WORK/><NUMBER/>
 </TEL>
 <TEL><CELL/><WORK/><NUMBER/>
 </TEL>
 <TEL><VOICE/><WORK/><NUMBER/>
 </TEL>
 <TEL><FAX/><WORK/><NUMBER/>
 </TEL>
 <TEL><PAGER/><HOME/><NUMBER/>
 </TEL>
 <TEL><CELL/><HOME/><NUMBER/>
 </TEL>
 <TEL><VOICE/><HOME/><NUMBER/>
 </TEL>
 <TEL><FAX/><HOME/><NUMBER/>
 </TEL>
 <ADR><WORK/><PCODE/>
 <REGION/>
 <STREET/>
 <CTRY/>
 <LOCALITY/>
 </ADR>
 <ADR><HOME/><PCODE/>
 <REGION/>
 <STREET/>
 <CTRY/>
 <LOCALITY/>
 </ADR>
 </vCard>

    + XEP-0008的 IQ-Based Avatars 实现方式现在已不被推荐,用官方协议来说:

WARNING: Consideration of this document  has been Deferred by the XMPP Standards Foundation. Implementation of the  protocol described herein is not recommended

    + XEP-0084 User Avatar 是通过基于 pubsub 协议的基础上实现用户 头像 的发布(publish) 与 其他用户的订阅(subscribe);这也是 beem 的实现方式(beem 中也提供了直接通过 url 的方式下载头像);

在 User Avatar 的协议中定义了两个 pubsub 节点,分别为:


      - metadata 节点:主要包括 avatar 的状态信息;
      - data 节点:就是 avatar 的数据;

该协议也指出可以通过 HTTP 协议方式访问 avatar 的存储;

按照官方协议说法,该协议的实现方式可能要替代其他两种实现方式:

  It is intended that this specification will supersede both IQ-Based Avatars [6]and vCard-Based Avatars [7] once the PEP subset of XMPP publish-subscribe isimplemented and deployed widely enough.

针对 user avatar 方式的实现,针对 publisher 与 subscriber 至少需要完成如下功能:

      - Publishing avatar data
      - Updating metadata about the current avatar
      - Disabling avatars

      - Discovering avatar availability
      - Receiving notification of avatar changes
      - Retrieving avatar data via pubsub
      - Retrieving avatar data via HTTP

上面只是对实现avatar相关XEP协议做一个初步的了解,我这里的实例仍然“偷懒”采用了VCard方式实现。

协议参考: http://xmpp.org/extensions/xep-0054.html

Smack 中的 VCard API 参考: http://www.igniterealtime.org/builds/smack/docs/latest/javadoc/

1,设置用户blueVCard中的头像avatar信息:

a)首先确认ProviderManager已经加入vcard-temp,如下代码:


ProviderManager pm =  ProviderManager.getInstance();


        // Private Data Storage

        pm.addIQProvider("query", "jabber:iq:private", new  PrivateDataManager.PrivateDataIQProvider());


        // Roster Exchange

        pm.addExtensionProvider("x", "jabber:x:roster",  new RosterExchangeProvider());


        // Message Events

        pm.addExtensionProvider("x", "jabber:x:event", new  MessageEventProvider());


        // Delayed Delivery

        pm.addExtensionProvider("x", "jabber:x:delay", new  DelayInformationProvider());


        // Version

        try {

            pm.addIQProvider("query", "jabber:iq:version",

                    Class.forName("org.jivesoftware.smackx.packet.Version"));

        } catch (ClassNotFoundException e) {

           // Not sure what's happening  here.

        }


//  VCard

pm.addIQProvider("vCard",  "vcard-temp", new VCardProvider());


        // Offline Message Requests

        pm.addIQProvider("offline",  "http://jabber.org/protocol/offline", new  OfflineMessageRequest.Provider());

b)设置用户选择的头像(其中还附带演示了设置用户blue的其他信息,如FirstNameLastName,以及NickName),如下示例代码:


    public class SetVCardTask extends AsyncTask<Uri, Integer, Long>

    {

        @Override

        protected Long doInBackground(Uri... params)

        {

           if (params.length < 1) {

               return Long.valueOf(-1);

           }


           Uri uriFile = params[0]; // 需要传输的头像文件

           ByteArrayOutputStream baos = new  ByteArrayOutputStream();

           FileInputStream fis;

           try

           {

               String[] proj = {  MediaStore.Images.Media.DATA };

               Cursor actualp_w_picpathcursor =  managedQuery(uriFile,proj,null,null,null);

               int actual_p_w_picpath_column_index

                       =  actualp_w_picpathcursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);

                actualp_w_picpathcursor.moveToFirst();

               String filePath =  actualp_w_picpathcursor.getString(actual_p_w_picpath_column_index);

               fis = new FileInputStream(new  File(filePath));

               byte[] buf = new byte[1024];

               int n;

               while (-1 != (n =  fis.read(buf)))

               {

                   baos.write(buf, 0, n);

               }

           }

           catch (Exception e)

           {

               e.printStackTrace();

           }

           byte[] bbytes =  baos.toByteArray();


// 设置和更新用户信息

VCard vCard = new VCard();

            vCard.setFirstName("Steven");

            vCard.setLastName("Hu");

            vCard.setNickName("安静的疯子");

vCard.setAvatar(bbytes);

           try

           {

               vCard.save(MainHelloIM.getInstance().getConnection());

           }

           catch (XMPPException e)

           {

               e.printStackTrace();

           }


           return Long.valueOf(0);

        }

    }


c)最终在服务端的数据库中可以看到如下数据(其中可以看到用户昵称也都设置成功了):

wKioL1MAGAqBKJLCAAojcJ8C17E269.jpg

d)通过spark登录成功后,可以看到头像已经更新如下:

   wKiom1MAF2vD6id2AABUBgbN3J0032.jpg

2,查看用户blueVCard信息

a)首先确认ProviderManager已经加入vcard-temp,同上;

b)采用异步任务来获取用户blueVCard信息中的昵称

    public class GetVCardTask extends AsyncTask<String, Integer,  Long>

    {

        @Override

        protected Long doInBackground(String... params)

        {

           if (params.length < 1) {

               return Long.valueOf(-1);

           }


// 获取用户 params[0] vcard 信息

           try

           {

// load(Connection connection, String  user)

vcard.load(MainHelloIM.getInstance().getConnection(),  params[0]);

               Log.d(TAG, "nickname:  " + vcard.getNickName());

           }

           catch (XMPPException e)

           {

               e.printStackTrace();

                return Long.valueOf(-2);

           }


           return Long.valueOf(0);

        }