編輯:關於Android編程
基於Android 6.0的源碼剖析, 分析android Service啟動流程中ActivityManagerService所扮演的角色
/frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
/frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java
/frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
/frameworks/base/core/java/android/app/IActivityManager.java
/frameworks/base/core/java/android/app/ActivityManagerNative.java (內含ActivityManagerProxy類)
/frameworks/base/core/java/android/app/ActivityManager.java
/frameworks/base/core/java/android/app/IApplicationThread.java
/frameworks/base/core/java/android/app/ApplicationThreadNative.java (內含ApplicationThreadProxy類)
/frameworks/base/core/java/android/app/ActivityThread.java (內含ApplicationThread類)
/frameworks/base/core/java/android/app/ContextImpl.java
看過前面介紹Binder系列文章,相信對Binder架構有了較深地理解。在Android系統啟動-開篇中講述了Binder的地位是非常之重要,整個Java framework的提供ActivityManagerService、PackageManagerService等服務都是基於Binder架構來通信的,另外 handle消息機制在進程內的通信使用非常多。本文將開啟對ActivityManagerService的分析。
ActivityManagerService是Android的Java framework的服務框架最重要的服務之一。對於Andorid的Activity、Service、Broadcast、ContentProvider四劍客的管理,包含其生命周期都是通過ActivityManagerService來完成的。對於這四劍客的介紹,此處先略過,後續博主會針對這4劍客分別闡述。
下面先看看ActivityManagerService相關的類圖:
單單就一個ActivityManagerService.java文件就代碼超過2萬行,我們需要需要一個線,再結合binder的知識,來把我們想要了解的東西串起來,那麼本文將從App啟動的視角來分析ActivityManagerService。
在app中啟動一個service,就一行語句搞定,
startService(); //或 binderService()
該過程如下:
當App通過調用Android API方法startService()或binderService()來生成並啟動服務的過程,主要是由ActivityManagerService來完成的。
啟動服務的流程圖:
點擊查看大圖
圖中涉及的首字母縮寫:
接下來,我們正式從代碼角度來分析服務啟動的過程。首先在我們應用程序的Activity類的調用startService()方法,該方法調用【流程1】的方法。
[-> ContextWrapper.java]
public class ContextWrapper extends Context {
@Override
public ComponentName startService(Intent service) {
return mBase.startService(service); //其中mBase為ContextImpl對象 【見流程2】
}
}
[-> ContextImpl.java]
class ContextImpl extends Context {
@Override
public ComponentName startService(Intent service) {
//當system進程調用此方法時輸出warn信息,system進程建立調用startServiceAsUser方法
warnIfCallingFromSystemProcess();
return startServiceCommon(service, mUser); //【見流程3】
}
[-> ContextImpl.java]
private ComponentName startServiceCommon(Intent service, UserHandle user) {
try {
//檢驗service,當service為空則throw異常
validateServiceIntent(service);
service.prepareToLeaveProcess();
// 調用ActivityManagerNative類 【見流程3.1 以及流程4】
ComponentName cn = ActivityManagerNative.getDefault().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver()), getOpPackageName(), user.getIdentifier());
if (cn != null) {
if (cn.getPackageName().equals("!")) {
throw new SecurityException("Not allowed to start service " +
service + " without permission " + cn.getClassName());
} else if (cn.getPackageName().equals("!!")) {
throw new SecurityException("Unable to start service " +
service ": " + cn.getClassName());
}
}
return cn;
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
}
[-> ActivityManagerNative.java]
static public IActivityManager getDefault() {
return gDefault.get();
}
gDefault為Singleton類型對象,此次采用單例模式,mInstance為IActivityManager類的代理對象,即ActivityManagerProxy。
public abstract class Singleton<T> {
public final T get() {
synchronized (this) {
if (mInstance == null) {
//首次調用create()來獲取AMP對象
mInstance = create();
}
return mInstance;
}
}
}
再來看看create()的過程:
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
//獲取名為"activity"的服務,服務都注冊到ServiceManager來統一管理
IBinder b = ServiceManager.getService("activity");
IActivityManager am = asInterface(b);
return am;
}
};
該方法返回的是ActivityManagerProxy對象,那麼下一步調用ActivityManagerProxy.startService()方法。
通過Binder通信過程中,提供了一個IActivityManager服務接口,ActivityManagerProxy類與ActivityManagerService類都實現了IActivityManager接口。ActivityManagerProxy作為binder通信的客戶端,ActivityManagerService作為binder通信的服務端,根據Binder系列文章,ActivityManagerProxy.startService()最終調用ActivityManagerService.startService(),整個流程圖如下:
該類位於文件ActivityManagerNative.java
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, String callingPackage, int userId) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
service.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeString(callingPackage);
data.writeInt(userId);
//通過Binder 傳遞數據 【見流程5】
mRemote.transact(START_SERVICE_TRANSACTION, data, reply, 0);
reply.readException();
ComponentName res = ComponentName.readFromParcel(reply);
data.recycle();
reply.recycle();
return res;
}
mRemote.transact()是binder通信的客戶端發起方法,經過binder驅動,最後回到binder服務端ActivityManagerNative的onTransact()方法。
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
...
case START_SERVICE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
//生成ApplicationThreadNative的代理對象,即ApplicationThreadProxy對象
IApplicationThread app = ApplicationThreadNative.asInterface(b);
Intent service = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
String callingPackage = data.readString();
int userId = data.readInt();
//調用ActivityManagerService的startService()方法【見流程6】
ComponentName cn = startService(app, service, resolvedType, callingPackage, userId);
reply.writeNoException();
ComponentName.writeToParcel(cn, reply);
return true;
}
}
在整個調用過程涉及兩個進程,不妨令startService的發起進程記為進程A,ServiceManagerService記為進程B;那麼進程A通過Binder機制(采用IActivityManager接口)向進程B發起請求服務,進程B則通過Binder機制(采用IApplicationThread接口)向進程A發起請求服務。也就是說進程A與進程B能相互間主動發起請求,進程通信。
這裡涉及IApplicationThread,那麼下面直接把其相關的類圖展示如下:
與IActivityManager的binder通信原理一樣,ApplicationThreadProxy
作為binder通信的客戶端,ApplicationThreadNative
作為Binder通信的服務端,其中ApplicationThread
繼承ApplicationThreadNative類,覆寫其中的部分方法。
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, String callingPackage, int userId)
throws TransactionTooLargeException {
//當調用者是孤立進程,則拋出異常。
enforceNotIsolatedCaller("startService");
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
if (callingPackage == null) {
throw new IllegalArgumentException("callingPackage cannot be null");
}
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"startService: " + service + " type=" + resolvedType);
synchronized(this) {
final int callingPid = Binder.getCallingPid(); //調用者pid
final int callingUid = Binder.getCallingUid(); //調用者uid
final long origId = Binder.clearCallingIdentity();
//此次的mServices為ActiveServices對象 【見流程7】
ComponentName res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid, callingPackage, userId);
Binder.restoreCallingIdentity(origId);
return res;
}
}
該方法參數說明:
[-> ActiveServices.java]
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
int callingPid, int callingUid, String callingPackage, int userId)
throws TransactionTooLargeException {
final boolean callerFg;
if (caller != null) {
final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
if (callerApp == null)
throw new SecurityException(""); //拋出異常,此處省略異常字符串
callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
} else {
callerFg = true;
}
//檢索服務信息
ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, callingPackage,
callingPid, callingUid, userId, true, callerFg);
if (res == null) {
return null;
}
if (res.record == null) {
return new ComponentName("!", res.permission != null
? res.permission : "private to package");
}
ServiceRecord r = res.record;
if (!mAm.getUserManagerLocked().exists(r.userId)) { //檢查是否存在啟動服務的user
return null;
}
NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked(
callingUid, r.packageName, service, service.getFlags(), null, r.userId);
r.lastActivity = SystemClock.uptimeMillis();
r.startRequested = true;
r.delayedStop = false;
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
service, neededGrants));
final ServiceMap smap = getServiceMap(r.userId);
boolean addToStarting = false;
//對於非前台進程的調度
if (!callerFg && r.app == null && mAm.mStartedUsers.get(r.userId) != null) {
ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
if (proc == null || proc.curProcState > ActivityManager.PROCESS_STATE_RECEIVER) {
if (r.delayed) { //已計劃延遲啟動
return r.name;
}
if (smap.mStartingBackground.size() >= mMaxStartingBackground) {
//當超出 同一時間允許後續啟動的最大服務數,則將該服務加入延遲啟動的隊列。
smap.mDelayedStartList.add(r);
r.delayed = true;
return r.name;
}
addToStarting = true;
} else if (proc.curProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
//將新的服務加入到後台啟動隊列,該隊列也包含當前正在運行其他services或者receivers的進程
addToStarting = true;
}
}
//【見流程8】
return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}
[-> ActiveServices.java]
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
ProcessStats.ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
}
r.callStart = false;
synchronized (r.stats.getBatteryStats()) {
r.stats.startRunningLocked(); //用於耗電統計,開啟運行的狀態
}
//【見流程9】
String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
if (error != null) {
return new ComponentName("!!", error);
}
if (r.startRequested && addToStarting) {
boolean first = smap.mStartingBackground.size() == 0;
smap.mStartingBackground.add(r);
r.startingBgTimeout = SystemClock.uptimeMillis() + BG_START_TIMEOUT;
if (first) {
smap.rescheduleDelayedStarts();
}
} else if (callerFg) {
smap.ensureNotStartingBackground(r);
}
return r.name;
}
[-> ActiveServices.java]
private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean whileRestarting) throws TransactionTooLargeException {
if (r.app != null && r.app.thread != null) {
sendServiceArgsLocked(r, execInFg, false);
return null;
}
if (!whileRestarting && r.restartDelay > 0) {
return null; //等待延遲重啟的過程,則直接返回
}
// 啟動service前,把service從重啟服務隊列中移除
if (mRestartingServices.remove(r)) {
r.resetRestartCounter();
clearRestartingIfNeededLocked(r);
}
//service正在啟動,將delayed設置為false
if (r.delayed) {
getServiceMap(r.userId).mDelayedStartList.remove(r);
r.delayed = false;
}
//確保擁有該服務的user已經啟動,否則停止;
if (mAm.mStartedUsers.get(r.userId) == null) {
String msg = "";
bringDownServiceLocked(r);
return msg;
}
//服務正在啟動,設置package停止狀態為false
AppGlobals.getPackageManager().setPackageStoppedState(
r.packageName, false, r.userId);
final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
final String procName = r.processName;
ProcessRecord app;
if (!isolated) {
//根據進程名和uid,查詢ProcessRecord
app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
if (app != null && app.thread != null) {
try {
app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
// 啟動服務 【見流程12】
realStartServiceLocked(r, app, execInFg);
return null;
} catch (TransactionTooLargeException e) {
throw e;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting service " + r.shortName, e);
}
}
} else {
app = r.isolatedProc;
}
//對於進程沒有啟動的情況
if (app == null) {
//啟動service所要運行的進程 【見流程10】
if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
"service", r.name, false, isolated, false)) == null) {
String msg = ""
bringDownServiceLocked(r); // 進程啟動失敗
return msg;
}
if (isolated) {
r.isolatedProc = app;
}
}
if (!mPendingServices.contains(r)) {
mPendingServices.add(r);
}
if (r.delayedStop) {
r.delayedStop = false;
if (r.startRequested) {
stopServiceLocked(r); //停止服務
}
}
return null;
}
對於非前台進程調用而需要啟動的服務,如果已經有其他的後台服務正在啟動中,那麼我們可能希望延遲其啟動。這是用來避免啟動同時啟動過多的進程(非必須的)。
關於startProcessLocked的過程, 詳見 理解Android進程啟動之全過程,經過層層調用最後會調用到AMS.attachApplicationLocked過程.
[-> ActivityManagerService.java]
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
...
thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked());
...
if (!badApp) {
try {
//尋找所有需要在該進程中運行的服務 【見流程11】
didSomething |= mServices.attachApplicationLocked(app, processName);
} catch (Exception e) {
badApp = true;
}
}
...
return true;
}
[-> ActiveServices.java]
boolean attachApplicationLocked(ProcessRecord proc, String processName)
throws RemoteException {
boolean didSomething = false;
//啟動mPendingServices隊列中,等待在該進程啟動的服務
if (mPendingServices.size() > 0) {
ServiceRecord sr = null;
try {
for (int i=0; i<mPendingServices.size(); i++) {
sr = mPendingServices.get(i);
if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid
|| !processName.equals(sr.processName))) {
continue;
}
mPendingServices.remove(i);
i--;
// 將當前服務的包信息加入到proc
proc.addPackage(sr.appInfo.packageName, sr.appInfo.versionCode,
mAm.mProcessStats);
// 啟動服務,即將進入服務的生命周期 【見流程10】
realStartServiceLocked(sr, proc, sr.createdFromFg);
didSomething = true;
}
} catch (RemoteException e) {
Slog.w(TAG, "Exception in new application when starting service "
+ sr.shortName, e);
throw e;
}
}
// 對於正在等待重啟並需要運行在該進程的服務,現在是啟動它們的大好時機
if (mRestartingServices.size() > 0) {
ServiceRecord sr = null;
for (int i=0; i<mRestartingServices.size(); i++) {
sr = mRestartingServices.get(i);
if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid
|| !processName.equals(sr.processName))) {
continue;
}
mAm.mHandler.removeCallbacks(sr.restarter);
mAm.mHandler.post(sr.restarter);
}
}
return didSomething;
}
[-> ActiveServices.java]
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
if (app.thread == null) {
throw new RemoteException();
}
r.app = app;
r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
final boolean newService = app.services.add(r);
//發送delay消息(SERVICE_TIMEOUT_MSG)
bumpServiceExecutingLocked(r, execInFg, "create");
mAm.updateLruProcessLocked(app, false, null);
mAm.updateOomAdjLocked();
boolean created = false;
try {
synchronized (r.stats.getBatteryStats()) {
r.stats.startLaunchedLocked();
}
mAm.ensurePackageDexOpt(r.serviceInfo.packageName);
app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
//服務進入 onCreate() 【見流程13】
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
r.postNotification();
created = true;
} catch (DeadObjectException e) {
mAm.appDiedLocked(app); //應用死亡處理
throw e;
} finally {
if (!created) {
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying);
if (newService) {
app.services.remove(r);
r.app = null;
}
//嘗試重新啟動服務
if (!inDestroying) {
scheduleServiceRestartLocked(r, false);
}
}
}
requestServiceBindingsLocked(r, execInFg);
updateServiceClientActivitiesLocked(app, null, true);
if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
null, null));
}
//服務 進入onStartCommand() 【見流程17】
sendServiceArgsLocked(r, execInFg, true);
if (r.delayed) {
getServiceMap(r.userId).mDelayedStartList.remove(r);
r.delayed = false;
}
if (r.delayedStop) {
r.delayedStop = false;
if (r.startRequested) {
stopServiceLocked(r); //停止服務
}
}
}
在bumpServiceExecutingLocked會發送一個延遲處理的消息SERVICE_TIMEOUT_MSG。在方法scheduleCreateService執行完成,也就是onCreate回調執行完成之後,便會remove掉該消息。但是如果沒能在延時時間之內remove該消息,則會進入執行service timeout流程。
[-> ApplicationThreadProxy.java]
public final void scheduleCreateService(IBinder token, ServiceInfo info,
CompatibilityInfo compatInfo, int processState) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
info.writeToParcel(data, 0);
compatInfo.writeToParcel(data, 0);
data.writeInt(processState);
try {
//【見流程14】
mRemote.transact(SCHEDULE_CREATE_SERVICE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
} catch (TransactionTooLargeException e) {
throw e;
}
data.recycle();
}
[-> ApplicationThreadNative.java]
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
case SCHEDULE_CREATE_SERVICE_TRANSACTION: {
data.enforceInterface(IApplicationThread.descriptor);
IBinder token = data.readStrongBinder();
ServiceInfo info = ServiceInfo.CREATOR.createFromParcel(data);
CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
int processState = data.readInt();
// 【見流程15】
scheduleCreateService(token, info, compatInfo, processState);
return true;
}
}
[-> ApplicationThread.java]
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData(); //准備服務創建所需的數據
s.token = token;
s.info = info;
s.compatInfo = compatInfo;
//發送消息 【見流程16】
sendMessage(H.CREATE_SERVICE, s);
}
該方法的執行在ActivityThread線程
[-> ActivityThread.java]
public void handleMessage(Message msg) {
switch (msg.what) {
...
case CREATE_SERVICE:
handleCreateService((CreateServiceData)msg.obj); //【見流程17】
break;
case BIND_SERVICE:
handleBindService((BindServiceData)msg.obj);
break;
case UNBIND_SERVICE:
handleUnbindService((BindServiceData)msg.obj);
break;
case SERVICE_ARGS:
handleServiceArgs((ServiceArgsData)msg.obj); // serviceStart
break;
case STOP_SERVICE:
handleStopService((IBinder)msg.obj);
maybeSnapshot();
break;
}
}
[-> ActivityThread.java]
private void handleCreateService(CreateServiceData data) {
//當應用處於後台即將進行GC,而此時被調回到活動狀態,則跳過本次gc。
unscheduleGcIdler();
//生成服務對象
LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
//
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to instantiate service " + data.info.name + ": " + e.toString(), e);
}
}
try {
//創建ContextImpl對象
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
service.onCreate(); //調用服務的 onCreate()方法 【見流程18】
mServices.put(data.token, service);
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
// nothing to do.
}
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to create service " + data.info.name
+ ": " + e.toString(), e);
}
}
}
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
public void onCreate(){ }
}
最終調用到抽象類Service.onCreate()方法,對於真正的Service都會通過覆寫該方式,調用真正Service子類的onCreate()方法。撥雲見日,到此總算是進入了Service的生命周期。
在整個startService過程,從進程角度看服務啟動過程
init
進程孵化而來的,用於創建Java層進程的母體,所有的Java層進程都是由Zygote進程孵化而來;圖中涉及3種IPC通信方式:Binder
、Socket
以及Handler
,在圖中分別用3種不同的顏色來代表這3種通信方式。一般來說,同一進程內的線程間通信采用的是 Handler消息隊列機制,不同進程間的通信采用的是binder機制,另外與Zygote進程通信采用的Socket
。
啟動流程:
到此,服務便正式啟動完成。當創建的是本地服務時,無需經過上述步驟2、3,直接創建服務即可。
android 在網絡上下載文件步驟 : 1.使用HTTP協議下載文件- 創建一個HttpURLConnection對象 : HttpURLConnection urlC
1. Handler + Thread 異步執行任務在UI線程中開啟子線程,使用Handler 發消息,通知主線程更新UI直接在UI線程中開啟子線程來更新TextView
一.信息發送:com.android.mms.data.WorkingMessage.java 類 send()函數: public void send() {
RatingBar簡單介紹RatingBar是基於SeekBar(拖動條)和ProgressBar(狀態條)的擴展,用星形來顯示等級評定,在使用默認RatingBar時,