前言:
现在基本不怎么搞安卓的开发了,主要做java开发,在学习各种的框架,由于学校还有项目没搞定,打算改进一下,以前做过原生servlet单文件上传,写的也比较麻烦,大概花了一天的时间,找了一些框架,自己又加调试了一下,做了一个多文件上传,简单记录下。
安卓端:
关联下jar:
dependencies {
//网络请求相关框架
compile 'org.xutils:xutils:3.5.0'
//选择本地文件框架
compile 'com.github.LuckSiege.PictureSelector:picture_library:v2.1.1'
//加载图片
compile 'com.squareup.picasso:picasso:2.5.2'
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
}
主要是用到了:
//网络请求相关框架
compile ‘org.xutils:xutils:3.5.0’
//选择本地文件框架
compile ‘com.github.LuckSiege.PictureSelector:picture_library:v2.1.1’
网络框架有很多,此处就选用了xutils了。既然要从手机里选择图片,由于安卓系统各种开源,各大厂商定制原因,自己以前也试着做过从相册选择照片,但是对于机型兼容不好,这个框架主要就是用来从手机里选择照片,当然,也可以选择其他文件,感兴趣可以去他的GitHub了解下。
权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
xutils需要在AndroidManifest.xml:
初始化下xutils:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
x.Ext.init(this);
}
}
接下来还有两个布局文件文件和一个实体类,一个自定义适配器类,一个主类直接贴代码了:
Commodity.java:
/**
* Created by Administrator on 2019/2/17.
* 商品对应实体类
*/
public class Commodity {
//唯一标,没有业务含义
private Long id;
//图片地址或者路径,1-6
private List<String> imageAddress=new ArrayList<>();
//商品标题
private String title;
//描述内容
private String content;
//商品价格
private Double price;
//商品分类
private Integer classify;
//联系手机
private String mobile;
//QQ or weChat
private String QQ;
//发布地址
private String address;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<String> getImageAddress() {
return imageAddress;
}
public void setImageAddress(List<String> imageAddress) {
this.imageAddress = imageAddress;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getClassify() {
return classify;
}
public void setClassify(Integer classify) {
this.classify = classify;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getQQ() {
return QQ;
}
public void setQQ(String QQ) {
this.QQ = QQ;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Commodity{" +
"imageAddress=" + imageAddress +
", title='" + title + '\'' +
", content='" + content + '\'' +
", price=" + price +
", classify=" + classify +
", mobile='" + mobile + '\'' +
", QQ='" + QQ + '\'' +
", address='" + address + '\'' +
'}';
}
}
CustomAdapter.java:
public class CustomAdapter extends BaseAdapter {
private List<LocalMedia> mData;
private LayoutInflater layoutInflater;
public CustomAdapter(Context context, List<LocalMedia> data) {
this.layoutInflater = LayoutInflater.from(context);
this.mData = data;
}
//适配器中数据源的个数
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//将布局文件转化为View对象
View view = layoutInflater.inflate(R.layout.listview_layout, null);
ImageView imageView =view.findViewById(R.id.listView_layout_image);
//获取相应索引的ItemBean对象
LocalMedia bean = mData.get(position);
//设置控件对应属性
Bitmap bitmap = BitmapFactory.decodeFile(bean.getPath());
imageView.setImageBitmap(bitmap);
return view;
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private ImageView imageView;
private GridView gridView;
private Button upload;
private Commodity commodity;
private EditText title;
private EditText content;
private EditText price;
private Spinner classify;
private EditText mobile;
private EditText weChat;
private EditText address;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
//控件初始化
private void init() {
imageView = (ImageView) findViewById(R.id.select);
imageView.setOnClickListener(this);
upload = (Button) findViewById(R.id.upload);
upload.setOnClickListener(this);
gridView = (GridView) findViewById(R.id.gridView);
title = (EditText) findViewById(R.id.title);
content = (EditText) findViewById(R.id.content);
price = (EditText) findViewById(R.id.price);
classify = (Spinner) findViewById(R.id.classify);
mobile = (EditText) findViewById(R.id.mobile);
weChat = (EditText) findViewById(R.id.weChat);
address = (EditText) findViewById(R.id.address);
commodity = new Commodity();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case PictureConfig.CHOOSE_REQUEST:
// 图片选择结果回调
List<LocalMedia> selectList = PictureSelector.obtainMultipleResult(data);
//需要判断下
if (selectList != null) {
List<String> imagePath = new ArrayList<>();
for (int i = 0; i < selectList.size(); i++) {
imagePath.add(selectList.get(i).getPath());
}
commodity.setImageAddress(imagePath);
gridView.setVisibility(View.VISIBLE);
gridView.setAdapter(new CustomAdapter(this, selectList));
}
break;
}
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.select:
select();
break;
case R.id.upload:
upload();
break;
default:
break;
}
}
/**
* 上传图片
*/
private void upload() {
final String URL = "http://192.168.0.105:8080/upload";
if (commodity.getImageAddress().size() < 1) {
showToast("至少选择一张图片!");
return;
}
String title = this.title.getText().toString();
//检查是否为空,为空返回false,取反true,不往下执行
if (!isEmpty(title)) {
showToast("标题输入不能为空,请重新输入");
return;
}
commodity.setTitle(title);
String content = this.content.getText().toString();
if (!isEmpty(content)) {
showToast("内容输入不能为空,请重新输入");
return;
}
commodity.setContent(content);
String price = this.price.getText().toString();
if (!isEmpty(price)) {
showToast("请输入价格");
return;
}
commodity.setPrice(Double.valueOf(price));
int classify = this.classify.getSelectedItemPosition();
if (classify == 0) {
showToast("请选择商品类别");
return;
}
commodity.setClassify(classify);
String mobile = this.mobile.getText().toString();
if (!isEmpty(mobile)) {
showToast("手机号不能为空,请重新输入");
return;
}
commodity.setMobile(mobile);
String QQ = this.weChat.getText().toString();
if (!isEmpty(QQ)) {
showToast("微信或者QQ不能为空,请重新输入");
return;
}
commodity.setQQ(QQ);
String address = this.address.getText().toString();
if (!isEmpty(address)) {
showToast("地址输入不能为空,请重新输入");
return;
}
commodity.setAddress(address);
RequestParams params = new RequestParams(URL);
//设置支持文件上传
params.setMultipart(true);
//设置多个图片名称
for (int i = 0; i < commodity.getImageAddress().size(); i++) {
String filePath = commodity.getImageAddress().get(i);
int end = filePath.lastIndexOf("/");
String fileName = filePath.substring(end + 1);
File file = new File(filePath);
params.addBodyParameter("pictures", file);
}
//不能直接传递对象,传递属性
params.addBodyParameter("imageAddress", commodity.getImageAddress(), null);
params.addBodyParameter("title", commodity.getTitle());
params.addBodyParameter("content", commodity.getContent());
params.addBodyParameter("price", commodity.getPrice().toString());
params.addBodyParameter("classify", commodity.getClassify().toString());
params.addBodyParameter("mobile", commodity.getMobile());
params.addBodyParameter("QQ", commodity.getQQ());
params.addBodyParameter("address", commodity.getAddress());
x.http().post(params, new Callback.CacheCallback<String>() {
@Override
public void onSuccess(String result) {
Log.e("TAG", result);
}
@Override
public void onError(Throwable ex, boolean isOnCallback) {
}
@Override
public void onCancelled(CancelledException cex) {
}
@Override
public void onFinished() {
}
@Override
public boolean onCache(String result) {
return false;
}
});
}
/**
* 给出Toast提示
*
* @param content 提示内容
*/
private void showToast(String content) {
Toast.makeText(this, content, Toast.LENGTH_SHORT).show();
}
/**
* 检查字符串是否为空
*
* @param string 被判断字符串
* @return 检查结果
*/
private Boolean isEmpty(String string) {
return string != null && !string.trim().equals("");
}
//打开文件选择照片
private void select() {
// 进入相册 以下是例子:用不到的 api 可以不写
PictureSelector.create(MainActivity.this)
.openGallery(PictureMimeType.ofImage())//全部.PictureMimeType.ofAll()、图片.ofImage()、视频.ofVideo()
.maxSelectNum(6)// 最大图片选择数量 int
.minSelectNum(1)// 最小选择数量 int
.imageSpanCount(4)// 每行显示个数 int
.selectionMode(PictureConfig.MULTIPLE)// 多选 or 单选 PictureConfig.MULTIPLE or PictureConfig.SINGLE
.previewImage(true)// 是否可预览图片 true or false
.isZoomAnim(true)// 图片列表点击 缩放效果 默认 true
.sizeMultiplier(0.5f)// glide 加载图片大小 0~1 之间 如设置 .glideOverride()无效
.hideBottomControls(true)// 是否显示 uCrop 工具栏,默认不显示 true or false
.isGif(true)// 是否显示 gif 图片 true or false
.forResult(PictureConfig.CHOOSE_REQUEST);//结果回调 onActivityResult code
}
}
核心的就是select()从相册中选择照片和upload()上传方法,里面一些校验数据的没什么用可以去掉。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.administrator.myapplication.MainActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/photo" />
<GridView
android:id="@+id/gridView"
android:layout_width="match_parent"
android:layout_height="200dp"
android:numColumns="3"
android:visibility="gone" />
<EditText
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="标题 品牌 型号都是买家最喜欢搜索的" />
<EditText
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="150dp"
android:hint="描述宝贝的转手原因和入手渠道,更多的详细可能帮助你更快卖出哦" />
<EditText
android:id="@+id/price"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="宝贝价格"
android:inputType="number" />
<Spinner
android:id="@+id/classify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/spinner"
android:spinnerMode="dialog" />
<EditText
android:id="@+id/mobile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="手机"
android:inputType="number" />
<EditText
android:id="@+id/weChat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="QQ/微信" />
<EditText
android:id="@+id/address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="地址" />
<Button
android:id="@+id/upload"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="发布信息" />
</LinearLayout>
</ScrollView>
</LinearLayout>
listview_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/listView_layout_image"
android:layout_width="80dp"
android:layout_height="80dp" />
</LinearLayout>
先来看下安卓端的效果:
服务端:
为了简便开发,服务直接就使用了springboot来开发了:
先使用idea创建一个支持web项目的springboot项目,此处就不演示了,此处使用了mybatisplus操作了下数据库,也对一些文字进行了处理,觉得比较烦就可以去掉了。。
maven关联:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
这是两个apache文件上传的jar,一定需要。
FileUploadApplication.java:
@SpringBootApplication
@ImportResource(locations = {"classpath:dispatcher.xml"})
public class FileUploadApplication {
public static void main(String[] args) {
SpringApplication.run(FileUploadApplication.class, args);
}
}
FileUploadController.java:
@RestController
public class FileUploadController {
@Autowired
private CommodityMapper mapper;
private Logger logger = LoggerFactory.getLogger(getClass());
//Commodity不需要加注解
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public String upload(@RequestParam(value = "pictures") MultipartFile[] pictures,
Commodity commodity) {
// 判断文件是否为空
if (pictures.length > 0 && pictures != null) {
try {
//判断文件目录是否存在,否则自动生成
String innerPath = ResourceUtils.getURL("classpath:").getPath();
String targetPath = innerPath + "static/images/upload/";
File f = new File(targetPath);
if (!f.exists()) {
f.mkdirs();
}
for (int i = 0; i < pictures.length; i++) {
MultipartFile file = pictures[i];
// 文件保存路径
String filePath = targetPath + file.getOriginalFilename();
// 转存文件
file.transferTo(new File(filePath));
}
} catch (Exception e) {
e.printStackTrace();
}
}
// StringBuilder builder = new StringBuilder();
// for (int i = 0; i < commodity.getImageAddress().size(); i++) {
// String randomName= UUID.randomUUID().toString().substring(0,10);
// String filePath = commodity.getImageAddress().get(i);
// String fileName = null;
// int end = filePath.lastIndexOf(".");
// //最后一张图片,做特殊处理
// if (i == (commodity.getImageAddress().size() - 1)) {
// fileName = filePath.substring(end, filePath.length() - 1);
// //改文件名
// //当前记录id+随机生成名称
// builder.append("4"+randomName+fileName);
// break;
// }
// fileName = filePath.substring(end);
// builder.append("4"+randomName+fileName + "@");
// }
// logger.info(builder.toString());
// commodity.setImagePath(builder.toString());
return "上传成功";
}
}
dispatcher.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://mybatis.org/schema/mybatis-spring
http://mybatis.org/schema/mybatis-spring.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置上传文件的bean-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
</bean>
<!--配置DI注解解析器@Autowired等注解-->
<context:annotation-config/>
<!--引入外部的db.properties文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据源和数据库连接信息 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--mybatis-spring整合-->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--<property name="configLocation" value="classpath:mybatis-config.xml"/>-->
</bean>
<!--扫描接口映射文件-->
<mybatis-spring:scan base-package="com.example.fileupload.mapper"/>
</beans>
运行测试下:
封装好的实体类属性也传递过来了,此处就不再截图了,可以自己尝试下。