FTP是基于FTP协议来实现文件的管理,理论上只要将协议逐个实现,就可以实现一个FTP的服务端了,但需要一些时间,而且还是个体力活。现在有了SwiFTP的开源库,只要对其稍加改造,就可以将手机快速变成一个FTP的服务器。这里提供一个SwiFTP的下载地址https://github.com/sparkleDai/swiftp。
我们先来看看SwiFTP源码中实现的效果图:
前一个图是FTP的配置,后一个图是FTP服务器控制。这两个画面,一看就是一嘛黑的,老外貌似比较喜欢这种风格。下面是修改后的效果图。
修改后的FTP服务端只有一个启动/停止的按钮,其他的都采用默认设置。下面我们来看具体的修改步骤。
1、跳过配置画面
SwiFTP一开启就会跳到配置画面,经查代码,发现是在ServerControlActivity的OnResume中跳转过来的,所以只要想办法跳过去就可以了。要跳过去,有几种方式,最直接的就是屏蔽掉。不过,考虑到原来有配置画面,这里可能需要留一个口来增加设置,所以这里我增加了一个配置函数,将所需要的配置项配置成了默认项,代码如下。(我将ServerControlActivity改成了MainActivity)
MainActivity.java
protected void onResume() {
super.onResume();
// If the required preferences are not present, launch the configuration
// Activity.
configSetting();
if (!requiredSettingsDefined()) {
launchCONFIG_KEYS();
}
UiUpdater.registerClient(handler);
updateUi();
// Register to receive wifi status broadcasts
myLog.l(Log.DEBUG, "Registered for wifi updates");
this.registerReceiver(wifiReceiver, new IntentFilter(
WifiManager.WIFI_STATE_CHANGED_ACTION));
}
private void configSetting() {
// Validation was successful, save the settings object
SharedPreferences settings = getSharedPreferences(
Defaults.getSettingsName(), Defaults.getSettingsMode());
SharedPreferences.Editor editor = settings.edit();
editor.putString(CONFIG_KEYS.USERNAME, Defaults.username);
editor.putString(CONFIG_KEYS.PASSWORD, Defaults.password);
editor.putInt(CONFIG_KEYS.PORTNUM, 2121);
editor.putString(CONFIG_KEYS.CHROOTDIR, Defaults.chrootDir);
editor.putBoolean(CONFIG_KEYS.ACCEPT_WIFI, Defaults.acceptWifi);
editor.putBoolean(CONFIG_KEYS.ACCEPT_NET, Defaults.acceptNet);
editor.putBoolean(CONFIG_KEYS.STAY_AWAKE, Defaults.stayAwake);
editor.putBoolean(CONFIG_KEYS.IS_ANONYMOUS, Defaults.isAnonymous);
editor.commit();
}
package com.sparkle.ftp;
public class CONFIG_KEYS {
public final static String USERNAME = "username";
public final static String PASSWORD = "password";
public final static String PORTNUM = "portNum";
public final static String CHROOTDIR = "chrootDir";
public final static String ACCEPT_WIFI = "allowWifi";
public final static String ACCEPT_NET = "allowNet";
public final static String STAY_AWAKE = "stayAwake";
public final static String IS_ANONYMOUS="isAnonymous";
}
Defaults.java部分代码
protected static int inputBufferSize = 256;
protected static int dataChunkSize = 65536; // do file I/O in 64k chunks
protected static int sessionMonitorScrollBack = 10;
protected static int serverLogScrollBack = 10;
protected static int uiLogLevel = Defaults.release ? Log.INFO : Log.DEBUG;
protected static int consoleLogLevel = Defaults.release ? Log.INFO : Log.DEBUG;
protected static String settingsName = "FTP";
public static String username = "Anonynous";
public static String password = "";
protected static int portNumber = 2121;
// protected static int ipRetrievalAttempts = 5;
public static final int tcpConnectionBacklog = 5;
public static final String chrootDir = Environment.getExternalStorageDirectory().getAbsolutePath();
public static final boolean acceptWifi = true;
public static final boolean acceptNet = false; // don't incur bandwidth charges
public static final boolean stayAwake = false;
public static final boolean isAnonymous=true;
public static final int REMOTE_PROXY_PORT = 2222;
public static final String STRING_ENCODING = "UTF-8";
public static final int SO_TIMEOUT_MS = 30000; // socket timeout millis
注:
(1)、Activity的生命周期是OnCreate->OnStart->OnResume->OnPause->OnStop->OnDestory。在activity1跳转到另一个activity2后,如果跳转时activity1没有finish掉,那么activity2关闭跳后,activity1会从activity堆栈中重新唤醒,也就是会调用OnResume。所以在配置的activity中,如果没有配置,当点cancel,配置的activity虽然被关闭了,但是当回到服务控制的activity后,又激活了OnResume,然后判断配置的情况,如果不符合,又会启动配置的activity。所以会发现,如果没有配置,即使点cancel也没有作用,不知道的还以为中镖了。
(2)、SharedPreference是共享数据的一种方式,可以实现跨activity的数据共享,是一个轻量的存储方式,本质上是一个xml的key-value对。对其修改数据时,需要请求edit,然后修改数据,最后还要commit。这个和提交代码到git/svn等类似。
(3)、CONFIG_KEYS是将原来配置的activity中的一些key写到了这个类中。
(4)、Defaults中增加了username、password、isAnonymous。其中isAnonymous是为了实现FTP的匿名访问而增设的一个配置项。
(5)、由于手机内存中的文件不一定都能访问,所以FTP默认的目录设置到了SD卡中。在不同的设备中,对于SD卡的路径有所不同,所以采用了系统自带的函数来实现,即chrootDir = Environment.getExternalStorageDirectory().getAbsolutePath()。
2、实现匿名访问
现在将服务端跑起来后,已经可以正常访问了,不过每次访问的时候都会弹出用户名和密码的输入框,让人很烦,所以就想着法子的屏蔽掉。既然会弹出这个框,程序中肯定会有对应的判断项,经过查找,发现是在FTP中的PASS命令中实现的。所以对CmdPASS.java作了些修改,修改部分的代码如下。
public void run() {
// User must have already executed a USER command to
// populate the Account object's username
myLog.l(Log.DEBUG, "Executing PASS");
Context ctx = Globals.getContext();
SharedPreferences settings = ctx.getSharedPreferences(
Defaults.getSettingsName(), Defaults.getSettingsMode());
boolean isAnonymous=settings.getBoolean(CONFIG_KEYS.IS_ANONYMOUS,false);
if(isAnonymous)
{
sessionThread.writeString("230 Access granted\r\n");
myLog.l(Log.INFO, "Anonymous visit!");
sessionThread.authAttempt(true);
return;
}
if(ctx == null) {
// This will probably never happen, since the global
// context is configured by the Service
myLog.l(Log.ERROR, "No global context in PASS\r\n");
}
String attemptPassword = getParameter(input, true); // silent
String attemptUsername = sessionThread.account.getUsername();
if(attemptUsername == null) {
sessionThread.writeString("503 Must send USER first\r\n");
return;
}
String username = settings.getString("username", null);
String password = settings.getString("password", null);
if(username == null || password == null) {
myLog.l(Log.ERROR, "Username or password misconfigured");
sessionThread.writeString("500 Internal error during authentication");
} else if(username.equals(attemptUsername) &&
password.equals(attemptPassword)) {
sessionThread.writeString("230 Access granted\r\n");
myLog.l(Log