הערה: הדף הזה מתייחס למחלקה מצלמה, שהוצאה משימוש. מומלץ להשתמש ב-CameraX או, במקרים ספציפיים, ב-Camera2. גם CameraX וגם Camera2 תומכים ב-Android 5.0 (רמת API 21) ואילך.
בשיעור הזה לומדים איך לצלם תמונה על ידי הענקת עבודה לאפליקציית מצלמה אחרת במכשיר. (אם אתם מעדיפים ליצור פונקציונליות משלכם למצלמה, תוכלו לעיין במאמר שליטה במצלמה).
נניח שאתם מטמיעים שירות מזג אוויר שמבוסס על נתוני קהלים, ויוצר מפת מזג אוויר גלובלית באמצעות שמשלב תמונות של השמיים שצולמו במכשירים שבהם פועלת אפליקציית הלקוח. שילוב תמונות הוא רק חלק קטן מהאפליקציה. רוצים לצלם תמונות במאמץ מינימלי, ולא להמציא מחדש המצלמה. למרבה המזל, ברוב המכשירים עם Android כבר מותקנת אפליקציית מצלמה אחת לפחות. בשיעור הזה תלמדו איך לגרום לו לצלם תמונה בשבילכם.
בקשה לפיצ'ר המצלמה
אם פונקציה חיונית של האפליקציה מצלםת תמונות, הגבילו את החשיפה שלה כך:
Google Play למכשירים שיש בהם מצלמה. כדי לפרסם שהאפליקציה שלך תלויה
מצלמה, לשים
תג <uses-feature>
ב-
קובץ המניפסט:
<manifest ... > <uses-feature android:name="android.hardware.camera" android:required="true" /> ... </manifest>
אם האפליקציה משתמשת במצלמה אבל לא נדרשת לה כדי לפעול, צריך להגדיר את android:required
לערך false
. לאחר מכן, מערכת Google Play תאפשר למכשירים
ללא מצלמה כדי להוריד את האפליקציה. לאחר מכן, באחריותכם לבדוק את הזמינות של המצלמה בזמן הריצה באמצעות קריאה ל-hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
.
אם אין מצלמה זמינה, צריך להשבית את תכונות המצלמה.
לקבלת התמונה הממוזערת
אם ההישג הפשוט של צילום תמונה הוא לא השגת השאפתנות של האפליקציה, אז כדאי סביר להניח שתרצו לשחזר את התמונה מאפליקציית המצלמה ולעשות איתה משהו.
האפליקציה 'מצלמת Android' מקודדת את התמונה בחזרה
Intent
נמסר אל
onActivityResult()
בתור Bitmap
קטן בתוספות,
מתחת למפתח "data"
. הקוד הבא מאחזר את התמונה הזו ומציג אותה
ImageView
Kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { val imageBitmap = data.extras.get("data") as Bitmap imageView.setImageBitmap(imageBitmap) } }
Java
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { Bundle extras = data.getExtras(); Bitmap imageBitmap = (Bitmap) extras.get("data"); imageView.setImageBitmap(imageBitmap); } }
הערה: התמונה הממוזערת הזו מ-"data"
יכולה להתאים לסמל,
אבל לא הרבה יותר. טיפול בתמונה בגודל מלא דורש קצת יותר עבודה.
שמירת התמונה בגודל מלא
האפליקציה 'מצלמת Android' שומרת תמונה בגודל מלא אם נותנים לה קובץ לשמור בו. שלך חייב לספק שם קובץ מלא שבו אפליקציית המצלמה צריכה לשמור את התמונה.
באופן כללי, יש לשמור במכשיר את כל התמונות שהמשתמש מצלם עם המצלמה של המכשיר
באחסון החיצוני הציבורי כדי שכל האפליקציות יוכלו לגשת אליהן. הספרייה המתאימה לשיתוף
התמונות סופקו על ידי
getExternalStoragePublicDirectory()
,
עם
DIRECTORY_PICTURES
ארגומנט. הספרייה שמסופקת בשיטה הזו משותפת בין כל האפליקציות. ב-Android 9 (רמת API)
28) ומטה, קריאה וכתיבה לספרייה הזו מחייבים את
READ_EXTERNAL_STORAGE
וגם
WRITE_EXTERNAL_STORAGE
בהתאמה:
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
בגרסה Android 10 (רמת API 29) ואילך, הספרייה המתאימה לשיתוף תמונות היא
טבלה MediaStore.Images
.
אין צורך להצהיר על הרשאות אחסון, כל עוד האפליקציה צריכה גישה רק
תמונות שהמשתמש צילם באמצעות האפליקציה.
עם זאת, אם אתם רוצים שהתמונות יישארו פרטיות רק לאפליקציה שלכם, תוכלו להשתמש בספרייה שמספקת Context.getExternalFilesDir()
.
ב-Android 4.3 ומטה, כדי לכתוב לספרייה הזו נדרש
WRITE_EXTERNAL_STORAGE
הרשאה. החל מ-Android 4.4, ההרשאה אינה נדרשת עוד כי הספרייה
לא נגישים לאפליקציות אחרות, לכן ניתן להצהיר שיש לבקש את ההרשאה רק
בגרסאות קודמות של Android על ידי הוספת
maxSdkVersion
:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> ... </manifest>
הערה: קבצים ששומרים בספריות שמספק getExternalFilesDir()
או getFilesDir()
נמחקים כשהמשתמש מסיר את האפליקציה.
אחרי שמחליטים על התיקייה של הקובץ, צריך ליצור שם קובץ עמיד בהתנגשויות.
כדאי גם לשמור את הנתיב במשתנה 'חבר' לשימוש במועד מאוחר יותר. הנה פתרון לדוגמה
בשיטה שמחזירה שם קובץ ייחודי לתמונה חדשה באמצעות חותמת תאריך ושעה.
(בדוגמה הזו ההנחה היא שאתם מפעילים את השיטה מתוך Context
).
Kotlin
lateinit var currentPhotoPath: String @Throws(IOException::class) private fun createImageFile(): File { // Create an image file name val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES) return File.createTempFile( "JPEG_${timeStamp}_", /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ).apply { // Save a file: path for use with ACTION_VIEW intents currentPhotoPath = absolutePath } }
Java
String currentPhotoPath; private File createImageFile() throws IOException { // Create an image file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File image = File.createTempFile( imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ); // Save a file: path for use with ACTION_VIEW intents currentPhotoPath = image.getAbsolutePath(); return image; }
בשיטה הזו אפשר ליצור קובץ לתמונה, עכשיו אפשר ליצור ולהפעיל
Intent
אוהבים את זה:
Kotlin
private fun dispatchTakePictureIntent() { Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> // Ensure that there's a camera activity to handle the intent takePictureIntent.resolveActivity(packageManager)?.also { // Create the File where the photo should go val photoFile: File? = try { createImageFile() } catch (ex: IOException) { // Error occurred while creating the File ... null } // Continue only if the File was successfully created photoFile?.also { val photoURI: Uri = FileProvider.getUriForFile( this, "com.example.android.fileprovider", it ) takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI) startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE) } } } }
Java
private void dispatchTakePictureIntent() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // Ensure that there's a camera activity to handle the intent if (takePictureIntent.resolveActivity(getPackageManager()) != null) { // Create the File where the photo should go File photoFile = null; try { photoFile = createImageFile(); } catch (IOException ex) { // Error occurred while creating the File ... } // Continue only if the File was successfully created if (photoFile != null) { Uri photoURI = FileProvider.getUriForFile(this, "com.example.android.fileprovider", photoFile); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); } } }
הערה: אנחנו משתמשים
getUriForFile(Context, String, File)
שמחזירה URI של content://
. לאפליקציות עדכניות יותר שמטרגטות ל-Android 7.0 (רמת API)
24) ואילך, העברת URI של file://
דרך גבולות חבילה גורמת ל-
FileUriExposedException
.
לכן, אנחנו מציגים עכשיו דרך גנרית יותר לאחסון תמונות באמצעות FileProvider
.
עכשיו צריך להגדיר
FileProvider
במניפסט של האפליקציה, מוסיפים ספק לאפליקציה:
<application> ... <provider android:name="androidx.core.content.FileProvider" android:authorities="com.example.android.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data> </provider> ... </application>
מוודאים שמחרוזת הרשות תואמת לארגומנט השני של getUriForFile(Context, String, File)
.
בקטע המטא-נתונים של הגדרת הספק, אפשר לראות שהספק מצפה שהנתיבים שעומדים בדרישות יוגדרו בקובץ משאב ייעודי,
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="my_images" path="Pictures" /> </paths>
רכיב הנתיב תואם לנתיב שמוחזר
getExternalFilesDir()
כשמתקשרים באמצעות
Environment.DIRECTORY_PICTURES
.
עליך להחליף את com.example.package.name
בשם החבילה האמיתי של
באפליקציה שלך. כמו כן, יש לעיין במסמכי התיעוד של
FileProvider
עבור
תיאור מקיף של מגדירי נתיבים שאפשר להשתמש בהם בנוסף ל-external-path
.
הוספת התמונה לגלריה
כשיוצרים תמונה מתוך כוונה, חשוב לדעת איפה התמונה ממוקמת, כי מלכתחילה אמרתם איפה לשמור אותו. עבור כולם, אולי הדרך הקלה ביותר התמונה שלך נגישה מספק המדיה של המערכת.
הערה: אם שמרתם את התמונה בספרייה של
getExternalFilesDir()
,
לסורק המדיה אין גישה לקבצים כי הם פרטיים לאפליקציה שלך.
בדוגמה הבאה מוסבר איך להפעיל את סורק המדיה של המערכת כדי להוסיף את התמונה שלכם למסד הנתונים של ספק המדיה, וכך להפוך אותה לזמינה באפליקציית הגלריה של Android ובאפליקציות אחרות.
Kotlin
private fun galleryAddPic() { Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent -> val f = File(currentPhotoPath) mediaScanIntent.data = Uri.fromFile(f) sendBroadcast(mediaScanIntent) } }
Java
private void galleryAddPic() { Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); File f = new File(currentPhotoPath); Uri contentUri = Uri.fromFile(f); mediaScanIntent.setData(contentUri); this.sendBroadcast(mediaScanIntent); }
פענוח של תמונה בגודל מותאם
ניהול של כמה תמונות בגודל מלא יכול להיות מסובך בגלל מגבלת זיכרון. אם האפליקציה שלכם נתקלת במחסור בזיכרון אחרי הצגת כמה תמונות בלבד, תוכלו להפחית באופן משמעותי את כמות האוסף הדינמי שנעשה בו שימוש על ידי הרחבת קובץ ה-JPEG למערך זיכרון שכבר עבר שינוי קנה מידה בהתאם לגודל של תצוגת היעד. השיטה הזו לדוגמה מדגימה את השיטה הזו.
Kotlin
private fun setPic() { // Get the dimensions of the View val targetW: Int = imageView.width val targetH: Int = imageView.height val bmOptions = BitmapFactory.Options().apply { // Get the dimensions of the bitmap inJustDecodeBounds = true BitmapFactory.decodeFile(currentPhotoPath, bmOptions) val photoW: Int = outWidth val photoH: Int = outHeight // Determine how much to scale down the image val scaleFactor: Int = Math.max(1, Math.min(photoW / targetW, photoH / targetH)) // Decode the image file into a Bitmap sized to fill the View inJustDecodeBounds = false inSampleSize = scaleFactor inPurgeable = true } BitmapFactory.decodeFile(currentPhotoPath, bmOptions)?.also { bitmap -> imageView.setImageBitmap(bitmap) } }
Java
private void setPic() { // Get the dimensions of the View int targetW = imageView.getWidth(); int targetH = imageView.getHeight(); // Get the dimensions of the bitmap BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; BitmapFactory.decodeFile(currentPhotoPath, bmOptions); int photoW = bmOptions.outWidth; int photoH = bmOptions.outHeight; // Determine how much to scale down the image int scaleFactor = Math.max(1, Math.min(photoW/targetW, photoH/targetH)); // Decode the image file into a Bitmap sized to fill the View bmOptions.inJustDecodeBounds = false; bmOptions.inSampleSize = scaleFactor; bmOptions.inPurgeable = true; Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bmOptions); imageView.setImageBitmap(bitmap); }