@@ -56,10 +56,32 @@ public abstract class ShellHandlerBase extends Handler {
5656 // (gọi từ release() khi dialog bị huỷ) để không giữ rác sau khi không cần nữa.
5757 private DataOutputStream stdin ;
5858
59+ // Runnable do ShellExecutor.execute() cung cấp qua onStart(Runnable) — khi gọi sẽ thực thi
60+ // "killtree" (kill toàn bộ cây tiến trình con sinh ra bởi phiên shell hiện tại, không chỉ
61+ // riêng process của app) rồi đóng stream + destroy process shell chính. Cần lưu lại tham
62+ // chiếu này ở lớp cha vì killApp() (phục vụ "exit:[kill]"/"exit:[restart]") cần gọi tới nó
63+ // TRƯỚC khi kill process của app — nếu không, các lệnh shell chạy ngầm (con của su/sh) sẽ
64+ // bị mồ côi và tiếp tục chạy nền dù app đã bị kill/restart.
65+ private Runnable forceStop ;
66+
5967 public ShellHandlerBase (Context context ) {
6068 this .context = context ;
6169 }
6270
71+ /**
72+ * Lưu lại runnable kill-tree do ShellExecutor cung cấp. Lớp con override onStart(Runnable)
73+ * (ví dụ để gắn vào nút "Dừng") CẦN gọi thêm dòng này trong phần implement của mình, ví dụ:
74+ *
75+ * {@literal @}Override
76+ * public void onStart(Runnable forceStop) {
77+ * bindForceStop(forceStop);
78+ * this.stopButtonAction = forceStop; // logic cũ của lớp con vẫn giữ nguyên
79+ * }
80+ */
81+ public void bindForceStop (Runnable forceStop ) {
82+ this .forceStop = forceStop ;
83+ }
84+
6385 /**
6486 * Gắn luồng stdin của process shell hiện tại, để UI (ô nhập liệu) có thể ghi dữ liệu
6587 * người dùng gõ vào ngay trong lúc script đang chạy (phục vụ lệnh `read` trong script).
@@ -236,21 +258,32 @@ private void killApp(boolean restart) {
236258 onKillRequest ();
237259 } catch (Exception ignored ) {
238260 // Không để lỗi dọn dẹp UI cản trở việc kill
239- } finally {
240- if (restart ) {
241- try {
242- Intent launch = context .getPackageManager ().getLaunchIntentForPackage (context .getPackageName ());
243- if (launch != null ) {
244- launch .addFlags (Intent .FLAG_ACTIVITY_NEW_TASK | Intent .FLAG_ACTIVITY_CLEAR_TASK );
245- context .startActivity (launch );
246- }
247- } catch (Exception ignored ) {
248- // Không để lỗi khởi động lại cản trở việc kill process cũ
261+ }
262+ // Kill cây tiến trình shell (root killtree theo sessionTag) TRƯỚC khi kill app process.
263+ // Nếu bỏ qua bước này, killProcess() bên dưới chỉ giết process Java của app — các tiến
264+ // trình su/sh/script con sẽ bị mồ côi, được init nhận nuôi, và tiếp tục chạy nền bình
265+ // thường dù app đã biến mất khỏi Recent Apps.
266+ Runnable forceStop = this .forceStop ;
267+ if (forceStop != null ) {
268+ try {
269+ forceStop .run ();
270+ } catch (Exception ignored ) {
271+ // Không để lỗi kill-tree cản trở việc kill process của app
272+ }
273+ }
274+ if (restart ) {
275+ try {
276+ Intent launch = context .getPackageManager ().getLaunchIntentForPackage (context .getPackageName ());
277+ if (launch != null ) {
278+ launch .addFlags (Intent .FLAG_ACTIVITY_NEW_TASK | Intent .FLAG_ACTIVITY_CLEAR_TASK );
279+ context .startActivity (launch );
249280 }
281+ } catch (Exception ignored ) {
282+ // Không để lỗi khởi động lại cản trở việc kill process cũ
250283 }
251- android .os .Process .killProcess (android .os .Process .myPid ());
252- System .exit (10 );
253284 }
285+ android .os .Process .killProcess (android .os .Process .myPid ());
286+ System .exit (10 );
254287 }
255288
256289 private String getAmHelp () {
0 commit comments