在使用Asmack的过程中,文件传输是阻碍我前行的一个极大的绊脚石。在翻遍了百度和Google的情况下,依然不得其解。而偶然的一个蓦然回首,我却解决了这个问题。所以,立此贴,一来为自己记录;而来希望能够帮到后来者。我希望自己能够做出一个像微信一样的即时通信,可以发送各种富媒体,接下来看我怎么做,实现文件传输。
第一点,版本问题。
Asmack是一个开源库,地址:https://github.com/Flowdalic/asmack,如大家所看到的一样,这个GitHub并不是完整的代码,你需要通过Grail去构建,而且现在的代码是4.0.3了,我反正不知道怎么用,网上查到的都是大家用的那个什么Cloudy版本的,大概是在2010年左右出的,所以和4.0.3有差距。我后来选择了http://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("http://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("http://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文件传输就可以跑起来了。成功传输文件。。。
如果有什么问题,可以问我。