Multiple improvements and refactors#616
Multiple improvements and refactors#616ggtlvkma356 wants to merge 10 commits intoGrapheneOS:mainfrom
Conversation
inthewaves
left a comment
There was a problem hiding this comment.
As a general note: Technically, JavaScript is single-threaded and the JS Bridge code blocks when calling bridge methods, so concurrency between bridge calls is less of an issue (i.e., JS will never concurrently call bridge methods). Of course, the JS bridge background thread in Java still can have concurrency issue with pure Android threads
From Chromium source on android_webview/docs/java-bridge.md: "Calls to interface methods are synchronous—JavaScript VM stops and waits for a result to be returned from the invoked method. In Chromium, this means that the IPC message sent from a renderer to the browser must be synchronous."
| if (properties == null || properties.isEmpty()) return ""; | ||
| String fileName = properties.getOrDefault(DocumentProperty.FileName, ""); | ||
| String title = properties.getOrDefault(DocumentProperty.Title, ""); | ||
| return !(fileName != null && fileName.isEmpty()) ? fileName : title; |
There was a problem hiding this comment.
Expanding it right now would be fileName == null || !fileName.isEmpty() so it will accept null if that's intentional. (although null never happens because of properties.getOrDefault
We can consider also just using !TextUtils.isEmpty. TextUtils.isEmpty is a framework method that returns true iff the string is null or 0-length
| fun retrieveDocumentProperties(properties: String, numPages: Int, uri: Uri) { | ||
| viewModelScope.launch(Dispatchers.IO) { | ||
| val loader = DocumentPropertiesRetriever(getApplication(), properties, numPages, uri) | ||
| val result = loader.retrieve() | ||
| withContext(Dispatchers.Main) { | ||
| if (documentPropertiesLoading) { | ||
| _documentProperties.value = result | ||
| } | ||
| } | ||
| } | ||
| documentPropertiesLoading = true |
There was a problem hiding this comment.
I think documentPropertiesLoading = true should be before viewModelScope.launch(Dispatchers.IO). Technically doesn't matter since this seems to be on main thread anyway, so withContext(Dispatchers.Main) would always catch this
| private volatile float zoomFocusY = 0f; | ||
| private boolean documentLoaded; | ||
| private volatile InputStream inputStream; | ||
| private volatile boolean documentPropertiesLoaded; |
There was a problem hiding this comment.
I don't think volatile works here for this pattern. For this compound operation of read-and-then-set, it only helps with read visibility
@JavascriptInterface
public void setDocumentProperties(final String properties) {
if (documentPropertiesLoaded) {
throw new SecurityException("setDocumentProperties already called");
}
documentPropertiesLoaded = true;
...
}https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html
Atomic actions cannot be interleaved, so they can be used without fear of thread interference. However, this does not eliminate all need to synchronize atomic actions, because memory consistency errors are still possible.
AtomicBoolean#compareAndSet would be more correct here.
Just as a note from my own looking: even if this bridge method itself is synchronous with respect to JS code, documentPropertiesLoaded is still technically read/written by Android main thread in loadPdf(), so doesn't seem correct to drop this pattern entirely
| } else if (itemId == R.id.action_save_as) { | ||
| saveDocument(); | ||
| return true; |
List of changes
Move UI operations to the main thread.
Make shared mutable state thread-safe.
Move file saving off UI thread.
Remove Loader. Document properties are loaded using ViewModel.
Remove
onSaveInstanceState. Persistent states are managed with ViewModel.Simplify Menu handling.
Display page number with Snackbar instead of Toast.
Remove Hungarian notation.