API Discussion
See original GitHub issueHi @topjohnwu
First of all, thanks for your hard work! Building a library of this kind is not easy and require a lot of time. As you ask, I posted here some thoughts about the library. I studied it when release (so before you left for your military training) and I had some concerns by the time. I did not want to bother you with it. But I am happy to see it received a lot of improvements since! 😄
I am the maintainer of AdAdaway, a root adblocker (https://github.com/AdAway/AdAway) and I had a quick look to see I could migrate from RootCommands (https://github.com/Free-Software-for-Android/RootCommands) to your library (I maintain a fork as a module of AdAway). So for my needs, I see some interesting additions:
- I could create dedicated shells for long running tasks (I run web server and tcpdump processes in background) without blocking other shell commands,
- I seems I can have a working shell without root (if a device is not rooted, I can still provide some features without crashing my app),
- Busybox in included so device support should be good enough.
But compared to RootCommands, there is no concept of command itself: all commands are strings. It was useful to have it: to set parameter, chain them, get error code, etc…
So I read through the code of latest version and I have few questions and remarks. Keep in mind I am not here to point some bad things in the code, just to start a discussion about the question you ask me 😉
In my opinion Shell.Sync
and Shell.Async
methods looks weird.
First, varargs and List are mixed. Then required parameter, commands
, is not at the same place for each sh
, su
, loadScript
variant. It could be the first parameter so it will be consistent to every other overloaded methods.
The output parameters, output
and error
lists, deserved a dedicated structure as a return type.
Doing so, List<String> su(String...)
, su(List<String>, String...)
and su(List<String>, List<String>, String...)
could be merged into only one method like Output su(String...)
where Output
contains twos List<String>
, output
and error
, which may be empty.
Creating an Output
class could also lead to move String cleanup like ShellUtils.isValidOutput
to this class.
Example: output or error list merged in one line, get the last line, etc…
And what about error code? Token is already used to detect the end of a command. It could be also printed and retrieved like it is done in boolean ShellUtils.fastCmdResult(Shell shell, String cmd)
.
About Shell.Initializer
, onShellInit
and onRootShellInit
could be the same.
Just look at the given shell in parameter to check if it is root or not (with getStatus()
for example or a dedicated function like isRoot()
).
Once cleaned, we could have a functional interface as shell initializer (BiPredicate<Context, Shell>
).
Same from throwing an exception, the documentation said “it is the same as returning false”. As duplicate, it could be removed.
Same about Shell.Task
, is throwing Exception redundant with stderr? Thrown exceptions in task look like to be related to I/O internals and implementation details. May be the user should not be aware of this kind of error and the exception message should be added to stderr instead? (while stack trace could stil be logged) Because if the user (ie the app developer) can’t do anything about this exception, it seems the library should in charge of handling it.
And, at last, it is usually recommended to return empty collections or arrays instead of null
. See Item 54 of Josh Bloch’s Effective Java. About the “all the commands are String”, I would recommend to read the Item 62 (Avoid String where other types are more appropriate), and “varargs and list”, check Item 53 (Use varargs judiciously) because the API allow the call Shell.Sync.su()
(without a command). Even if it should work, do we really want it?
I still have a lot of questions when I start reading the implementation details, specially about threading, locking, input/output stream encoding, etc…, but it is already late here and I guess this issue is already long enough! Anyway, thanks for your work! 👍
Issue Analytics
- State:
- Created 5 years ago
- Reactions:3
- Comments:20 (15 by maintainers)
Top GitHub Comments
@victorlapin there are still bugs in the old versions and I don’t want to maintain two branches alongside. All methods and classes in the shim are annotated
@Deprecated
, so developers using a decent IDE will be notified to use the newer API.The shim is fairly simple so it wouldn’t be too complicated to maintain, since the previous API is mostly a collection of method overrides and actually only rely on a few key methods. I just need to transform the few key methods and it’s good to go!
libsu
is meant to have 0 dependencies itself so I kept it nice and clean. Creating RxJava2 wrapper is definitely very cool! I think it is also possible to wrap it with Support Library’s Lifecycle-Aware Components. I’ll leave these to other developers though, my main goal should be making this library as robust as possible!@PerfectSlayer so I’ve made some changes after your suggestions.
enqueue
is renamed tosubmit
. The original idea is from JobIntentService, but I personally also prefer the Java Executor wayJob.onResult
and add an overloadJob.submit(ResultCallback)
I added chaining operations, so the final
Job
class is like this:Along with these changes two non-static APIs in
Shell
are replaced with a new one:To directly interact with a
Shell
instance will be likeshell.newJob().add(InputStream).add(cmds....).add(...).exec()
However, I did not touch the high level APIs, because in most cases people will just use
Shell.su(cmds...)
orShell.su(in)
(at least in my case). New operations can also be added to the returnedJob
from the high level APIs, so nothing is missing there.Also, I personally do not like method names to be too verbose (contradict with common Java code naming conventions LOL), so I named my API very simple, like
add
andto
. Also I would love to leave theto(stdout)
andto(stdout, stderr)
methods untouched too, because in the case ofto(stdout)
, it will check the global flagFLAG_REDIRECT_STDERR
to decide whether STDERR should be collected. To only collect STDERR, just useto(null, List)
.