├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── orzangleli │ │ └── retrofitmultifilesuploadclient │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── orzangleli │ │ │ └── retrofitmultifilesuploadclient │ │ │ ├── FlaskClient.java │ │ │ ├── GlideImageLoader.java │ │ │ ├── MainActivity.java │ │ │ ├── ServiceGenerator.java │ │ │ └── UploadResult.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── orzangleli │ └── retrofitmultifilesuploadclient │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :bus: Android 用Retrofit 2实现多文件上传实战 2 | 3 | 这是使用基于Retrofit2实现的Client端 详细教程见:[我的博客](http://www.orzangleli.com/2017/04/03/2017-04-03_Android%20%E7%94%A8%20Retrofit%202%20%E5%AE%9E%E7%8E%B0%E5%A4%9A%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E5%AE%9E%E6%88%98/) 4 | 5 | Server端项目见:https://github.com/hust201010701/MultiFileUploadServer 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.1" 6 | defaultConfig { 7 | applicationId "com.orzangleli.retrofitmultifilesuploadclient" 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.2.0' 28 | testCompile 'junit:junit:4.12' 29 | compile 'com.squareup.retrofit2:retrofit:2.1.0' 30 | compile 'com.squareup.retrofit2:converter-gson:2.1.0' 31 | compile 'com.github.bumptech.glide:glide:3.7.0' 32 | compile 'com.lzy.widget:imagepicker:0.4.1' 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\AndroidSdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/orzangleli/retrofitmultifilesuploadclient/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.orzangleli.retrofitmultifilesuploadclient; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.orzangleli.retrofitmultifilesuploadclient", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/orzangleli/retrofitmultifilesuploadclient/FlaskClient.java: -------------------------------------------------------------------------------- 1 | package com.orzangleli.retrofitmultifilesuploadclient; 2 | 3 | import java.util.Map; 4 | 5 | import okhttp3.RequestBody; 6 | import okhttp3.Response; 7 | import retrofit2.Call; 8 | import retrofit2.http.Multipart; 9 | import retrofit2.http.POST; 10 | import retrofit2.http.PartMap; 11 | 12 | /** 13 | * Created by lee on 2017/4/3. 14 | */ 15 | 16 | public interface FlaskClient { 17 | 18 | //上传图片 19 | @Multipart 20 | @POST("/upload") 21 | Call uploadMultipleFiles( 22 | @PartMap Map files); 23 | 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/orzangleli/retrofitmultifilesuploadclient/GlideImageLoader.java: -------------------------------------------------------------------------------- 1 | package com.orzangleli.retrofitmultifilesuploadclient; 2 | 3 | import android.app.Activity; 4 | import android.widget.ImageView; 5 | 6 | import com.bumptech.glide.Glide; 7 | import com.lzy.imagepicker.loader.ImageLoader; 8 | 9 | import java.io.File; 10 | 11 | /** 12 | * Created by lee on 2017/3/17. 13 | */ 14 | 15 | public class GlideImageLoader implements ImageLoader { 16 | 17 | @Override 18 | public void displayImage(Activity activity, String path, ImageView imageView, int width, int height) { 19 | Glide.with(activity)// 20 | .load(path.contains("http")?path:new File(path))// 21 | .placeholder(R.mipmap.default_image)// 22 | .error(R.mipmap.default_image)// 23 | .fitCenter() 24 | .into(imageView); 25 | } 26 | 27 | @Override 28 | public void clearMemoryCache() { 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/orzangleli/retrofitmultifilesuploadclient/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.orzangleli.retrofitmultifilesuploadclient; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.util.Log; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.AbsListView; 11 | import android.widget.BaseAdapter; 12 | import android.widget.Button; 13 | import android.widget.GridView; 14 | import android.widget.ImageView; 15 | import android.widget.TextView; 16 | import android.widget.Toast; 17 | 18 | import com.lzy.imagepicker.ImagePicker; 19 | import com.lzy.imagepicker.bean.ImageItem; 20 | import com.lzy.imagepicker.ui.ImageGridActivity; 21 | import com.lzy.imagepicker.view.CropImageView; 22 | 23 | import java.io.File; 24 | import java.util.ArrayList; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | import okhttp3.MediaType; 30 | import okhttp3.RequestBody; 31 | import retrofit2.Call; 32 | import retrofit2.Callback; 33 | import retrofit2.Response; 34 | 35 | public class MainActivity extends AppCompatActivity { 36 | 37 | TextView images; 38 | Button upload; 39 | GridView gridView; 40 | ImagePicker imagePicker; 41 | ArrayList imagesList; 42 | 43 | @Override 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | setContentView(R.layout.activity_main); 47 | 48 | images = bindView(R.id.images); 49 | upload = bindView(R.id.upload); 50 | gridView = bindView(R.id.gridView); 51 | 52 | imagesList = new ArrayList<>(); 53 | 54 | imagePicker = ImagePicker.getInstance(); 55 | imagePicker.setImageLoader(new GlideImageLoader()); //设置图片加载器 56 | imagePicker.setShowCamera(true); //显示拍照按钮 57 | imagePicker.setCrop(true); //允许裁剪(单选才有效) 58 | imagePicker.setSaveRectangle(true); //是否按矩形区域保存 59 | imagePicker.setSelectLimit(9); //选中数量限制 60 | imagePicker.setStyle(CropImageView.Style.RECTANGLE); //裁剪框的形状 61 | imagePicker.setFocusWidth(800); //裁剪框的宽度。单位像素(圆形自动取宽高最小值) 62 | imagePicker.setFocusHeight(800); //裁剪框的高度。单位像素(圆形自动取宽高最小值) 63 | imagePicker.setOutPutX(1000);//保存文件的宽度。单位像素 64 | imagePicker.setOutPutY(1000);//保存文件的高度。单位像素 65 | 66 | 67 | images.setOnClickListener(new View.OnClickListener() { 68 | @Override 69 | public void onClick(View v) { 70 | Intent intent = new Intent(MainActivity.this, ImageGridActivity.class); 71 | startActivityForResult(intent, 1); 72 | } 73 | }); 74 | 75 | upload.setOnClickListener(new View.OnClickListener() { 76 | @Override 77 | public void onClick(View v) { 78 | Toast.makeText(MainActivity.this, "正在上传", Toast.LENGTH_SHORT).show(); 79 | uploadFiles(); 80 | } 81 | }); 82 | 83 | } 84 | 85 | 86 | public T bindView(int id) { 87 | return (T) this.findViewById(id); 88 | } 89 | 90 | public void uploadFiles() { 91 | if(imagesList.size() == 0) { 92 | Toast.makeText(MainActivity.this, "不能不选择图片", Toast.LENGTH_SHORT).show(); 93 | return; 94 | } 95 | Map files = new HashMap<>(); 96 | final FlaskClient service = ServiceGenerator.createService(FlaskClient.class); 97 | for (int i = 0; i < imagesList.size(); i++) { 98 | File file = new File(imagesList.get(i).path); 99 | files.put("file" + i + "\"; filename=\"" + file.getName(), RequestBody.create(MediaType.parse(imagesList.get(i).mimeType), file)); 100 | } 101 | Call call = service.uploadMultipleFiles(files); 102 | call.enqueue(new Callback() { 103 | @Override 104 | public void onResponse(Call call, Response response) { 105 | if (response.isSuccessful() && response.body().code == 1) { 106 | Toast.makeText(MainActivity.this, "上传成功", Toast.LENGTH_SHORT).show(); 107 | Log.i("orzangleli", "---------------------上传成功-----------------------"); 108 | Log.i("orzangleli", "基础地址为:" + ServiceGenerator.API_BASE_URL); 109 | Log.i("orzangleli", "图片相对地址为:" + listToString(response.body().image_urls,',')); 110 | Log.i("orzangleli", "---------------------END-----------------------"); 111 | } 112 | } 113 | 114 | @Override 115 | public void onFailure(Call call, Throwable t) { 116 | Toast.makeText(MainActivity.this, "上传失败", Toast.LENGTH_SHORT).show(); 117 | } 118 | }); 119 | 120 | } 121 | 122 | 123 | @Override 124 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 125 | super.onActivityResult(requestCode, resultCode, data); 126 | if (resultCode == ImagePicker.RESULT_CODE_ITEMS) { 127 | if (data != null && requestCode == 1) { 128 | imagesList = (ArrayList) data.getSerializableExtra(ImagePicker.EXTRA_RESULT_ITEMS); 129 | MyAdapter adapter = new MyAdapter(imagesList); 130 | gridView.setAdapter(adapter); 131 | images.setText("已选择" + imagesList.size() + "张"); 132 | } else { 133 | Toast.makeText(this, "没有选择图片", Toast.LENGTH_SHORT).show(); 134 | } 135 | } 136 | } 137 | 138 | 139 | private class MyAdapter extends BaseAdapter { 140 | 141 | private List items; 142 | 143 | public MyAdapter(List items) { 144 | this.items = items; 145 | } 146 | 147 | public void setData(List items) { 148 | this.items = items; 149 | notifyDataSetChanged(); 150 | } 151 | 152 | @Override 153 | public int getCount() { 154 | return items.size(); 155 | } 156 | 157 | @Override 158 | public ImageItem getItem(int position) { 159 | return items.get(position); 160 | } 161 | 162 | @Override 163 | public long getItemId(int position) { 164 | return position; 165 | } 166 | 167 | @Override 168 | public View getView(int position, View convertView, ViewGroup parent) { 169 | ImageView imageView; 170 | int size = gridView.getWidth() / 3; 171 | if (convertView == null) { 172 | imageView = new ImageView(MainActivity.this); 173 | AbsListView.LayoutParams params = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, size); 174 | imageView.setLayoutParams(params); 175 | imageView.setBackgroundColor(Color.parseColor("#88888888")); 176 | } else { 177 | imageView = (ImageView) convertView; 178 | } 179 | imagePicker.getImageLoader().displayImage(MainActivity.this, getItem(position).path, imageView, size, size); 180 | return imageView; 181 | } 182 | } 183 | 184 | 185 | public String listToString(List list, char separator) { 186 | StringBuilder sb = new StringBuilder(); 187 | for (int i = 0; i < list.size(); i++) { 188 | sb.append(list.get(i)).append(separator); 189 | } 190 | return sb.toString().substring(0, sb.toString().length() - 1); 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /app/src/main/java/com/orzangleli/retrofitmultifilesuploadclient/ServiceGenerator.java: -------------------------------------------------------------------------------- 1 | package com.orzangleli.retrofitmultifilesuploadclient; 2 | 3 | import okhttp3.OkHttpClient; 4 | import retrofit2.Retrofit; 5 | import retrofit2.converter.gson.GsonConverterFactory; 6 | 7 | /** 8 | * Created by lee on 2017/3/7. 9 | */ 10 | 11 | public class ServiceGenerator { 12 | 13 | public static final String API_BASE_URL = "http://192.168.1.102:5000/"; 14 | // set your desired log level 15 | private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); 16 | 17 | private static Retrofit.Builder builder = 18 | new Retrofit.Builder() 19 | .baseUrl(API_BASE_URL) 20 | .addConverterFactory(GsonConverterFactory.create()); 21 | 22 | public static S createService(Class serviceClass) { 23 | Retrofit retrofit = builder.client(httpClient.build()).build(); 24 | return retrofit.create(serviceClass); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/orzangleli/retrofitmultifilesuploadclient/UploadResult.java: -------------------------------------------------------------------------------- 1 | package com.orzangleli.retrofitmultifilesuploadclient; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Author: lee 7 | * Created by: ModelGenerator on 2017/4/3 8 | */ 9 | public class UploadResult { 10 | public int code; // 1 11 | public List image_urls; 12 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 29 | 30 |