Android總結篇系列:Android Service
Service
通常總是稱之為“後臺服務”,其中“後臺”一詞是相對於前臺而言的,具體是指其本身的執行並不依賴於使用者可視的UI介面,因此,從實際業務需求上來理解,Service的適用場景應該具備以下條件:
1.並不依賴於使用者可視的UI介面(當然,這一條其實也不是絕對的,如前臺Service就是與Notification介面結合使用的);
2.具有較長時間的執行特性。
1.Service AndroidManifest.xml 宣告
一般而言,從Service
的啟動方式上,可以將Service
分為Started Service
和Bound Service
。無論哪種具體的Service
啟動型別,都是通過繼承Service基類自定義而來。在使用Service
時,要想系統能夠找到此自定義Service
,無論哪種型別,都需要在AndroidManifest.xml
中宣告,語法格式如下:
<service android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</service>
其中,android:exported
屬性上一篇博文中對此已進行詳盡描述,android:name
對應Service
類名,android:permission
是許可權宣告,android:process
設定具體的程式名稱。需要注意的是Service
能否單獨使用一個程式與其啟動方式有關,本後下面會給出具體說明。其他的屬性此處與其他元件基本相同,不再過多描述。
注:如果自定義Service
沒有在AndroidManifest.xml
中宣告,當具體使用時,不會像Activity
那樣直接崩潰報錯,對於顯式Intent
啟動的Service
,此時也會給出waring
資訊“IllegalArgumentException: Service not registered
”,有時候不容易發現忘了宣告而一時定位不到問題。
2.Started Service
Started Service
相對比較簡單,通過context.startService(Intent serviceIntent)
啟動Service
,context.stopService(Intent serviceIntent)
停止此Service
。當然,在Service
內部,也可以通過stopSelf(...)
方式停止其本身。
1)Started Service自定義
下面程式碼片段顯示的是一個最基本的Started Service
的自定義方式:
public class MyService extends Service {
public static final String TAG = "MyService";
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.w(TAG, "in onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.w(TAG, "in onStartCommand");
Log.w(TAG, "MyService:" + this);
String name = intent.getStringExtra("name");
Log.w(TAG, "name:" + name);
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.w(TAG, "in onDestroy");
}
}
其中,onBind(...)
函式是Service
基類中的唯一抽象方法,子類都必須重寫實現,此函式的返回值是針對Bound Service
型別的Service
才有用的,在Started Service
型別中,此函式直接返回 null
即可。onCreate(...)、onStartCommand(...)
和onDestroy()
都是Started Service
相應生命週期階段的回撥函式。
2) Started Service使用
public class MainActivity extends Activity {
public static final String TAG = "MainActivity";
private Button startServiceBtn;
private Button stopServideBtn;
private Button goBtn;
private Intent serviceIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startServiceBtn = (Button) findViewById(R.id.start_service);
stopServideBtn = (Button) findViewById(R.id.stop_service);
goBtn = (Button) findViewById(R.id.go);
startServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
serviceIntent = new Intent(MainActivity.this, MyService.class);
startService(serviceIntent);
}
});
stopServideBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopService(serviceIntent);
}
});
goBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, BActivity.class);
startActivity(intent);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.w(TAG, "in onDestroy");
}
}
如上程式碼片段,
當Client
呼叫startService(Intent serviceIntent)
後,如果MyService
是第一次啟動,首先會執行 onCreate()
回撥,然後再執行onStartCommand(Intent intent, int flags, int startId)
,當Client再次呼叫startService(Intent serviceIntent)
,將只執行onStartCommand(Intent intent, int flags, int startId)
,因為此時Service已經建立了,無需執行onCreate()
回撥。無論多少次的startService
,只需要一次stopService()
即可將此Service終止,執行onDestroy()
函式(其實很好理解,因為onDestroy()
與onCreate()
回撥是相對的)。
下面重點關注下onStartCommand(Intent intent, int flags, int startId)
方法。
其中引數flags預設情況下是0,對應的常量名為START_STICKY_COMPATIBILITY
。startId是一個唯一的整型,用於表示此次Client執行startService(…)的請求請求標識,在多次startService(…)的情況下,呈現0,1,2…遞增。另外,此函式具有一個int型的返回值,具體的可選值及含義如下:
- START_NOT_STICKY:當Service因為記憶體不足而被系統kill後,接下來未來的某個時間內,即使系統記憶體足夠可用,系統也不會嘗試重新建立此Service。除非程式中Client明確再次呼叫startService(…)啟動此Service。
- START_STICKY:當Service因為記憶體不足而被系統kill後,接下來未來的某個時間內,當系統記憶體足夠可用的情況下,系統將會嘗試重新建立此Service,一旦建立成功後將回撥onStartCommand(…)方法,但其中的Intent將是null,pendingintent除外。
- START_REDELIVER_INTENT:與START_STICKY唯一不同的是,回撥onStartCommand(…)方法時,其中的Intent將是非空,將是最後一次呼叫startService(…)中的intent。
- START_STICKY_COMPATIBILITY:compatibility version of {@link #START_STICKY} that does not guarantee that {@link #onStartCommand} will be called again after being killed。此值一般不會使用,所以注意前面三種情形就好。
以上的描述中,”當Service因為記憶體不足而被系統kill後“一定要非常注意,因為此函式的返回值設定只是針對此種情況才有意義的,換言之,當認為的kill掉Service程式,此函式返回值無論怎麼設定,接下來未來的某個時間內,即使系統記憶體足夠可用,Service也不會重啟。
小米手機針對此處做了變更:
另外,需要注意的是,小米手機針對此處做了一定的修改。在“自啟動管理”中有一個自啟動應用列表,預設情況下,只有少應用(如微信、QQ、YY、360等)預設是可以自啟動的,其他應用預設都是禁止的。使用者可以手動新增自啟動應用,新增後的應用中如果Started Service onStartCommand(…)回撥返回值是START_STICKY或START_REDELIVER_INTENT,當使用者在小米手機上長按Home鍵結束App後,接下來未來的某個時間內,當系統記憶體足夠可用時,Service依然可以按照上述規定重啟。當然,如果使用者在 設定 >> 應用 >> 強制kill掉App程式,此時Service是不會重啟的。
注:以上實驗結論基於小米2S親測。
3) Started Service生命週期及程式相關
1.onCreate(Client首次startService(…)) >> onStartCommand >> onStartCommand - optional … >> onDestroy(Client呼叫stopService(…))
注:onStartCommand(…)可以多次被呼叫,onDestroy()
與onCreate()
想匹配,當使用者強制kill掉程式時,onDestroy()
是不會執行的。
2.對於同一型別的Service,Service例項一次永遠只存在一個,而不管Client是否是相同的元件,也不管Client是否處於相同的程式中。
3.Service通過startService(…)啟動Service後,此時Service的生命週期與Client本身的什麼週期是沒有任何關係的,只有Client呼叫stopService(…)或Service本身呼叫stopSelf(…)才能停止此Service。當然,當使用者強制kill掉Service程式或系統因記憶體不足也可能kill掉此Service。
4.Client A 通過startService(…)啟動Service後,可以在其他Client(如Client B、Client C)通過呼叫stopService(…)結束此Service。
5.Client呼叫stopService(…)時,如果當前Service沒有啟動,也不會出現任何報錯或問題,也就是說,stopService(…)無需做當前Service是否有效的判斷。
6.startService(Intent serviceIntent),其中的intent既可以是顯式Intent,也可以是隱式Intent,當Client與Service同處於一個App時,一般推薦使用顯示Intent。當處於不同App時,只能使用隱式Intent。
當Service需要執行在單獨的程式中,AndroidManifest.xml宣告時需要通過android:process指明此程式名稱,當此Service需要對其他App開放時,android:exported屬性值需要設定為true(當然,在有intent-filter時預設值就是true)。
<service
android:name=".MyService"
android:exported="true"
android:process=":MyCorn" >
<intent-filter>
<action android:name="com.example.androidtest.myservice" />
</intent-filter>
</service>
4)Started Service Client
與Service
通訊相關
當Client
呼叫startService(Intent serviceIntent)
啟動Service
時,Client
可以將引數通過Intent
直接傳遞給Service
。Service
執行過程中,如果需要將引數傳遞給Client
,一般可以通過藉助於傳送廣播的方式(此時,Client
需要註冊此廣播)。
3.Bound Service
相對於Started Service
,Bound Service
具有更多的知識點。Bound Service
的主要特性在於Service
的生命週期是依附於Client
的生命週期的,當Client不存在時,Bound Service
將執行onDestroy
,同時通過Service
中的Binder
物件可以較為方便進行Client-Service通訊。Bound Service一般使用過程如下:
1.自定義Service繼承基類Service,並重寫onBind(Intent intent)方法,此方法中需要返回具體的Binder物件;
2.Client通過實現ServiceConnection介面來自定義ServiceConnection,並通過bindService (Intent service, ServiceConnection sc, int flags)方法將Service繫結到此Client上;
3.自定義的ServiceConnection中實現onServiceConnected(ComponentName name, IBinder binder)方法,獲取Service端Binder例項;
4.通過獲取的Binder例項進行Service端其他公共方法的呼叫,以完成Client-Service通訊;
5.當Client在恰當的生命週期(如onDestroy等)時,此時需要解綁之前已經繫結的Service,通過呼叫函式unbindService(ServiceConnection sc)。
在Bound Service具體使用過程中,根據onBind(Intent intent)方法放回的Binder物件的定義方式不同,又可以將其分為以下三種方式,且每種方式具有不同的特點和適用場景:
1).Extending the Binder class
這是Bound Service中最常見的一種使用方式,也是Bound Service中最簡單的一種。
侷限:Clinet與Service必須同屬於同一個程式,不能實現程式間通訊(IPC)。否則則會出現類似於“android.os.BinderProxy cannot be cast to xxx”錯誤。
下面通過程式碼片段看下具體的使用:
public class MyBindService extends Service {
public static final String TAG = "MyBindService";
private MyBinder mBinder = new MyBinder();
public class MyBinder extends Binder {
MyBindService getService() {
return MyBindService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
Log.w(TAG, "in onCreate");
}
@Override
public IBinder onBind(Intent intent) {
Log.w(TAG, "in onBind");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.w(TAG, "in onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.w(TAG, "in onDestroy");
}
}
public class BActivity extends Activity {
public static final String TAG = "BActivity";
private Button bindServiceBtn;
private Button unbindServiceBtn;
private Button startIntentService;
private Intent serviceIntent;
private ServiceConnection sc = new MyServiceConnection();
private MyBinder mBinder;
private MyBindService mBindService;
private boolean mBound;
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.w(TAG, "in MyServiceConnection onServiceConnected");
mBinder = (MyBinder) binder;
mBindService = mBinder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
Log.w(TAG, "in MyServiceConnection onServiceDisconnected");
mBound = false;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.b);
bindServiceBtn = (Button) findViewById(R.id.bind_service);
unbindServiceBtn = (Button) findViewById(R.id.unbind_service);
startIntentService = (Button) findViewById(R.id.start_intentservice);
bindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(BActivity.this, MyBindService.class);
bindService(intent, sc, Context.BIND_AUTO_CREATE);
}
});
unbindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
excuteUnbindService();
}
});
startIntentService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(BActivity.this, MyIntentService.class);
startService(intent);
}
});
}
private void excuteUnbindService() {
if (mBound) {
unbindService(sc);
mBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.w(TAG, "in onDestroy");
excuteUnbindService();
}
}
首次點選bindServiceBtn
進行bindService(..)
時,依次回撥順序如下:
MyBindService(13457): in onCreate
MyBindService(13457): in onBind
BActivity(13457): in MyServiceConnection onServiceConnected
再次點選bindServiceBtn
按鈕時,發現沒有任何輸出,說明MyBindService
沒有進行任何回撥。
點選unbindServiceBtn
進行unbindService(..)
時,回撥順序為:
MyBindService(13457): in onUnbind
MyBindService(13457): in onDestroy
注:在四大基本元件中,需要注意的的是BroadcastReceiver
不能作為Bound Service
的Client
,因為BroadcastReceiver
的生命週期很短,當執行完onReceive(..)
回撥時,BroadcastReceiver
生命週期完結。而Bound Service
又與Client
本身的生命週期相關,因此,Android中不允許BroadcastReceiver
去bindService(..)
,當有此類需求時,可以考慮通過startService(..)
替代。
2)Using a Messenger
Messenger
,在此可以理解成”信使“,通過Messenger
方式返回Binder物件可以不用考慮Clinet - Service
是否屬於同一個程式的問題,並且,可以實現Client - Service
之間的雙向通訊。極大方便了此類業務需求的實現。
侷限:不支援嚴格意義上的多執行緒併發處理,實際上是以佇列去處理
下面直接看下具體的使用:
public class MyMessengerService extends Service {
public static final String TAG = "MyMessengerService";
public static final int MSG_FROM_CLIENT_TO_SERVER = 1;
public static final int MSG_FROM_SERVER_TO_CLIENT = 2;
private Messenger mClientMessenger;
private Messenger mServerMessenger = new Messenger(new ServerHandler());
@Override
public IBinder onBind(Intent intent) {
Log.w(TAG, "in onBind");
return mServerMessenger.getBinder();
}
class ServerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
Log.w(TAG, "thread name:" + Thread.currentThread().getName());
switch (msg.what) {
case MSG_FROM_CLIENT_TO_SERVER:
Log.w(TAG, "receive msg from client");
mClientMessenger = msg.replyTo;
// service傳送訊息給client
Message toClientMsg = Message.obtain(null, MSG_FROM_SERVER_TO_CLIENT);
try {
Log.w(TAG, "server begin send msg to client");
mClientMessenger.send(toClientMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
@Override
public boolean onUnbind(Intent intent) {
Log.w(TAG, "in onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.w(TAG, "in onDestroy");
super.onDestroy();
}
}
public class CActivity extends Activity {
public static final String TAG = "CActivity";
private Button bindServiceBtn;
private Button unbindServiceBtn;
private Button sendMsgToServerBtn;
private ServiceConnection sc = new MyServiceConnection();
private boolean mBound;
private Messenger mServerMessenger;
private Handler mClientHandler = new MyClientHandler();
private Messenger mClientMessenger = new Messenger(mClientHandler);
private class MyClientHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == MyMessengerService.MSG_FROM_SERVER_TO_CLIENT) {
Log.w(TAG, "reveive msg from server");
}
}
}
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.w(TAG, "in MyServiceConnection onServiceConnected");
mServerMessenger = new Messenger(binder);
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
Log.w(TAG, "in MyServiceConnection onServiceDisconnected");
mBound = false;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.c);
bindServiceBtn = (Button) findViewById(R.id.bind_service);
unbindServiceBtn = (Button) findViewById(R.id.unbind_service);
sendMsgToServerBtn = (Button) findViewById(R.id.send_msg_to_server);
bindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(CActivity.this, MyMessengerService.class);
bindService(intent, sc, Context.BIND_AUTO_CREATE);
}
});
unbindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
excuteUnbindService();
}
});
sendMsgToServerBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sayHello();
}
});
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(CActivity.this, MyAlarmBroadcastReceiver.class);
sendBroadcast(intent);
}
}, 3 * 1000);
}
public void sayHello() {
if (!mBound)
return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message.obtain(null, MyMessengerService.MSG_FROM_CLIENT_TO_SERVER, 0, 0);
// 通過replyTo把client端的Messenger(信使)傳遞給service
msg.replyTo = mClientMessenger;
try {
mServerMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void excuteUnbindService() {
if (mBound) {
unbindService(sc);
mBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.w(TAG, "in onDestroy");
excuteUnbindService();
}
}
其中,需要注意的幾點是:
1.MyMessengerService
自定中,通過new Messenger(new ServerHandler())
建立Messenger
物件,在onBind(..)
回撥中,通過呼叫Messenger
物件的getBinder()
方法,將Binder
返回;
2.Client在ServiceConnection
的onServiceConnected(..)
的回撥中,通過new Messenger(binder)
獲取到Service
傳遞過來的mServerMessenger
;
3.接下來,就可以通過mServerMessenger.send(msg)
方法向Service
傳送message
,Service
中的Messenger
構造器中的Handler
即可接收到此資訊,在handleMessage(..)
回撥中處理;
4.至此只是完成了從Client傳送訊息到Service,同樣的道理,想實現Service傳送訊息到Client,可以在客戶端定義一個Handler,並得到相應的Messenger,在Clinet傳送訊息給Service時,通過msg.replyTo = mClientMessenger方式將Client信使傳遞給Service;
5.Service接收到Client信使後,獲取此信使,並通過mClientMessenger.send(toClientMsg)方式將Service訊息傳送給Client。
至此,完成了Client - Service之間的雙向通訊流程。
3).AIDL(Android Interface Definition Language)
一般情況下,Messenger這種方式都是可以滿足需求的,當然,通過自定義AIDL方式相對更加靈活。
這種方式需要自己在專案中自定義xxx.aidl檔案,然後系統會自動在gen目錄下生成相應的介面類檔案,接下來整個的流程與Messenger
方式差別不大,網上也有不少例項,在此不再具體給出。
注:無論哪種方式的Bound Service,在進行unbind(…)操作時,都需要注意當前Service是否處於已經繫結狀態,否則可能會因為當前Service已經解綁後繼續執行unbind(…)會導致崩潰。這點與Started Service區別很大(如前文所述:stopService(…)無需做當前Service是否有效的判斷)。
4.Local Service VS Remote Service
Local Service:不少人又稱之為”本地服務“,是指Client - Service同處於一個程式;
Remote Service:又稱之為”遠端服務“,一般是指Service處於單獨的一個程式中。
其他使用上上文中基本上都有所述。
5.Service特性
1.Service本身都是執行在其所在程式的主執行緒(如果Service與Clinet同屬於一個程式,則是執行於UI執行緒),但Service一般都是需要進行”長期“操作,所以經常寫法是在自定義Service中處理”長期“操作時需要新建執行緒,以免阻塞UI執行緒或導致ANR;
2.Service一旦建立,需要停止時都需要顯示呼叫相應的方法(Started Service需要呼叫stopService(…)或Service本身呼叫stopSelf(…), Bound Service需要呼叫unbindService(…)),否則對於Started Service將處於一直執行狀態,對於Bound Service,當Client生命週期結束時也將因此問題。也就是說,Service執行完畢後,必須人為的去停止它。
6.IntentService
IntentService是系統提供給我們的一個已經繼承自Service類的特殊類,IntentService特殊性是相對於Service本身的特性而言的:
1.預設直接實現了onBind(..)
方法,直接返回null
,並定義了抽象方法onHandlerIntent(..)
,使用者自定義子類時,需要實現此方法;
2.onHandlerIntent(..)
主要就是用來處於相應的”長期“任務的,並且已經自動在新的執行緒中,使用者無語自定義新執行緒;
3.當”長期“任務執行完畢後(也就是onHandlerIntent(..)
執行完畢後),此IntentService
將自動結束,無需人為呼叫方法使其結束;
4.IntentService
處於任務時,也是按照佇列的方式一個個去處理,而非真正意義上的多執行緒併發方式。
下面是一個基本的繼承自IntentService
的自定義Service
:
public class MyIntentService extends IntentService {
public static final String TAG = "MyIntentService";
public MyIntentService() {
super(TAG);
}
public MyIntentService(String name) {
super(name);
}
@Override
protected void onHandleIntent(Intent intent) {
Log.w(TAG, "in onHandleIntent");
Log.w(TAG, "thread name:" + Thread.currentThread().getName());
}
}
7.前臺Service
Android中Service介面中還提供了一個稱之為”前臺Service“的概念。通過Service.startForeground (int id, Notification notification)
方法可以將此Service設定為前臺Service。在UI顯示上,notification
將是一個處於onGoing
狀態的通知,使得前臺Service
擁有更高的程式優先順序,並且Service
可以直接notification
通訊。
下面是一個簡單的前臺Service使用例項:
public class MyService extends Service {
public static final String TAG = "MyService";
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.w(TAG, "in onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.w(TAG, "in onStartCommand");
Log.w(TAG, "MyService:" + this);
String name = intent.getStringExtra("name");
Log.w(TAG, "name:" + name);
Notification notification = new Notification(R.drawable.ic_launcher, "test", System.currentTimeMillis());
Intent notificationIntent = new Intent(this, DActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntesnt, 0);
notification.setLatestEventInfo(this, "title", "content", pendingIntent);
startForeground(1, notification);
return START_REDELIVER_INTENT;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.w(TAG, "in onDestroy");
}
}
相關文章
- Android中Service總結Android
- Android Service和IntentService知識點詳細總結AndroidIntent
- Android ServiceAndroid
- Android 系列教程 彙總Android
- Android四大元件之Service篇Android元件
- android webview總結AndroidWebView
- Android Intent ServiceAndroidIntent
- Android Service SecurityAndroid
- Android面試總結Android面試
- Android之Service設定android:process作用Android
- Android Service完全解析Android
- 看得見的資料結構Android版之開篇前言+完篇總結資料結構Android
- redis 系列:總結篇Redis
- Android面試最新總結Android面試
- Android之SpannableString、SpannableStringBuilder總結AndroidUI
- Android之主題(Theme)總結Android
- Android鍵盤操作總結Android
- android IO流操作總結Android
- Android Handler面試總結Android面試
- Android Service詳解(一)Android
- Android Service詳解(二)Android
- Android中的Web ServiceAndroidWeb
- Android 之Service使用攻略Android
- Android-Service詳解Android
- Android開發經驗總結Android
- Android Sensor原始碼分析總結Android原始碼
- Android TV開發總結【RecycleView】AndroidView
- 20180521-Android-面試總結Android面試
- Android:巢狀滑動總結Android巢狀
- android混淆總結(基礎版)Android
- 面試題總結-Android部分面試題Android
- Android聲音相關總結Android
- Android 效能監控系列一(原理篇)Android
- Android必知必會的四大元件 -- Service篇Android元件
- Android Service最全面的解析Android
- Android 劉海屏適配總結Android
- 在 Android 中使用 JNI 的總結Android
- Android 日常開發問題總結Android