詳解Android壁紙服務(wù)的啟動過程
壁紙基礎(chǔ)
android中的壁紙分為動態(tài)壁紙和靜態(tài)壁紙兩種,兩種類型的壁紙都以Service的類型運(yùn)行在系統(tǒng)后臺。
- 靜態(tài)壁紙:僅以圖片的形式進(jìn)行展示對于靜態(tài)壁紙,可以使用WallpaperManager中的getDrawable()等接口獲取到當(dāng)前的bitmap圖像。
- 動態(tài)壁紙:顯示的內(nèi)容為動態(tài)的內(nèi)容,同時(shí)可以對用戶的操作做出響應(yīng)對于動態(tài)壁紙的實(shí)時(shí)圖像,是沒辦法通過android中原生的接口獲取到,需要獲取到動態(tài)壁紙的圖像得自己修改源碼。
壁紙實(shí)現(xiàn)時(shí)涉及的幾個(gè)主要的類:
- WallpaperService及其內(nèi)部類Engine:壁紙?jiān)赪allpaperService這個(gè)服務(wù)中運(yùn)行,當(dāng)需要實(shí)現(xiàn)自己的壁紙時(shí),繼承和實(shí)現(xiàn)這個(gè)類,是首先需要做的。Engine是WallpaperService中的一個(gè)內(nèi)部類,實(shí)現(xiàn)了壁紙服務(wù)窗口的創(chuàng)建以及Surface的維護(hù),同時(shí)Engine內(nèi)部類還提供了onVisibilityChanged(),onCommand()等回調(diào)方法,用于可見狀態(tài)變化和用戶觸摸事件等。Engine類因此也是壁紙實(shí)現(xiàn)的核心類,實(shí)現(xiàn)和重寫其接口在開發(fā)中也相當(dāng)重要。
- WallpaperManagerService和WallpaperManager:WallpaperManagerService用于管理壁紙的運(yùn)行與切換,并通過WallpaperManager對外界提供操作壁紙的接口。
- WindowMangerService:該類用于計(jì)算壁紙窗口的Z序,可見性以及為壁紙窗口應(yīng)用動畫。
壁紙服務(wù)的兩種啟動場景
非首次重啟壁紙服務(wù)啟動流程
SystemService進(jìn)程啟動時(shí),會啟動各種系統(tǒng)服務(wù)。在該類的startOtherServices()方法中會首先拉起
WallpaperManagerService,通過該類,WallpaperService后面才得以啟動。
if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {
t.traceBegin("StartWallpaperManagerService");
mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
t.traceEnd();
} else {
Slog.i(TAG, "Wallpaper service disabled by config");
}
WallpaperManagerService啟動之后systemReady()方法中會通過loadSettingsLocked()方法加載用戶設(shè)置過的壁紙信息,然后監(jiān)聽用戶切換用戶switchUser(),切換用戶時(shí),switchWallpaper()會調(diào)用bindWallpaperComponentLocked()方法拉起對應(yīng)的壁紙服務(wù)。
手動切換時(shí)壁紙服務(wù)的啟動流程
手動切換壁紙服務(wù)時(shí)需要通過WallpaperManager.getIWallpaperManager().setWallpaperComponent()方法完成,我們在這個(gè)接口中傳入壁紙服務(wù)對應(yīng)的ComponentName,getIWallpaperManager返回的是WallpaperManagerService的Bp(binder proxy binder代理)端,在WallpaperManagerService端,我們可以查看到setWallpaperComponent的具體實(shí)現(xiàn),
private void setWallpaperComponent(ComponentName name, int userId) {
...
/* 首先調(diào)用該方法的時(shí)候回去校驗(yàn)權(quán)限,該權(quán)限定義在frameworks/base/core/res/AndroidManifest.xml,
<permission android:name="android.permission.SET_WALLPAPER_COMPONENT"
android:protectionLevel="signature|privileged" />
查看protectionLevel,只有是特權(quán)應(yīng)用或者系統(tǒng)簽名的應(yīng)用才能獲取到這個(gè)系統(tǒng)權(quán)限,所以普通的應(yīng)用是沒有辦法進(jìn)行壁紙?jiān)O(shè)置的
*/
checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
int which = FLAG_SYSTEM;
boolean shouldNotifyColors = false;
WallpaperData wallpaper;
synchronized (mLock) {
Slog.v(TAG, "setWallpaperComponent name=" + name);
/*
此處會先通過當(dāng)前的用戶ID獲取到與該用戶相關(guān)的壁紙信息,WallpaperManagerService支持多用戶機(jī)制,用戶的信息在mWallpaperMap中存儲,每一個(gè)用戶對應(yīng)一個(gè)WallpaperData,WallpaperData存儲壁紙相關(guān)信息
*/
wallpaper = mWallpaperMap.get(userId);
if (wallpaper == null) {
throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
}
...
// 在這里真正會去拉起對應(yīng)的WallPaperService
if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
...
}
setWallpaperComponent最終也是通過bindWallpaperComponentLocked拉起壁紙服務(wù)
壁紙服務(wù)啟動過程
1.校驗(yàn)是否是壁紙服務(wù)
bindWallpaperComponentLocked()方法將會啟動該ComponentName所指定的WallpaperService,在啟動的時(shí)候首先會進(jìn)行校驗(yàn),以確定待拉起的服務(wù)是一個(gè)壁紙服務(wù),
private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
...
int serviceUserId = wallpaper.userId;
ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId);
if (si == null) {
// The wallpaper component we're trying to use doesn't exist
Slog.w(TAG, "Attempted wallpaper " + componentName + " is unavailable");
return false;
}
/*
第一個(gè)校驗(yàn):
啟動的時(shí)候首先會校驗(yàn)這個(gè)壁紙服務(wù)是否聲明權(quán)限為BIND_WALLPAPER權(quán)限,
該權(quán)限的定義同樣也在fwk/base/core/res/manifest.xml
<permission android:name="android.permission.BIND_WALLPAPER"
android:protectionLevel="signature|privileged" />
該權(quán)限也是系統(tǒng)級別的,防止三方應(yīng)用切換壁紙,
*/
if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
String msg = "Selected service does not have "
+ android.Manifest.permission.BIND_WALLPAPER
+ ": " + componentName;
if (fromUser) {
throw new SecurityException(msg);
}
Slog.w(TAG, msg);
return false;
}
WallpaperInfo wi = null;
Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
if (componentName != null && !componentName.equals(mImageWallpaper)) {
// Make sure the selected service is actually a wallpaper service.
/*
第二個(gè)校驗(yàn):
這個(gè)檢查來校驗(yàn)服務(wù)是否聲明了android.service.wallpaper.WallpaperService這個(gè)action。如果這個(gè)服務(wù)沒有聲明這個(gè)action的話那么,ris中就不會含有這個(gè)component信息,
*/
List<ResolveInfo> ris =
mIPackageManager.queryIntentServices(intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
PackageManager.GET_META_DATA, serviceUserId).getList();
for (int i=0; i<ris.size(); i++) {
ServiceInfo rsi = ris.get(i).serviceInfo;
if (rsi.name.equals(si.name) &&
rsi.packageName.equals(si.packageName)) {
try {
/*
第三個(gè)檢查:
獲取名為android.service.wallpaper中的meta-data信息,該meta-data信息中提供了縮略圖,開發(fā)者,簡單的描述等。會將這些信息轉(zhuǎn)換成WallpaperInfo
*/
wi = new WallpaperInfo(mContext, ris.get(i));
} catch (XmlPullParserException e) {
if (fromUser) {
throw new IllegalArgumentException(e);
}
Slog.w(TAG, e);
return false;
} catch (IOException e) {
if (fromUser) {
throw new IllegalArgumentException(e);
}
Slog.w(TAG, e);
return false;
}
break;
}
}
if (wi == null) {
String msg = "Selected service is not a wallpaper: "
+ componentName;
if (fromUser) {
throw new SecurityException(msg);
}
Slog.w(TAG, msg);
return false;
}
}
// 當(dāng)壁紙服務(wù)支持在ambient模式下進(jìn)行繪制的時(shí)候,需要檢查是否有AMBIENT_WALLPAPER權(quán)限,
if (wi != null && wi.supportsAmbientMode()) {
final int hasPrivilege = mIPackageManager.checkPermission(
android.Manifest.permission.AMBIENT_WALLPAPER, wi.getPackageName(),
serviceUserId);
if (hasPrivilege != PackageManager.PERMISSION_GRANTED) {
String msg = "Selected service does not have "
+ android.Manifest.permission.AMBIENT_WALLPAPER
+ ": " + componentName;
if (fromUser) {
throw new SecurityException(msg);
}
Slog.w(TAG, msg);
return false;
}
}
// 檢驗(yàn)完畢,這里才會開始bind 壁紙服務(wù),如果校驗(yàn)失敗的話,會返回false
...
}
上面的校驗(yàn)可以看出一共校驗(yàn)了三個(gè)條件:
- 啟動的時(shí)候首先會校驗(yàn)這個(gè)壁紙服務(wù)是否聲明權(quán)限為BIND_WALLPAPER權(quán)限, 該權(quán)限的定義在fwk/base/core/res/manifest.xml中,< permission android:name=“android.permission.BIND_WALLPAPER”
android:protectionLevel=“signature|privileged” />
該權(quán)限也是系統(tǒng)級別的,防止三方應(yīng)用切換壁紙。 - 這個(gè)檢查來校驗(yàn)服務(wù)是否聲明了android.service.wallpaper.WallpaperService這個(gè)action。如果這個(gè)服務(wù)沒有聲明這個(gè)action的話那么,ris中就不會含有這個(gè)component信息。
- 獲取名為android.service.wallpaper中的meta-data信息,該meta-data信息中提供了縮略圖,開發(fā)者,簡單的描述等。會將這些信息轉(zhuǎn)換成WallpaperInfo。
2.綁定壁紙服務(wù)
壁紙服務(wù)的校驗(yàn)滿足后,開始啟動和綁定目標(biāo)服務(wù):
private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
// 校驗(yàn)服務(wù)是否符合要求結(jié)束后,開始著手啟動服務(wù)
...
//1. 創(chuàng)建一個(gè)WallpaperConnection,該對象可以監(jiān)聽和WallpaperService之間的連接狀態(tài),同時(shí)歸對象繼承了IWallpaperConnection.Stub,這樣該對象有擁有了跨進(jìn)程通信的能力,當(dāng)服務(wù)綁定成功后,onServiceConnected()方法調(diào)用中,WallpaperConnection實(shí)力會被發(fā)送到WallpaperService,該實(shí)例可以用于WallpaperService想WallpaperManagerService進(jìn)行通信的橋梁。
intent.setComponent(componentName);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.wallpaper_binding_label);
...
/* 2. 這里啟動指定的壁紙服務(wù),服務(wù)啟動后,壁紙還沒有辦法進(jìn)行顯示,還需要WallpaperConnection.onServiceConnected中進(jìn)行相應(yīng)的處理*/
if (!mContext.bindServiceAsUser(intent, newConn,Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE |
Context.BIND_INCLUDE_CAPABILITIES,new UserHandle(serviceUserId))) {
}
/*3. 新的壁紙服務(wù)啟動之后,就開始銷毀舊服務(wù)*/
if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null && !wallpaper.equals(mFallbackWallpaper)) {
detachWallpaperLocked(mLastWallpaper);
}
/* 4.將新的壁紙服務(wù)信息進(jìn)行保存*/
wallpaper.wallpaperComponent = componentName;
wallpaper.connection = newConn;
...
bindWallpaperComponentLocked函數(shù)在拉起壁紙服務(wù)的時(shí)候主要做了下面幾件事情:
- 創(chuàng)建了WallpaperConnection對象,由于實(shí)現(xiàn)了ServiceConnection接口,所以WallpaperConnection可以用來監(jiān)聽和壁紙服務(wù)的連接狀態(tài),另外由于繼承了IWallpoaperConnection.Stub接口,所以WallpaperConnection具有了跨進(jìn)程通信的能力。
- 啟動壁紙服務(wù):這里僅僅是拉起服務(wù),和拉起普通服務(wù)的方式基本一致,拉起方式上則使用了bindServiceAsUser,查看官方注解,該接口增加了校驗(yàn)該用戶是否能拉起該服務(wù),其余的行為和bindService相同。
- 保存當(dāng)前WallpaperConnection實(shí)例,ConponentName,到WallpaperData中
bindWallpaperComponentLocked()函數(shù)將壁紙服務(wù)拉了起來,但是僅僅將壁紙服務(wù)拉起來是沒有辦法顯示圖像的,因?yàn)閱拥姆?wù)并沒有窗口令牌,這樣就沒辦法添加窗口。剩下的這部分顯示的工作在WallpaperConnection的onServiceConnected()方法中進(jìn)行,在該回調(diào)中同樣也能拿到壁紙服務(wù)端服務(wù)端提供的Binder對象。
WallpaperService在被bind的時(shí)候返回了一個(gè)IWallpaperServiceWrapper對象,從代碼中可以看到,該對象中保存了WallpaperService實(shí)例,看了代碼后再去理解這個(gè)對象的命名(包裝WallpaperService),果然名副其實(shí)。
class IWallpaperServiceWrapper extends IWallpaperService.Stub {
private final WallpaperService mTarget;
private IWallpaperEngineWrapper mEngineWrapper;
public IWallpaperServiceWrapper(WallpaperService context) {
mTarget = context;
}
@Override
public void attach(IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,int displayId) {
...
}
@Override
public void detach() {
...
}
}
該接口中一共有兩個(gè)接口,attach和detach,attach接口在創(chuàng)建的時(shí)候可以將相關(guān)信息傳遞到壁紙服務(wù)中,對應(yīng)的,detach接口在服務(wù)銷毀的時(shí)候調(diào)用。
3.引擎的創(chuàng)建和初始化
引擎的創(chuàng)建準(zhǔn)備工作開始于onServiceConnected()回調(diào)處,該回調(diào)會傳遞壁紙服務(wù)需要的窗口令牌和ServiceConnection對象等。
WallpaperManagerService.java
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
if (mWallpaper.connection == this) {
mService = IWallpaperService.Stub.asInterface(service);
attachServiceLocked(this, mWallpaper);
// XXX should probably do saveSettingsLocked() later
// when we have an engine, but I'm not sure about
// locking there and anyway we always need to be able to
// recover if there is something wrong.
if (!mWallpaper.equals(mFallbackWallpaper)) {
// 保存當(dāng)前的壁紙信息到文件系統(tǒng)中,這樣重啟的時(shí)候就可以加載之前用戶設(shè)置過的壁紙
saveSettingsLocked(mWallpaper.userId);
}
FgThread.getHandler().removeCallbacks(mResetRunnable);
mContext.getMainThreadHandler().removeCallbacks(mTryToRebindRunnable);
}
}
}
onServiceConnected()函數(shù)中,首先將返回的binder對象進(jìn)行了保存,然后在attachServiceLocked()方法中會調(diào)用connectLocked()方法,connectLocked()接口中調(diào)用了attach方法傳遞了壁紙服務(wù)所需要的信息。
void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) {
...
mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);
final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
try {
connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
wpdData.mWidth, wpdData.mHeight,
wpdData.mPadding, mDisplayId);
}
...
}
attach接口回傳了許多信息,其中
- connection為WallpaperConnection的實(shí)例。WallpaperConnection之所以具有跨進(jìn)程通信的能力是因?yàn)槔^承了IWallpaperConnection.Stub類,該Stub對象中比較重要的一個(gè)接口就是attachEngine(),因?yàn)镋ngine實(shí)現(xiàn)才是動態(tài)壁紙的核心,WallpaperService會將創(chuàng)建好的Engine引用通過attachEngine()回傳給WallpaperManagerService進(jìn)行管理。
- mToken是向WMS注冊過的窗口令牌,只有擁有了這個(gè)令牌,WallpaperService才有權(quán)添加壁紙窗口。
傳遞了WallpaperService需要的信息之后,WallPaperService開始進(jìn)行引擎的創(chuàng)建。查看WallpaperService中attach()方法的實(shí)現(xiàn),
class IWallpaperServiceWrapper extends IWallpaperService.Stub {
...
@Override
public void attach(IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
int displayId) {
mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,
windowType, isPreview, reqWidth, reqHeight, padding, displayId);
}
...
}
attach方法創(chuàng)建了一個(gè)IWallpaperEngineWrapper,顧名思義,該對象有壁紙服務(wù)創(chuàng)建的引擎的引用,在創(chuàng)建IWallpaperEngineWrapper對象的時(shí)候,會發(fā)送DO_ATTACH消息,該消息用于壁紙服務(wù)引擎的創(chuàng)建,
IWallpaperEngineWrapper.java
IWallpaperEngineWrapper(WallpaperService context,
IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
int displayId) {
mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
...
Message msg = mCaller.obtainMessage(DO_ATTACH);
mCaller.sendMessage(msg);
}
...
@Override
public void executeMessage(Message message) {
switch (message.what) {
case DO_ATTACH: {
try {
// 將IWallpaperEngineWapper對象傳遞給WallpaperConnection進(jìn)行保存,通過這個(gè)引用,WallpaperManagerService也可以通過它與engine進(jìn)行通信
mConnection.attachEngine(this, mDisplayId);
} catch (RemoteException e) {
Log.w(TAG, "Wallpaper host disappeared", e);
return;
}
// 創(chuàng)建一個(gè)引擎,該方法為抽象方法,需要子類根據(jù)自身實(shí)現(xiàn)具體的引擎
Engine engine = onCreateEngine();
mEngine = engine;
mActiveEngines.add(engine);
// 該方法中會完成窗口的創(chuàng)建,surface創(chuàng)建等工作。
engine.attach(this);
return;
}
由于mConnection.attachEngine()方法將IWallpaperEngineWrapper傳遞給了WallpaperManagerService,因此WallpaperManagerService可以轉(zhuǎn)發(fā)相關(guān)的請求和設(shè)置到Engine對象中,實(shí)現(xiàn)WallpaperManagerService到壁紙的通信。
onCreateEngine方法執(zhí)行后,引擎創(chuàng)建完成,之后通過engine.attach()方法進(jìn)行引擎相關(guān)的初始化:
void attach(IWallpaperEngineWrapper wrapper) {
...
mIWallpaperEngine = wrapper;
mCaller = wrapper.mCaller;
mConnection = wrapper.mConnection;
mWindowToken = wrapper.mWindowToken;
mSurfaceHolder.setSizeFromLayout();
mInitializing = true;
// 這個(gè)session用于和WMS進(jìn)行通信
mSession = WindowManagerGlobal.getWindowSession();
// mWindow是一個(gè)IWindow對象,用于接收從WMS發(fā)送過來的消息
mWindow.setSession(mSession);
mLayout.packageName = getPackageName();
mIWallpaperEngine.mDisplayManager.registerDisplayListener(mDisplayListener,
mCaller.getHandler());
mDisplay = mIWallpaperEngine.mDisplay;
mDisplayContext = createDisplayContext(mDisplay);
mDisplayState = mDisplay.getState();
if (DEBUG) Log.v(TAG, "onCreate(): " + this);
// 子類可以重寫該接口,在該接口中可以修改mSurfaceHolder相關(guān)的屬性,這個(gè)時(shí)候
// 窗口尚未創(chuàng)建。設(shè)置的相關(guān)屬性將在updateSurface中創(chuàng)建窗口時(shí)使用
onCreate(mSurfaceHolder);
mInitializing = false;
mReportedVisible = false;
// updateSurface會進(jìn)行窗口以及Surface的創(chuàng)建。
updateSurface(false, false, false);
}
attach方法執(zhí)行的完成,標(biāo)志著壁紙啟動的完成,之后可以調(diào)用壁紙的surface顯示圖像。
壁紙服務(wù)的啟動流程總結(jié)
壁紙服務(wù)的啟動相比于普通服務(wù)的啟動較為復(fù)雜,接下來用下面的示意圖對整體的流程進(jìn)行梳理:

壁紙服務(wù)在啟動的時(shí)候,大體可以分為兩個(gè)階段,首先就要是拉起對應(yīng)的服務(wù),拉起服務(wù)后然后將WindowToken等參數(shù)傳遞給引擎進(jìn)行窗口的創(chuàng)建,surface的創(chuàng)建。在WallpaperManagerService和WallpaperService交互的過程中,主要有下面三個(gè)跨進(jìn)程通信的Binder對象:
- WallpaperConnection:實(shí)現(xiàn)在WallpaperManagerService中,并通過IWallpaperService.attach回調(diào)傳遞給了IWallpaperEngineWrapper,通過WallpaperConnection.attachEngine()方法,WallpaperService將IWallpaperEngineWrapper回傳給了WallpaperManagerService,實(shí)現(xiàn)了雙向的通信。
- IWallpaperService:實(shí)現(xiàn)在WallpaperService中,該對象提供了attach方法,用于從WallpaperManagerService獲取引擎創(chuàng)建時(shí)需要的WindowToken等信息。
- IWallpaperEngineWrapper:實(shí)現(xiàn)在壁紙服務(wù)進(jìn)程中,同時(shí)引用交給了WallpaperManagerService,該對象封裝了Engine類,WallpaperManagerService對引擎相關(guān)的控制需要通過該對象提供的接口實(shí)現(xiàn)。
自己最近因?yàn)樾枰ㄎ灰粋€(gè)開機(jī)壁紙服務(wù)啟動慢的問題,所以熟悉了下壁紙服務(wù)的啟動過程,在此記錄啟動流程。定位該問題梳理從bind到onServiceConnected和引擎相關(guān)初始化,對比每一個(gè)階段的時(shí)間,最終確定問題的原因。
參考:
- 《深入理解Android卷3》第八章:深入理解android壁紙服務(wù)
- framework-base源碼
到此這篇關(guān)于詳解Android壁紙服務(wù)的啟動過程的文章就介紹到這了,更多相關(guān)Android壁紙內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中應(yīng)用界面主題Theme使用方法和頁面定時(shí)跳轉(zhuǎn)應(yīng)用
在Android SDK中內(nèi)置了下面的Theme,可以按標(biāo)題欄Title Bar和狀態(tài)欄Status Bar是否可見來分類,感興趣的朋友可以了解下哈2013-06-06
android 仿微信demo——注冊功能實(shí)現(xiàn)(移動端)
本篇文章主要介紹了微信小程序-閱讀小程序?qū)嵗╠emo),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望能給你們提供幫助2021-06-06
Android 檢測鍵盤顯示或隱藏鍵盤的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 檢測鍵盤顯示或隱藏鍵盤的實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-07-07
Android onKeyDown監(jiān)聽返回鍵無效的解決辦法
這篇文章主要介紹了 Android onKeyDown監(jiān)聽返回鍵無效的解決辦法的相關(guān)資料,需要的朋友可以參考下2017-06-06
Android 標(biāo)準(zhǔn)Intent的使用詳解
這篇文章主要介紹了Android 標(biāo)準(zhǔn)Intent的使用詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03
淺析android studio3.5中使用recycleview的包
這篇文章主要介紹了android studio3.5中使用recycleview的包,本文雖然內(nèi)容不長,但是給出了思路,需要的朋友可以參考下2019-11-11

