Android從系統(tǒng)調(diào)用相冊O(shè)kHttp上傳到服務(wù)器(activity數(shù)據(jù)傳遞)
想寫技術(shù)博客已經(jīng)好久,今天終于下定決心堅持一周一個小博客淑际,我是一名安卓工程師鹰霍,工作剛剛一個月,水平有限炊昆,此文章是結(jié)合郭霖大神的《第一行代碼》以及自己參考網(wǎng)絡(luò)大神上的一些參考代碼結(jié)合起來的,在自己項目中進行調(diào)用威根,雖然有些雜亂凤巨,但是也算是基本的實現(xiàn)了功能。
下面先來展示一下功能截圖
功能流程相信各位也都能看懂洛搀,在第一個注冊信息完成后點擊下一步后繼續(xù)完成第二個頁面的注冊信息敢茁,然后將注冊信息上傳到服務(wù)器。
我也是第一次工作第一次自己進行獨立開發(fā)留美,所以可能會走不少彎路彰檬,但是總算功能能夠?qū)崿F(xiàn)了。
下面來給大家展示一下代碼谎砾,我會著重將一些重要的代碼展示出來
牽扯到的知識點
- Android調(diào)用系統(tǒng)相冊及相機(隱性intent)
- ContentProvider
- okHttp
- popupWindow
- Bundle
代碼講解
為了能夠更好的點擊btn彈出相機或者相冊逢倍,我自定義了一個popupWindow,這個如果你不想寫景图,可以百度得到较雕。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="10dp"
android:orientation="vertical"
android:paddingBottom="10dp">
<Button
android:id="@+id/btn_pop_album"
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="#ffff"
android:text="本地相冊"
android:textSize="18sp" />
<Button
android:id="@+id/btn_pop_camera"
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="#ffff"
android:text="相機拍攝"
android:textSize="18sp" />
<Button
android:id="@+id/btn_pop_cancel"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_marginTop="10dp"
android:background="#ffff"
android:text="取消"
android:textSize="18sp" />
</LinearLayout>
</RelativeLayout>
>
點擊popupWindow調(diào)用相機或相冊
View popView = View.inflate(this, R.layout.popup_choose_pic, null);
Button btnPopAlbum = (Button) popView.findViewById(R.id.btn_pop_album);
Button btnPopCamera = (Button) popView.findViewById(R.id.btn_pop_camera);
Button btnPopCancel = (Button) popView.findViewById(R.id.btn_pop_cancel);
//獲取屏幕寬高
int widthPixels = getResources().getDisplayMetrics().widthPixels;
int heightPixels = getResources().getDisplayMetrics().heightPixels * 1 / 3;
final PopupWindow popupWindow = new PopupWindow(popView, widthPixels, heightPixels);
popupWindow.setAnimationStyle(R.style.anim_popup_dir);
popupWindow.setFocusable(true);
//點擊popup外部消失
popupWindow.setOutsideTouchable(true);
//消失時屏幕變?yōu)榘胪该? popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
WindowManager.LayoutParams params = getWindow().getAttributes();
params.alpha = 1.0f;
getWindow().setAttributes(params);
}
});
//出現(xiàn)時屏幕變?yōu)橥该? WindowManager.LayoutParams params = getWindow().getAttributes();
params.alpha = 0.5f;
getWindow().setAttributes(params);
popupWindow.showAtLocation(popView, Gravity.BOTTOM, 0, 50);
btnPopAlbum.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popupWindow.dismiss();
//調(diào)用相機
invokeAlbum();
}
});
btnPopCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popupWindow.dismiss();
//打開相機
openCarema();
}
});
btnPopCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popupWindow.dismiss();
}
});
可以直接拷貝到xml里面看預(yù)覽圖
下面我們就著重講解點擊頭像這個功能邏輯
其實對于調(diào)用相冊以及相機這幾個功能,許多的API我也不是特別了解挚币,也不能詳細的跟大家解釋一下亮蒋,在這里我將我拷貝這些代碼的時候遇到的坑跟大家說下-------功能摘自《第一行代碼第二版》相機相冊那個章節(jié)。
調(diào)用相冊
private void invokeAlbum() {
//動態(tài)申請危險時權(quán)限忘晤,運行時權(quán)限
if (ContextCompat.checkSelfPermission(HgCompleteInfoActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(HgCompleteInfoActivity.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
openAlbum();
}
}
private void openAlbum() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent, CHOOSE_PHOTO);
}
在這里先進行了動態(tài)時權(quán)限宛蚓,這是6.0之后支持的激捏,具體的可以看郭霖的blog设塔,目前著重實現(xiàn)功能
/**
* 動態(tài)獲取到的權(quán)限后的重寫
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openAlbum();
} else {
Toast.makeText(HgCompleteInfoActivity.this, "you denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
這里是動態(tài)獲取到權(quán)限后的重寫方法,如果不夠理解,可以想一下
app安裝時軟件介紹會解釋獲取哪些權(quán)限闰蛔,那些權(quán)限是屬于manifests里注冊的痕钢,而動態(tài)獲取權(quán)限時,當(dāng)你第一次打開app時序六,比如需要獲取你的地理位置任连,你點了拒絕,會彈出上面方法里面case1-->else邏輯例诀。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case CHOOSE_PHOTO:
if (resultCode == RESULT_OK) {
//判斷手機系統(tǒng)版本號
if (Build.VERSION.SDK_INT >= 19) {
handleImageOnKitKat(data);
} else {
handleImageBeforeKitKat(data);
}
}
break;
}
}
private void handleImageBeforeKitKat(Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(uri, null);
displayImage(imagePath);
}
private void handleImageOnKitKat(Intent data) {
String imagePath = null;
Uri uri = data.getData();
//Log.i("uri", uri + "");
if (DocumentsContract.isDocumentUri(HgCompleteInfoActivity.this, uri)) {
//如果是document類型的uri随抠,則通過document id 處理
String docId = DocumentsContract.getDocumentId(uri);
Log.i("type of document", docId);
if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
String id = docId.split(":")[1];//解析出數(shù)字格式的id
Log.i("type of document id", id);
String selection = MediaStore.Images.Media._ID + "=" + id;
Log.i("selection", selection);
imagePath = getImagePath(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(contentUri, null);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
//如果是content類型的uri,就用普通方式處理
imagePath = getImagePath(uri, null);
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
//如果是file類型的Uri繁涂,直接獲取圖片路徑
imagePath = uri.getPath();
}
displayImage(imagePath);
}
private String getImagePath(Uri uri, String selection) {
String path = null;
//通過uri和selection來獲取真實的圖片路徑
Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToNext()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
private void displayImage(String imagePath) {
if (imagePath != null) {
bitmap = BitmapFactory.decodeFile(imagePath);
headFile = saveMyBitmap(bitmap, "head");
//保存file到sp
saveFile(headFile.getName());
Glide.with(this).asBitmap().load(headFile).thumbnail(0.1f).into(civHead);
} else {
Toast.makeText(HgCompleteInfoActivity.this, "failed to get image ", Toast.LENGTH_SHORT).show();
}
}
//將bitmap轉(zhuǎn)化為png格式
public File saveMyBitmap(Bitmap mBitmap, String prefix) {
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
File file = null;
try {
file = File.createTempFile(
prefix, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
FileOutputStream fos = new FileOutputStream(file);
mBitmap.compress(Bitmap.CompressFormat.JPEG, 10, fos);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
return file;
}
/**
* 保存file到sp
*
* @param fileName
*/
private void saveFile(String fileName) {
SharedPreferences sp = getSharedPreferences("image", MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putString(HEAD_KEY, fileName);
//提交edit
edit.commit();
Log.i(TAG, "saveFile: 保存成功" + sp.getString("head", null));
}
這是阻擾我時間最多的邏輯部分拱她,首先解釋一下這些代碼的邏輯
1.點擊獲取到的相冊顯示到我上圖中的CircleImageView(記得一定要壓縮,不然拍完照返回照片到ImageView會特別的慢扔罪,在這里我做的也不夠好秉沼,所以不展開講)
2.然后自己寫了兩個方法,savaFile和savaMyBitmap
3.在savaMyBitmap方法中矿酵,首先獲取了系統(tǒng)相冊的地址唬复,然后每次我拍下照片或者選擇照片時,都進行簡單的壓縮全肮,因為圖片要上傳到服務(wù)器進行審核管理敞咧,做成微信頭像之類的超級壓縮方法有損畫質(zhì),所以我只是進行了簡單的壓縮上傳最后寫成一個文件辜腺。(在第二個頁面的時候可能會有幾個問題妄均,三個ImageView即是三個bitmap,三個File文件哪自。如何判斷及正確的顯示丰包,我們可以定義成全局變量,因為都是點擊事件壤巷,所以bitmap會在點擊后修改邑彪,然后保存下來。
4.在savaFile中胧华,我靈機一動(哈哈)寄症,想到了SharedPreferences這個神奇功能,我們沒有必要用sp去存file矩动,因為沒有那個方法有巧,我們可以將名字存起來啊,edit.putString()中的HEAD_KEY就是我自己定的方法悲没。你可以把sp當(dāng)成一個map篮迎,key是不會變的,但是value會變,所以理論上不管我們點多少次都會改變甜橱。
5.這樣文件就保存在本地了(可是不知道為啥我找不到,但是上傳到服務(wù)器里可以顯示)逊笆。
調(diào)用相機
private void openCarema() {
//創(chuàng)建File對象,用于存儲拍照后的照片
File outputImage = new File(getExternalCacheDir(), "output_image.jpg");
if (outputImage.exists()) {
outputImage.delete();
}
try {
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= 24) {
imageUri = FileProvider.getUriForFile(HgCompleteInfoActivity.this, "com.qryl.qrylyh.activity.login.complete.fileprovider", outputImage);
} else {
imageUri = Uri.fromFile(outputImage);
}
//啟動相機程序
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO);
}
這里不多贅述岂傲,調(diào)用Uri的邏輯功能代碼都是郭霖的难裆,我是cv戰(zhàn)士。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case TAKE_PHOTO:
if (resultCode == RESULT_OK) {
//將拍攝的圖片顯示出來
try {
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
headFile = saveMyBitmap(bitmap, "head");
//保存file到sp
saveFile(headFile.getName());
Glide.with(this).asBitmap().load(headFile).thumbnail(0.1f).into(civHead);
Log.i("wechat", "壓縮后圖片的大小" + ("字節(jié)碼:" + " 寬度為:" + bitmap.getWidth() + " 高度為:" + bitmap.getHeight()));
Log.i(TAG, "File:" + headFile.getName() + " 路徑:" + headFile.getAbsolutePath());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
}
}
和上面的方法基本都一樣镊掖。
使用Bundle以及SharedPreferences的理由
因為我要把第一頁的數(shù)據(jù)全部保存下來乃戈,全部在第二個頁面點擊注冊時一起上傳,這樣的話只是請求一次服務(wù)器亩进,所以我想到了sp方法將文件名字存儲起來偏化,在第二個頁面直接調(diào)用File方法把文件取出來即可
而Bundle只是為了區(qū)別分開,本來我是打開實現(xiàn)serializable的一個Map工具類集合進行數(shù)據(jù)的傳遞镐侯,技術(shù)有限侦讨,老是異常,就想到了用File存儲的方法
其他注冊信息Bundle方法
/**
* 傳遞數(shù)據(jù)到下個頁面
*/
private void putExtra() {
Intent intent = new Intent(HgCompleteInfoActivity.this, CompletePicActivity.class);
//傳遞數(shù)據(jù)
Bundle bundle = new Bundle();
//bundle.putByteArray("head", bytes);
bundle.putString("name", ageDialogText);
bundle.putString("identity", identityDialogText);
bundle.putString("gender", genderDialogText);
bundle.putString("age", ageDialogText);
bundle.putString("workexperience", workExperienceDialogText);
bundle.putString("begoodat", beGoodAtWorkDialogText);
bundle.putString("localservice", null);
intent.putExtras(bundle);
startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_complete_pic);
initView();
Bundle bundle = getIntent().getExtras();
String name = (String) bundle.get("name");
String indentity = (String) bundle.get("identity");
String gender = (String) bundle.get("gender");
String age = (String) bundle.get("age");
String workexperience = (String) bundle.get("workexperience");
String begoodat = (String) bundle.get("begoodat");
String localservice = (String) bundle.get("localservice");
//dataMap.put("head", head.toString());
dataMap.put("name", name);
dataMap.put("indentity", indentity);
dataMap.put("gender", gender);
dataMap.put("age", age);
dataMap.put("workexperience", workexperience);
dataMap.put("begoodat", begoodat);
dataMap.put("localservice", localservice);
}
這沒啥好說的苟翻,第二個頁面直接取出來韵卤,我為了以后后期方便維護,定義了一個HashMap,如果沒有此需求可以直接取出來當(dāng)成全局變量進行調(diào)用即可崇猫。
調(diào)用圖片上傳的拓展
不知道大家是不是還記得第二個頁面有三個上傳照片的圖片沈条,在剛才我也提過,如何在正確的相框中實現(xiàn)正確的圖片诅炉,是一個不難但是挺啰嗦的事情
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case TAKE_PHOTO:
if (resultCode == RESULT_OK) {
//將拍攝的圖片顯示出來
try {
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
if (choosed_image == R.id.sfz_image) {//身份證
//保存file到sp
sfzFile = saveMyBitmap(bitmap, "sfz");
saveFile(SFZ_KEY, sfzFile.getName());
Glide.with(this).asBitmap().load(sfzFile).thumbnail(0.1f).into(sfzImage);
} else if (choosed_image == R.id.jkz_image) {//健康證
jkzFile = saveMyBitmap(bitmap, "jkz");
//保存file到sp
saveFile(JKZ_KEY, jkzFile.getName());
Glide.with(this).asBitmap().load(jkzFile).thumbnail(0.1f).into(jkzImage);
} else if (choosed_image == R.id.zgz_image) {//從業(yè)資格證
zgzFile = saveMyBitmap(bitmap, "zgz");
//保存file到sp
saveFile(ZGZ_KEY, zgzFile.getName());
Glide.with(this).asBitmap().load(zgzFile).thumbnail(0.1f).into(zgzImage);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
case CHOOSE_PHOTO:
if (resultCode == RESULT_OK) {
//判斷手機系統(tǒng)版本號
if (Build.VERSION.SDK_INT >= 19) {
handleImageOnKitKat(data);
} else {
handleImageBeforeKitKat(data);
}
}
break;
}
}
這些代碼看著很繁瑣蜡歹,其實東西沒多少,只是比上面的邏輯多了個if涕烧,在這里我定義了一個全局的int變量月而,將三個相冊區(qū)別開來,R.id.sfz_image就是一個int值不是嗎议纯。這樣看起來更加具像化父款,最好不用數(shù)字區(qū)分開富腊。savaFile一樣梳凛。
這樣基本的上傳照片基本完成撇吞,我們可以點擊注冊把數(shù)據(jù)整合起來直接上傳到服務(wù)器了辅甥,在這里我用了okHttp框架
在第一行代碼里,okHttp只是簡單的展示了一下方法栋艳,大家可以百度一下各種okHttp方法抓谴,因為我們這個項目钧椰,定的統(tǒng)一是post請求蛛壳,而且因為數(shù)據(jù)類型可能不同(File,String,int)杏瞻,所以我經(jīng)過半天的不懈努力所刀,看到了一個nb的API:addFormDataPart()
/**
* 向服務(wù)器發(fā)送請求
*/
private void postData() {
SharedPreferences pref = getSharedPreferences("image", Context.MODE_PRIVATE);
String headImage = pref.getString(HEAD_KEY, null);
String sfzImage = pref.getString(SFZ_KEY, null);
String jkzImage = pref.getString(JKZ_KEY, null);
String zgzName = pref.getString(ZGZ_KEY, null);
//Log.i(TAG, "postData: 頭像圖片名字" + imageName);
OkHttpClient client = new OkHttpClient();
MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
File headFile = new File(storageDir, headImage);
File sfzFile = new File(storageDir, sfzImage);
File jkzFile = new File(storageDir, jkzImage);
File zgzFile = new File(storageDir, zgzName);
if (headFile != null) {
// MediaType.parse() 里面是上傳的文件類型。
RequestBody body = RequestBody.create(MediaType.parse("image/*"), headFile);
// 參數(shù)分別為伐憾, 請求key ,文件名稱 赫模, RequestBody
builder.addFormDataPart("txImg", headFile.getName(), body);
}
if (sfzFile != null) {
// MediaType.parse() 里面是上傳的文件類型树肃。
RequestBody body = RequestBody.create(MediaType.parse("image/*"), sfzFile);
// 參數(shù)分別為, 請求key 瀑罗,文件名稱 胸嘴, RequestBody
builder.addFormDataPart("sfzImg", sfzFile.getName(), body);
}
if (jkzFile != null) {
// MediaType.parse() 里面是上傳的文件類型。
RequestBody body = RequestBody.create(MediaType.parse("image/*"), jkzFile);
// 參數(shù)分別為斩祭, 請求key 劣像,文件名稱 , RequestBody
builder.addFormDataPart("jkzImg", jkzFile.getName(), body);
}
if (zgzFile != null) {
// MediaType.parse() 里面是上傳的文件類型摧玫。
RequestBody body = RequestBody.create(MediaType.parse("image/*"), zgzFile);
// 參數(shù)分別為耳奕, 請求key ,文件名稱 诬像, RequestBody
builder.addFormDataPart("zgzImg", zgzFile.getName(), body);
}
builder.addFormDataPart("loginId", "1");
//builder.add("sfzImg", "1");
//builder.add("zgzImg", "1");
//builder.add("jkzImg", "1");
builder.addFormDataPart("realName", "sdfdf");
builder.addFormDataPart("gender", "0");
builder.addFormDataPart("age", "10");
builder.addFormDataPart("workYears", "10");
builder.addFormDataPart("introduce", "sdfsdfsf");
MultipartBody requestBody = builder.build();
Request request = new Request.Builder().url("服務(wù)器地址").post(requestBody).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i(TAG, "onFailure: 失敗");
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.i(TAG, "onResponse: 成功 " + response.body().string());
}
});
}
可以忽略那些沒用的提取數(shù)據(jù)代碼
寫到后面也比較懶了屋群,因為是周末,晚上還有極限挑戰(zhàn)哈哈坏挠,所以要抓緊
在這client的回調(diào)我覺得用過okHttp的人都能看懂芍躏,重要的newCall方法里面的參數(shù),在這里我是用MultipartBody類降狠,大家可以看上面那一段代碼的第12行開始
MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
這段代碼是定義的這是上傳的表單類型
RequestBody body = RequestBody.create(MediaType.parse("image/*"), headFile);
// 參數(shù)分別為对竣, 請求key ,文件名稱 榜配, RequestBody
builder.addFormDataPart("txImg", headFile.getName(), body);
image/*是因為否纬,我的圖片文件是.jpg格式。
在這里蛋褥,我打了詳細的注釋,幫助大家也幫助自己理解烦味。這樣,我們在兩個頁面進行實現(xiàn)注冊頁面壁拉,用了一次請求就將數(shù)據(jù)上傳到了服務(wù)器谬俄。其實上傳的方式有很多種,用流的方式上傳圖片也是可以的弃理。第一次博客溃论,因為自己本身是一個剛工作一個月的菜鳥,自己一個人搞安卓端的開發(fā)痘昌,也算是獨立開發(fā)了钥勋。所以要多走不少彎路炬转,實現(xiàn)一個功能可能會麻煩不少。希望以后好好做一個cv戰(zhàn)士算灸。