feat: benchmark MediaStore API of Android

This commit is contained in:
2026-03-07 18:48:57 +01:00
parent 65ea8ba27c
commit 754b96a319
5 changed files with 179 additions and 1 deletions

View File

@@ -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>

View File

@@ -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);
});
} }
} }

View File

@@ -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"
}
}

View File

@@ -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>

View 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>