Kuvien koon muuttaminen paremman lähetys- / lataustehokkuuden parantamiseksi. Android-kehitys.

Ei ole kovin yleistä nähdä projekti, joka ei vaadi valokuvien lähettämistä muodossa tai toisessa. Suurin osa Android-laitteista luo nykyään jopa 2 Mt valokuvan. Tämä on ongelma, miten? Kuvittele, että olet rakentamassa mobiilisovellusta, joka vaatii käyttäjiltäsi profiilikuvan, kun otetaan huomioon skaalautuvuus, jokaisen kuvan ei saa olla yli 100 kt, eikö totta ?. Et halua tallentaa 1 Mt kuvaa kullekin käyttäjälle. Se ei vaikuta pelkästään UX: ään, myös palvelimesi kärsii. Suuren valokuvan lataaminen etäpalvelimelle voi aiheuttaa yhden tai kaikki seuraavat ongelmat

  • Hidas lataus / lataus laitteissa, joiden verkon kaista on pieni
  • Vaikuttaa tuotteen skaalautuvuuteen
  • Ja enemmän…

RATKAISU

Todellinen ilmeinen ratkaisu on saada kuvan koko muutettavaksi ennen lähettämistä etäpalvelimelle. Hyvä!. Mutta miten? Se on mitä yritän näyttää.

Minulla ei ollut suurta ongelmaa selvittää, kuinka voisin ratkaista tämän, kun aloin kirjoittaa sovelluksia androidille, joten päätin vain jakaa sen tänään. Odotan innolla joku, joka näyttää minulle paremman tavan tehdä se. Mennään!

olettamus

  • Oletan, että Java-ohjelmointikieli on sinulle sopiva (Eikä siis se, mitä käytän, voit helposti tarttua siihen, jos sinulla on kokemusta myös muista ohjelmointikieleistä.)
  • Android-studio ja SDK on asennettu ja valmis.
  • Luo uusi android-studioprojekti. Soitan minun EasyPhotoUpload -sovellukselle

Projektin nimi - EasyPhotoUpload

Min SDK - 4.0.3, API 15

Periaatteessa yritämme

  • Anna käyttäjän valita kuvan galleriasta
  • Hanki valittu valokuvapolku
  • Koon valitun valokuvan koon muuttaminen taustalankaksi ja palautus tulokseksi pääkierteeseen - Kuten näette, kuvan koon muuttaminen on kallista toimenpidettä, jota ei pidä suorittaa päälankaan.

Tämän artikkelin lopullinen koodi on saatavana githubissa - https://github.com/adigunhammedolalekan/easyphotoupload

Kun android-studio on valmistunut projektin rakentamisesta ja olet kaikki valmis, luo nämä paketit - ydin, kuuntelijat, työkalut

Luo uuden paketin alla Util.java, seuraava on tiedoston sisältö.

'Julkisen luokan Util {

// SDF tuottaa pakatulle tiedostolle yksilöivän nimen.
 julkinen staattinen lopullinen SimpleDateFormat SDF = uusi SimpleDateFormat (”vvvvmmddhmmss”, Locale.getDefault ());

/ *
 pakata tiedosto / valokuva @param polusta yksityiseen sijaintiin nykyisellä laitteella ja palauta pakattu tiedosto.
 @param path = Alkuperäinen kuvan polku
 @param context = Nykyinen android-konteksti
 * /
 julkinen staattinen tiedosto getCompressed (kontekstiyhteys, merkkipolku) heittää IOException {

if (konteksti == nolla)
 heitä uusi NullPointerException (“Konteksti ei saa olla tyhjä.”);
 // laitteen ulkoisen välimuistihakemiston hakeminen, ei ehkä ole saatavana joillekin laitteille,
 // joten koodimme palaa takaisin sisäiseen tallennusvälimuistihakemistoon, joka on aina saatavilla, mutta pienemmässä määrin
 Tiedosto cacheDir = context.getExternalCacheDir ();
 if (cacheDir == nolla)
 //perääntyä
 cacheDir = konteksti.getCacheDir ();

Merkkijono rootDir = cacheDir.getAbsolutePath () + “/ ImageCompressor”;
 Tiedoston juuri = uusi tiedosto (rootDir);

// Luo ImageCompressor-kansio, jos sitä ei ole jo olemassa.
 if (! root.exists ())
 root.mkdirs ();

// purkaa ja muuttaa alkuperäisen bittikartan kokoa @param -polusta.
 Bittikartta bittikartta = decodeImageFromFiles (polku, / * haluamasi leveys * / 300, / * haluamasi korkeus * / 300);

// Luo paikkamerkki pakattuun kuvatiedostoon
 Tiedosto pakattu = uusi tiedosto (juuri, SDF.muoto (uusi päivämäärä ()) + “.jpg” / * haluamasi muoto * /);

// muuntaa dekoodattu bittikartta streamiksi
 ByteArrayOutputStream byteArrayOutputStream = uusi ByteArrayOutputStream ();

/ * pakata bittikartta osaksi byteArrayOutputStream
 Bitmap.compress (muoto, laatu, OutputStream)

Missä laatu vaihtelee välillä 1–100.
 * /
 bitmap.compress (Bitmap.CompressFormat.JPEG, 80, byteArrayOutputStream);

/ *
 Tällä hetkellä meillä on bittikartta byteArrayOutputStream -objektissa, seuraavaksi tarvitsemme vain kirjoittaa sen aikaisemmin luomaan pakattuun tiedostoon,
 java.io.FileOutputStream voi auttaa meitä tekemään juuri sen!

* /
 FileOutputStream fileOutputStream = uusi FileOutputStream (pakattu);
 fileOutputStream.write (byteArrayOutputStream.toByteArray ());
 fileOutputStream.flush ();

fileOutputStream.close ();

// Tiedosto kirjoitettu, palaa soittajalle. Tehty!
 palata pakattu;
 }

julkinen staattinen bittikartta decodeImageFromFiles (merkkijonon polku, int leveys, int korkeus) {
 BitmapFactory.Options scaleOptions = uusi BitmapFactory.Options ();
 scaleOptions.inJustDecodeBounds = true;
 BitmapFactory.decodeFile (path, scaleOptions);
 int-asteikko = 1;
 while (scaleOptions.outWidth / scale / 2> = leveys
 && scaleOptions.outHeight / scale / 2> = korkeus) {
 asteikko * = 2;
 }
 // purkaa näytteen koosta
 BitmapFactory.Options outOptions = uusi BitmapFactory.Options ();
 outOptions.inSampleSize = mittakaava;
 return BitmapFactory.decodeFile (polku, outOptions);
 }
}’

Menetelmä, joka käsittelee valokuvien pakkaamista ja tallentamista, on 'staattinen tiedoston hankkiminen pakattu (konteksti, merkkijono)', kuten olet nähnyt yllä olevasta koodista, tämä menetelmä kulkee polun laitteessa olevan valokuvan kokoon, muuttaa sen kokoa, tallentaa sen yksityinen sijainti laitteella ja palauttaa juuri pakatun tiedoston. Voila!

Seuraava tutkittavana oleva tiedosto on nimeltään ImageCompressTask.java, tämä luokka toteuttaa suoritettavan, kolmella argumenttirakentajalla ja run () -menetelmässään pakkaaminen tapahtuu kaikilla taustakierroilla, se lähettää lopullisen tuloksen pääohjelmaan säiettä android.os.Handlerin avulla tai ilmoita virheestä muuten.

ImageCompressTask.java

'Public class ImageCompressTask toteuttaa Runnable {

yksityinen konteksti mContext;
 yksityinen lista originalPaths = new ArrayList <> ();
 yksityinen käsittelijä mHandler = uusi käsittelijä (Looper.getMainLooper ());
 yksityinen luettelo tulos = uusi ArrayList <> ();
 yksityinen IImageCompressTaskListener mIImageCompressTaskListener;

public ImageCompressTask (asiayhteys, merkkijono, IImageCompressTaskListener compressTaskListener) {

originalPaths.add (polku);
 mContext = konteksti;

mIImageCompressTaskListener = compressTaskListener;
 }
 public ImageCompressTask (kontekstiyhteys, luettelo polut, IImageCompressTaskListener compressTaskListener) {
 originalPaths = polut;
 mContext = konteksti;
 mIImageCompressTaskListener = compressTaskListener;
 }
 @Ohittaa
 julkinen mitätön ajo () {

yrittää {

// Selaa kaikki annetut polut ja kerää pakattu tiedosto Util.getCompressed -sovelluksesta (konteksti, merkkijono)
 varten (merkkijono: originalPaths) {
 Tiedostotiedosto = Util.getCompressed (mContext, polku);
 // lisää se!
 result.add (tiedosto);
 }
 // Käytä Handler -sovellusta postittaaksesi tulos takaisin pääketjuun
 mHandler.post (uusi suoritettava () {
 @Ohittaa
 julkinen mitätön ajo () {

if (mIImageCompressTaskListener! = nolla)
 mIImageCompressTaskListener.onComplete (tulos);
 }
 });
 } saalis (lopullinen IOException ex) {
 // Tapahtui virhe, ilmoita virheestä takaisinsoiton kautta
 mHandler.post (uusi suoritettava () {
 @Ohittaa
 julkinen mitätön ajo () {
 if (mIImageCompressTaskListener! = nolla)
 mIImageCompressTaskListener.onError (ex);
 }
 });
 }
 }
}’

Luo lopuksi MainActivity.java, käyttöliittymä koko näytteen sovellukselle.

MainActivity.java


julkisen luokan MainActivity laajentaa AppCompatActivity {

Painike selectImage;
 ImageView selectedImage;

yksityinen staattinen lopullinen int REQUEST_STORAGE_PERMISSION = 100;
 yksityinen staattinen lopullinen int REQUEST_PICK_PHOTO = 101;

// luo yksi säiepooli kuvanpakkausluokkaan.
 yksityinen ExecutorService mExecutorService = Executors.newFixedThreadPool (1);

yksityinen ImageCompressTask imageCompressTask;

@Ohittaa
 suojattu tyhjä onCreate (Bundle savedInstanceState) {
 super.onCreate (savedInstanceState);
 setContentView (R.layout.activity_main);

selectedImage = (ImageView) findViewById (R.id.iv_selected_photo);
 selectImage = (Button) findViewById (R.id.btn_select_image);

selectImage.setOnClickListener (uusi View.OnClickListener () {
 @Ohittaa
 public void onClick (Näytä näkymä) {
 requestPermission ();
 }
 });
 }

void requestPermission () {

if (PackageManager.PERMISSION_GRANTED! =
 ContextCompat.checkSelfPermission (tämä, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
 if (ActivityCompat.shouldShowRequestPermissionRationale (tämä, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
 ActivityCompat.requestPermissions (tämä, uusi merkkijono [] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
 REQUEST_STORAGE_PERMISSION);
 } muuta {
 //Joo! Haluan, että molemmat lohkot tekevät saman asian, voit kirjoittaa oman logiikkaasi, mutta tämä toimii minulle.
 ActivityCompat.requestPermissions (tämä, uusi merkkijono [] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
 REQUEST_STORAGE_PERMISSION);
 }
 } muuta {
 // Lupa myönnetty, antaa mennä valita valokuva
Aikomus aikomus = uusi tarkoitus (Intent.ACTION_PICK);
intent.setAction (Intent.ACTION_GET_CONTENT);
intent.setType ( ”image / *”);
startActivityForResult (tarkoitus, REQUEST_PICK_PHOTO);
 }

}

@Ohittaa
 suojattu tyhjä onActivityResult (int requestCode, int rezultātsCode, Intent data) {
 super.onActivityResult (requestCode, resultCode, data);
 if (requestCode == REQUEST_PICK_PHOTO && resultCode == RESULT_OK &&
 tiedot! = nolla) {
 // ota absoluuttisen kuvan polku Uri-palvelusta
 Uri uri = data.getData ();
 Kohdistimen kohdistin = MediaStore.Images.Media.query (getContentResolver (), uri, uusi merkkijono [] {MediaStore.Images.Media.DATA});
 if (kohdistin! = nolla) {
 Merkkijono = cursor.getString (cursor.getColumnIndexOrThrow (MediaStore.Images.Media.DATA));

// Luo ImageCompressTask ja suorita Executorilla.
 imageCompressTask = uusi ImageCompressTask (tämä, polku, iImageCompressTaskListener);

mExecutorService.execute (imageCompressTask);
 }
 }
 }

// kuvanpakkaus tehtävän takaisinsoitto
 yksityinen IImageCompressTaskListener iImageCompressTaskListener = uusi IImageCompressTaskListener () {
 @Ohittaa
 public void onComplete (luettelo pakattu) {
 // valokuva pakattu. Jee!

// valmistaudu lataamiseen.

Tiedostotiedosto = pakattu.get (0);

selectedImage.setImageBitmap (BitmapFactory.decodeFile (file.getAbsolutePath ()));
 }

@Ohittaa
 public void onError (heitettävä virhe) {
 // erittäin epätodennäköistä, mutta se voi tapahtua laitteessa, jonka tallennustila on erittäin vähäinen.
 // kirjaa se, kirjaa.WhatTheFuck?, tai näytä valintaikkuna, jossa pyydetään käyttäjää poistamaan joitain tiedostoja .etc jne.
 Log.wtf (“ImageCompressor”, “Virhe tapahtui”, virhe);
 }
 };

@Ohittaa
 suojattu tyhjä onDestroy () {
 super.onDestroy ();

//siivota!
 mExecutorService.shutdown ();

mExecutorService = nolla;
 imageCompressTask = nolla;
 }
}’

Kaikki koodit kommentoidaan hyvin, mutta jos sinulla on ongelmia jollakin osalla. Kerro minulle! Kaikki koodit ovat githubissa. Katso se paremmin. Kuvakaappaus lopputuloksesta.

Rakastan panostasi tähän. Kiitos!

Minusta

Olen intohimoinen mobiilisovellusten kehittäjä, jolla on yli 2,5-kokemus erinomaisten sovellusten rakentamisesta android-alustalle. Jos tarvitset lahjakkuutta, ota rohkeasti yhteyttä minuun!

Github - www.github.com/adigunhammedolalekan

Posti - adigunhammed.lekan@gmail.com

Yhteyshenkilö - 07035452307