Scoped Storage in Android 10: Getting Started

文章推薦指數: 80 %
投票人數:10人

Scoped storage came on the scene when Android 10 officially launched on September 3, 2019. It changed things a bit. As the name suggests, it ... Group Group Group Group Group Group Group Group Group Group Shape Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group Group OurBiggestBlackFridaySale—Ever! Introducingunlimitedaccesstoallcourses,allbooks,andournewmonthlyliveprofessionaldevelopmentseries!Just$899$399peryearduringourBlackFridayevent Getitnow raywenderlich.com raywenderlich.com Hidecontents ScopedStorageinAndroid10:GettingStarted GettingStarted WhatIsScopedStorage? WhyDoYouNeedScopedStorage? ImplementingNon-ScopedStorageinAndroid10 AddingtheRequiredPermissions FetchingImagesUsingMediaStore DeletinganImageFromMediaStore ListeningforChangesWithContentObserver RegisteringtheContentObserver UnregisteringtheContentObserver WheretoGoFromHere? WorkingwiththefilesystemisanimportantpartofdevelopinganyAndroidapp.UpuntilAndroid10,whenyougaveanappstoragepermission,itcouldaccessanyfileonthedevice.However,mostappsdon’tneedaccesstothewholestoragesystem.Theyusuallyperformtasksonasinglefileorasmallgroupoffiles.Thiscreatedathreattouserprivacy. InAndroid10,Googleintroducedtheconceptofscopedstorage,whichenhancesusercontrolandprivacywhilecuttingbackthefileclutterthatremovedappsleavebehind. Inthistutorial,you’llbuildasimplegalleryappcalledScopeo,whichdisplaysthephotosfromthedevice’ssharedstorage.Alongtheway,you’lllearnabout: Whatscopedstorageisandwhyyouneedit. Addingthecorrectpermissionswhenworkingwithfiles. UsingMediaStoreAPIstoaccessthefiles. Howtooptoutofscopedstorage,ifrequired. DeletingfilesinMediaStoredirectlybyusinganIntentSender. Note:Thistutorialassumesyou’refamiliarwiththebasicsofAndroiddevelopmentinKotlin.Ifyou’recompletelynewtoKotlinandAndroid,checkoutthisKotlintutorialandthisBeginningAndroidDevelopmenttutorial. YoushouldalsohavesomefamiliaritywithGoogle’sarchitecturecomponents,suchasLiveDataandViewModel.GothroughAndroidJetpackArchitectureComponents:GettingStartedtolearnmoreaboutthem. GettingStarted ClicktheDownloadMaterialsbuttonatthetoporbottomofthepagetodownloadthestarterandfinalprojects. OpenAndroidStudio3.6.1orlaterandchooseOpenanexistingAndroidStudioproject.ThennavigatetothestarterprojectdirectoryinsidethemainfolderandclickOpen.WaitforGradletosyncsuccessfullyandtakesometimetofamiliarizeyourselfwiththecode. Asyouseefromthescreenshotabove,thestarterprojectcontainsthefollowingfiles: Image.ktisasimpledataclassthatcontainssomepropertiesoftheimage.ItalsoincludesaDiffCallbacktoefficientlyupdatetheimagelistwhentheuserdeletesanimage. MainActivity.ktiswherealltheUIinteractionsoccur.Itcontainsalotofboilerplatecodealreadyimplementedforyou.Forsimplicity,italsocontainsGalleryAdapterandImageViewHolderinthesamefile. MainActivityViewModel.ktcontainsallthebusinesslogicoftheapp.You’lladdcodeinthisclasstoperformtime-consumingoperationsinthebackground.You’llobservethechangesusingLiveData.Allthebackgroundtasksusetherecommendedapproach,Kotlincoroutines,whichworkwellwithViewModel. Note:IfyouwanttoknowmoreaboutDiffUtil.Callback,lookatAndroid’scallbackdocumentationortheofficialKotlincoroutinesdocumentation. Nowthatyou’velookedoverthecodebase,buildandrun.You’llseethefollowingscreen: OPENALBUMdoesn’tdoanythingrightnow.Asyouprogressthroughthetutorial,you’llbuildacompletelyfunctionalgalleryapp. WhatIsScopedStorage? TheStorageAccessFramework(SAF),introducedinAndroid4.4,madeiteasyfordeveloperstobrowseandopenfilesonthedevice.IntentactionslikeACTION_CREATE_DOCUMENT,ACTION_OPEN_DOCUMENTandACTION_OPEN_DOCUMENT_TREEperformtherequiredoperations. Althoughitworks,SAFisslowandhighlyunpopularamongthedevelopercommunity. Note:TakealookatAndroid’sofficialStorageAccessFrameworkguidetolearnmore. ScopedstoragecameonthescenewhenAndroid10officiallylaunchedonSeptember3,2019.Itchangedthingsabit.Asthenamesuggests,itprovidesscoped—orlimited—accesstothefilesystem.Appsthatusescopedstoragehaveaccessonlytotheirappdirectoryonexternalstorageplusanymediatheappcreated. Imagineyou’recreatingavoicerecorderapp.IfyouimplementscopedstorageinyourappforAndroid10andabove,you’llhavealimitedscopeforreadingandwritingfiles.Sinceyouraudiofilesresideintheappdirectory,youdon’tneedpermissiontoaccessormodifythem. WhyDoYouNeedScopedStorage? Therearethreemainreasonsforusingscopedstorage: Improvingsecurity:Developersshouldhavecontroloverthefilestheirappscreate.Scopedstorageprovidesthiscontrol,lettingdevelopersworkwithfileswithouthavingtorequeststoragepermissionsAtthesametime,scopedstoragealsoprovidesbetterappdataprotectionbecauseotherappscan’taccessthesefileseasily. Reducingleftoverappdata:Whenauseruninstallsanappfromtheirdevice,someappdatastillremainsinthesharedstorage.Withscopedstorage,thisproblemdisappearsasalltheappdataresidesexclusivelyintheappdirectory. LimitingtheabuseofREAD_EXTERNAL_STORAGEpermission:Developershaveabusedthispermissionheavilybecauseitgavethemaccesstotheentireexternalstorage.Usingscopedstorage,appscanaccessonlytheirownfiles,foldersandothermediafiletypesusingstorageAPIs. Eventhoughscopedstorageisuseful,theremaybetimeswhenyoudon’twanttouseit.Luckily,youcanstilloptoutofusingitinAndroid10. ImplementingNon-ScopedStorageinAndroid10 AneasywaytooptoutofscopedstorageistosetrequestLegacyExternalStorageinyourapplicationinAndroidManifest.xmltotrue. ThisattributeisfalsebydefaultonappstargetingAndroid10andhigher.Ifyousetittotrue,itwillmakeanexceptionforyourapp,allowingyoutouselegacystoragesolutions.Thisgrantsaccesstodifferentdirectoriesandmediafileswithoutanyissues. Thisflaggivesdevelopersmoretimetotesttheirappsbeforemigratingtoscopedstorage.However,thisisn’trecommendedbecause,fromAndroid11onwards,thisattributewillnolongerbeavailable. Note:Youwon’tusethisoptioninthistutorial.Ifyou’reeagertoseehowtosetitproperly,lookattheofficialdatastoragedocumentation. Now,it’stimetogetstartedwithusingscopedstorageinanactualapp. AddingtheRequiredPermissions Foryourfirststep,you’lladdpermissionstoletyourappaccessandmodifyimagesfromotherapps. OpenAndroidManifest.xmlandpastethefollowingcodejustbelowtheTODO: Here,youuseREAD_EXTERNAL_STORAGEpermissiontoaccessimagestakenbyotherapps.WRITE_EXTERNAL_STORAGEpermissionletsyoudeletetheseimages.Yousetandroid:maxSdkVersionto28becauseinAndroid10andabove,youdon’tneedthispermissionanymore. Laterinthetutorial,you’llexplicitlyasktheuser’spermissiontohandleimagedeletioninAndroid10. Next,openMainActivity.ktandaddthefollowingpermissionsinsiderequestPermissions(): if(!haveStoragePermission()){ valpermissions=arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) ActivityCompat.requestPermissions( //1 this, //2 permissions, //3 READ_EXTERNAL_STORAGE_REQUEST ) } Thecodeaboverequestsruntimepermissions,whicharemandatoryfromAndroid6.0andhigher. requestPermissions()—fromthesupportlibrary—takesthreeparameters: ThereferenceoftheActivityrequestingpermissions. Astringarrayoftherequiredpermissions. TherequestCode,whichmustbeuniquesinceonRequestPermissionsResult()usesthissamecodetohandlevarioususeractions. Buildandrun.Now,tapOPENALBUM,whichshowsadialogaskingtheuserforstoragepermission: Note:ThedialogdesignmaychangedependingontheAndroidversionyou’reusing.You’llseeadialogliketheoneshownaboveonanAndroid10deviceoremulator. TapDenyandtheappwillshowtherationaleforthepermission. Now,whenyoutapGRANTPERMISSIONandthentapAllow,itwillshowanemptyscreenasbelow: Thescreenisemptybecauseitdoesn’tcontainanyimagesyet.You’lladdthemnext! FetchingImagesUsingMediaStore OpenMainActivityViewModel.ktandaddthefollowinginsidequeryImages(),justafterthe//TODOcomment. //1 valprojection=arrayOf( MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATE_TAKEN ) //2 valselection="${MediaStore.Images.Media.DATE_TAKEN}>=?" //3 valselectionArgs=arrayOf( dateToTimestamp(day=1,month=1,year=2020).toString() ) //4 valsortOrder="${MediaStore.Images.Media.DATE_TAKEN}DESC" //5 getApplication().contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder )?.use{cursor-> imageList=addImagesFromCursor(cursor) } AddanymissingimportsbypressingOption-EnteronMacorAlt-EnteronPC. Here’sastep-by-stepbreakdown: projection:Anarraythatcontainsalltheinformationyouneed.It’ssimilartotheSELECTclauseofanSQLstatement. selection:SimilartotheWHEREclauseinSQL,thisletsyouspecifyanycondition.The?inthestatementisaplaceholderthatwillgetitsvaluefromselectionArgs. selectionArgs:Anarraycontainingthevaluethatwillreplace?inthestatementstoredinselection.Inthiscase,you’rerequestingalltheimagesfromthisyear.dateToTimestamp()isautilityfunctionthatacceptsaday,amonthandayear.Itreturnsthecorrespondingtimestampvalue,whichselectionArgsrequires. sortOrder:Asthenamesuggests,thiscontainstheordertoreturntheimages.Thedefaultorderisascending,buthereyouaddtheDESCkeywordafterthevariablenametoswitchtodescendingorder. query():AmethodofContentResolverthattakesinalltheaboveasparametersaswellasanadditionalUriparameterthatmapstotherequiredtableintheprovider.Inthiscase,therequiredUriisEXTERNAL_CONTENT_URIsinceyouarerequestingimagesfromoutsidetheapp.Uriisalwaysmandatory.Hence,itisanon-nullableparameterwhiletherestoftheparametersarenullable. Phew!Thatwasalottotakein.Keepitup. Buildandruntoseewhatyou’veachievedsofar.Assumingyougrantedthepermissionearlier,you’llnowseesomephotosinsteadoftheblankscreen: Theimageswillbedifferentonyourdevice.Ifyouhaven’ttakenapictureintheyear2020,thenthescreenwillstillbeblank.Inthatcase,goaheadandtakeaselfiewithyourcatordog!Whenyouopentheappagain,you’llseeyourpicturethere.Tapanyimageinthegridanditwillshowadeletedialog. TappingtheDELETEbuttonwon’tdoanythingyet.Getreadytodeleteanimage! DeletinganImageFromMediaStore JumptoperformDeleteImage()insideMainActivityViewModel.ktandaddthefollowingcode: try{ //1 getApplication().contentResolver.delete( image.contentUri,"${MediaStore.Images.Media._ID}=?", arrayOf(image.id.toString()) ) } //2 catch(securityException:SecurityException){ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.Q){ valrecoverableSecurityException= securityExceptionas?RecoverableSecurityException ?:throwsecurityException pendingDeleteImage=image _permissionNeededForDelete.postValue( recoverableSecurityException.userAction.actionIntent.intentSender ) }else{ throwsecurityException } } AddanymissingimportsbypressingOption-EnteronMacorAlt-EnteronPC. Thereareafewimportantthingsyoushouldknowaboutthisblockofcode: Here,youcallcontentResolver.delete()insideatryblocksincethismethodcanthrowaSecurityExceptionatruntime.ThemethodrequirestheContentUrioftheimageyouwanttodelete.Inthewhereparameter,youspecifythatyouwanttodeleteanimagebasedonits_ID.Inthefinalparameter,youpassthestringversionofthe_IDinanarray. InAndroid10andabove,itisn’tpossibletodeleteormodifyitemsfromMediaStoredirectly.Youneedpermissionfortheseactions.ThecorrectapproachistofirstcatchRecoverableSecurityException,whichcontainsanintentSenderthatcanprompttheusertograntpermission.YoupassintentSendertotheactivitybycallingpostValue()onyourMutableLiveData. Note:YoucanlearnmoreaboutMutableLiveDataandpostValue()fromAndroid’sMutableLiveDatadocumentation. Now,gotoMainActivity.ktandaddthefollowingcodetoviewModel.permissionNeededForDelete.observe(),justafterthe//TODOcomment. intentSender?.let{ startIntentSenderForResult( intentSender, DELETE_PERMISSION_REQUEST, null, 0, 0, 0, null ) } startIntentSenderForResult()launchesintentSender,whichyoupassedtoit.DELETE_PERMISSION_REQUESTisauniquerequestcodeusedtoidentifyandhandletheactionwhentherequestcompletes. Beforeyoutrythenewdeletefeature,Scopeoneedsafewmorefinishingtouches! ListeningforChangesWithContentObserver ContentObserverisaclassthatlistensforchangeswheneverthedatainthecontentproviderchanges.Sincedatawillchangewheneveryoudeleteanyimageintheapp,youneedtouseaContentObserver. RegisteringtheContentObserver StartbyregisteringtheContentObserver. OpenMainActivityViewModel.ktandaddthefollowingcodeinsideloadImages(),justafterthe//TODOcomment. contentObserver=getApplication().contentResolver.registerObserver( MediaStore.Images.Media.EXTERNAL_CONTENT_URI ){ loadImages() } ThecodeabovejustcallstheextensionmethodContentResolver.registerObserver(uri:Uri,observer:(selfChange:Boolean)->Unit),whichisalreadyimplementedasshownbelow: /** *Extensionmethodtoregistera[ContentObserver] */ privatefunContentResolver.registerObserver( uri:Uri, observer:(selfChange:Boolean)->Unit ):ContentObserver{ //1 valcontentObserver=object:ContentObserver(Handler()){ overridefunonChange(selfChange:Boolean){ observer(selfChange) } } //2 registerContentObserver(uri,true,contentObserver) returncontentObserver } Lookcloselyatthecode,andyou’llnoticetwothingsarehappening: contentObserveroverridesonChange().Thismethoddefineswhathappensifthedataintheproviderchanges.Inthiscase,itwillcallloadImages()passedasalambda.AbestpracticeistoalwaysuseaHandler()whencreatingContentObserver. Next,theextensionmethodregisterstheContentObserverusingtheuripassedtoit.ThesecondparameterpassedastrueindicatesthatalltheotherdescendantURIs,startingwiththegivenURI,shouldtriggerthemethodcall.ThefinalparameteristheinstanceoftheContentObserveryoucreatedearlier. Nowthatyou’velearnedhowtoregistertheContentObserver,takeamomenttofindouthowandwhytounregisteritagain. UnregisteringtheContentObserver BeingagoodAndroidcitizen,youshouldalsounregisteryourContentObsersertopreventmemoryleaks.AddthefollowingcodetoonCleared()insideMainActivityViewModel.kt. contentObserver?.let{ getApplication().contentResolver.unregisterContentObserver(it) } ThiscodejustcallsunregisterContentObserver()oftheContentResolver.ViewModelcallsonCleared()whenit’snotusedanymore,soit’stheperfectplacetounregister. Well,it’sfinallydone!Goaheadandrunyourapptocheckoutthedeletefeature.IfyoutrydeletinganimageonanAndroid10device,itwillnowaskforpermission: Thedialogwon’tshowondevicesrunningolderversionsofAndroidsincescopedstorageisonlyavailableondevicesrunningAndroid10orabove. Ifyoudenypermission,nothinghappens.Butifyougrantpermission,you’lldeletetheimagepermanentlyandtheappwillloadtheupdatedlist. WheretoGoFromHere? DownloadthecompletedprojectfilesbyclickingtheDownloadMaterialsbuttonatthetoporbottomofthetutorial. Inthistutorial,youlearnedaboutscopedstorageandworkedonthemostpopularusecasewhereitapplies.Ifyouwanttolearnaboutotherusecases,checkoutthisvideofromAndroidDevelopersSummit2019. ThisisagreattimetogetyourappsworkingwithscopedstorageasitwillbemandatoryforallnewappsinAugust2020andeveryapptargetingAndroid11. ManynewscopedstoragechangeswillcomeintoeffectwithAndroid11.Checkthemouthere:StorageUpdatesinAndroid11. Also,takealookatDataPrivacyforAndroidtolearnmoreaboutuserprivacyandsecurity. Ihopeyoulikedthistutorial.Ifyouhaveanyquestionsorcomments,pleasejointheforumdiscussionbelow. CoreConcepts Android&KotlinTutorials DownloadMaterials raywenderlich.comWeekly Theraywenderlich.comnewsletteristheeasiestwaytostayup-to-dateoneverythingyouneedtoknowasamobiledeveloper. Signupnow Website Getaweeklydigestofourtutorialsandcourses,andreceiveafreein-depthemailcourseasabonus! OurBiggestBlackFridaySale—Ever! Introducingunlimitedaccesstoallvideocourses,allbooks,andournewmonthlyliveprofessionaldevelopmentseries!Just$899$399peryearduringourBlackFridayevent. Getitnow Morelikethis MarkComplete(AllChapters) ClearProgress(AllChapters) MarkComplete ClearProgress Completed New New ComposeforDesktop:GetYourWeather! Android&Kotlin GettingStarted Nov222021·Article(25mins) BuildadesktopweatherappwithComposeforDesktop!You’llgetuserinput,fetchnetworkdataanddisplayitallwiththeComposeUItoolkit. Nov222021·Article(25mins) Completed BuildadesktopweatherappwithComposeforDesktop!You’llgetuserinput,fetchnetworkdataanddisplayitallwiththeComposeUItoolkit. Nov222021·Article(25mins) Completed MarkComplete(AllChapters) ClearProgress(AllChapters) MarkComplete ClearProgress Completed New New BlackFridaySale:ProSubscriptions+MonthlyProSeminarsfor$399 iOS&Swift Announcements Nov222021·Article(10mins) AnnouncingournewProfessionalDevelopmentSeminars,partofourBlackFriday2021event! Nov222021·Article(10mins) Completed AnnouncingournewProfessionalDevelopmentSeminars,partofourBlackFriday2021event! Nov222021·Article(10mins) Completed MarkComplete(AllChapters) ClearProgress(AllChapters) MarkComplete ClearProgress Completed New New iPadOS15Tutorial:What’sNewforDevelopers iOS&Swift OtherCoreAPIs Nov222021·Article(30mins) Seewhat’snewiniPadOS15andtakeyourapptothenextlevelwithgroundbreakingchanges! Nov222021·Article(30mins) Completed Seewhat’snewiniPadOS15andtakeyourapptothenextlevelwithgroundbreakingchanges! Nov222021·Article(30mins) Completed MarkComplete(AllChapters) ClearProgress(AllChapters) MarkComplete ClearProgress Completed New New BlackFridaySaleComingSoon! iOS&Swift Announcements Nov192021·Article(2mins) Ourbiggest-everBlackFridayandCyberMondaysalelaunchesnextweek.Don’tmissit! Nov192021·Article(2mins) Completed Ourbiggest-everBlackFridayandCyberMondaysalelaunchesnextweek.Don’tmissit! Nov192021·Article(2mins) Completed Contributors AnshdeepSingh AnshdeepisaGoogleCertifiedAndroidDeveloperbasedinVancouver,Canada.Heispassionateaboutbuildinggreatuser-centric... Author FabioBombardi FabioisanItaliandeveloper.Hehasbeenspendingmanyhoursofhislifeinfrontofamonitorsincehewaseightyearsold... TechEditor SandraGrauschopf SandraGrauschopfistheBookTeamLeadforraywenderlich.com.She'sanexperiencedwriter,editor,andcontentmanageraswell... Editor JuliaZinchenko GraphicdesignerandillustratorbasedinUkraine Illustrator AldoOlivares MobileDeveloperpassionateaboutcreatingamazingappswithgreatuserinterfaces.Founderofaldominium.com,company... Fpe EricSoto EricisAndroidTutorialTeamLeadandaProfessionalSoftwareEngineerandcertifiedAgile-ScrumMasterfocusingonAppleiOS... TeamLead Inthistutorial,you’lllearnhowtousescopedstorageinyourAndroid10appbybuildingasimpleimagegallery. Comments ShowComments. OurBiggestBlackFridaySale—Ever! Introducingunlimitedaccesstoallvideocourses,allbooks,andournewmonthlyliveprofessionaldevelopmentseries!Just$899$399peryearduringourBlackFridayevent. Getitnow



請為這篇文章評分?