閱讀611 返回首頁    go 阿裏雲 go 技術社區[雲棲]


XMPP協議Asmack實現文件傳輸的真正解決方法

在使用Asmack的過程中,文件傳輸是阻礙我前行的一個極大的絆腳石。在翻遍了百度和Google的情況下,依然不得其解。而偶然的一個驀然回首,我卻解決了這個問題。所以,立此貼,一來為自己記錄;而來希望能夠幫到後來者。我希望自己能夠做出一個像微信一樣的即時通信,可以發送各種富媒體,接下來看我怎麼做,實現文件傳輸。



第一點,版本問題。

Asmack是一個開源庫,地址:https://github.com/Flowdalic/asmack,如大家所看到的一樣,這個GitHub並不是完整的代碼,你需要通過Grail去構建,而且現在的代碼是4.0.3了,我反正不知道怎麼用,網上查到的都是大家用的那個什麼Cloudy版本的,大概是在2010年左右出的,所以和4.0.3有差距。我後來選擇了https://asmack.freakempire.de/0.8.1.1/  ,對,就是0.8.1.1,。。我之所以要說明版本是因為,不同版本,變化挺大的,特別是4.XX和0.xx,相差非常大。


第二點,Jar還是源碼

國內大多數都套用一個叫asmack-jse-buddycloud-2010.12.11.jar的jar包,然後所有的可查資料,貼子,基本和這個相關。但是,如果你需要深入了解Asmcack,我建議是選擇源碼。這也是我為什麼要選擇0.8.1.1的原因,版本低,一般而言相對簡單,而且有源碼,我們就可以打日誌跟蹤。


第三點,一步步教你如何使用Asmack傳輸文件


1)平台初始化。

這是隔住我問題的所在,導致我怎麼也查不到問題所在的地方。

你需要調用AndroidSmack進行平台的初始化。記住這是使用Asmack必須的一步。我們很容易忽略它。至於它做了什麼,我現在可以告訴大家它用AppClassLoader加載了一些類,而觸發執行這些類的靜態代碼。詳細的過程和技術細節我後續會再開一個篇來講解。因為那個技術還挺難的。

public class SanLiaoApplication extends Application {
	private List<Activity> activityList = new LinkedList<Activity>();
	private SmackAndroid smackAndroid;//必須在APP的Application類中初始化平台

	public void onCreate()
	{
		super.onCreate();
		//使用log4j開啟日誌係統
		LogConfigurator logConfigurator =new LogConfigurator();
		SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
		 java.util.Date date=new java.util.Date();
		 String dateString=date.getYear()+"_"+(date.getMonth()+1)+"_"+date.getDate()+"_"+date.getHours()+"_"+date.getMinutes()+"_"+date.getSeconds();
	 
		String pathString=Environment.getExternalStorageDirectory()+
				File.separator+"sanliao"+File.separator+"logs"+File.separator+"sanliao"+dateString+".log";
		Log.d("EIM",pathString);
		logConfigurator.setFileName(pathString);
		logConfigurator.setRootLevel(Level.DEBUG	);
		logConfigurator.setLevel("org.apache", Level.ERROR);
		logConfigurator.setFilePattern("%d %t %-5p [%c{2}]-[%L] [%M] %m%n");
		logConfigurator.setMaxFileSize(1024*1024*5);
		logConfigurator.setImmediateFlush(true);
		logConfigurator.configure();
		Logger logger =Logger.getLogger(EimApplication.class);
		logger.info("EIM APP is created info");
		logger.debug("EIM APP is creatd debug");
		logger.error("EIM APP is create error");
		smackAndroid=SmackAndroid.init(getApplicationContext());//初始化Asmack平台,必須的,否則會導致後續傳輸文件中出現iterm not find (404)錯誤。
		
		XMPPConnection.DEBUG_ENABLED=true;
	
		logger.debug("XMPPConnectionDebug enable");
	 
	}
	
	// 添加Activity到容器中
	public void addActivity(Activity activity) {
		activityList.add(activity);
	}

	// 遍曆所有Activity並finish
	public void exit() {
		XmppConnectionManager.getInstance().disconnect();
		smackAndroid.onDestroy();
		for (Activity activity : activityList) {
			activity.finish();
		}
	}
}

上述代碼其實主要包含三個部分:1)初始化log4j日誌係統;2)初始化Asmack平台;3)將Activity都放到一個list,這個主要是為了管理Activity,在退出等處理上可以用到。


2)初始化FileTransferManager

這一步其實在網上大家都能夠查到。但是我有必要提醒的是,至少0.8.1.1版本中,網上的代碼通常會讓你根本跑步起來。

先看代碼。

	//if you need transport  file . you need call this method after connect and login
	public   boolean   initFileTransport()
	{
		if(connection==null)
		{
			return false;
		}
		else if(fileTransferManager!=null)
		{
			return true;
		}
		else
		{
			//fileTransferManager = new FileTransferManager(connection);//網上的放在這裏,這回導致Asmack崩潰
			ServiceDiscoveryManager sdManager= ServiceDiscoveryManager.getInstanceFor(connection);
			if(sdManager==null)
			{
				sdManager=new ServiceDiscoveryManager(connection);
			}
			sdManager.addFeature("https://jabber.org/protocol/disco#info");
			sdManager.addFeature("jabber:iq:privacy");
			FileTransferNegotiator.setServiceEnabled(connection, true);
			 fileTransferManager = new FileTransferManager(connection);//在0.8.1.1版本(之後應該也是如此),正確的應該是放在這裏。
			
	 
				return true;
		}
	}

我把初始化都封裝到一個函數裏麵。需要提醒三點:

(1)上麵代碼裏麵的順序和網上的一般不同,希望大家注意;

(2),我把fileTransferManager 當做XMPPConnection的一個成員。我之所以這麼改是因為connection是全局的,你可以在你的其他全局(至少生命期和APP基本是同步)的,而不是該XMPPConnect的代碼。所以,這是設計問題。

(3)這個函數的調用要在login登入之後調用。

為了給大家一個完整的上下文,我這裏還是巨細無比的給出登入的代碼。

// 登錄
	private Integer login() {
		String username = loginConfig.getUsername();
		String password = loginConfig.getPassword();
		Log.d(LOG_TAG, "login now");
		try {
			XMPPConnection connection = XmppConnectionManager.getInstance().getConnection();
			Log.d(LOG_TAG,"login c2");
			logger.debug("XMPPConnection create ");
			connection.connect();
			logger.debug("XMPPConnection connected ");
			Log.d(LOG_TAG,"login c3");
			logger.debug("username:"+username+",passwd:"+password);
			connection.login(username, password); // 登錄
			logger.debug("XMPPConnection logined ");
			//init the file transport   can not put it here
			XmppConnectionManager.getInstance().initFileTransport();//文件傳輸管理的初始化
		 
			// OfflineMsgManager.getInstance(activitySupport).dealOfflineMsg(connection);//處理離線消息
			connection.sendPacket(new Presence(Presence.Type.available));
			Log.d(LOG_TAG, "login c5");
			if (loginConfig.isNovisible()) {// 隱身登錄
				Presence presence = new Presence(Presence.Type.unavailable);
				Collection<RosterEntry> rosters = connection.getRoster()
						.getEntries();
				for (RosterEntry rosterEntry : rosters) {
					presence.setTo(rosterEntry.getUser());
					connection.sendPacket(presence);
				}
			}
			loginConfig.setUsername(username);
			if (loginConfig.isRemember()) {// 保存密碼
				loginConfig.setPassword(password);
			} else {
				loginConfig.setPassword("");
			}
			loginConfig.setOnline(true);
			
			return Constant.LOGIN_SECCESS;
		} catch (Exception xee) {
			Log.d(LOG_TAG,xee.toString() );
			if (xee instanceof XMPPException) {
				XMPPException xe = (XMPPException) xee;
				Log.d(LOG_TAG,xe.toString() );
				final XMPPError error = xe.getXMPPError();
			 
				int errorCode = 0;
				if (error != null) {
					errorCode = error.getCode();
	 
				}
				if (errorCode == 401) {
					return Constant.LOGIN_ERROR_ACCOUNT_PASS;
				}else if (errorCode == 403) {
					return Constant.LOGIN_ERROR_ACCOUNT_PASS;
				} else {
					return Constant.SERVER_UNAVAILABLE;
				}
			} else {
				return Constant.LOGIN_ERROR;
			}
		}
	}

到這裏,初始化工作算是告一段落。現在,接下來就是要實現如何傳輸了。

3)發送方。

發送圖片為例子。



如上圖所示,我們希望的是點擊“圖片”的時候,我們就可以選擇圖片,選擇完圖片後,就可以把圖片發送出去。相信,這就是我們需要的,畢竟這就是和微信一樣的。

首先“圖片”控件需要響應點擊。

///////transport image button
			tupianBtn = (ImageButton)getActivity().findViewById(R.id.imageButton_tupian);
			tupianBtn.setOnClickListener(new OnClickListener() {
				@Override
				public void onClick(View v) {
					// TODO Auto-generated method stub
					 Toast.makeText(getActivity(), "send file",  Toast.LENGTH_SHORT).show();
					 Intent intent=new Intent();
					 intent.setType("image/*");//選擇圖片,這個采用係統的意圖。
					intent.setAction(Intent.ACTION_GET_CONTENT);
					startActivityForResult(intent, TUPIAN_RESULT);//選擇完之後,會回到本Activity來。
				 
				}
			});
選擇完圖片後,會回到Activity,所以,我們如下處理。
public void onActivityResult(int requestCode, int resultCode,Intent data) {
		if(requestCode==TUPIAN_RESULT)
		{
			Uri uri=data.getData();
			String[] proj={MediaStore.Images.Media.DATA};
			Cursor cursor=getActivity().managedQuery(uri,proj,null,null,null);
			int index=cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
			cursor.moveToFirst();
			String img_path=cursor.getString(index);
			Log.d("FragMultiFunc","img_path:"+img_path);
			String toId=((AChatActivity)getActivity()).getTo();
			new SendFileTask().execute(img_path,toId);//圖片路徑和我要向誰發送。
			
		}
	}

對,開一個AsynCTask來發送圖片,給任務傳輸兩個參數,圖片文件的路徑和我要向誰發送。接著,我給大家看這個SendFileTask的所有代碼。

public class SendFileTask extends AsyncTask<String, Integer, Integer>{
	private final static Logger logger=Logger.getLogger(SendFileTask.class);
	private final static String TAG="SendFileTask";
	@Override
	protected Integer doInBackground(String... params) {
		// TODO Auto-generated method stub
	       if  (params.length < 2) {
	    	   Log.d(TAG,"parameter invalide");
               return Integer.valueOf(-1);
          }
	       String img_path=params[0];
	       String toId=params[1]+"/Smack";
	       Log.d(TAG,"img_path:"+img_path);
	       logger.debug("img_path:"+img_path);
	     
	     //  XmppConnectionManager.getInstance().initFileTransport();
	       FileTransferManager fileTransferManager=XmppConnectionManager.getInstance().getFileTransferManager();//獲取文件傳輸管理對象(它被我寫成XMPPConnect裏麵了)
	       if(fileTransferManager==null)
	       {
	    	   Log.d(TAG, "get FileTransferManager failed");
	    	   return -1 ;
	       }
	       File filetosend= new File(img_path);
	       if(filetosend.exists()==false)
	       {
	    	   Log.d(TAG,"file:"+img_path+" is  not exist");
	    	   return -1;
	       }
	       Log.d(TAG,"to user:"+toId);
	       OutgoingFileTransfer oft=fileTransferManager.createOutgoingFileTransfer(toId);//創建一個輸出文件傳輸對象
	       try {
			oft.sendFile(filetosend, "recv img");
			/////
			  while(!oft.isDone())
		       {
		    	   if (oft.getStatus().equals(FileTransfer.Status.error))
	               {
	                   Log.e(TAG, "send failed");
	               }
	               else
	               {
	                   Log.i(TAG, "status:" + oft.getStatus() + "|progress:" +  oft.getProgress());//打印進度
	               }
		    	   try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		       }
		} catch (XMPPException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
 
          return 0;
          
	}
}
上述代碼是在一個AsyncTask來發送我們的文件,同時每隔1秒打印下進度。(後續可以傳遞給UI來顯示)

這就是發送端。

那麼接受端呢?

4)接收方

對於接收方,我在看網上的代碼的時候,我最為痛苦的是,接收方的代碼放在哪裏。而不是接收方代碼本身如何寫。有人把它當做一個服務,畢竟接受是屬於“listener”,我試過,至少我沒有成功的跑起來。或許是因為我當時沒有進行平台初始化,不過這都不管了。我就告訴我能行的方法。我覺得當你選擇一個好友進行對話的時候,就可以監聽。

所以,我在和好友的對話ChatActivity一開始,我就設置文件監聽。在Oncreate中調用如下init函數。

	public void init()
	{
		FragChatTitle fragChatTitle = new FragChatTitle();  
        getFragmentManager().beginTransaction().replace(R.id.chat_title_layout,fragChatTitle).commit();  
        FragChatHistory fragChatHistory=new FragChatHistory();
       getFragmentManager().beginTransaction().replace(R.id.chat_history_layout,fragChatHistory).commit();  
        FragChatBottom fragChatBottom=new FragChatBottom();
        getFragmentManager().beginTransaction().replace(R.id.chat_bottom_layout,fragChatBottom).commit();  
        FragMultiFunc fragMultiFunc=new FragMultiFunc();
        getFragmentManager().beginTransaction().replace(R.id.chat_multifunc_layout,fragMultiFunc).hide(fragMultiFunc).commit();  
        
        
        ///add listener for file transport listener
        XMPPConnection connection = XmppConnectionManager.getInstance().getConnection();
        FileTransferManager manager = XmppConnectionManager.getInstance().getFileTransferManager();
      
        ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection);  
        if (sdm == null)  
            sdm = new ServiceDiscoveryManager(connection);  
        sdm.addFeature("https://jabber.org/protocol/disco#info");  
        sdm.addFeature("jabber:iq:privacy");  
        // Create the file transfer manager  
 
        FileTransferNegotiator.setServiceEnabled(connection, true);  
        
        
        manager.addFileTransferListener(new FileTransferListener() {
		@Override
		public void fileTransferRequest(final FileTransferRequest request) {
			Log.d("NewChatActivity","recv file");
			// TODO Auto-generated method stub
            new Thread(){
                @Override
                public void run() {
                   IncomingFileTransfer transfer = request.accept();
                   Log.d(LOG_TAG,"file_name:"+transfer.getFileName());
                   File mf = Environment.getExternalStorageDirectory();
                 String save_path=  Environment.getExternalStorageDirectory()+File.separator+"Eim"+File.separator+transfer.getFileName();
                   File file = new File(save_path);
                   try{
                       transfer.recieveFile(file);
                       while(!transfer.isDone()) {
                          try{
                             Thread.sleep(1000L);
                          }catch (Exception e) {
                             Log.e("NewChat", e.getMessage());
                          }
                          if(transfer.getStatus().equals(Status.error)) {
                             Log.e("ERROR!!! ", transfer.getError() +"");
                          }
                          if(transfer.getException() != null) {
                             transfer.getException().printStackTrace();
                          }
                       }
                    }catch (Exception e) {
                       Log.e("NewChatActivit", e.getMessage());
                   }
                };
              }.start();
		}
         });
        
	}

這個函數會被Activity的Oncreate調用。這樣,完整的Asmack文件傳輸就可以跑起來了。成功傳輸文件。。。

如果有什麼問題,可以問我。







最後更新:2017-04-03 05:40:12

  上一篇:go 關於USRP和gnuradio的數據類型
  下一篇:go Java4android學習之對象導論