1. 程式人生 > >Android程序保活全攻略(中)

Android程序保活全攻略(中)

在上一篇部落格Android程序保活全攻略(上)中介紹了程序保活的背景和一些方法的思路和實現方式,本篇部落格我將承接上篇部落格,繼續進行介紹。

9) 1畫素懸浮層
**思路:**1畫素懸浮層是傳說的QQ黑科技,監控手機鎖屏解鎖事件,在螢幕鎖屏時啟動1個畫素的 Activity,在使用者解鎖時將 Activity 銷燬掉。注意該 Activity 需設計成使用者無感知。通過該方案,可以使程序的優先順序在螢幕鎖屏時間由4提升為最高優先順序1。
保活強度:
前臺程序,跟前臺服務差不多。需要許可權,不敵force-stop
實現程式碼:
首先定義 Activity,並設定 Activity 的大小為1畫素:

public class MainActivity extendsAppCompatActivity {
    private static final StringTAG="keeplive";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        Window window = getWindow();
        window.setGravity(Gravity.LEFT|Gravity.TOP);
        WindowManager.LayoutParams params = window.getAttributes();
        params.x=0
; params.y=0; params.height=1; params.width=1; window.setAttributes(params); } }

其次,從 AndroidManifest 中通過如下屬性,排除 Activity 在 RecentTask 中的顯示:

<activity
    android:name=".KeepAliveActivity"
    android:excludeFromRecents="true"
    android:exported="false"
    android:finishOnTaskLaunch
="false" android:launchMode="singleInstance" android:process=":live" android:theme="@style/LiveActivityStyle" >
</activity>

最後,控制 Activity 為透明:

<stylename="LiveActivityStyle">
   <itemname="android:windowBackground">@android:color/transparent</item>
   <itemname="android:windowFrame">@null</item>
   <itemname="android:windowNoTitle">true</item>
   <itemname="android:windowIsFloating">true</item>
   <itemname="android:windowIsTranslucent">true</item>
   <itemname="android:windowContentOverlay">@null</item>
   <itemname="android:windowAnimationStyle">@null</item>
   <itemname="android:windowDisablePreview">true</item>
   <itemname="android:windowNoDisplay">true</item>
</style>

Activity 啟動與銷燬時機的控制:

public class KeepLiveReceiver extendsBroadcastReceiver {
    privateContextmContext;

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action.equals(Intent.ACTION_SCREEN_OFF)) {
            KeepLiveManeger.getInstance(mContext).startKeepLiveActivity();
        } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
            KeepLiveManeger.getInstance(mContext).destroyKeepLiveActivity();
        }
        KeepLiveManeger.getInstance(mContext).startKeepLiveService();
    }
}

10) 應用間互相拉起
**思路:**app之間知道包名就可以相互喚醒了,比如你殺了我qq,只要微信還在就能確保隨時喚醒qq。還有百度全系app都通過bdshare實現互拉互保,自定義一個廣播,定時發,其他app收廣播自起等

11) 心跳喚醒
思路:微信保活技術,依賴系統特性:長連線網路回包機制
保活強度:不敵force-stop,需要網路,API level >= 23的doze模式會關閉所有的網路
程式碼實現:

public class HeartbeatService extends Service implements Runnable {
    private Thread mThread;
    public int count = 0;
    private boolean isTip = true;
    private static String mRestMsg;
    private static String KEY_REST_MSG = "KEY_REST_MSG";

    @Override
    public void run() {
        while (true) {
            try {
                if (count > 1) {

                    count = 1;
                    if (isTip) {
                        //判斷應用是否在執行
                        ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
                        List<RunningTaskInfo> list = am.getRunningTasks(3);
                        for (RunningTaskInfo info : list) {
                            if (info.topActivity.getPackageName().equals("org.yhn.demo")) {
                                //通知應用,顯示提示“連線不到伺服器”
                                Intent intent = new Intent("org.yhn.demo");
                                intent.putExtra("msg", true);
                                sendBroadcast(intent);
                                break;
                            }
                        }

                        isTip = false;
                    }
                }
                if (mRestMsg != "" && mRestMsg != null) {
                    //向伺服器傳送心跳包
                    sendHeartbeatPackage(mRestMsg);
                    count += 1;
                }

                Thread.sleep(1000 * 3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void sendHeartbeatPackage(String msg) {
        HttpGet httpGet = new HttpGet(msg);
        DefaultHttpClient httpClient = new DefaultHttpClient();
        // 傳送請求
        HttpResponse httpResponse = null;
        try {
            httpResponse = httpClient.execute(httpGet);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (httpResponse == null) {
            return;
        }

        // 處理返回結果
        final int responseCode = httpResponse.getStatusLine().getStatusCode();
        if (responseCode == HttpStatus.SC_OK) {
            //只要伺服器有迴應就OK
            count = 0;
            isTip = true;
        } else {
            Log.i("@qi", "responseCode " + responseCode);
        }

    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public void onCreate() {
        super.onCreate();
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    public void onStart(Intent intent, int startId) {
        Log.i("@qi", "service onStart");
        //從本地讀取伺服器的URL,如果沒有就用傳進來的URL
        mRestMsg = getRestMsg();
        if (mRestMsg == null || mRestMsg == "") {
            mRestMsg = intent.getExtras().getString("url");
        }
        setRestMsg(mRestMsg);

        mThread = new Thread(this);
        mThread.start();
        count = 0;

        super.onStart(intent, startId);
    }

    public String getRestMsg() {
        SharedPreferences prefer = getSharedPreferences("settings.data", Context.MODE_PRIVATE);

        return prefer.getString(KEY_REST_MSG, "");
    }

    public void setRestMsg(String restMsg) {
        SharedPreferences prefer = getSharedPreferences("settings.data", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefer.edit();
        editor.putString(KEY_REST_MSG, restMsg);
        editor.commit();
    }

}

最後別忘了註冊Server和GET_TASKS

<service
    android:name=".demo.HeartbeatService"
    android:label="QServer"
    android:persistent="true" >
    <intent-filter>
        <action android:name="HeartbeatService" />
    </intent-filter>
</service>
<uses-permission android:name="android.permission.GET_TASKS" />

<uses-permission android:name="android.permission.GET_TASKS" />

12) Native程序拉起
思路:開啟native子程序,定時發intent
保活強度:單殺可以殺死,force close 5.0以上無效,5.0以下部分手機無效,第三方軟體下無效,且無法保證實時常駐
實現程式碼:
首先開啟一個c程序,將需要保活的service名字傳遞進去

private static void start(Context context, Class<?> daemonClazzName, int interval) {
   String cmd = context.getDir(BIN_DIR_NAME, Context.MODE_PRIVATE)
      .getAbsolutePath() + File.separator + DAEMON_BIN_NAME;

   /* create the command string */
   StringBuilder cmdBuilder = new StringBuilder();
   cmdBuilder.append(cmd);
   cmdBuilder.append(" -p ");
   cmdBuilder.append(context.getPackageName());
   cmdBuilder.append(" -s ");
   cmdBuilder.append(daemonClazzName.getName());
   cmdBuilder.append(" -t ");
   cmdBuilder.append(interval);

   try {
      Runtime.getRuntime().exec(cmdBuilder.toString()).waitFor();
   } catch (IOException | InterruptedException e) {
      Log.e(TAG, "start daemon error: " + e.getMessage());
   }
}

然後定時給自己主程序發一個intent,如果主程序掛掉了,就可以順利拉起來保證存活。

while(sig_running)
{
   interval = interval < SLEEP_INTERVAL ? SLEEP_INTERVAL : interval;
   select_sleep(interval, 0);

   LOGD(LOG_TAG, "check the service once, interval: %d", interval);

   /* start service */
   start_service(package_name, service_name);
}

但這只是一個沒有主動權的訊息輪詢器,說是守護其實很勉強,而且,這是要建立在保證c程序不掛的基礎上,才能輪詢,但是就目前來看,只有5.0以下的非國產機才會有這樣的漏洞。也就是說在force close的時候,系統忽略c程序的存在,5.0以上包括5.0的哪怕源生系統也會連同c程序一起清理掉,國產機就更不用說了。就算是這樣,在5.0以下的非國產機上,如果安裝了獲取root許可權的360\cm的話,也是可以直接清理掉,也就是說會失效。

native程序守護缺點非常明顯,那就是守護是單向的,也就是說只能a保b,b保不了a;a保b也不是在b死了立刻拉起來,要等到了時間才會去拉。那如何解決這個native程序的缺點呢?那就是通過雙程序守護,下一篇我將詳細講解如何通過linux層來實現雙程序守護。