android中Context深入詳解
以下分別通過Context認(rèn)知角度,繼承關(guān)系,對(duì)象創(chuàng)建等方面android中Context做了深入的解釋,一起學(xué)習(xí)下。
1、Context認(rèn)知。
Context譯為場(chǎng)景,一個(gè)應(yīng)用程序可以認(rèn)為是一個(gè)工作環(huán)境,在這個(gè)工作環(huán)境中可以存在許多場(chǎng)景,coding代碼的場(chǎng)景 ,打電話的場(chǎng)景,開會(huì)的場(chǎng)景。這些場(chǎng)景可以類比不同的Activity,service。
2、從兩個(gè)角度認(rèn)識(shí)Context。
第一:Activity繼承自Context,同時(shí)Activity還實(shí)現(xiàn)了其他的interface,我們可以這樣看,activity在語法上extends了Context,其本質(zhì)上是一個(gè)Context,但同時(shí)其實(shí)現(xiàn)了許多interface,擴(kuò)充了Context的功能,擴(kuò)充之后的類成為Activity或者Service。
第二:Context本質(zhì)上包含了場(chǎng)景的所有元素,故而設(shè)定其為abstract,Activity和Service繼承自Context,它們本質(zhì)上可以認(rèn)為就是Context。
3、Context繼承關(guān)系圖
4、Application對(duì)象的ContextImpl對(duì)象創(chuàng)建過程。
step 1、Ams通過遠(yuǎn)程Binder調(diào)用ActivityThread的內(nèi)部類ApplicationThread的bingApplication方法,參數(shù)包括ApplicationInfo,這個(gè)對(duì)象由Ams創(chuàng)建,通過IPC傳遞到ActivityThread的內(nèi)部類ApplicationThread中。
public final void bindApplication(String processName, ApplicationInfo appInfo, List<ProviderInfo> providers, ComponentName instrumentationName, String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler, Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher, int debugMode, boolean isRestrictedBackupMode, boolean persistent, Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings) { if (services != null) { // Setup the service cache in the ServiceManager ServiceManager.initServiceCache(services); } setCoreSettings(coreSettings); AppBindData data = new AppBindData(); data.processName = processName; data.appInfo = appInfo; data.providers = providers; data.instrumentationName = instrumentationName; data.instrumentationArgs = instrumentationArgs; data.instrumentationWatcher = instrumentationWatcher; data.debugMode = debugMode; data.restrictedBackupMode = isRestrictedBackupMode; data.persistent = persistent; data.config = config; data.compatInfo = compatInfo; data.initProfileFile = profileFile; data.initProfileFd = profileFd; data.initAutoStopProfiler = false; queueOrSendMessage(H.BIND_APPLICATION, data); }
step 2、構(gòu)建AppBindData對(duì)象,如上代碼所示。
step 3、調(diào)用H Handler,執(zhí)行handleBindApplication()方法。
static final class AppBindData { LoadedApk info; String processName; ApplicationInfo appInfo; List<ProviderInfo> providers; ComponentName instrumentationName; Bundle instrumentationArgs; IInstrumentationWatcher instrumentationWatcher; int debugMode; boolean restrictedBackupMode; boolean persistent; Configuration config; CompatibilityInfo compatInfo; /** Initial values for {@link Profiler}. */ String initProfileFile; ParcelFileDescriptor initProfileFd; boolean initAutoStopProfiler; public String toString() { return "AppBindData{appInfo=" + appInfo + "}"; } } private void handleBindApplication(AppBindData data) { mBoundApplication = data; mConfiguration = new Configuration(data.config); mCompatConfiguration = new Configuration(data.config); //.......... TimeZone.setDefault(null); /* * Initialize the default locale in this process for the reasons we set the time zone. */ Locale.setDefault(data.config.locale); data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);//data.info對(duì)象為L(zhǎng)oadApk,此時(shí)data.info為null,使用getPackageINfoNoCheck創(chuàng)建此對(duì)象。 if (data.instrumentationName != null) {//該條件盡在Android Unit Test工程時(shí)會(huì)執(zhí)行到,此處直接看else語句 ContextImpl appContext = new ContextImpl(); appContext.init(data.info, null, this); InstrumentationInfo ii = null; try { ii = appContext.getPackageManager(). getInstrumentationInfo(data.instrumentationName, 0); } catch (PackageManager.NameNotFoundException e) { } if (ii == null) { throw new RuntimeException( "Unable to find instrumentation info for: " + data.instrumentationName); } mInstrumentationAppDir = ii.sourceDir; mInstrumentationAppPackage = ii.packageName; mInstrumentedAppDir = data.info.getAppDir(); ApplicationInfo instrApp = new ApplicationInfo(); instrApp.packageName = ii.packageName; instrApp.sourceDir = ii.sourceDir; instrApp.publicSourceDir = ii.publicSourceDir; instrApp.dataDir = ii.dataDir; instrApp.nativeLibraryDir = ii.nativeLibraryDir; LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, appContext.getClassLoader(), false, true); ContextImpl instrContext = new ContextImpl(); instrContext.init(pi, null, this); try { java.lang.ClassLoader cl = instrContext.getClassLoader(); mInstrumentation = (Instrumentation) cl.loadClass(data.instrumentationName.getClassName()).newInstance(); } catch (Exception e) { throw new RuntimeException( "Unable to instantiate instrumentation " + data.instrumentationName + ": " + e.toString(), e); } mInstrumentation.init(this, instrContext, appContext, new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher); if (mProfiler.profileFile != null && !ii.handleProfiling && mProfiler.profileFd == null) { mProfiler.handlingProfiling = true; File file = new File(mProfiler.profileFile); file.getParentFile().mkdirs(); Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024); } try { mInstrumentation.onCreate(data.instrumentationArgs); } catch (Exception e) { throw new RuntimeException( "Exception thrown in onCreate() of " + data.instrumentationName + ": " + e.toString(), e); } } else { mInstrumentation = new Instrumentation();//初始化Instrumentation對(duì)象,一個(gè)應(yīng)用程序?qū)?yīng)一個(gè)Instrumentation對(duì)象 } Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; try { mInstrumentation.callApplicationOnCreate(app);//調(diào)用Application程序都應(yīng)的onCreate方法。 } catch (Exception e) { if (!mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to create application " + app.getClass().getName() + ": " + e.toString(), e); } } }
第三步可以又可以分為三小步。
step 3.1、給AppBindData的info變量賦值。
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);//data.info對(duì)象為L(zhǎng)oadApk,此時(shí)data.info為null,使用getPackageINfoNoCheck創(chuàng)建此對(duì)象。
step 3.2、初始化Instrumentation對(duì)象。
mInstrumentation = new Instrumentation();//初始化Instrumentation對(duì)象,一個(gè)應(yīng)用程序?qū)?yīng)一個(gè)Instrumentation對(duì)象
step 3.3、創(chuàng)建Application對(duì)象。
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
我們著重看一下step 3.1和step3.3.
step 3.1:mPackages和mResourcePackages集合,以packageName為key值,我們知道一個(gè)應(yīng)用程序中的packageName是相同的,也就是說,此處一旦創(chuàng)建,其他地方再次調(diào)用此函數(shù),就不需要?jiǎng)?chuàng)建了??偨Y(jié):也就是說一個(gè)應(yīng)用程序中的LoadedApk對(duì)象是唯一的。此處的LoadedApk,也被稱為packageInfo。
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { return getPackageInfo(ai, compatInfo, null, false, true); } private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {/*includeCode 默認(rèn)為true*/ synchronized (mPackages) { WeakReference<LoadedApk> ref; if (includeCode) {//1、首先從mPackages或者mResourcePackages 集合中以packageName為Key值,獲取LoadApk對(duì)象。 ref = mPackages.get(aInfo.packageName); } else { ref = mResourcePackages.get(aInfo.packageName); } LoadedApk packageInfo = ref != null ? ref.get() : null; if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) { if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " : "Loading resource-only package ") + aInfo.packageName + " (in " + (mBoundApplication != null ? mBoundApplication.processName : null) + ")"); packageInfo = new LoadedApk(this, aInfo, compatInfo, this, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);//2、如果packageInfo對(duì)象為null,則new初始化此對(duì)象 if (includeCode) {//3、最后將創(chuàng)建的此packageInfo對(duì)象,加入到mPackages或者mResourcePackages集合中。 mPackages.put(aInfo.packageName, new WeakReference<LoadedApk>(packageInfo)); } else { mResourcePackages.put(aInfo.packageName, new WeakReference<LoadedApk>(packageInfo)); } } return packageInfo; } }
step 3.3、總結(jié):每個(gè)應(yīng)用程序都存在一個(gè)Application,用戶可以在AndroidManifest中重寫它,如果不重寫也存在一個(gè)默認(rèn)的Application對(duì)象。
framework/base/core/java/android/app/LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { return mApplication; } Application app = null; String appClass = mApplicationInfo.className; if (forceDefaultAppClass || (appClass == null)) { appClass = "android.app.Application";//1、每個(gè)工程都存在一個(gè)Application對(duì)象,默認(rèn)的Application對(duì)象為android.app.Application,客戶端可以重寫 } try { java.lang.ClassLoader cl = getClassLoader(); ContextImpl appContext = new ContextImpl();//2、創(chuàng)建ContextImpl對(duì)象,這才是Context的實(shí)際實(shí)現(xiàn)類 appContext.init(this, null, mActivityThread);//3、執(zhí)行ContextImpl對(duì)象的init方法,initResource等對(duì)象 app = mActivityThread.mInstrumentation.newApplication(//4、以appContext為參數(shù)得到Application對(duì)象。 cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { if (!mActivityThread.mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to instantiate application " + appClass + ": " + e.toString(), e); } } mActivityThread.mAllApplications.add(app);//5、將創(chuàng)建的Application對(duì)象,加入到A來了Application中。 mApplication = app; if (instrumentation != null) {//6、此時(shí)的instrumentation為null。 try { instrumentation.callApplicationOnCreate(app); } catch (Exception e) { if (!instrumentation.onException(app, e)) { throw new RuntimeException( "Unable to create application " + app.getClass().getName() + ": " + e.toString(), e); } } } return app; }
5、Activity中Context的創(chuàng)建過程
step 1、Ams通過遠(yuǎn)程Binder調(diào)用ActivityThread的Application的scheduleLaunchActivity方法,參數(shù)包括ActivityInfo,這個(gè)對(duì)象由Ams創(chuàng)建,通過IPC傳遞到ActivityThread的內(nèi)部類ApplicationThread中。
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, Bundle state, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { ActivityClientRecord r = new ActivityClientRecord(); r.token = token; r.ident = ident; r.intent = intent; r.activityInfo = info; r.compatInfo = compatInfo; r.state = state; r.pendingResults = pendingResults; r.pendingIntents = pendingNewIntents; r.startsNotResumed = notResumed; r.isForward = isForward; r.profileFile = profileName; r.profileFd = profileFd; r.autoStopProfiler = autoStopProfiler; updatePendingConfiguration(curConfig); queueOrSendMessage(H.LAUNCH_ACTIVITY, r); }
step 2、構(gòu)建ActivityClientRecord對(duì)象,如上代碼所示。
step 3、調(diào)用H Handler,執(zhí)行handleLaunchActivity()方法。
其中step 3,又可分為10小步。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); ActivityInfo aInfo = r.activityInfo; if (r.packageInfo == null) {//1、如果packageInfo為null,則調(diào)用getPackageInfo的得到LoadedApk r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } ComponentName component = r.intent.getComponent(); if (component == null) { component = r.intent.resolveActivity( mInitialApplication.getPackageManager()); r.intent.setComponent(component); } if (r.activityInfo.targetActivity != null) { component = new ComponentName(r.activityInfo.packageName, r.activityInfo.targetActivity); } Activity activity = null; try {//2、調(diào)用mInstrumentation的newActivity方法,得到Activity對(duì)象 java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); if (r.state != null) { r.state.setClassLoader(cl); } } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to instantiate activity " + component + ": " + e.toString(), e); } } try { Application app = r.packageInfo.makeApplication(false, mInstrumentation);//3、獲取Application對(duì)象 if (localLOGV) Slog.v(TAG, "Performing launch of " + r); if (localLOGV) Slog.v( TAG, r + ": app=" + app + ", appName=" + app.getPackageName() + ", pkg=" + r.packageInfo.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() + ", dir=" + r.packageInfo.getAppDir()); if (activity != null) {//4、創(chuàng)建ContextImpl對(duì)象 ContextImpl appContext = new ContextImpl(); appContext.init(r.packageInfo, r.token, this); appContext.setOuterContext(activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config);//5、執(zhí)行Activity的attach方法,將此ContextImpl對(duì)象,設(shè)置給Activity,activity會(huì)調(diào)用attachBaseContext if (customIntent != null) { activity.mIntent = customIntent; } r.lastNonConfigurationInstances = null; activity.mStartedActivity = false; int theme = r.activityInfo.getThemeResource();//6、設(shè)置主題 if (theme != 0) { activity.setTheme(theme); } activity.mCalled = false; mInstrumentation.callActivityOnCreate(activity, r.state);//7、執(zhí)行Activity的onCreate方法 if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onCreate()"); } r.activity = activity; r.stopped = true; if (!r.activity.mFinished) { activity.performStart();//8、執(zhí)行Activity的onStart方法 r.stopped = false; } if (!r.activity.mFinished) { if (r.state != null) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);//9、質(zhì)細(xì)膩感onRestoresInstanceState方法 } } if (!r.activity.mFinished) { activity.mCalled = false; mInstrumentation.callActivityOnPostCreate(activity, r.state); if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onPostCreate()"); } } } r.paused = true; mActivities.put(r.token, r);//10、將包含activity信息集的r對(duì)象,也就是ActivityClientRecord,加入到mActivities中,r.token為key值。 } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to start activity " + component + ": " + e.toString(), e); } } return activity; }
總結(jié):activity的packageInfo對(duì)象和application的packageInfo是同一個(gè)對(duì)象。
6、Service中Context的創(chuàng)建過程
step 1、Ams通過遠(yuǎn)程Binder調(diào)用ActivityThread的內(nèi)部類ApplicationThread的scheduleCreateService方法,參數(shù)包括serviceInfo,這個(gè)對(duì)象由Ams創(chuàng)建,通過IPC傳遞到ActivityThread的內(nèi)部類ApplicationThread中。
public final void scheduleCreateService(IBinder token, ServiceInfo info, CompatibilityInfo compatInfo) { CreateServiceData s = new CreateServiceData(); s.token = token; s.info = info; s.compatInfo = compatInfo; queueOrSendMessage(H.CREATE_SERVICE, s); }
step 2、構(gòu)建CreateServiceData對(duì)象,如上代碼所示。
step 3、調(diào)用H Handler,執(zhí)行handleCreateService()方法。
其中step 3又可分為一下5步。
private void handleCreateService(CreateServiceData data) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo);//1、得到packageInfo,調(diào)用getPackageInfoNoCheck 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 { if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); ContextImpl context = new ContextImpl();//2、創(chuàng)建ContextImpl對(duì)象 context.init(packageInfo, null, this); Application app = packageInfo.makeApplication(false, mInstrumentation);//3、得到Application對(duì)象 context.setOuterContext(service); service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault());//4、調(diào)用service的attach方法,將實(shí)例化的ContextImpl設(shè)置給Service service.onCreate(); mServices.put(data.token, service);//5、將service對(duì)象加入到mService集合中,key值為data.token。 try { ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, 0, 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); } } }
綜上所述:
1、無論是Application還是Activity、Service,他們的LoadedApk對(duì)象都是同一個(gè),或者說packageInfo為同一個(gè)對(duì)象。
2、在創(chuàng)建ContextImpl對(duì)象時(shí),Application和SErvice通過getPackageInfoNoCheck方法,Activity通過getPackageInfo方法得到。
3、一個(gè)應(yīng)用程序中Context的個(gè)數(shù) = Activity的數(shù)量+Service的數(shù)量 +1。這里的1代表Application。
4、應(yīng)用程序中包含著多個(gè)ContextImpl對(duì)象,其內(nèi)部的PackageInfo卻是同一個(gè)。這樣設(shè)計(jì)意味著ContextImpl是一個(gè)輕量級(jí)類,PackageInfo是一個(gè)重量級(jí)類,所有和包相關(guān)的操作封裝到PackageInfo中,有利于代碼的封裝與隱藏。
class ContextImpl extends Context { private final static String TAG = "ApplicationContext"; private final static boolean DEBUG = false; private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs = new HashMap<String, SharedPreferencesImpl>(); /*package*/ LoadedApk mPackageInfo;
以上就是本篇文章的全部?jī)?nèi)容,希望大家通過學(xué)習(xí)能夠?qū)ontext有更深入的理解。
- Android 中Context的使用方法詳解
- Android編程實(shí)現(xiàn)全局獲取Context及使用Intent傳遞對(duì)象的方法詳解
- Android全局獲取Context實(shí)例詳解
- Android編程實(shí)現(xiàn)為L(zhǎng)istView創(chuàng)建上下文菜單(ContextMenu)的方法
- Android context源碼詳解及深入分析
- Android面試筆記之常問的Context
- 談?wù)凙ndroid里的Context的使用實(shí)例
- 避免 Android中Context引起的內(nèi)存泄露
- 安卓Android Context類實(shí)例詳解
- 詳解Android中的Context抽象類
- 深入解析Android App開發(fā)中Context的用法
- Android編程獲取全局Context的方法
- Android編程中context及全局變量實(shí)例詳解
- Android中ContextMenu用法實(shí)例
- android基礎(chǔ)教程之context使用詳解
- Android獲取其他包的Context實(shí)例代碼
相關(guān)文章
android防止提交事件時(shí)觸發(fā)多個(gè)表單中的按鈕
這篇文章主要介紹了android防止提交事件時(shí)觸發(fā)多個(gè)表單中的按鈕,2015-05-05Android Studio 3.0 新功能全面解析和舊項(xiàng)目適配問題
Android Studio是Android的官方IDE。接下來通過本文給大家分享Android Studio 3.0 新功能全面解析和舊項(xiàng)目適配問題,需要的朋友可以參考下2017-11-11Okhttp3實(shí)現(xiàn)爬取驗(yàn)證碼及獲取Cookie的示例
本篇文章主要介紹了Okhttp3實(shí)現(xiàn)爬取驗(yàn)證碼及獲取Cookie的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10詳解Android ConstraintLayout 約束布局的用法
本篇文章主要介紹了詳解Android ConstraintLayout 約束布局的用法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02Android getViewById和getLayoutInflater().inflate()的詳解及比較
這篇文章主要介紹了Android getViewById和getLayoutInflater().inflate()的詳解及比較的相關(guān)資料,這里對(duì)這兩種方法進(jìn)行了詳細(xì)的對(duì)比,對(duì)于開始學(xué)習(xí)Android的朋友使用這兩種方法是個(gè)很好的資料,需要的朋友可以參考下2016-11-11Android 動(dòng)態(tài)改變布局實(shí)例詳解
這篇文章主要介紹了Android 動(dòng)態(tài)改變布局實(shí)例詳解的相關(guān)資料,這里舉例說明如何實(shí)現(xiàn)動(dòng)態(tài)改變布局的例子,幫助大家學(xué)習(xí)理解,需要的朋友可以參考下2016-11-11Android通過應(yīng)用程序創(chuàng)建快捷方式的方法
這篇文章主要介紹了Android通過應(yīng)用程序創(chuàng)建快捷方式的方法,涉及Android基于應(yīng)用程序創(chuàng)建快捷方式的圖標(biāo)及動(dòng)作等技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09Android獲取當(dāng)前運(yùn)行的類名或者方法
這篇文章主要介紹了Android獲取當(dāng)前運(yùn)行的類名或者方法,涉及Android操作類與方法的技巧,需要的朋友可以參考下2015-05-05使用PHP開發(fā)Android應(yīng)用程序技術(shù)介紹
這篇文章主要介紹了使用PHP開發(fā)Android應(yīng)用程序技術(shù)介紹,本文講解了安裝PHP for Android、設(shè)置PHP for Android開發(fā)環(huán)境、使用PHP構(gòu)建Android應(yīng)用程序,需要的朋友可以參考下2015-03-03Android中Activity滑動(dòng)關(guān)閉的效果
這篇文章主要介紹了Android中Activity滑動(dòng)關(guān)閉的效果,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-05-05