前言:
现在基本不怎么搞安卓的开发了,主要做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'
}

Android 上传仓库 本地aar 安卓开发 上传文件_Android 上传仓库 本地aar


主要是用到了:

//网络请求相关框架
 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:

Android 上传仓库 本地aar 安卓开发 上传文件_Text_02


初始化下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>

先来看下安卓端的效果:

Android 上传仓库 本地aar 安卓开发 上传文件_安卓开发_03


Android 上传仓库 本地aar 安卓开发 上传文件_Android 上传仓库 本地aar_04


Android 上传仓库 本地aar 安卓开发 上传文件_网络访问_05


服务端:

为了简便开发,服务直接就使用了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,一定需要。

Android 上传仓库 本地aar 安卓开发 上传文件_文件上传_06


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>

运行测试下:

Android 上传仓库 本地aar 安卓开发 上传文件_安卓开发_07


Android 上传仓库 本地aar 安卓开发 上传文件_安卓开发_08


封装好的实体类属性也传递过来了,此处就不再截图了,可以自己尝试下。