Service進程防殺

什么是進程

進程(Process)是計算機中的程序關于某數據集合上的一次運行活動,是系統(tǒng)進行資源分配和調度的基本單位,是操作系統(tǒng)結構的基礎。

進程的概念主要有兩點:第一,進程是一個實體。每一個進程都有它自己的地址空間,一般情況下,包括文本區(qū)域(text region)、數據區(qū)域(data region)和堆棧(stack region)。文本區(qū)域存儲處理器執(zhí)行的代碼;數據區(qū)域存儲變量和進程執(zhí)行期間使用的動態(tài)分配的內存;堆棧區(qū)域存儲著活動過程調用的指令和本地變量。第二,進程是一個“執(zhí)行中的程序”。程序是一個沒有生命的實體,只有處理器賦予程序生命時(操作系統(tǒng)執(zhí)行之),它才能成為一個活動的實體,我們稱其為進程。

Android Service

Service是Android系統(tǒng)中的四大組件之一,它是一種長生命周期的,沒有可視化界面運行于后臺的一種服務程序。

當一個程序第一次啟動的時候,Android會啟動一個LINUX進程和一個主線程。默認的情況下,所有該程序的組件都將在該進程和線程中運行。 同時,Android會為每個應用程序分配一個單獨的LINUX用戶。Android會盡量保留一個正在運行進程,只在內存資源出現不足時,Android會嘗試停止一些進程從而釋放足夠的資源給其他新的進程使用, 也能保證用戶正在訪問的當前進程有足夠的資源去及時地響應用戶的事件。

我們可以將一些組件運行在其他進程中,并且可以為任意的進程添加線程。組件運行在哪個進程中是在manifest文件里設置的,其中<Activity>,<Service>,<receiver>和<provider>都有一個process屬性來指定該組件運行在哪個進程之中。我們可以設置這個屬性,使得每個組件運行在它們自己的進程中,或是幾個組件共同享用一個進程,或是不共同享用。<application>元素也有一個process屬性,用來指定所有的組件的默認屬性。

Service的主要作用:

  1. 后臺運行
    ?Service沒有自己的界面,用戶察覺不到,看似在后臺運行。默認情況下Service是運行在主線程中的,為了不造成主線程阻塞,所以在Service中要處理耗時操作需要放到其他線程中去處理。
  2. 跨進程通信
    ? 由于進程與進程之間是獨立分開的,一個進程不能訪問另外一個進程的資源。Android提供了一種跨進程通信的方式AIDL。這種方式通過一個進程綁定另一個進程的Binder Service來實現。

進程優(yōu)先級

Android系統(tǒng)會盡可能長的延續(xù)一個應用程序進程,但在內存過低的時候,仍然會不可避免需要移除舊的進程。為了決定哪些進程留下,哪些進程被殺死,系統(tǒng)根據在進程中在運行的組件及組件的狀態(tài),為每一個進程分配了一個優(yōu)先級等級。優(yōu)先級最低的進程首先被殺死。這個進程重要性的層次結構主要有五個等級:

  • 前臺進程
  • 可見進程
  • 服務進程
  • 后臺進程
  • 空進程

前臺進程

前臺進程是用戶當前正在使用的進程。只有一些前臺進程可以在任何時候都存在。他們是最后一個被結束的,當內存低到根本連他們都不能運行的時候。一般來說, 在這種情況下,設備會進行內存調度,中止一些前臺進程來保持對用戶交互的響應。因為殺死前臺進行需要用戶交互,所以前臺進程優(yōu)先級是最高的。

前臺進程常見情形:

  • 進程持有一個正在與用戶交互的Activity(這個Activity的onResume方法被調用)
  • 進程持有一個Service,這個Service處于以下幾種狀態(tài)
    • Service與用戶正在交互的Activity綁定
    • Service是在前臺運行的(其調用了startForeground())
    • Service正在執(zhí)行其生命周期回調方法(onCreate(),onStart(),onDestroy())
  • 進程持有一個BroadcastReceiver正在執(zhí)行其onReceive方法

可見進程

如果一個進程不含有任何前臺的組件,但仍然可被用戶再屏幕上所見。可見進程也被認為很重要的,一般不會被銷毀,除非是為了保證所有前臺進程的運行而不得不殺死可見進程的時候。

可見進程常見情形:

  • 進程持有一個Activity,這個Activity不在前臺,但是仍然被用戶可見(onPause)

  • 進程持有一個Service,這個Service和一個可見的(或前臺的)Activity綁定。

服務進程

如果一個進程中運行著一個Service,這個Service是通過startService()開啟的,并且不屬于上面兩種較高優(yōu)先級的情況,這個進程就是一個服務進程。

盡管服務進程沒有和用戶可以看到的東西綁定,但是它們一般在做的事情是用戶關心的,比如后臺播放音樂,后臺下載數據等。所以系統(tǒng)會盡量維持它們的運行,除非系統(tǒng)內存不足以維持前臺進程和可見進程的運行需要。

后臺進程

如果進程不屬于上面三種情況,但是進程持有一個用戶不可見的activity(activity的onStop()被調用,但是onDestroy()沒有調用的狀態(tài)),就認為進程是一個后臺進程。

后臺進程不直接影響用戶體驗,系統(tǒng)會為了前臺進程、可見進程、服務進程而任意殺死后臺進程。

通常會有很多個后臺進程存在,它們會被保存在一個LRU (least recently used)列表中,這樣就可以確保用戶最近使用的activity最后被銷毀,即最先銷毀時間最遠的activity。

空進程

如果一個進程不包含任何活躍的應用組件,則認為是空進程。
例如:一個進程當中已經沒有數據在運行了,但是內存當中還為這個應用駐留了一個進程空間。
保存這種進程的唯一理由是為了緩存的需要,為了加快下次要啟動這個進程中的組件時的啟動時間。系統(tǒng)為了平衡進程緩存和底層內核緩存的資源,經常會殺死空進程。

使用進程的優(yōu)先級

Android 對進程的重要性評級的時候,選取它最高的級別。例如,如果一個進程含有一個service和一個可視activity,進程將被歸入一個可視進程而不是service進程。

另外,當被某個進程被另外的一個進程依賴的時候,它的級別可能會增高。也就意味著一個為其他進程服務的進程永遠會比那些被服務的進程重要級高。

因為服務進程后臺進程重要級高,因此一個要進行耗時工作的Activity最好啟動一個Service來做這個工作,而不是在Activity中開啟一個子線程――特別是這個操作需要的時間比Activity存在的時間還要長的時候。例如,在后臺播放音樂,向網上上傳攝像頭拍到的圖片,使用Service可以讓其進程至少獲取到服務進程級別的重要級,而不用考慮Activity目前是什么狀態(tài)。BroadcastReceiver做費時的工作的時候,也應該啟用一個服務而不是開一個線程。

進程防殺

提高進程優(yōu)先級

提升進程優(yōu)先級至當前進程

之前小米工程師在微博上爆出QQ為了保活,采取在鎖屏的時候啟動一個1個像素的Activity,當用戶解鎖以后將這個Activity結束掉(順便同時把自己的核心服務再開啟一次)。這種做法很不容易被用戶發(fā)現。

修改Manifest android:priority

在AndroidManifest.xml文件中對于intent-filter可以通過android:priority = “1000”這個屬性設置最高優(yōu)先級,1000是最高值,如果數字越小則優(yōu)先級越低,同時適用于廣播。

Service提升為前臺進程

在onStartCommand里面調用 startForeground()方法把Service提升為前臺進程級別,然后在onDestroy里面要記得調用stopForeground ()方法。

Service onStartCommand 返回 START_STICKY。

在Service的onStartCommand方法中手動返回START_STICKY,親測當service因內存不足被kill,當內存又有的時候,service又被重新創(chuàng)建,比較不錯,但是不能保證任何情況下都被重建,比如進程被干掉了。

補充說明:onStartCommand()方法,返回的是一個int整形。
這個整形可以有以下四個取值:

  1. START_STICKY:如果service進程被kill掉,保留service的狀態(tài)為開始狀態(tài),但不保留遞送的intent對象。隨后系統(tǒng)會嘗試重新創(chuàng)建service,由于服務狀態(tài)為開始狀態(tài),所以創(chuàng)建服務后一定會調用onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啟動命令被傳遞到service,那么參數Intent將為null。
  2. START_NOT_STICKY:“非粘性的”。使用這個返回值時,如果在執(zhí)行完onStartCommand后,服務被異常kill掉,系統(tǒng)不會自動重啟該服務
  3. START_REDELIVER_INTENT:重傳Intent。使用這個返回值時,如果在執(zhí)行完onStartCommand后,服務被異常kill掉,系統(tǒng)會自動重啟該服務,并將Intent的值傳入。
  4. START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保證服務被kill后一定能重啟。

在onDestroy方法里發(fā)廣播重啟service

service + broadcast 方式,就是當service走ondestory的時候,發(fā)送一個自定義的廣播,當收到廣播的時候,重新啟動service。(第三方應用或是在setting里-應用-強制停止時,APP進程就直接被干掉了,onDestroy方法都進不來,所以無法保證會執(zhí)行)

監(jiān)聽系統(tǒng)廣播判斷Service狀態(tài)

通過系統(tǒng)的一些廣播,比如:手機重啟、界面喚醒、應用狀態(tài)改變等等監(jiān)聽并捕獲到,然后判斷我們的Service是否還存活,如果我們的Service不存活時就啟動起來。

Application加上Persistent屬性

看Android的文檔知道,當進程長期不活動,或系統(tǒng)需要資源時,會自動清理門戶,殺死一些Service,和不可見的Activity等所在的進程。但是如果某個進程不想被殺死(如數據緩存進程,或狀態(tài)監(jiān)控進程,或遠程服務進程),應該怎么做,才能使進程不被殺死。

add android:persistent=”true” into the section in your AndroidManifest.xml

加上以上屬性相當于將該進程設置為常駐內存進程。
切記,這個不可濫用,一般只適用于放在/system/app下的app,系統(tǒng)中用這個的service,app一多,整個系統(tǒng)可能就會崩潰。
比如系統(tǒng)phone中配置了android:persistent=”true”屬性,并且Phone.apk是安裝在/system/app/目錄下的,所以在開機時會自動啟動PhoneApp類。

手機廠商白名單

app運營商和某些手機廠商可能有合作關系,讓自家的進程怎么殺也殺不死,讓自家的應用卸載不了。

雙進程守護

上述防進程被殺的方法,也并不能完全保證進程不被系統(tǒng)殺死。大多做法都能被用戶在Setting設置里給停掉,或者被用戶安裝的清理軟件給殺掉。

那么怎么才能讓我們的進程被殺掉后再活過來呢?想必大家都聽說過雙進程守護的方式吧。具體實現原理是使用兩個進程來相互監(jiān)聽存活狀態(tài),相互啟動。為何要使用兩個進程呢?

我們都知道在Android中讓兩個進程相互通信使用的是AIDL技術。所以我們需要用到Service的遠程綁定,讓每個進程中都啟動一個Service,啟動之后再與另一個進程的Service進行綁定,綁定成功之后監(jiān)聽對面Service的狀態(tài)。如果其中一個Service被干掉了,另一個Service會再通過AIDL把被干掉的Service啟動起來。

以下是具體實現的一個例子,在同一個程序內開啟了兩個服務,LocalService,RemoteService。這兩個服務分別屬于不同進程。

項目結構:


這里寫圖片描述

首先定義一個AIDL文件:

// IKeepLiveConnection.aidl
package com.congwiny.keepliveprocess;

// Declare any non-default types here with import statements

interface IKeepLiveConnection {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.congwiny.keepliveprocess">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".LocalService"/>

        <service
            android:name=".RemoteService"
            android:process=":remote" />
    </application>

</manifest>

MainActivity

package com.congwiny.keepliveprocess;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startService(new Intent(this,LocalService.class));
        startService(new Intent(this,RemoteService.class));
    }
}

LocalService

package com.congwiny.keepliveprocess;

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class LocalService extends Service {

    private static final String TAG = LocalService.class.getSimpleName();

    private KeepLiveBinder binder;
    private KeepLiveServiceConnection conn;

    @Override
    public void onCreate() {
        super.onCreate();
        binder = new KeepLiveBinder();
        conn = new KeepLiveServiceConnection();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Intent intentNew = new Intent();
        intentNew.setClass(this, RemoteService.class);
        bindService(intentNew, conn, BIND_IMPORTANT);
        return START_STICKY;
    }

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

    private class KeepLiveServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "remote service onServiceConnected");

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "remote service onServiceDisconnected");
            //重新綁定RemoteService
            Intent intent = new Intent();
            intent.setClass(LocalService.this, RemoteService.class);
            startService(intent);
            bindService(intent, conn, BIND_IMPORTANT);
        }
    }

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

    private class KeepLiveBinder extends IKeepLiveConnection.Stub {

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }
    }
}

RemoteService

package com.congwiny.keepliveprocess;

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class RemoteService extends Service {

    private static final String TAG = RemoteService.class.getSimpleName();
    private KeepLiveBinder binder;
    private KeepLiveServiceConnection conn;

    @Override
    public void onCreate() {
        super.onCreate();
        binder = new KeepLiveBinder();
        conn = new KeepLiveServiceConnection();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Intent intentNew = new Intent();
        intentNew.setClass(this, LocalService.class);
        bindService(intentNew, conn, BIND_IMPORTANT);

        return START_STICKY;
    }

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

    private class KeepLiveServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "local service onServiceConnected");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "local service onServiceDisconnected");

            Intent intent = new Intent();
            intent.setClass(RemoteService.this, LocalService.class);
            //LocalService已經掛了,先必須先啟動,后綁定
            startService(intent);
            bindService(intent, conn, BIND_IMPORTANT);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //解綁,否則會出現 leaked ServiceConnection
        unbindService(conn);
    }

    private class KeepLiveBinder extends IKeepLiveConnection.Stub {

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }
    }
}

運行效果圖:


這里寫圖片描述

JobScheduler

JobScheduler是Android5.0新增的API,JobScheduler API允許開發(fā)者在符合某些條件時創(chuàng)建執(zhí)行在后臺的任務。具體細節(jié)這里就不做描述。參考
在Android 5.0中使用JobScheduler

我們?yōu)槭裁匆褂眠@個API呢?

當我們手動點擊找到該應用信息頁面,有個結束運行或者強行停止按鈕,如果我們點擊這個按鈕后,我們的雙進程守護也不管用了,我們的程序的進程會被強制殺死,再也啟動不起來。

這里寫圖片描述

通過實際操作不同的手機,有的手機中運行的進程被殺死后,能在使用JobScheduler的情況下啟動,而有些手機是不可以的。
代碼實現:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.congwiny.keepliveprocess">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".LocalService"/>

        <service
            android:name=".RemoteService"
            android:process=":remote" />

        <service android:name=".JobSchedulerService"
            android:permission="android.permission.BIND_JOB_SERVICE"/>
    </application>

</manifest>
package com.congwiny.keepliveprocess;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startService(new Intent(this,LocalService.class));
        startService(new Intent(this,RemoteService.class));
        startService(new Intent(this, JobSchedulerService.class));
    }
}
package com.congwiny.keepliveprocess;

import java.util.List;

import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

@SuppressLint("NewApi")
public class JobSchedulerService extends JobService {

    private static final String TAG = JobSchedulerService.class.getSimpleName();

    private int kJobId = 0;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate");

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand");
        scheduleJob(getJobInfo());
        return START_STICKY;
    }

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

    @Override
    public boolean onStartJob(JobParameters params) {
        Log.e(TAG, "onStartJob");
        boolean isLocalServiceWork = isServiceWork(this, "com.congwiny.keepliveprocess.LocalService");
        boolean isRemoteServiceWork = isServiceWork(this, "com.congwiny.keepliveprocess.RemoteService");

        if (!isLocalServiceWork ||
                !isRemoteServiceWork) {
            this.startService(new Intent(this, LocalService.class));
            this.startService(new Intent(this, RemoteService.class));
            Toast.makeText(this, "job scheduler service start", Toast.LENGTH_SHORT).show();
        }
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        Log.e(TAG, "onStopJob");
        Toast.makeText(this, "job scheduler service stop", Toast.LENGTH_SHORT).show();
        scheduleJob(getJobInfo());
        return true;
    }

    /**
     * Send job to the JobScheduler.
     */
    public void scheduleJob(JobInfo t) {
        JobScheduler tm =
                (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        tm.schedule(t);
    }

    public JobInfo getJobInfo() {
        JobInfo.Builder builder = new JobInfo.Builder(kJobId++, new ComponentName(this, JobSchedulerService.class));
        builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
        builder.setPersisted(true);
        builder.setRequiresCharging(false);
        builder.setRequiresDeviceIdle(false);
        builder.setPeriodic(1000);//間隔時間--周期
        return builder.build();
    }


    /**
     * 判斷某個服務是否正在運行的方法
     *
     * @return true代表正在運行,false代表服務沒有正在運行
     */
    public boolean isServiceWork(Context mContext, String serviceName) {
        boolean isWork = false;
        ActivityManager myAM = (ActivityManager) mContext
                .getSystemService(Context.ACTIVITY_SERVICE);
        List<RunningServiceInfo> myList = myAM.getRunningServices(100);
        if (myList.size() <= 0) {
            return false;
        }
        for (int i = 0; i < myList.size(); i++) {
            String mName = myList.get(i).service.getClassName().toString();
            if (mName.equals(serviceName)) {
                isWork = true;
                break;
            }
        }
        return isWork;
    }
}

擴展

Service和Thread的關系

Android中Service和Thread有什么區(qū)別,為什么有時候放著方便的Thread不用,而要使用Service呢?

要解答這個問題,首先我們要弄明白Service和Thread分別是怎么定義的:

  • Thread 是程序執(zhí)行的最小單元,它是分配CPU的基本單位。可以用 Thread 來執(zhí)行一些異步的操作。

  • Service是Android的四大組件之一,被用來執(zhí)行長時間的后臺任務。默認情況下Service是運行在主線程中的。

二者的使用上的區(qū)別

  1. 在android中,Thread只是一個用來執(zhí)行后臺任務的工具類,它可以在Activity中被創(chuàng)建,也可以在Service中被創(chuàng)建。

  2. Service組件主要有兩個作用:后臺運行和跨進程訪問。service可以在android系統(tǒng)后臺獨立運行并且可以跨進程運行,線程是不可以。

  3. 如果需要執(zhí)行復雜耗時的操作,必須在Service中再創(chuàng)建一個Thread來執(zhí)行任務。Service的優(yōu)先級高于后臺掛起的Activity,當然也高于Activity所創(chuàng)建的Thread,因此,系統(tǒng)可能在內存不足的時候優(yōu)先殺死后臺的Activity或者Thread,而不會輕易殺死Service組件,即使被迫殺死Service,也會在資源可用時重啟被殺死的Service。

Activity和Service是否處于同一進程

默認同一個應用程序的Activity和Service都屬于同一個進程,Activity和Service都運行在主線程里。但如果在AndroidManifest.xml中給Service設置android:process="xxx",此Service就是屬于"xxx"進程了。

總結

上述進程防殺給出一些思路,由于Android手機定制機型太多,有些底層已經被改,所以上述方法并不能保證在每個手機上都行的通,只是做個參考而已。進程防殺這個話題,引出的知識點還是很多,以前沒有好好總結過,不成系統(tǒng),現在總結出來,擴充自己的系統(tǒng),以備自己以后復習使用。下面參考的一些文章有時間的話就學習下。

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,532評論 25 708
  • Service service:是一個后臺服務,專門用來處理常駐后臺的工作的組件。 即時通訊:service來做常...
    出云月閱讀 540評論 0 0
  • 如何能讓我們的應用能夠在系統(tǒng)后臺持續(xù)地運行是一個自Android從娘胎里出來時就議論不停的話題,而且這似乎成了一個...
    駿駿的簡書閱讀 1,132評論 1 19
  • 勃起的山脈 鎮(zhèn)壓著村落對自由的向往 風在此折翼 海沫如血般潑濺在彼岸 那凋零的意志之花的臉龐 罪惡的利劍 割斷了信...
    死亡詩約閱讀 742評論 4 3
  • 太陽已經升到了半空中,撫摸著正懶洋洋的坐在潤溪湖畔的宿舍樓。上午九時,樓棟中空空如也。小江緩緩推開被子,在床上睡眼...
    北方淡季閱讀 467評論 2 4