基本步驟:打開相機(jī)和相冊獲取一張照片灶搜,轉(zhuǎn)為base64,異步上傳到百度ai平臺共啃,拿到返回結(jié)果占调。
總結(jié)知識點:
- 安卓系統(tǒng)相機(jī)和相冊的使用
- 百度開放平臺識別接口的使用
- okhttp
- 使用handler異步上傳圖片
- 使用bundle線程間傳遞數(shù)據(jù)
- 識別結(jié)果JSON的解析
- dialog的使用
由于幾個比較簡單就不詳述,重點講下相機(jī)移剪,百度,handler薪者,bundle
通過相機(jī)和相冊獲取照片
權(quán)限注冊:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- 彈出一個dialog詢問是相機(jī)還是相冊:
String items [] = {"相機(jī)","相冊"};// 用于作為dialog的選項
AlertDialog dialog = new AlertDialog.Builder(getContext())
.setTitle("請選擇圖片獲取途徑")
.setItems(items, new DialogInterface.OnClickListener() {
@Override // dialog的點擊事件
public void onClick(DialogInterface dialogInterface, int i) {
switch (i){
case 0:
openCamera();
break;
case 1:
openAlbum();
break;
}
}
}).create();
dialog.show();
2.詳解openCamera()
public static final int PHOTO_REQUEST_CAMERA = 1;// 拍照
private Intent intent_camera;
public static File tempFile;
private void openCamera() {
//獲取系統(tǒng)版本
int currentapiVersion = android.os.Build.VERSION.SDK_INT;
// 創(chuàng)建隱式調(diào)用相機(jī)的intent
intent_camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 判斷存儲卡是否可以用纵苛,可用進(jìn)行存儲
if (hasSdcard()) {
// 取得當(dāng)前時間命名文件
SimpleDateFormat timeStampFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
String filename = timeStampFormat.format(new Date());
// 創(chuàng)建存放照片的文件
tempFile = new File(Environment.getExternalStorageDirectory(), filename + ".jpg");
if (currentapiVersion < 24) {
// 從文件中創(chuàng)建uri
imageUri = Uri.fromFile(tempFile);
//將當(dāng)前uri放到系統(tǒng)指定的位置,(系統(tǒng)會自動將照片放到這個位置的uri中)
intent_camera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent_camera, PHOTO_REQUEST_CAMERA);
} else {
//兼容android7.0 使用共享文件的形式
contentValues = new ContentValues(1);
contentValues.put(MediaStore.Images.Media.DATA, tempFile.getAbsolutePath());
imageUri = getActivity().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
//檢查是否有存儲權(quán)限言津,以免崩潰
if ( ContextCompat.checkSelfPermission(getActivity(),
Manifest.permission.WRITE_EXTERNAL_STORAGE )
!= PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(getActivity(),
Manifest.permission.CAMERA)
!=PackageManager.PERMISSION_GRANTED ) {
//申請WRITE_EXTERNAL_STORAGE權(quán)限
ActivityCompat.requestPermissions(getActivity(),
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.CAMERA},
REQUEST_PERMISSSION_CAMERA);
}else { // 有權(quán)限
intent_camera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
// 開啟一個帶有返回值的Activity攻人,請求碼為PHOTO_REQUEST_CMERA
startActivityForResult(intent, PHOTO_REQUEST_CAMERA);
}
}
}
}
首先創(chuàng)建一個用于啟動系統(tǒng)相機(jī)的intent和一個用于存放照片的file,接著獲取當(dāng)前的手機(jī)系統(tǒng)版本悬槽,(由于安卓7.0前后uri的獲取方式有了變化怀吻,7.0以后使用共享文件的形式獲取uri),如果有物理內(nèi)存初婆,獲取當(dāng)前的時間用于命名相冊file蓬坡,相對路徑構(gòu)造file猿棉,如果是7.0以下版本,直接用Uri.fromFile(tempFile)獲得uri屑咳,放到系統(tǒng)指定的位置(MediaStore.EXTRA_OUTPUT)萨赁,系統(tǒng)會將照片復(fù)制到這個uri上(我也不知道是不是復(fù)制啊,反正就是在拍照前指定MediaStore.EXTRA_OUTPUT一個uri兆龙,拍照后這個uri就是照片)杖爽,然后startActivityForResult(intent_camera, PHOTO_REQUEST_CAMERA);在回調(diào)中拿到uri(照片),7.0以上除了uri獲取不一樣其他一模一樣紫皇,就是那三行慰安,核心使用contentValues,思路和7.0以下一樣聪铺。
// 判斷是否含有存儲空間
private boolean hasSdcard() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
3.詳解openAlbum()
public static final int PHOTO_REQUEST_ALBUM = 0; // 相冊
private Intent intent_album;
// 打開相冊
private void openAlbum() {
intent_album = new Intent(Intent.ACTION_PICK);
intent_album.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");
startActivityForResult(intent_album,PHOTO_REQUEST_ALBUM);
}
先創(chuàng)建一個隱式打開相冊的intent泻帮,(其實這個是我百度過來的,沒看懂计寇,缺個解析锣杂,19.4.6)
4.處理回調(diào)
// 處理活動回調(diào)
Bitmap bitmap;
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode){
case PHOTO_REQUEST_CAREMA : // 處理相機(jī)的回調(diào)
// 自定義handler接收子線程傳回來的result
myHandler = new MyHandler();
if (resultCode == RESULT_OK){
// 處理照片
try {
// 照片的bitmap
bitmap = BitmapFactory.decodeStream(getActivity().getContentResolver().openInputStream(imageUri));
imageView.setImageBitmap(bitmap);
// 識別
identify(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
case PHOTO_REQUEST_ALBUM: // 相冊的回調(diào)
myHandler = new MyHandler();
try {
Uri selectedImage = data.getData();
String[] filePathColumns = {MediaStore.Images.Media.DATA};
Cursor c = getActivity().getContentResolver().query(selectedImage, filePathColumns, null, null, null);
c.moveToFirst();
int columnIndex = c.getColumnIndex(filePathColumns[0]);
String imagePath = c.getString(columnIndex);
Bitmap bitmap = BitmapFactory.decodeFile(imaePath);
imageView.setImageBitmap(bitmap);
identify(bitmap);
c.close();
}catch (NullPointerException e){
Log.d("E",e.getMessage());
}
break;
}
}
- 先說相機(jī)回調(diào):通過ContentValues拿到上文的uri,在變變變成bitmap番宁,傳入 identify(bitmap)方法中去識別元莫,在下文百度ai中詳細(xì)講這個方法。
- 再談相冊回調(diào):都說是百度的了蝶押,前面都沒看懂后面怎么可能會踱蠢,先欠著,大致是一頓操作過后拿到bitmap傳入identify()中去棋电,和相機(jī)的回調(diào)收尾一樣茎截。
下面詳細(xì)講解identify()
百度ai開放平臺
我們先貼上identify()代碼
// 識別
String access_token ;
String param;
private void identify(Bitmap bitmap) {
// 壓縮到0.1 拿到base64編碼,合成param
ByteArrayOutputStream bStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 10, bStream);
byte[] bytes = bStream.toByteArray();
String base64 = Base64Util.encode(bytes);
try {
String image_param = URLEncoder.encode(base64,"UTF-8");
param = "image=" + image_param + "&top_num=" + 6;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 獲得access token 24.1b561ba2365e2ab484e69e953f09a48c.2592000.1555152863.282335-15652740
String request_access_token = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=aHWDOH5MERuoXkwUIOtbwU7j&client_secret=2QbMhquS9aRY2Gd4hpjlU8SXpIGfC3zm&";
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder()
.get()
.url(request_access_token)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
ToastUtil.toast(getContext(),"access__token請求失敗"+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) {
// 解析拿到acces_stoken
String jsonString = null;
try {
jsonString = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
Log.d("tag",jsonString);
Gson gson = new Gson();
JsonAccess jsonAccess = gson.fromJson(jsonString ,JsonAccess.class);
access_token = jsonAccess.access_token;
}
});
// 開啟子線程上傳base64
new Thread(new Runnable() {
String url = "https://aip.baidubce.com/rest/2.0/image-classify/v1/animal";
@Override
public void run() {
try {
String result = HttpUtil.post(url,"24.1b561ba2365e2ab484e69e953f09a48c.2592000.1555152863.282335-15652740",param);
// bundle用于傳遞線程數(shù)據(jù)
Bundle bundle = new Bundle();
bundle.putString("result",result);
// 通過message發(fā)送到主線程
Message message = new Message();
message.setData(bundle);
message.what = 1;
myHandler.sendMessage(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
使用的是百度ai的動物識別接口,官方文檔地址:https://ai.baidu.com/docs#/ImageClassify-API/top
在看之前赶盔,可以先看一下我的觀后感:
- 先注冊賬號企锌,創(chuàng)建應(yīng)用,拿到Api key和Secret key于未。
- 然后看文檔
我們傳進(jìn)來的是bitmap撕攒,百度需要的base64編碼,去掉編碼頭后再進(jìn)行urlencode烘浦,所以第一步先將我們的bitmap轉(zhuǎn)變?yōu)閎ase64抖坪,先創(chuàng)建ByteArrayOutputStream流,將bitmap壓縮到bStream流里(通過compress方法闷叉,參數(shù)1為壓縮類型擦俐,參數(shù)2為壓縮比例10代表原圖片的0.1,參數(shù)3為bitmap壓縮的目標(biāo)流)握侧,將流轉(zhuǎn)為byte[]蚯瞧,通過百度官方提供的Base64轉(zhuǎn)換類的encode方法將byte轉(zhuǎn)為base64嘿期,base64轉(zhuǎn)換工具下載地址:https://ai.baidu.com/file/C8D81F3301E24D2892968F09AE1AD6E2
將得到的base64指定為UTF-8,然和合成post請求的param,指定參數(shù)image和top_num状知,下面一段代碼是按照官方提示獲得accessToken秽五,沒什么好說的,(注意我在代碼中拿到的accesstoken是在子線程中的饥悴,我在下文使用的時候直接傳的值坦喘,沒有傳變量,如果傳變量需要用到bundle和handler)我們現(xiàn)在所有的操作都是在主線程中西设,無法進(jìn)行耗時操作瓣铣,發(fā)送圖片為耗時操作,所以我們開啟一個線程去post我們的圖片給百度贷揽,請求返回結(jié)果棠笑,在子線程中,我們準(zhǔn)備好請求的uri地址禽绪,accessToken和存放圖片的param蓖救,通過百度的請求工具獲取result,請求工具地址:https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
現(xiàn)在我們已經(jīng)在子線程中拿到識別結(jié)果了印屁,下面需要將他們傳到主線程循捺,解析,展示雄人。
handler與bundle線程間傳遞數(shù)據(jù)
先定義MyHandler類用于處理子線程發(fā)送來的message
class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 取出bundle得到結(jié)果
Bundle bundle = msg.getData();
String result = bundle.getString("result");
String s = "";
try {
JSONObject jsonObject = new JSONObject(result);
JSONArray jsonArray = jsonObject.getJSONArray("result");
for (int i = 0; i <1;i++ ){
JSONObject obj = (JSONObject) jsonArray.get(i);
s = s+obj.getString("name")+"\n";
}
textView.setText(s);
} catch (JSONException e) {
e.printStackTrace();
}
textView.setVisibility(View.VISIBLE);
intent_show = new Intent(getContext(),ShowActivity.class);
intent_show.putExtra("result",result);
// getContext().startActivity(intent_show);
}
}
子線程發(fā)送數(shù)據(jù)(上文identify()中的子線程)
new Thread(new Runnable() {
String url = "https://aip.baidubce.com/rest/2.0/image-classify/v1/animal";
@Override
public void run() {
try {
String result = HttpUtil.post(url,"24.1b561ba2365e2ab484e69e953f09a48c.2592000.1555152863.282335-15652740",param);
// bundle用于傳遞線程數(shù)據(jù)
Bundle bundle = new Bundle();
bundle.putString("result",result);
// 通過message發(fā)送到主線程
Message message = new Message();
message.setData(bundle);
message.what = 1;
myHandler.sendMessage(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
在子線程拿到了返回的result从橘,通過bundle和myHandler傳遞到主線程,bundle用于存放數(shù)據(jù)础钠,handler用于發(fā)送數(shù)據(jù)(需要借助handler的message類)恰力,在自定義的MyHandler類中我們handleMessage,取出bundle旗吁,取出result踩萎,解析JSON,然后展示給用戶阵漏,這些handleMessage的邏輯是寫在類里面的驻民,什么時候執(zhí)行啊,在我們的MyHandler被實例話的時候會執(zhí)行履怯,仔細(xì)去看下上文的onActivityResult在處理相機(jī)和相冊的兩個回調(diào)中,我們是不是初始化了我們的myHandler裆泳。