Android上传手机图片到服务器

通过安卓app选取本地图片然后上传到服务器的整体流程步骤如下:

样式

布局代码
<?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:layout_width="200dp"
android:layout_height="200dp"
android:background="#555555"
android:id="@+id/iv_image"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/xz"
android:text="选择图片"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/sc"
android:text="上传图片"/>
</LinearLayout>
id:iv_image用于呈现选择的图片
id:xz用于选择图片的按钮
id:sc用于上传的按钮

流程:点击“选择图片”在本机选取图片然后呈现到ImageView中(这个操作过程是不需要申请任何权限的)
(1)获取“选择图片”按钮,并设置监听事件。
xz = (Button) findViewById(R.id.xz);//选择照片按钮
xz.setOnClickListener(this);//设置监听
(2)获取ImageView,便于之后呈现图片
iv_image = (ImageView) findViewById(R.id.iv_image);//展示图片按钮
(3)点击“选择图片”后操作,选取图片
点击事件
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.xz:
xzImage();//选择图片
break;
}
}
xzImage()函数进行图片选择
private void xzImage() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent,CHOOSE_PHOTO); // 打开本地存储
//CHOOSE_PHOTO:全局常量,标识
}
CHOOSE_PHOTO:是一个全局常量,用于标识这是选择图片的这个操作,便于在回调函数中使用。
public static final int CHOOSE_PHOTO = 1;
(4)重写选择图片后的回调函数
当在手机上选择完图片后会回调onActivityResult函数,将一下“选择的信息”返回供用户操作。
requestCode:标识码
data:选择的图片的信息
data.getData()可以获取到图片的路径,但是是虚拟路径不是真实的存储路径,虚拟路径在ImageView组件中可以使用,但是如果通过虚拟路径进行创建文件(new File(“路径”))是不可能的。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//requestCode:标识码
//data:选择的图片的信息
switch (requestCode) {
case CHOOSE_PHOTO:
//显示图片
iv_image.setImageURI(data.getData());//放在ImageView中呈现
break;
default:
break;
}
}
以上操作全部是无需权限申请的

上述操作的完整代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button xz;
private ImageView iv_image;
public static final int CHOOSE_PHOTO = 1;//标识选择图片
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//按钮
xz = (Button) findViewById(R.id.xz);//选择照片按钮
//图片
iv_image = (ImageView) findViewById(R.id.iv_image);//展示图片
//设置点击事件监听
xz.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.xz:
xzImage();
break;
}
}
private void xzImage() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent,CHOOSE_PHOTO); // 打开本地存储
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case CHOOSE_PHOTO://判断是不是选择照片后的操作
//显示图片
iv_image.setImageURI(data.getData());
break;
default:
break;
}
}
}

由于data.getData()获取到的是图片的虚拟路径,所以我们需要对路径进行路径转换,从虚拟路径到真实路径的转换。(通过Utils.getRealPath(this, data)方法进行转换)
Utils.getRealPath(this, data)方法是封装的工具类

public class Utils {
public static String getRealPath(Context context,Intent data){
// 判断手机系统版本号
if (Build.VERSION.SDK_INT >= 19) {
// 4.4及以上系统使用这个方法处理图片
return handleImageOnKitKat(context,data);
} else {
// 4.4以下系统使用这个方法处理图片
return handleImageBeforeKitKat(context,data);
}
}
@TargetApi(19)
private static String handleImageOnKitKat(Context context,Intent data) {
String imagePath = null;
Uri uri = data.getData();
if (DocumentsContract.isDocumentUri(context, uri)) {
// 如果是document类型的Uri,则通过document id处理
String docId = DocumentsContract.getDocumentId(uri);
if("com.android.providers.media.documents".equals(uri.getAuthority())) {
String id = docId.split(":")[1]; // 解析出数字格式的id
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePath(context,MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public downloads"), Long.valueOf(docId));
imagePath = getImagePath(context,contentUri, null);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
// 如果是content类型的Uri,则使用普通方式处理
imagePath = getImagePath(context,uri, null);
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
// 如果是file类型的Uri,直接获取图片路径即可
imagePath = uri.getPath();
}
//displayImage(imagePath); // 根据图片路径显示图片
return imagePath;
}
private static String handleImageBeforeKitKat(Context context,Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(context,uri, null);
return imagePath;
}
@SuppressLint("Range")
private static String getImagePath(Context context,Uri uri, String selection) {
String path = null;
// 通过Uri和selection来获取真实的图片路径
Cursor cursor = context.getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
}
由于在工具类中进行路径转换时候需要用到存储权限,所以我们在之前代码的基础上加上动态权限申请

注意:是在点击“选择图片”后进行权限申请,申请完后再去选择图片
顺序是这样子的:点击“选择图片按钮” == > 权限申请 ==》选择图片·
(1)在Manifest.xml中加入权限声明
<!--写-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--读-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
(2)在onclick处加上权限检测、申请步骤
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.xz:
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION);
} else {
xzImage();
}
break;
}
}
Manifest.permission.WRITE_EXTERNAL_STORAGE既包括读权限又包括写权限,所以在Manifest.xml中才声明了两个
STORAGE_PERMISSION:是一个全局常量,用于标识申请的是什么权限,方便在权限的回调函数中使用。
public static final int STORAGE_PERMISSION = 1;
(3)添加权限的回调函数
//选择权限后的回调函数
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case STORAGE_PERMISSION:
//检查是否有读取存储卡的权限,如果有则选择图片,如果没有则提示
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
xzImage();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
STORAGE_PERMISSION:标识申请的是存储权限,是全局常量。
(4)测试
我们在onActivityResult中输出虚拟路径和真实路径看一下(如果不做上述的权限申请,则在调用Utils.getRealPath(this, data)方法进行路径转换时会报错)
//选择图片后的回调函数
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case CHOOSE_PHOTO:
//显示图片
iv_image.setImageURI(data.getData());
System.out.println("图片在手机上的虚拟路径为:"+data.getData());
String realPath = Utils.getRealPath(this, data);
System.out.println("图片在手机上的真实路径为:"+realPath);
break;
default:
break;
}
}
申请权限

输出

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button xz;
private ImageView iv_image;
public static final int CHOOSE_PHOTO = 1;//标识选择图片
public static final int STORAGE_PERMISSION = 1;//标识权限申请
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//按钮
xz = (Button) findViewById(R.id.xz);//选择照片按钮
//图片
iv_image = (ImageView) findViewById(R.id.iv_image);//展示图片
//设置点击事件监听
xz.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.xz:
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION);
} else {
xzImage();
}
break;
}
}
private void xzImage() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent,CHOOSE_PHOTO); // 打开本地存储
}
//选择图片后的回调函数
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case CHOOSE_PHOTO:
//显示图片
iv_image.setImageURI(data.getData());
System.out.println("图片在手机上的虚拟路径为:"+data.getData());
String realPath = Utils.getRealPath(this, data);
System.out.println("图片在手机上的真实路径为:"+realPath);
break;
default:
break;
}
}
//选择权限后的回调函数
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case STORAGE_PERMISSION:
//检查是否有读取存储卡的权限,如果有则选择图片,如果没有则提示
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
xzImage();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
}

当我们通过Utils.getRealPath(this, data)方法获取到真实的图片路径后就可以通过new File(路径的方式将图片封装成File对象了)
String realPath = Utils.getRealPath(this, data);
file = new File(realPath);
这里的file是全局变量,便于后续上传的时候使用
private File file=null;
所以此时选择图片后的回调函数变为如下:
//选择图片后的回调函数
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case CHOOSE_PHOTO:
//显示图片
iv_image.setImageURI(data.getData());
//System.out.println("图片在手机上的虚拟路径为:"+data.getData());
String realPath = Utils.getRealPath(this, data);
file = new File(realPath);
//System.out.println("图片在手机上的真实路径为:"+realPath);
break;
default:
break;
}
}
至此从相册选择图片到将图片创建成file对象已经全部完成。
这半部分全部完成

为便于测试,该项目的服务器代码比较简单,通过一个接口接收文件(uploadfile),以及传过来的其余参数(name)。输出其余参数,并将图片存储到项目的当前目录下命名为a.jpg
@RestController
@RequestMapping("/test")
@CrossOrigin
public class TestController {
/**
*
* @param uploadfile 接收文件
* @param name 接收其余参数
* @return
* @throws IOException
*/
@PostMapping("/upload")
public String upload(MultipartFile uploadfile,String name) throws IOException {
//1、输出测试
System.out.println("==============");
System.out.println(uploadfile);
System.out.println(name);
System.out.println(uploadfile.getName());
System.out.println(uploadfile.getSize());
//2、将上传的图片存储到硬盘
InputStream inputStream = uploadfile.getInputStream();
FileChannel inChannel = (FileChannel) Channels.newChannel(inputStream);
FileChannel outChannel = new FileOutputStream("./a.jpg").getChannel();//当前目录下,命名为a.jpg
inChannel.transferTo(0,inChannel.size(),outChannel);
//3、关闭流
inChannel.close();
outChannel.close();
inputStream.close();
//4、返回成功信息
return "success";
}
}
(springboot项目)
目前为止服务器端的代码也以及全部搞定

Android端的东西全部搞定,服务器端的东西也全部搞定,接下来就是通信了,也就是将文件从Android端传输到服务器端。
传输文件采用okhttp进行数据的传输
(1)权限申请
由于传输的时候使用到网络,所以在Manifest.xml中声明网络权限
<!-- 网络-->
<uses-permission android:name="android.permission.INTERNET" />
并在Manifest.xml中加入这个,我们不采用https进行传输

(2)引入okhttp的依赖
//okhttp
implementation 'com.squareup.okhttp3:okhttp:3.14.9'

刷新后便可以将okhttp的依赖加载到本地
(3)传输文件的工具类
通过okhttp封装成上传文件的工具类
public class HttpUtil {
/**
*
* @param address 服务器地址
* @param requestBody 请求体数据
* @param callback 回调接口
*/
public static void uploadFile(String address,RequestBody requestBody ,okhttp3.Callback callback){
//发送请求
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)//设置连接超时时间
.readTimeout(60, TimeUnit.SECONDS)//设置读取超时时间
.build();
Request request = new Request.Builder()
.url(address)
.post(requestBody)
.build();
client.newCall(request).enqueue(callback);
}
}
(4)上传函数
获取“上传按钮”并设置监听事件
sc = (Button) findViewById(R.id.sc);//上传按钮
sc.setOnClickListener(this);//上传
在点击事件中添加scImage()函数,在点击"上传图片"按钮后触发
onclick中

@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.xz:
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION);
} else {
xzImage();
}
break;
case R.id.sc:
scImage();
break;
}
}
scImage()函数
private void scImage() {
//1、创建请求体
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)//请求类型
.addFormDataPart("name", "lisi")//参数1
.addFormDataPart("uploadfile", "uploadfile", RequestBody.create(MediaType.parse("*/*"), file)) // 第一个参数传到服务器的字段名,第二个你自己的文件名,第三个MediaType.parse("*/*")数据类型,这个是所有类型的意思,file就是我们之前创建的全局file,里面是创建的图片
.build();
//2、调用工具类上传图片以及参数
HttpUtil.uploadFile("http://你的服务器IP:8080/test/upload", requestBody, new Callback() {
//请求失败回调函数
@Override
public void onFailure(Call call, IOException e) {
System.out.println("=============");
System.out.println("异常::");
e.printStackTrace();
}
//请求成功响应函数
@Override
public void onResponse(Call call, Response response) throws IOException {
showResponse(response.body().string());//在主线程中显示提示框
}
});
}
注意:安卓端的字段名要与服务器端接收的字段名字一样


由于在子线程不能操作ui,所以这里调用showResponse在主线程中提示,响应结果。
showResponse()
//ui操作,提示框
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// 在这里进行UI操作,将结果显示到界面上
Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show();
}
});
}
至此已经完成了所有步骤“Android端”、“服务器端”、“传输数据”的代码编写。
我们将服务器端代码在服务器部署好,然后在Android端输入对应的ip,进行测试。
服务的“当前文件夹目录”

传输

传输完后

可以拉到本地打开看一下


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 网络-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
android:usesCleartextTraffic="true"
>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>
</manifest>
<?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:layout_width="200dp"
android:layout_height="200dp"
android:background="#555555"
android:id="@+id/iv_image"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/xz"
android:text="选择图片"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/sc"
android:text="上传图片"/>
</LinearLayout>
public class HttpUtil {
/**
*
* @param address 服务器地址
* @param requestBody 请求体数据
* @param callback 回调接口
*/
public static void uploadFile(String address,RequestBody requestBody ,okhttp3.Callback callback){
//发送请求
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)//设置连接超时时间
.readTimeout(60, TimeUnit.SECONDS)//设置读取超时时间
.build();
Request request = new Request.Builder()
.url(address)
.post(requestBody)
.build();
client.newCall(request).enqueue(callback);
}
}
public class Utils {
public static String getRealPath(Context context,Intent data){
// 判断手机系统版本号
if (Build.VERSION.SDK_INT >= 19) {
// 4.4及以上系统使用这个方法处理图片
return handleImageOnKitKat(context,data);
} else {
// 4.4以下系统使用这个方法处理图片
return handleImageBeforeKitKat(context,data);
}
}
@TargetApi(19)
private static String handleImageOnKitKat(Context context,Intent data) {
String imagePath = null;
Uri uri = data.getData();
if (DocumentsContract.isDocumentUri(context, uri)) {
// 如果是document类型的Uri,则通过document id处理
String docId = DocumentsContract.getDocumentId(uri);
if("com.android.providers.media.documents".equals(uri.getAuthority())) {
String id = docId.split(":")[1]; // 解析出数字格式的id
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePath(context,MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public downloads"), Long.valueOf(docId));
imagePath = getImagePath(context,contentUri, null);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
// 如果是content类型的Uri,则使用普通方式处理
imagePath = getImagePath(context,uri, null);
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
// 如果是file类型的Uri,直接获取图片路径即可
imagePath = uri.getPath();
}
//displayImage(imagePath); // 根据图片路径显示图片
return imagePath;
}
private static String handleImageBeforeKitKat(Context context,Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(context,uri, null);
return imagePath;
}
@SuppressLint("Range")
private static String getImagePath(Context context,Uri uri, String selection) {
String path = null;
// 通过Uri和selection来获取真实的图片路径
Cursor cursor = context.getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button xz;
private Button sc;
private ImageView iv_image;
public static final int CHOOSE_PHOTO = 1;
public static final int STORAGE_PERMISSION = 1;
private File file=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//按钮
xz = (Button) findViewById(R.id.xz);//选择照片按钮
sc = (Button) findViewById(R.id.sc);//上传按钮
//图片
iv_image = (ImageView) findViewById(R.id.iv_image);//展示图片
//设置点击事件监听
xz.setOnClickListener(this);//选择
sc.setOnClickListener(this);//上传
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.xz:
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION);
} else {
xzImage();
}
break;
case R.id.sc:
scImage();
break;
}
}
private void xzImage() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent,CHOOSE_PHOTO); // 打开本地存储
}
private void scImage() {
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("name", "lisi")
.addFormDataPart("uploadfile", "uploadfile", RequestBody.create(MediaType.parse("*/*"), file)) // 第一个参数传到服务器的字段名,第二个你自己的文件名,第三个MediaType.parse("*/*")数据类型,这个是所有类型的意思,file就是我们之前创建的全局file,里面是创建的图片
.build();
HttpUtil.uploadFile("http://你自己的ip:8080/test/upload", requestBody, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.out.println("=============");
System.out.println("异常::");
e.printStackTrace();
//Toast.makeText(MainActivity.this, "上传异常", Toast.LENGTH_SHORT).show();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
showResponse(response.body().string());
//Toast.makeText(MainActivity.this, "上传成功", Toast.LENGTH_SHORT).show();
}
});
}
//选择图片后的回调函数
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case CHOOSE_PHOTO:
//显示图片
iv_image.setImageURI(data.getData());
//System.out.println("图片在手机上的虚拟路径为:"+data.getData());
String realPath = Utils.getRealPath(this, data);
file = new File(realPath);
//System.out.println("图片在手机上的真实路径为:"+realPath);
break;
default:
break;
}
}
//选择权限后的回调函数
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case STORAGE_PERMISSION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
xzImage();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
//ui操作,提示框
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// 在这里进行UI操作,将结果显示到界面上
Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show();
}
});
}
}
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的
最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru
在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo
我是Rails的新手,所以请原谅简单的问题。我正在为一家公司创建一个网站。那家公司想在网站上展示它的客户。我想让客户自己管理这个。我正在为“客户”生成一个表格,我想要的三列是:公司名称、公司描述和Logo。对于名称,我使用的是name:string但不确定如何在脚本/生成脚手架终端命令中最好地创建描述列(因为我打算将其设置为文本区域)和图片。我怀疑描述(我想成为一个文本区域)应该仍然是描述:字符串,然后以实际形式进行调整。不确定如何处理图片字段。那么……说来话长:我在脚手架命令中输入什么来生成描述和图片列? 最佳答案 对于“文本”数
您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除
我有带有Logo图像的公司模型has_attached_file:logo我用他们的Logo创建了许多公司。现在,我需要添加新样式has_attached_file:logo,:styles=>{:small=>"30x15>",:medium=>"155x85>"}我是否应该重新上传所有旧数据以重新生成新样式?我不这么认为……或者有什么rake任务可以重新生成样式吗? 最佳答案 参见Thumbnail-Generation.如果rake任务不适合你,你应该能够在控制台中使用一个片段来调用重新处理!关于相关公司
我在Rails应用程序中使用CarrierWave/Fog将视频上传到AmazonS3。有没有办法判断上传的进度,让我可以显示上传进度如何? 最佳答案 CarrierWave和Fog本身没有这种功能;你需要一个前端uploader来显示进度。当我不得不解决这个问题时,我使用了jQueryfileupload因为我的堆栈中已经有jQuery。甚至还有apostonCarrierWaveintegration因此您只需按照那里的说明操作即可获得适用于您的应用的进度条。 关于ruby-on-r
其实做自媒体的成本并不高,入门只需要一部手机即可!在手机上找视频素材、使用手机剪辑视频、最后使用手机发布视频作品获得收益!方法并不难,今天这期内容就来给粉丝们分享一种小方法,每天稳定收益100-300,抓紧点赞收藏!1、找素材(1)使用手机拍摄自己喜欢的经典段落,使用程序把文案内容提取出来(2)也可以在豆瓣、知乎、微博等网站中找一些自己需要的文案素材(3)把文案进行润色修改,可以加入一些自己的观点(4)视频素材可以使用软件中自带的素材,也可以在素材网站中下载完整版的素材2、文案配音(1)把复制好的文案直接导入小程序中(2)调整音色、音调后一键合成音频即可(3)可以选择自己朗读配音,需要花一点时
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,