📜 ⬆️ ⬇️

Create an album gallery

The article describes the creation of synchronization functionality for album covers.
Comments are found both directly in the code, and in the form of explanations to commits.
There will be quite a lot of code and very few images.

Task:
- create an album list
- Find missing covers for each album
- Save them on your device so that album covers can be seen by our player as well as third-party ones.

118: create a child activity in which we will display the loaded album art

- Create an empty activ stub
- Add a context menu in the actionbar, consisting of a "wheel" update.
- When creating a menu - "the wheel starts spinning" (replaced by the progress bar).
- Define custom ActionBar background
')
Further, I will leave mainly references to commits, with a few explanations. Since otherwise the article will be too voluminous.

/** * Created by recoil on 26.01.14. */ public class ActArtworks extends Activity { private AQuery aq; private Menu optionsMenu; private boolean refreshing = true; private Activity activity; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //    getActionBar().setBackgroundDrawable(getResources().getDrawable(R.drawable.ab_bgr)); //   getActionBar().setDisplayHomeAsUpEnabled(true); activity = this; aq = new AQuery(activity); } @Override public boolean onCreateOptionsMenu(Menu menu) { this.optionsMenu = menu; //    MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.artwork, menu); //      -   update(); return super.onCreateOptionsMenu(menu); } public void update() { AQUtility.debug("Update progress"); //   "" refreshing = true; //  setRefreshActionButtonState(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: //      finish(); return true; } return super.onOptionsItemSelected(item); } public void setRefreshActionButtonState() { //   -       if (optionsMenu != null) { final MenuItem refreshItem = optionsMenu .findItem(R.id.menu_refresh); if (refreshItem != null) { if (refreshing) { refreshItem.setActionView(R.layout.actionbar_indeterminate_progress); } else { refreshItem.setActionView(null); } } } } } 


118: create the fillmediastoretracks class to read all the tracks from the media library

- The class reads records from the MediaStore database table.
When the phone is restarted, the system starts the service that scans the added files. Files that fall under the definition of “media” are added to the database.
- Upon completion of reading all the data from the table of tracks - the serialized ArrayList is written as a binary object.

118: read the tracks

- First we try to create an ArrayList of all tracks by serializing it from a saved object.
- If “save” but not, we form a list of tracks and “save”

118: displaying the list of albums

- Add GridView to activity
- Adding an adapter to display data
- In the adapter we define getView, consisting of a single text field
- Display a list of all album titles known to android

118: throwing out duplicate albums

- Sort the track list by album.
- We start the iterator, and go through the list. When duplicating an album in the list, we throw it out of the array.

118: added compatibility with android 8+ (phew!)

- Add the source code of the compatibility library appcompat
- We rewrite all the context menus - we add our own scheme and within it define how to show the menu item
 <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:FreeAmp="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menu_refresh" android:icon="@drawable/ic_action_refresh" android:title="" android:alphabeticShortcut="r" android:orderInCategory="1" FreeAmp:showAsAction="always" /> </menu> 

- Inherited from the built-in style library
 <style name="theme" parent="@style/Theme.AppCompat"> 

- All activity is inherited from ActionBarActivity
- Override menu creation
 - refreshItem.setActionView(R.layout.actionbar_indeterminate_progress); + MenuItemCompat.setActionView(refreshItem, R.layout.actionbar_indeterminate_progress); 

- Import the ListPopupWindow library - from the compatibility library

I had to digress a little bit to a few weakly related modifications (mostly bugs)
118: notification background
118: ape without tags processed. Check on other ape
118: added support for .opus files

119: curl library added

- curl for android
libcurl.so size:
(default is ftp, https with ares)
https: ~ 169K (including http, https)
ares: ~ 28K (adding to https, with ares support)
ipv6: ~ 0K (no extra size)
+ full: ~ 278K (all protocols, with ares)
This is quite a bold and in many ways an experimental solution that has both unconditional advantages and unconditional disadvantages. Since I plan to write a player for all popular platforms - if possible I try cross-platform libraries. Whether curla comes out is good - we'll see.

Again, a slight hiccup from the main line:
119: set ringtone

- Method to set the current track as a ringtone (torn from the sorts).

119: easy curl request

- Class wrapper to call curl. So far everything is quite ascetic. At the input url - at the output an array of bytes (ByteArrayOutputStream). If you want, make a string of it, but if you don't, make a bitmap. Or something else.

People came and started asking for support from external sd cart

121: we throw out an album not found and create a grid with a list of albums

- Wrap the generation of the list of covers in the asynchronous task
121: generic pictures for gridview

- Read json with the album cover with last.fm through a wrapper around curl
  String url = String.format("http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=0cb75104931acd7f44f571ed12cff105&artist=%s&album=%s&format=json", Uri.encode(track.getArtist()),Uri.encode(currentAlbum)); getHttpData = new GetHttpData(); getHttpData.setUrl(url); getHttpData.request(); String result = new String(getHttpData.getByteArray()); 


- Parsim answer
 JSONObject jsonObject = new JSONObject(result); jsonObject = jsonObject.getJSONObject("album"); JSONArray image = jsonObject.getJSONArray("image"); for (int i=0;i<image.length();i++) { jsonObject = image.getJSONObject(i); if (jsonObject.getString("size").equals("extralarge")) { albumArtImageLink = Uri.decode(jsonObject.getString("#text")); AQUtility.debug(track.getArtist()+":"+currentAlbum,albumArtImageLink); } } 


- Upload a picture
 //download image getHttpData = new GetHttpData(); getHttpData.setUrl(albumArtImageLink); getHttpData.request(); ContentResolver res = activity.getContentResolver(); Bitmap bm = BitmapFactory.decodeByteArray(getHttpData.getByteArray(),0,getHttpData.getByteArray().length); 


- Save the link to the image in the database table (peeped in the source code, we try to spoil in the same folders that already shat android)

 // Put the newly found artwork in the database. // Note that this shouldn't be done for the "unknown" album, // but if this method is called correctly, that won't happen. // first write it somewhere String file = Environment.getExternalStorageDirectory() + "/albumthumbs/" + String.valueOf(System.currentTimeMillis()); if (FileUtils.ensureFileExists(file)) { try { OutputStream outstream = new FileOutputStream(file); if (bm.getConfig() == null) { bm = bm.copy(Bitmap.Config.RGB_565, false); if (bm == null) { //return getDefaultArtwork(context); } } boolean success = bm.compress(Bitmap.CompressFormat.JPEG, 75, outstream); outstream.close(); if (success) { ContentValues values = new ContentValues(); values.put("album_id", track.getAlbumId()); values.put("_data", file); Uri newuri = res.insert(MediaUtils.sArtworkUri, values); if (newuri == null) { // Failed to insert in to the database. The most likely // cause of this is that the item already existed in the // database, and the most likely cause of that is that // the album was scanned before, but the user deleted the // album art from the sd card. // We can ignore that case here, since the media provider // will regenerate the album art for those entries when // it detects this. success = false; } } if (!success) { File f = new File(file); f.delete(); iterator.remove(); } } catch (FileNotFoundException e) { AQUtility.debug( "error creating file", e); } catch (IOException e) { AQUtility.debug( "error creating file", e); } } 


121: out of memory

- Scroll the grid up and down and find two things:
1. Creepy lags (that's right, you need to optimize)
2. out of memory (also true, though caught a bit earlier than expected, since a surprise crept into the code stolen from the sorts)

121: LRU Cashe

- Create a dedicated memory area for storing images, which will be repressed as it fills

 // LruCache: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html + int cacheSize = 20 * 360000; // <7MiB = 300width * 300heigth * 4bytesperpixel * 20images + LruCache bitmapCache = new LruCache(cacheSize) { + protected int sizeOf(int key, Bitmap value) { + return value.getRowBytes() * value.getHeight();//    + } + }; + + public void addBitmapToMemoryCache(int key, Bitmap bitmap) { + synchronized (bitmapCache) { + if (getBitmapFromMemCache(key) == null) { + bitmapCache.put(key, bitmap); + } + } + } + + public Bitmap getBitmapFromMemCache(int key) { + return (Bitmap) bitmapCache.get(key); + } 


- Read previously downloaded pictures from the cache, add new pictures to the cache when loading

121: load images asynchronously

After switching to the forcing cache, two things happened:
1. Missing out of memory (which is logical)
2. But the brakes when scrolling remained

- Read pictures asynchronously

121: onScrollStateChanged, onConfigurationChanged

- Turn off the loading of the picture, at the moment when the user "katnul" scroll and it "soars" by inertia

- We recreate the adapter when the device orientation changes

 void applyAdapter() { if (tracks == null) return; adapter = new AdpArtworks(activity,tracks); int iDisplayWidth = getResources().getDisplayMetrics().widthPixels ; int numColumns = (iDisplayWidth / 310); gridView.setColumnWidth( (iDisplayWidth / numColumns) ); gridView.setNumColumns(numColumns); gridView.setStretchMode( GridView.NO_STRETCH ) ; gridView.setAdapter(adapter); gridView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { adapter.setScrollState(scrollState); if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { adapter.notifyDataSetChanged(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }); } 

121: convert drawable 2 bitmap, update

- Convert xml placeholder to image
- We hang up the update call by clicking on the "wheel" in the menu

 + final Drawable imgBgr = activity.getResources().getDrawable(R.drawable.row_bgr); + final Bitmap bitmap = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + imgBgr.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + imgBgr.draw(canvas); + this.placeHolder = bitmap; 


121: download indicator
121: notification

- Add progress to indicate the progress of the download and display a notification about the number of albumists found.

And now there will be slides!

image

image

Source: https://habr.com/ru/post/212125/


All Articles