Smooth Operator: Using StrictMode to make your Android App ANR free
source link: https://riggaroo.dev/smooth-operator-using-strictmode-to-make-your-android-app-anr-free/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Smooth Operator: Using StrictMode to make your Android App ANR free
Performing any kind of long blocking operations or disk IO operations on the Android Main thread can cause ANR issues. (Application Not Responding). You may not even realise that you have a potential ANR until it is too late and is already in your user’s hands.
If you are lucky, the library or framework you are using will not allow you to perform disk operations on the main thread (Room for instance, makes it explicit when you want to turn it off).
But how can you pick up on these issues in your app, when the libraries or frameworks don’t explicitly prevent this kind of operation? Luckily there is a class in Android called StrictMode that can help you find these issues.
What is StrictMode?
StrictMode
is a developer tool that you enable on start up of your application, which can help pick up operations that are happening on the main thread. It can automatically terminate your app (or log it to logcat), when a violation has occurred.
This can help prevent ANR’s from happening and overall make your app a smoother experience, since you will be made more aware of the potential issues your app has.
Enabling StrictMode
To enable StrictMode
doesn’t require much. In your Application
class, add the following (Make sure to only enable it for debug/developer mode in your app):
class CustomApplication : OverApplication() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG){
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
}
}
}
You can choose how you want StrictMode
to react when certain violations occur. In the above case for the threading policy, I’ve set it to penaltyLog()
, which will only log the violations in logcat. You can also set it to penaltyDeath()
if you would prefer the application to crash when a violation has occurred. I would recommend first setting penaltyLog()
and once you have fixed all your StrictMode
issues, set it to penaltyDeath()
since the errors will then be more visible when adding new code.
Now, run your application and use it as per usual, if a violation occurs, you should see the StrictMode
violation logged out to the Android logger.
Example of a StrictMode violation
Here is an example of a Disk I/O operation violation reported by StrictMode
in an app of mine. You can see it points out that its a DiskReadViolation
and points to the specific line in my code where I am reading the disk on the main thread:
StrictMode policy violation; ~duration=154 ms: android.os.strictmode.DiskReadViolation
at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1556)
at libcore.io.BlockGuardOs.access(BlockGuardOs.java:69)
at libcore.io.ForwardingOs.access(ForwardingOs.java:73)
at android.app.ActivityThread$AndroidOs.access(ActivityThread.java:7246)
at java.io.UnixFileSystem.checkAccess(UnixFileSystem.java:281)
at java.io.File.exists(File.java:815)
at android.app.ContextImpl.ensurePrivateDirExists(ContextImpl.java:645)
at android.app.ContextImpl.ensurePrivateDirExists(ContextImpl.java:636)
at android.app.ContextImpl.getFilesDir(ContextImpl.java:681)
at android.content.ContextWrapper.getFilesDir(ContextWrapper.java:243)
at dev.riggaroo.ImageLoader.loadImage(ImageLoader.kt:41)
Below is the snippet of code that StrictMode
has highlighted. Although I am loading the actual image off the main thread using Glide, accessing context.getFilesDir()
is happening on the main thread:
val file = File(context.filesDir, "/path/to/image.jpg")
GlideApp.with(itemView.context)
.load(file)
.into(filterImageView)
To fix this wasn’t as simple as one would hope. I ended up creating a custom class that would be passed to Glide and then Glide would run a custom ModelLoader, which gets access to the filesDir
inside of it. This is the class I created which doesn’t do any disk access when its created:
data class FilesDirPath(val path: String) {
fun getUri(context: Context): Uri {
return Uri.fromFile(File(context.filesDir, path))
}
}
Then I needed to implement a custom ModelLoader
which took the class above and used it to load up the image. It is worth noting that buildLoadData
runs off the main thread, so it is a good place to access the file system:
class CustomFilesDirImageLoader(
private val context: Context,
private val modelLoader: ModelLoader<Uri, InputStream>
) : ModelLoader<FilesDirPath, InputStream> {
override fun buildLoadData(model: FilesDirPath, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream>? {
val uri: Uri = model.getUri(context)
return modelLoader.buildLoadData(uri, width, height, options)
}
override fun handles(model: FilesDirPath): Boolean = true
class Factory(
private val applicationContext: Context
) : ModelLoaderFactory<FilesDirPath, InputStream> {
override fun build(
multiFactory: MultiModelLoaderFactory
): ModelLoader<FilesDirPath, InputStream> {
val modelLoader = multiFactory.build(Uri::class.java, InputStream::class.java)
return CustomFilesDirImageLoader(
applicationContext,
modelLoader
)
}
override fun teardown() {}
}
}
Then in our custom AppGlideModule
, we need to register this custom ModelLoader
that should be used when the FilesDirPath
is used.
@GlideModule
class CustomGlideAppModule : AppGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
super.registerComponents(context, glide, registry)
registry.append(FilesDirPath::class.java,
InputStream::class.java,
CustomFilesDirImageLoader.Factory(context))
}
}
Then I create a FilesDirPath
object where I was previously accessing the file system on the main thread:
val filesDirPath = FilesDirPath("/path/to/image.jpg")
GlideApp.with(itemView.context)
.load(filesDirPath)
.into(filterImageView)
And this removes the StrictMode
DiskReadViolation
error! We can confirm that we are indeed fixing the StrictMode
issues by running our app again and seeing if anything is logged.
Summary
There are plenty of other ways in which you can be violating StrictMode
policies, this is just one such example of an issue and how I resolved it.
I’ve found StrictMode
to be an extremely valuable tool to help with these issues. You may think you are doing everything off the main thread, but sometimes small things can creep in and cause these issues. StrictMode
helps keep our apps in check and should definitely be enabled whilst developing your applications.
Have something to add or ask? Find me on Twitter @riggaroo.
Recommend
-
117
Smooth (CLI) Operator Sade is a small but powerful tool for building command...
-
44
Easy State is a practical state management library for React. React is changing swiftly these days and the ecosystem has to keep up. Easy State's v6...
-
9
Repository initialization without StrictMode violationsInitializing your app’s database at startup — easier said than done!
-
9
Using Framer Motion to Create Smooth Lazy Load Image EffectsGraeme FultonMaking Prototypr and Letter.soRecently I created a nice lazy-loading image effect for so...
-
4
Android性能调优利器StrictMode – Android开发中文站你的位置:Android开发中文站 > Android开发 >
-
3
How do I make a smooth transition when moving an item from the left margin: -9999px to the page? advertisements Here's some...
-
7
Setting up a Smooth [Prometheus] OperatorPhoto by Ayoola Salako on
-
1
What should we do to make the pattern of the aluminum plate smooth? xuan posted @ 2022年9月30日 11:13 in
-
5
StrictMode.ThreadPolicy.Builder | Android Developers Android API Reference ...
-
0
Uber's smooth operator Dara Khosrowshahi has done something Travis Kalanick never could: turn a profit
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK