編輯:關於Android編程
Android 程序上實現拍照功能的方式分為兩種:第一種是利用相機的 API 來自定義相機,第二種是利用 Intent 調用系統指定的相機拍照。下面講的內容都是針對第二種實現方式的適配。
通常情況下,我們調用拍照的業務場景是如下面這樣的:
A 界面,點擊按鈕調用相機拍照;
A 界面得到拍完照片,跳轉到 B 界面進行預覽;
B 界面有個按鈕,點擊後觸發某個業務流程來處理這張照片;
實現的大體流程代碼如下:
01.
//1、調用相機
02.
FilemPhotoFile=
new
File(folder,filename);
03.
IntentcaptureIntent=
new
Intent(MediaStore.ACTION_IMAGE_CAPTURE);
04.
UrifileUri=Uri.fromFile(mPhotoFile);
05.
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT,fileUri);
06.
mActivity.startActivityForResult(captureIntent,CAPTURE_PHOTO_REQUEST_CODE);
07.
08.
//2、拿到照片
09.
@Override
10.
protected
void
onActivityResult(
int
requestCode,
int
resultCode,Intentdata){
11.
if
(requestCode==CapturePhotoHelper.CAPTURE_PHOTO_REQUEST_CODE&&resultCode==RESULT_OK){
12.
FilephotoFile=mCapturePhotoHelper.getPhoto();
//獲取拍完的照片
13.
if
(photoFile!=
null
){
14.
PhotoPreviewActivity.preview(
this
,photoFile);
//跳轉到預覽界面
15.
}
16.
finish();
17.
}
else
{
18.
super
.onActivityResult(requestCode,resultCode,data);
19.
}
20.
}
21.
22.
//3、各種各樣處理這張圖片的業務代碼
到這裡基本科普完了如何調用系統相機拍照,相信這些網上一搜一大把的代碼,很多童鞋都能看懂。
前面講到我們是調用系統指定的相機app來拍照,那麼系統是否存在可以被我們調用的app呢?這個我們不敢確定,畢竟 Android 奇葩問題多,還真有遇到過這種極端的情況導致閃退的。雖然很極端,但作為客戶端人員還是要進行處理,方式有二:
調用相機時,簡單粗暴的 try-catch
調用相機前,檢測系統有沒有相機 app 可用
try-catch 這種粗暴的方式大家肯定很熟悉了,那麼要如何檢測系統有沒有相機 app 可用呢?系統在 PackageManager 裡為我們提供這樣一個 API
通過這樣一個 API ,可以知道系統是否存在 action 為 MediaStore.ACTION_IMAGE_CAPTURE 的 intent 可以喚起的拍照界面,具體實現代碼如下:
01.
/**
02.
*判斷系統中是否存在可以啟動的相機應用
03.
*
04.
*@return存在返回true,不存在返回false
05.
*/
06.
public
boolean
hasCamera(){
07.
PackageManagerpackageManager=mActivity.getPackageManager();
08.
Intentintent=
new
Intent(MediaStore.ACTION_IMAGE_CAPTURE);
09.
Listlist=packageManager.queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY);
10.
return
list.size()>
0
;
11.
}
經常會遇到一種情況,拍照時看到照片是正的,但是當我們的 app 獲取到這張照片時,卻發現旋轉了 90 度(也有可能是180、270,不過90度比較多見,貌似都是由於手機傳感器導致的)。很多童鞋對此感到很困擾,因為不是所有手機都會出現這種情況,就算會是出現這種情況的手機上,也並非每次必現。要怎麼解決這個問題呢?從解決的思路上看,只要獲取到照片旋轉的角度,利用 Matrix 來進行角度糾正即可。那麼問題來了,要怎麼知道照片旋轉的角度呢?細心的童鞋可能會發現,拍完一張照片去到相冊點擊屬性查看,能看到下面這樣一堆關於照片的屬性數據
沒錯,這裡面就有一個旋轉角度,倘若拍照後保存的成像照片文件發生了角度旋轉,這個圖片的屬性參數就能告訴我們到底旋轉了多少度。只要獲取到這個角度值,我們就能進行糾正的工作了。 Android 系統提供了 ExifInterface 類來滿足獲取圖片各個屬性的操作
通過 ExifInterface 類拿到 TAG_ORIENTATION 屬性對應的值,即為我們想要得到旋轉角度。再根據利用 Matrix 進行旋轉糾正即可。實現代碼大致如下:
01.
/**
02.
*獲取圖片的旋轉角度
03.
*
04.
*@parampath圖片絕對路徑
05.
*@return圖片的旋轉角度
06.
*/
07.
public
static
int
getBitmapDegree(Stringpath){
08.
int
degree=
0
;
09.
try
{
10.
//從指定路徑下讀取圖片,並獲取其EXIF信息
11.
ExifInterfaceexifInterface=
new
ExifInterface(path);
12.
//獲取圖片的旋轉信息
13.
int
orientation=exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL);
14.
switch
(orientation){
15.
case
ExifInterface.ORIENTATION_ROTATE_90:
16.
degree=
90
;
17.
break
;
18.
case
ExifInterface.ORIENTATION_ROTATE_180:
19.
degree=
180
;
20.
break
;
21.
case
ExifInterface.ORIENTATION_ROTATE_270:
22.
degree=
270
;
23.
break
;
24.
}
25.
}
catch
(IOExceptione){
26.
e.printStackTrace();
27.
}
28.
return
degree;
29.
}
30.
31.
/**
32.
*將圖片按照指定的角度進行旋轉
33.
*
34.
*@parambitmap需要旋轉的圖片
35.
*@paramdegree指定的旋轉角度
36.
*@return旋轉後的圖片
37.
*/
38.
public
static
BitmaprotateBitmapByDegree(Bitmapbitmap,
int
degree){
39.
//根據旋轉角度,生成旋轉矩陣
40.
Matrixmatrix=
new
Matrix();
41.
matrix.postRotate(degree);
42.
//將原始圖片按照旋轉矩陣進行旋轉,並得到新的圖片
43.
BitmapnewBitmap=Bitmap.createBitmap(bitmap,
0
,
0
,bitmap.getWidth(),bitmap.getHeight(),matrix,
true
);
44.
if
(bitmap!=
null
&&!bitmap.isRecycled()){
45.
bitmap.recycle();
46.
}
47.
return
newBitmap;
48.
}
ExifInterface 能拿到的信息遠遠不止旋轉角度,其他的參數感興趣的童鞋可以看看 API 文檔。
曾在小米和魅族的某些機型上遇到過這樣的問題,調用系統相機拍照,拍完點擊確定回到自己的app裡面卻莫名奇妙的閃退了。這種閃退有兩個特點:
沒有什麼錯誤日志(有些機子啥日志都沒有,有些機子會出來個空異常錯誤日志);
同個機子上非必現(有時候怎麼拍都不閃退,有時候一拍就閃退);
對待非必現問題往往比較頭疼,當初遇到這樣的問題也是非常不解。上網搜羅了一圈也沒方案,後來留意到一個比較有意思信息:有些系統廠商的 ROM 會給自帶相機應用做優化,當某個 app 通過 intent 進入相機拍照界面時,系統會把這個 app 當前最上層的 Activity 銷毀回收。(注意:我遇到的情況是有時候很快就回收掉,有時候怎麼等也不回收,沒有什麼必現規律)為了驗證一下,便在啟動相機的 Activity 中對 onDestory 方法進行加 log 。果不其然,終於發現進入拍照界面的時候 onDestory 方法被執行了。所以,前面提到的閃退基本可以推測是 Activity 被回收導致某些非UI控件的成員變量為空導致的。(有些機子會報出空異常錯誤日志,但是有些機子閃退了什麼都不報,是不是覺得很奇葩!)
既然涉及到 Activity 被回收的問題,自然要想起 onSaveInstanceState 和 onRestoreInstanceState 這對方法。去到 onSaveInstanceState 把數據保存,並在 onRestoreInstanceState 方法中進行恢復即可。大體代碼思路如下:
01.
@Override
02.
protected
void
onSaveInstanceState(BundleoutState){
03.
super
.onSaveInstanceState(outState);
04.
mRestorePhotoFile=mCapturePhotoHelper.getPhoto();
05.
if
(mRestorePhotoFile!=
null
){
06.
outState.putSerializable(EXTRA_RESTORE_PHOTO,mRestorePhotoFile);
07.
}
08.
09.
}
10.
11.
@Override
12.
protected
void
onRestoreInstanceState(BundlesavedInstanceState){
13.
super
.onRestoreInstanceState(savedInstanceState);
14.
mRestorePhotoFile=(File)savedInstanceState.getSerializable(EXTRA_RESTORE_PHOTO);
15.
mCapturePhotoHelper.setPhoto(mRestorePhotoFile);
16.
}
對於 onSaveInstanceState 和 onRestoreInstanceState 方法的作用還不熟悉的童鞋,網上資料很多,可以自行搜索。
到這裡,可能有童鞋要問,這種閃退並不能保證復現,我要怎麼知道問題所在和是否修復了呢?我們可以去到開發者選項裡開啟不保留活動這一項進行調試驗證
它作用是保留當前和用戶接觸的 Activity ,並將目前無法和用戶交互 Activity 進行銷毀回收。打開這個調試選項就可以滿足驗證的需求,當你的 app 的某個 Activity 跳轉到拍照的 Activity 後,這個 Activity 立馬就會被系統銷毀回收,這樣就可以很好的完全復現閃退的場景,幫助開發者確認問題有沒有修復了。
涉及到 Activity 被銷毀,還想提一下代碼實現上的問題。假設當前有兩個 Activity ,MainActivity 中有個 Button ,點擊可以調用系統相機拍照並顯示到 PreviewActivity 進行預覽。有下面兩種實現方案:
方案一:MainActivity 中點擊 Button 後,啟動系統相機拍照,並在 MainActivity 的 onActivityResult 方法中獲取拍下來的照片,並啟動跳轉到 PreviewActivity 界面進行效果預覽;
方案二:MainActivity 中點擊 Button 後,啟動 PreviewActivity 界面,在 PreviewActivity 的 onCreate(或者onStart、onResume)方法中啟動系統相機拍照,然後在 PreviewActivity 的 onActivityResult 方法中獲取拍下來的照片進行預覽;
上面兩種方案得到的實現效果是一模一樣的,但是第二種方案卻存在很大的問題。因為啟動相機的代碼放在 onCreate(或者onStart、onResume)中,當進入拍照界面後,PreviewActivity 隨即被銷毀,拍完照確認後回到 PreviewActivity 時,被銷毀的 PreviewActivity 需要重建,又要走一遍 onCreate、onStart、onResume,又調用了啟動相機拍照的代碼,周而復始的進入了死循環狀態。為了避免讓你的用戶抓狂,果斷明智的選擇方案一。
以上這種情況提到調用系統拍照時,Activity就回收的情況,在小米4S和小米4 LTE機子上(MIUI的版本是7.3,Android系統版本是6.0)出現的概率很高。 所以,建議看到此文的童鞋也可以去驗證適配一下。
圖片無法顯示這個問題也是略坑,如何坑法?往下看,同樣是在小米4S和小米4 LTE機子上(MIUI的版本是7.3,Android系統版本是6.0)出現概率很高的場景(當然,不保證其他機子沒出現過)。按照我們前面提到的業務場景,調用相機拍照完成後,我們的 app 會有一個預覽圖片的界面。但是在用了小米的機子進行拍照後,自己 app 的預覽界面卻怎麼也無法顯示出照片來,同樣是相當郁悶,郁悶完後還是要一步一步去排查解決問題的!為此,需要一步一步猜測驗證問題所在。
猜測一:沒有拿到照片路徑,所以無法顯示?
直接斷點打 log 跟蹤,猜測一很快被推翻,路徑是有的。
猜測二:Bitmap太大了,無法顯示?
直接在 AS 的 log 控制台仔細的觀察了一下系統 log ,發現了一些蛛絲馬跡
1.
OpenGLRenderer:Bitmaptoolargetobeuploadedintoatexture
每次拍完照片,都會出現上面這樣的 log ,果然,因為圖片太大而導致在 ImageView 上無法顯示。到這裡有童鞋要吐槽了,沒對圖片的采樣率inSampleSize做處理?天地良心啊,絕對做處理了,直接看代碼:
01.
/**
02.
*壓縮Bitmap的大小
03.
*
04.
*@paramimagePath圖片文件路徑
05.
*@paramrequestWidth壓縮到想要的寬度
06.
*@paramrequestHeight壓縮到想要的高度
07.
*@return
08.
*/
09.
public
static
BitmapdecodeBitmapFromFile(StringimagePath,
int
requestWidth,
int
requestHeight){
10.
if
(!TextUtils.isEmpty(imagePath)){
11.
if
(requestWidth<=
0
||requestHeight<=
0
){
12.
Bitmapbitmap=BitmapFactory.decodeFile(imagePath);
13.
return
bitmap;
14.
}
15.
BitmapFactory.Optionsoptions=
new
BitmapFactory.Options();
16.
options.inJustDecodeBounds=
true
;
//不加載圖片到內存,僅獲得圖片寬高
17.
BitmapFactory.decodeFile(imagePath,options);
18.
options.inSampleSize=calculateInSampleSize(options,requestWidth,requestHeight);
//計算獲取新的采樣率
19.
options.inJustDecodeBounds=
false
;
20.
return
BitmapFactory.decodeFile(imagePath,options);
21.
22.
}
else
{
23.
return
null
;
24.
}
25.
}
26.
27.
public
static
int
calculateInSampleSize(BitmapFactory.Optionsoptions,
int
reqWidth,
int
reqHeight){
28.
final
int
height=options.outHeight;
29.
final
int
width=options.outWidth;
30.
int
inSampleSize=
1
;
31.
Log.i(TAG,
"height:"
+height);
32.
Log.i(TAG,
"width:"
+width);
33.
if
(height>reqHeight||width>reqWidth){
34.
35.
final
int
halfHeight=height/
2
;
36.
final
int
halfWidth=width/
2
;
37.
38.
while
((halfHeight/inSampleSize)>reqHeight&&(halfWidth/inSampleSize)>reqWidth){
39.
inSampleSize*=
2
;
40.
}
41.
42.
long
totalPixels=width*height/inSampleSize;
43.
44.
final
long
totalReqPixelsCap=reqWidth*reqHeight*
2
;
45.
46.
while
(totalPixels>totalReqPixelsCap){
47.
inSampleSize*=
2
;
48.
totalPixels/=
2
;
49.
}
50.
}
51.
return
inSampleSize;
52.
}
瞄了代碼後,是不是覺得沒有問題了?沒錯,inSampleSize 確確實實經過處理,那為什麼圖片還是太大而顯示不出來呢? requestWidth、int requestHeight 設置得太大導致 inSampleSize 太小了?不可能啊,我都試著把長寬都設置成 100 了還是沒法顯示!干脆,直接打印 inSampleSize 值,一打印,inSampleSize 值居然為 1 。 我去,徹底打臉了,明明說好的處理過了,居然還是1!!!!為了一探究竟,干脆加 log 。
01.
public
static
BitmapdecodeBitmapFromFile(StringimagePath,
int
requestWidth,
int
requestHeight){
02.
if
(!TextUtils.isEmpty(imagePath)){
03.
Log.i(TAG,
"requestWidth:"
+requestWidth);
04.
Log.i(TAG,
"requestHeight:"
+requestHeight);
05.
if
(requestWidth<=
0
||requestHeight<=
0
){
06.
Bitmapbitmap=BitmapFactory.decodeFile(imagePath);
07.
return
bitmap;
08.
}
09.
BitmapFactory.Optionsoptions=
new
BitmapFactory.Options();
10.
options.inJustDecodeBounds=
true
;
//不加載圖片到內存,僅獲得圖片寬高
11.
BitmapFactory.decodeFile(imagePath,options);
12.
Log.i(TAG,
"originalheight:"
+options.outHeight);
13.
Log.i(TAG,
"originalwidth:"
+options.outWidth);
14.
options.inSampleSize=calculateInSampleSize(options,requestWidth,requestHeight);
//計算獲取新的采樣率
15.
Log.i(TAG,
"inSampleSize:"
+options.inSampleSize);
16.
options.inJustDecodeBounds=
false
;
17.
return
BitmapFactory.decodeFile(imagePath,options);
18.
19.
}
else
{
20.
return
null
;
21.
}
22.
}
運行打印出來的日志如下:
圖片原來的寬高居然都是 -1 ,真是奇葩了!難怪,inSampleSize 經過處理之後結果還是 1 。狠狠的吐槽了之後,總是要回來解決問題的。那麼,圖片的寬高信息都丟失了,我去哪裡找啊? 像下面這樣?
01.
public
static
BitmapdecodeBitmapFromFile(StringimagePath,
int
requestWidth,
int
requestHeight){
02.
...
03.
BitmapFactory.Optionsoptions=
new
BitmapFactory.Options();
04.
options.inJustDecodeBounds=
true
;
//不加載圖片到內存,僅獲得圖片寬高
05.
Bitmapbitmap=BitmapFactory.decodeFile(imagePath,options);
06.
bitmap.getWidth();
07.
bitmap.getHeight();
08.
...
09.
}
else
{
10.
return
null
;
11.
}
12.
}
no,此方案行不通,inJustDecodeBounds = true 時,BitmapFactory 獲得 Bitmap 對象是 null;那要怎樣才能獲圖片的寬高呢?前面提到的 ExifInterface 再次幫了我們大忙,通過它的下面兩個屬性即可拿到圖片真正的寬高
順手吐槽一下,為什麼高不是 TAG_IMAGE_HEIGHT 而是 TAG_IMAGE_LENGTH。改良過後的代碼實現如下:
01.
public
static
BitmapdecodeBitmapFromFile(StringimagePath,
int
requestWidth,
int
requestHeight){
02.
if
(!TextUtils.isEmpty(imagePath)){
03.
Log.i(TAG,
"requestWidth:"
+requestWidth);
04.
Log.i(TAG,
"requestHeight:"
+requestHeight);
05.
if
(requestWidth<=
0
||requestHeight<=
0
){
06.
Bitmapbitmap=BitmapFactory.decodeFile(imagePath);
07.
return
bitmap;
08.
}
09.
BitmapFactory.Optionsoptions=
new
BitmapFactory.Options();
10.
options.inJustDecodeBounds=
true
;
//不加載圖片到內存,僅獲得圖片寬高
11.
BitmapFactory.decodeFile(imagePath,options);
12.
Log.i(TAG,
"originalheight:"
+options.outHeight);
13.
Log.i(TAG,
"originalwidth:"
+options.outWidth);
14.
if
(options.outHeight==-
1
||options.outWidth==-
1
){
15.
try
{
16.
ExifInterfaceexifInterface=
new
ExifInterface(imagePath);
17.
int
height=exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH,ExifInterface.ORIENTATION_NORMAL);
//獲取圖片的高度
18.
int
width=exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH,ExifInterface.ORIENTATION_NORMAL);
//獲取圖片的寬度
19.
Log.i(TAG,
"exifheight:"
+height);
20.
Log.i(TAG,
"exifwidth:"
+width);
21.
options.outWidth=width;
22.
options.outHeight=height;
23.
}
catch
(IOExceptione){
24.
e.printStackTrace();
25.
}
26.
}
27.
options.inSampleSize=calculateInSampleSize(options,requestWidth,requestHeight);
//計算獲取新的采樣率
28.
Log.i(TAG,
"inSampleSize:"
+options.inSampleSize);
29.
options.inJustDecodeBounds=
false
;
30.
return
BitmapFactory.decodeFile(imagePath,options);
31.
32.
}
else
{
33.
return
null
;
34.
}
35.
}
再看一下,打印出來的log
這樣就可以解決問題啦。
以上總結了這麼些身邊童鞋經常問起,但網上又不多見的適配問題,希望可以幫到一些開發童鞋少走彎路。文中多次提到小米的機子,並不代表只有MIUI上有這樣的問題存在,僅僅只是因為我身邊帶的幾部機子大都是小米的。對待適配問題,在搜索引擎都無法提供多少有效的信息時,我們只能靠斷點、打log、觀察控制台的日志、以及API文檔來尋找一些蛛絲馬跡作為突破口,相信辦法總比困難多。
以上的示例代碼已經整理到:https://github.com/D-clock/AndroidStudyCode,主要的代碼在下面紅圈部分
新浪微博擁有超過5億注冊用戶通過140字記錄,“織圍脖”是網友隨時隨地記錄生活、分享社會新鮮事的生活方式。新浪微博客戶端的朋友登錄之
先給大家展示下效果圖,具體效果圖如下所示:具體代碼如下所示: 1.index.js//index.js//獲取應用實例var app = getApp()Page({ d
上篇文章我們安裝了NDK系統,在NDK系統文件中包含samples文件夾,打開該文件夾,我們發現裡面有大量的案例項目,這裡我們通過Eclipse導入一個名為hello-j
Android的熱修復前言:隨著時代的發展,由於公司的項目需要去求變化平凡計劃總趕不上變化,H5的高靈活性,開發周期短,更新速度快H5以及一些混合開發越來越被看好,然而主