一般Android开发需要涉及到本地相册的上传以及文件下载到相册。
1.访问相册并上传到服务器
// 系统默认的图片选择程序
private void OpenFile(View view) {
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.setType("image/*");
startActivityForResult(galleryIntent, REQUEST_GALLERY);
}
//完成图片的选择
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Uri uri = data.getData();
//这里的REQUEST_GALLERY 是一个常量,我也没深究过
//private static final int REQUEST_GALLERY = 100;
saveUriToFile(uri, REQUEST_GALLERY);
}
private void saveUriToFile(Uri uri, int from) {
Bitmap bitmap = null;
if (uri != null) {
try {
//将本地已经选择的图片利用bitmap渲染出来
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; // 图片宽高都为原来的二分之一,即图片为原来的四分之一
bitmap = BitmapFactory.decodeStream(getContext().getContentResolver().openInputStream(uri), null, options);
upload_image.setImageBitmap(bitmap);
//处理文件上传
String filePath = FileUtils.getRealFilePath(getContext(), uri);
file_add_name.setText(filePath.substring(filePath.lastIndexOf("/") + 1));
sendRequest(filePath);
} catch (Exception e) {
Toast.makeText(getActivity(), "上传文件失败~", Toast.LENGTH_SHORT).show();
}
}
}
给出我这里使用到的FileUtils
工具类(直接使用大佬提供的源代码),目的是获取到图片的绝对存储路径。
public class FileUtils {
/**
* 通过Uri获取图片的路径以及文件名
*
* @param context
* @param uri
* @return
*/
public static String getRealFilePath(final Context context, final Uri uri) {
if (null == uri) return null;
final String scheme = uri.getScheme();
String data = null;
if (scheme == null)
data = uri.getPath();
else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
data = uri.getPath();
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);
if (null != cursor) {
if (cursor.moveToFirst()) {
int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
if (index > -1) {
data = cursor.getString(index);
}
}
cursor.close();
}
}
return data;
}
}
给出图片上传函数:
//根据获取到的图片路径上传文件流到服务端
private void sendRequest(String filePath) {
File file = new File(filePath);
//在前端实现了文件名称的唯一化,其实更建议在后端实现
String newFileName = UUID.randomUUID().toString().replace("-", "") + ".jpg";//这里可能写的不太好,锁定了文件类型
Retrofit retrofit = new Retrofit.Builder().baseUrl(Config.serverUrl).build();
UploadService uploadService = retrofit.create(UploadService.class);
MediaType mediaType = MediaType.parse("multipart/form-data");
RequestBody fileBody = RequestBody.create(mediaType, file);
MultipartBody.Part part = MultipartBody.Part.createFormData("file", newFileName, fileBody);
Call<ResponseBody> call = uploadService.upload(part);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.body() != null) {
try {
String result = response.body().string();
Log.d("nightowl", "onResponse(file): " + result);
JsonObject returnData = new JsonParser().parse(result).getAsJsonObject();
if (!returnData.get("code").toString().equals("200")) {
Toast.makeText(getContext(), "服务端错误~", Toast.LENGTH_SHORT).show();
} else {
uploadFileUrl = returnData.get("data").toString();//记录上传返回之后的图片路径
Toast.makeText(getContext(), "文件上传成功!!", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
Log.d("nightowl", "onResponse -- > " + "返回数据为空");
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.d("nightowl", "onFailure -- > " + t.toString());
}
});
}
2.下载网络图片到相册
由于我学的实在是不够深入,因此这里总结分享一种可行性方法,导入大佬开源的Download
工具类(能够显示下载进度):
public class Download {
private String fileSavePath = "";//保存文件的本地路径
private String fileDownLoad_path = "";//下载的URL
private String fileName = "";//下载的文件名字
private boolean mIsCancel = false;
private int mProgress;
private ProgressBar mProgressBar;
private TextView text;
private Dialog mDownloadDialog;
private final Context context;
private static final int DOWNLOADING = 1;
private static final int DOWNLOAD_FINISH = 2;
@SuppressLint("HandlerLeak")
private final Handler mUpdateProgressHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case DOWNLOADING:
// 设置进度条
mProgressBar.setProgress(mProgress);
text.setText(String.valueOf(mProgress));
break;
case DOWNLOAD_FINISH:
// 隐藏当前下载对话框
mDownloadDialog.dismiss();
}
}
};
/**
* 下载初始化
*
* @param context 上下文
* @param fileDownLoad_path 下载的URL
* @param fileName 下载的文件名字
* @param fileSavePath 保存文件的本地路径
*/
public Download(Context context, String fileDownLoad_path, String fileSavePath, String fileName) {
this.context = context;
this.fileDownLoad_path = fileDownLoad_path;
this.fileName = fileName;
this.fileSavePath = Environment.getExternalStorageDirectory() + "/" + fileSavePath;
showDownloadDialog();
}
/**
* 显示正在下载的对话框
*/
protected void showDownloadDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("下载中");
View view = LayoutInflater.from(context).inflate(R.layout.dialog_progress, null);
mProgressBar = (ProgressBar) view.findViewById(R.id.id_progress);
text = view.findViewById(R.id.id_text);
builder.setView(view);
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 隐藏当前对话框
dialog.dismiss();
// 设置下载状态为取消
mIsCancel = true;
}
});
mDownloadDialog = builder.create();
mDownloadDialog.show();
downloadFile();
}
public static ContentValues getImageContentValues(Context paramContext, File paramFile, long paramLong) {
ContentValues localContentValues = new ContentValues();
localContentValues.put("title", paramFile.getName());
localContentValues.put("_display_name", paramFile.getName());
localContentValues.put("mime_type", "image/jpeg");
localContentValues.put("datetaken", Long.valueOf(paramLong));
localContentValues.put("date_modified", Long.valueOf(paramLong));
localContentValues.put("date_added", Long.valueOf(paramLong));
localContentValues.put("orientation", Integer.valueOf(0));
localContentValues.put("_data", paramFile.getAbsolutePath());
localContentValues.put("_size", Long.valueOf(paramFile.length()));
return localContentValues;
}
/**
* 下载文件
*/
private void downloadFile() {
new Thread(new Runnable() {
@Override
public void run() {
try {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File dir = new File(fileSavePath);
if (!dir.exists()) {
dir.mkdirs();
}
HttpURLConnection conn = (HttpURLConnection) new URL(fileDownLoad_path).openConnection();
conn.connect();
InputStream is = conn.getInputStream();
int length = conn.getContentLength();
File imageFile = new File(fileSavePath, fileName);
FileOutputStream fos = new FileOutputStream(imageFile);
int count = 0;
byte[] buffer = new byte[1024];
while (!mIsCancel) {
int numread = is.read(buffer);
count += numread;
// 计算进度条当前位置
mProgress = (int) (((float) count / length) * 100);
// 更新进度条
mUpdateProgressHandler.sendEmptyMessage(DOWNLOADING);
// 下载完成
if (numread < 0) {
mUpdateProgressHandler.sendEmptyMessage(DOWNLOAD_FINISH);
break;
}
fos.write(buffer, 0, numread);
}
//将文件写入相册
ContentResolver localContentResolver = context.getContentResolver();
ContentValues localContentValues = getImageContentValues(context, imageFile, System.currentTimeMillis());
localContentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, localContentValues);
Intent localIntent = new Intent("android.intent.action.MEDIA_SCANNER_SCAN_FILE");
final Uri localUri = Uri.fromFile(imageFile);
localIntent.setData(localUri);
context.sendBroadcast(localIntent);
Log.d("nightowl:", "run: " + "文件下载完成," + fileSavePath + fileName);
fos.close();
is.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
具体使用如下:
public void downloadFile(String targetUrl) {
String suffixName = targetUrl.substring(targetUrl.lastIndexOf("."));
String dirName = "campus_notice/";//指定文件的存储目录
String fileName = UUID.randomUUID().toString().replace("-", "") + suffixName;
Download download = new Download(this, targetUrl, dirName, fileName);
//这个DetailsActivity是我自己的下载页面,可以使用getActivity()代替并通用化
//这波属于预先展示下载完成,如果为了友好,可以将download设计成内部类,下载完成之后提示,当降低了重用性,也可以进一步探索异步函数的使用,在download设计一个需要被继承的方法等等。
Toast.makeText(DetailsActivity.this, "已存储到相册!", Toast.LENGTH_SHORT).show();
}
3.这里顺便分享一手后端的对接方法
代码是基于Springboot
的,其实就是将前端传过来的文件写入服务器中。
@CrossOrigin
@EnableAsync
@RestController
public class FileController {
private static String storePath =System.getProperty("user.dir")+"/static/";
private static final String serverUrl = "https://android.nightowl.top/static/";
@RequestMapping(value = "/upload", method = {RequestMethod.POST})
public ResultData fileUpload(@RequestParam(value = "file", required = true) MultipartFile file) {
if (file.isEmpty()) {
return ResultData.error("上传图片不存在~");
} else {
String fileName = file.getOriginalFilename(); // 文件名
String suffixName = fileName.substring(fileName.lastIndexOf("."));//只支持JPG、JPEG、PNG
// 验证上传的文件是否图片
if (!".jpg".equalsIgnoreCase(suffixName)
&& !".jpeg".equalsIgnoreCase(suffixName)
&& !".png".equalsIgnoreCase(suffixName)) {
return ResultData.error("只支持JPG、JPEG、PNG三种类型~");
}
File dest = new File(storePath + fileName);
if (fileName.startsWith("/") && !dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
try {
file.transferTo(dest);
System.out.println("输出文件路径:" + serverUrl + fileName);
return ResultData.success("上传成功~", 0, serverUrl + fileName);
} catch (IOException e) {
e.printStackTrace();
return ResultData.error("图片上传失败~");
}
}
}
}
4.生产环境资源配置
注意,这里在生产环境部署的时候如需要加以调整(因为项目被打成jar包了
,所以无法再访问对应路径,需要重新规定静态资源目录
),需要额外做一些静态资源配置application.yaml
,否则只能在测试环境中完成文件读写,具体的配置方法如下(主要是配置静态资源访问文件):
server:
servlet:
context-path: /
port: 8000
#前后只做位置参考,因为yaml配置利用的是缩进
spring:
application:
name: spring-android
mvc:
#主要是这里
static-path-pattern: /static/**
#前后只做位置参考,因为yaml配置利用的是缩进
#mysql数据库
#...
最后请加上WebSecurityConfig
类确保资源注册类能够生效。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebSecurityConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/","file:static/");
}
}
给出宝塔面板部署的效果(主要是为了体现这里的相对资源路径static
):
5.后端项目打包
因为我主要是负责前端开发的,对于springboot框架知之甚少,因此只负责了项目部署等少量工作,部署的话需要首先将项目打包,打包的maven配置如下(本质上是使用了maven-surefire-plugin
打包工具):
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<!--忽略测试-->
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
最后建议将打包时候使用的JavaJDK版本修改成1.8(否则容易因版本不一样导致不能运行项目):
<properties>
<java.version>8</java.version>
</properties>