android--studio用sshj,而不是sftp上传和下载文件以及错误提醒
主要代码:SFTPUtil.java, 使用sshj,而不是sftp
package com.mth.sftp_filehanlder;import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.sftp.RemoteResourceInfo;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.sftp.SFTPException;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;import java.io.File;
import java.io.IOException;
import java.util.List;/*** SFTP工具类(基于SSHJ实现,方法签名与SFTPUtil_backup一致)*/
public class SFTPUtil {private static final String TAG = "MTH_Morgan"; public static final String host = "your ip address";public static final int port = 22;public static final String username = "your login-in name";public static final String password = "your login-in password";public static final String remoteDir = "your path to the file you will handle";/*** 连接SFTP服务器*/public static SSHClient connect(String host, Integer port, String user, String password) throws IOException {SSHClient ssh = new SSHClient(new DefaultConfig());ssh.addHostKeyVerifier(new PromiscuousVerifier());ssh.connect(host, port);ssh.authPassword(user, password);return ssh;}/*** 下载文件或目录(递归)*/public static void download(String directory, String srcFile, String saveFile, SFTPClient sftp) throws IOException {String remotePath = srcFile;File localTarget = new File(saveFile);try {List<RemoteResourceInfo> entries = sftp.ls(remotePath);// 如果是单文件if (entries != null && entries.size() == 1) {RemoteResourceInfo single = entries.get(0);if (!single.isDirectory() && normalizePath(single.getPath()).equals(normalizePath(remotePath))) {if (localTarget.exists() && localTarget.isFile()) {sftp.get(remotePath, localTarget.getPath());} else {if (!localTarget.exists()) localTarget.mkdirs();String filename = new File(remotePath).getName();String localFilePath = localTarget.getPath() + File.separator + filename;sftp.get(remotePath, localFilePath);}return;}}// 否则递归下载目录if (entries == null || entries.isEmpty()) return;for (RemoteResourceInfo entry : entries) {String name = entry.getName();if (".".equals(name) || "..".equals(name)) continue;if (entry.isDirectory()) {String childRemotePath = remotePath.endsWith("/") ? remotePath + name : remotePath + "/" + name;String childLocalPath = localTarget.getPath() + File.separator + name;File dirLocal = new File(childLocalPath);if (!dirLocal.exists()) dirLocal.mkdirs();download(directory, childRemotePath, childLocalPath, sftp);} else {if (!localTarget.exists()) localTarget.mkdirs();String remoteFilePath = remotePath.endsWith("/") ? remotePath + name : remotePath + "/" + name;String localFilePath = localTarget.getPath() + File.separator + name;sftp.get(remoteFilePath, localFilePath);}}} catch (SFTPException lsEx) {// 如果 ls 失败,尝试直接下载文件try {if (localTarget.exists() && localTarget.isFile()) {sftp.get(remotePath, localTarget.getPath());} else {if (!localTarget.exists()) localTarget.mkdirs();String filename = new File(remotePath).getName();String localFilePath = localTarget.getPath() + File.separator + filename;sftp.get(remotePath, localFilePath);}} catch (SFTPException getEx) {throw new IOException("Remote path cannot be listed or downloaded. ls error: " + lsEx.getMessage()+ " ; get error: " + getEx.getMessage(), getEx);}}}private static String normalizePath(String p) {if (p == null) return "";return p.replaceAll("/+", "/").replaceAll("/$", "");}/*** downloadtxt:下载指定文件到本地路径*/public static void downloadtxt(String localPath, String uploadFile) {System.out.println(TAG + " downloadtxt => " + remoteDir);downloadfile(remoteDir, remoteDir + "/" + uploadFile, localPath);}/*** downloadfile:下载文件或目录*/public static void downloadfile(String remoteDir, String remoteDirtxt, String localFile) {SSHClient ssh = null;SFTPClient sftp = null;try {System.out.println(TAG + " => downloadfile");ssh = connect(host, port, username, password);sftp = ssh.newSFTPClient();System.out.println(TAG + " remoteDir=" + remoteDir);System.out.println(TAG + " remoteDirtxt=" + remoteDirtxt);System.out.println(TAG + " localFile=" + localFile);download(remoteDir, remoteDirtxt, localFile, sftp);} catch (Exception e) {System.out.println(TAG + " downloadfile error: " + e);e.printStackTrace();} finally {try {if (sftp != null) sftp.close();if (ssh != null) ssh.disconnect();} catch (IOException e) {e.printStackTrace();}}}/*** putFile:上传文件*/public static void putFile(String localPath, String localFile, String remotePath, SFTPClient sftp) throws IOException {try {sftp.mkdirs(remotePath);} catch (Exception ignored) {}String localFilePath = localPath.endsWith(File.separator) ? localPath + localFile : localPath + File.separator + localFile;String remoteFilePath = remotePath.endsWith("/") ? remotePath + localFile : remotePath + "/" + localFile;System.out.println(TAG + " => Uploading " + localFilePath + " to " + remoteFilePath);sftp.put(localFilePath, remoteFilePath);}/*** uploadtxt:上传文件*/public static void uploadtxt(String localPath, String uploadFile) {SSHClient ssh = null;SFTPClient sftp = null;try {ssh = connect(host, port, username, password);sftp = ssh.newSFTPClient();putFile(localPath, uploadFile, remoteDir, sftp);} catch (Exception e) {e.printStackTrace();} finally {try {if (sftp != null) sftp.close();if (ssh != null) ssh.disconnect();} catch (IOException e) {e.printStackTrace();}}}
}
build文件:
implementation 'com.hierynomus:sshj:0.33.0'//implementation 'org.bouncycastle:bcprov-jdk15on:1.70'// 添加 Conscrypt(提供 Android 上完整的 ECDSA/ECDH 支持)implementation 'org.conscrypt:conscrypt-android:2.5.2'// 可选:让 sshj 的日志能输出到 logcat(便于调试)implementation 'org.slf4j:slf4j-android:1.7.36'
服务器上不需要特别设定hotkey
sudo nano /etc/ssh/sshd_config

- 错误代码
downloadfile error: net.schmizz.sshj.transport.TransportException: Connection reset
解决方法: 公司内部网络,IP限制,需要用外网
- 错误代码
downloadfile error: net.schmizz.sshj.transport.TransportException: The BC provider no longer provides an implementation for MessageDigest.SHA-256. Please see https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html for more details.
解决办法:
》》 增加My Application.java

package com.mth.sftp_filehanlder;import android.app.Application;
import android.util.Log;import androidx.multidex.MultiDex;import org.conscrypt.Conscrypt;import java.security.Provider;
import java.security.Security;public class MyApplication extends Application {private static final String TAG = "MTH_Conscrypt";@Overridepublic void onCreate() {super.onCreate();// MultiDex 支持(minSdk < 21 时需要)try {MultiDex.install(this);} catch (Throwable t) {Log.w(TAG, "MultiDex.install failed", t);}// 1) 插入 Conscrypt 到 provider 列表首位try {Security.insertProviderAt(Conscrypt.newProvider(), 1);Log.i(TAG, "Conscrypt provider inserted");} catch (Throwable t) {Log.e(TAG, "Failed to insert Conscrypt provider", t);}// 2) 尝试移除系统/应用范围内的 BC provider(避免 sshj 指向不完整的 BC 实现)try {// 如果存在,会被移除;若不存在或不允许移除,会抛异常,我们捕获并记录但不终止应用启动if (Security.getProvider("BC") != null) {Security.removeProvider("BC");Log.i(TAG, "Removed provider: BC");} else {Log.i(TAG, "Provider BC not present (no removal needed)");}} catch (Throwable t) {Log.w(TAG, "Failed to remove provider BC (continuing)", t);}// 3) 打印当前 provider 列表,便于在 logcat 中确认优先级与是否已移除 BCtry {Provider[] providers = Security.getProviders();for (Provider p : providers) {Log.i(TAG, "Provider: " + p.getName() + " ver:" + p.getVersion());}} catch (Throwable t) {Log.w(TAG, "print providers failed", t);}}
}
并且在AndroidManifest.xml注明:

3 build错误
Unsupported class file major version 59
解决办法:
gradle.properties文件如下就可:
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=false
