布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:padding="3dp"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/input"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<EditText
android:editable="false"
android:clickable="false"
android:focusable="false"
android:id="@+id/hostname"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:gravity="center_vertical"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold" />
<EditText
android:id="@+id/script"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toRightOf="@+id/hostname"
android:layout_weight="1"
android:gravity="left|center_vertical"
android:imeOptions="actionGo"
android:inputType="text"
android:singleLine="true"
android:textColor="@android:color/white" />
</LinearLayout>
<ScrollView
android:id="@+id/scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/input">
<TextView
android:id="@+id/result"
android:ellipsize="start"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white" />
</ScrollView>
</RelativeLayout>
清单文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Xten">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>
</manifest>
代码
package com.tencent.mmlite;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class MainActivity extends Activity implements TextView.OnEditorActionListener {
EditText cmd, hostname;
TextView messageView;
Handler handler;
ScrollView scrollView;
private static final String TAG = "Xten";
static final String HOSTNAME = Build.DEVICE;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
cmd = findViewById(R.id.script);
cmd.clearFocus();
hostname = findViewById(R.id.hostname);
hostname.setText(HOSTNAME + " $ ");
messageView = findViewById(R.id.result);
messageView.setMovementMethod(ScrollingMovementMethod.getInstance());
scrollView = findViewById(R.id.scroll);
scrollView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
scrollView.post(new Runnable() {
public void run() {
scrollView.fullScroll(View.FOCUS_DOWN);
}
});
}
});
cmd.setOnEditorActionListener(this);
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x1) {
messageView.append("\n");
messageView.append(String.valueOf(msg.obj));
}
}
};
requestPermission(false);
}
private void println(String newText) {
Message message = new Message();
message.what = 0x1;
message.obj = newText;
handler.sendMessage(message);
}
public void onClick() {
final String script = cmd.getText().toString().trim();
cmd.setText("");
if (TextUtils.isEmpty(script)) {
println(HOSTNAME + " $ ");
return;
}
if (TextUtils.equals("ctrl+c", script)) {
reset();
return;
}
if (TextUtils.equals("clear", script)) {
messageView.setText("");
return;
}
cmd.clearFocus();
runScript(script);
}
private Thread scriptShell;
private Thread normal, error;
Process mProcess;
InputStream err, is;
private void reset() {
if (mProcess != null) {
mProcess.destroy();
mProcess = null;
}
if (scriptShell != null && scriptShell.isAlive()) {
scriptShell.interrupt();
scriptShell = null;
}
if (normal != null && normal.isAlive()) {
normal.interrupt();
normal = null;
}
if (error != null && error.isAlive()) {
error.interrupt();
error = null;
}
if (err != null) {
try {
err.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void runScript(String text) {
println("\n" + HOSTNAME + " $ " + text);
reset();
scriptShell = new Thread() {
@Override
public void run() {
super.run();
String[] cmds = new String[]{"sh", "-c", text};
ProcessBuilder builder = new ProcessBuilder(cmds);
builder.redirectErrorStream();
try {
mProcess = builder.start();
is = mProcess.getInputStream();
err = mProcess.getErrorStream();
read(is, err);
} catch (IOException e) {
e.printStackTrace();
println(text + ": error " + e.getMessage());
}
}
};
scriptShell.start();
}
private void read(InputStream inputStream, InputStream errorStream) {
if (normal != null && normal.isAlive()) {
normal.interrupt();
normal = null;
}
if (error != null && error.isAlive()) {
error.interrupt();
error = null;
}
normal = new Thread() {
@Override
public void run() {
super.run();
try {
InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader mReader = new BufferedReader(isr);
String string;
while ((string = mReader.readLine()) != null) {
println(string);
}
} catch (Exception e) {
}
}
};
error = new Thread() {
@Override
public void run() {
super.run();
try {
InputStreamReader isr = new InputStreamReader(errorStream);
BufferedReader mReader = new BufferedReader(isr);
String string;
while ((string = mReader.readLine()) != null) {
println(string);
}
} catch (Exception e) {
}
}
};
if (inputStream != null) {
normal.start();
}
if (errorStream != null) {
error.start();
}
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
Log.d(TAG, "onEditorAction: " + actionId + " " + event);
if (actionId == EditorInfo.IME_ACTION_GO) {
onClick();
return true;
}
if (event != null) {
if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER || event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) {
if (event.getAction() == 1) {
onClick();
return true;
}
}
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 100) {
requestPermission(true);
}
}
private void requestPermission(boolean exit) {
int w = checkSelfPermission(WRITE_EXTERNAL_STORAGE);
int r = checkSelfPermission(READ_EXTERNAL_STORAGE);
if (w != PackageManager.PERMISSION_GRANTED || r != PackageManager.PERMISSION_GRANTED) {
if (!exit) {
requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE}, 100);
} else {
Toast.makeText(this, "不要拒绝权限啊", Toast.LENGTH_LONG).show();
System.exit(0);
}
}
}
}
效果展示
image.png
image.png