feat: benchmark MediaStore API of Android
This commit is contained in:
@@ -6,6 +6,9 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
@@ -23,7 +26,14 @@
|
|||||||
android:theme="@style/Theme.Lockstep">
|
android:theme="@style/Theme.Lockstep">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="at.lockstep.app.MediaStoreBenchmarkActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package at.lockstep.app;
|
package at.lockstep.app;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ import at.lockstep.pb.PlaybackEngine;
|
|||||||
public class MainActivity extends Activity {
|
public class MainActivity extends Activity {
|
||||||
private Button btnStart;
|
private Button btnStart;
|
||||||
private Button btnStop;
|
private Button btnStop;
|
||||||
|
private Button btnMediaStoreBenchmark;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -20,6 +22,7 @@ public class MainActivity extends Activity {
|
|||||||
|
|
||||||
btnStart = findViewById(R.id.btnStart);
|
btnStart = findViewById(R.id.btnStart);
|
||||||
btnStop = findViewById(R.id.btnStop);
|
btnStop = findViewById(R.id.btnStop);
|
||||||
|
btnMediaStoreBenchmark = findViewById(R.id.btnMediaStoreBenchmark);
|
||||||
|
|
||||||
// TODO: handle clicking START button twice
|
// TODO: handle clicking START button twice
|
||||||
btnStart.setOnClickListener(v ->
|
btnStart.setOnClickListener(v ->
|
||||||
@@ -39,5 +42,10 @@ public class MainActivity extends Activity {
|
|||||||
btnStop.setOnClickListener(v ->
|
btnStop.setOnClickListener(v ->
|
||||||
startService(LstForegroundService.stopIntent(MainActivity.this))
|
startService(LstForegroundService.stopIntent(MainActivity.this))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
btnMediaStoreBenchmark.setOnClickListener(v -> {
|
||||||
|
Intent intent = new Intent(MainActivity.this, MediaStoreBenchmarkActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package at.lockstep.app;
|
||||||
|
|
||||||
|
import static androidx.activity.result.ActivityResultCallerKt.registerForActivityResult;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import at.lockstep.R;
|
||||||
|
|
||||||
|
public class MediaStoreBenchmarkActivity extends Activity {
|
||||||
|
|
||||||
|
private TextView resultTextView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_media_store_benchmark);
|
||||||
|
|
||||||
|
resultTextView = findViewById(R.id.resultTextView);
|
||||||
|
|
||||||
|
if (hasReadPermission()) {
|
||||||
|
loadMusic();
|
||||||
|
} else {
|
||||||
|
requestReadPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int REQUEST_READ_PERMISSION = 1001;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(
|
||||||
|
int requestCode,
|
||||||
|
String[] permissions,
|
||||||
|
int[] grantResults
|
||||||
|
) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
|
||||||
|
if (requestCode == REQUEST_READ_PERMISSION) {
|
||||||
|
boolean granted = grantResults.length > 0
|
||||||
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||||
|
|
||||||
|
if (granted) {
|
||||||
|
loadMusic();
|
||||||
|
} else {
|
||||||
|
resultTextView.setText("Permission denied.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasReadPermission() {
|
||||||
|
String permission;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
permission = Manifest.permission.READ_MEDIA_AUDIO;
|
||||||
|
} else {
|
||||||
|
permission = Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContextCompat.checkSelfPermission(this, permission)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestReadPermission() {
|
||||||
|
String permission;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
permission = Manifest.permission.READ_MEDIA_AUDIO;
|
||||||
|
} else {
|
||||||
|
permission = Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
//permissionLauncher.launch(permission);
|
||||||
|
requestPermissions(
|
||||||
|
new String[]{ permission },
|
||||||
|
REQUEST_READ_PERMISSION
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMusic() {
|
||||||
|
List<String> musicList = new ArrayList<>();
|
||||||
|
|
||||||
|
android.net.Uri collection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||||
|
|
||||||
|
String[] projection = new String[] {
|
||||||
|
MediaStore.Audio.Media._ID,
|
||||||
|
MediaStore.Audio.Media.TITLE,
|
||||||
|
MediaStore.Audio.Media.DATA
|
||||||
|
};
|
||||||
|
|
||||||
|
String selection = MediaStore.Audio.Media.IS_MUSIC + " != 0";
|
||||||
|
|
||||||
|
long start = SystemClock.elapsedRealtime();
|
||||||
|
|
||||||
|
try (android.database.Cursor cursor = getContentResolver().query(
|
||||||
|
collection,
|
||||||
|
projection,
|
||||||
|
selection,
|
||||||
|
null,
|
||||||
|
MediaStore.Audio.Media.TITLE + " ASC"
|
||||||
|
)) {
|
||||||
|
if (cursor != null) {
|
||||||
|
int titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
|
||||||
|
int dataColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DATA);
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
String title = cursor.getString(titleColumn);
|
||||||
|
String path = dataColumn != -1 ? cursor.getString(dataColumn) : null;
|
||||||
|
|
||||||
|
if (path != null) {
|
||||||
|
musicList.add(title + "\n" + path);
|
||||||
|
} else {
|
||||||
|
musicList.add(title + "\n[path unavailable]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long elapsedMs = SystemClock.elapsedRealtime() - start;
|
||||||
|
|
||||||
|
StringBuilder header = new StringBuilder();
|
||||||
|
header.append("Found ").append(musicList.size()).append(" music files\n");
|
||||||
|
header.append("Query time: ").append(elapsedMs).append(" ms\n\n");
|
||||||
|
|
||||||
|
resultTextView.setText(header.toString() + String.join("\n\n", musicList));
|
||||||
|
|
||||||
|
// 200 music files in 32 ms
|
||||||
|
// paths like "/storage/emulated/0/Download/...mp3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,4 +18,10 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:text="Stop collection" />
|
android:text="Stop collection" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnMediaStoreBenchmark"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Benchmark media store" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
13
app/src/main/res/layout/activity_media_store_benchmark.xml
Normal file
13
app/src/main/res/layout/activity_media_store_benchmark.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/resultTextView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:text="Loading..."
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</ScrollView>
|
||||||
Reference in New Issue
Block a user