1. 程式人生 > >Android程序啟動與Activity顯示

Android程序啟動與Activity顯示

code小生,一個專注於 Android 領域的技術分享平臺

作者:Jensen95
地址:https://www.jianshu.com/p/8d58804d4bf4
宣告:本文是 Jensen95 原創,轉發等請聯絡原作者授權。

前言

這段時間,leader安排的任務進行Android外掛化,熱修復相關的調研,對於外掛化和熱修復涉及到的核心技術點,在於對於類裝載,資源裝載的認識還有對於啟動流程的熟悉,帶著該任務,於是有了接下來,一系列的文章,從程序啟動,Activity顯示,Dex裝載,資源裝載,最後主流幾個外掛化,熱修復原始碼實現的分析。本篇先從程序的啟動,到一個Activity的顯示流程出發分析。

啟動一個程序

在Anroid中,程序是一個執行元件的容器,系統執行一個元件的時候,啟動包含它的程序,當元件不再使用,程序會被關閉。AMS負責對應應用程序的啟動。

開啟一個新的程序,在AMS中首先呼叫addAppLocked

final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated,
       String abiOverride)
{
   ProcessRecord app;
   if (!isolated) {
       //從已經開啟記錄下的程序中查詢程序記錄
       app = getProcessRecordLocked(info.processName, info.uid, true
);
   } else {
       app = null;
   }
   //app為空的時候,建立一個新的程序,同時更新內部程序管理結構
   if (app == null) {
       app = newProcessRecordLocked(info, null, isolated, 0);
       updateLruProcessLocked(app, false, null);
       updateOomAdjLocked();
   }

   .....
   if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
       app.persistent = true
;
       app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
   }
   if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
       mPersistentStartingProcesses.add(app);
     //呼叫程序的啟動方法,啟動一個新的程序
       startProcessLocked(app, "added application", app.processName, abiOverride,
               null /* entryPoint */, null /* entryPointArgs */);
   }

   return app;
}

首先會從已經啟動的程序中查詢相應的程序資訊,ProcessRecord,如果不存在則會建立一個出來,然後呼叫startProcessLocked方法,來開啟一個新的程序。

private final void startProcessLocked(ProcessRecord app, String hostingType,
       String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs)
{
   //啟動應用
   ....
   Process.ProcessStartResult startResult = Process.start(entryPoint,
           app.processName, uid, uid, gids, debugFlags, mountExternal,
           app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
           app.info.dataDir, entryPointArgs);
     ....
 //傳送定時訊息,如果App的啟動超時,則會ANR
   synchronized (mPidsSelfLocked) {
       this.mPidsSelfLocked.put(startResult.pid, app);
       if (isActivityProcess) {
           Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
           msg.obj = app;
           mHandler.sendMessageDelayed(msg, startResult.usingWrapper
                   ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
       }
   }
   ....

}

程序的開啟呼叫的是Process的start方法。

public static final ProcessStartResult start(final String processClass,
                             final String niceName,
                             int uid, int gid, int[] gids,
                             int debugFlags, int mountExternal,
                             int targetSdkVersion,
                             String seInfo,
                             String abi,
                             String instructionSet,
                             String appDataDir,
                             String[] zygoteArgs)
{
   try {
       return startViaZygote(processClass, niceName, uid, gid, gids,
               debugFlags, mountExternal, targetSdkVersion, seInfo,
               abi, instructionSet, appDataDir, zygoteArgs);
   } catch (ZygoteStartFailedEx ex) {

   }
}

start方法中,通過呼叫startViaZygote,通過zygote程序來開啟一個新的程序。

private static ProcessStartResult startViaZygote(final String processClass, ...){

   //配置通過Zygote啟動的引數,最終通過socket寫入到Zygote程序,來開啟一個新的程序

}

方法的具體執行是通過socket將程序的啟動資訊,寫入到zygote中,然後通過其啟動一個新的程序,同時對於該程序,也指定了一個類作為執行的入口類,這個類就是ActivityThread。

entryPoint = "android.app.ActivityThread";

在start方法中的entryPoint,這個就是程序啟動後要執行的Java類,程序啟動後,所有操作就轉交到ActivityThread的執行,因此,這個類也是整個應用執行的核心。這個類首先被執行的是其main函式。

image.png
public static void main(String[] args) {
     ...
     Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
   thread.attach(false);
    Looper.loop();
   ....
}

ActivityThread的attach方法。

private void attach(boolean system) {
     ......
final IActivityManager mgr = ActivityManagerNative.getDefault();
   try {
       //呼叫AMS的attachApplication方法,傳遞ApplicationThread進去
       mgr.attachApplication(mAppThread);
   } catch (RemoteException ex) {
       throw ex.rethrowFromSystemServer();
   }
     .....
}

Ams的attachApplication方法會呼叫到Ams的attachApplicationLocked方法,

thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,...)

這個方法的主要功能是創建出應用程式中的各種物件,是比較核心的方法。

private void handleBindApplication(AppBindData data) {
   // 註冊當前UI執行緒到Runtime作為一個敏感執行緒
   VMRuntime.registerSensitiveThread();

   // 設定程序的啟動時間
   Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());

   // 建立程序的配置物件
   mBoundApplication = data;
   mConfiguration = new Configuration(data.config);
   mCompatConfiguration = new Configuration(data.config);


   //當版本低於Honeycomb MR1,將AsyncTask的實現通過使用執行緒池
   if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
       AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
   }

   //設定應用的時區和應用的地區
   TimeZone.setDefault(null);
   LocaleList.setDefault(data.config.getLocales());

   synchronized (mResourcesManager) {
       mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
       mCurDefaultDisplayDpi = data.config.densityDpi;
       applyCompatConfiguration(mCurDefaultDisplayDpi);
   }

   // 建立Instrumentation
   final InstrumentationInfo ii;
   if (data.instrumentationName != null) {
       try {
           ii = new ApplicationPackageManager(null, getPackageManager())
                   .getInstrumentationInfo(data.instrumentationName, 0);
       } catch (PackageManager.NameNotFoundException e) {
           throw new RuntimeException(
                   "Unable to find instrumentation info for: " + data.instrumentationName);
       }

       mInstrumentationPackageName = ii.packageName;
       mInstrumentationAppDir = ii.sourceDir;
       mInstrumentationSplitAppDirs = ii.splitSourceDirs;
       mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii);
       mInstrumentedAppDir = data.info.getAppDir();
       mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
       mInstrumentedLibDir = data.info.getLibDir();
   } else {
       ii = null;
   }

   final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
   updateLocaleListFromAppContext(appContext,
           mResourcesManager.getConfiguration().getLocales());

   // Continue loading instrumentation.
   if (ii != null) {
       final ApplicationInfo instrApp = new ApplicationInfo();
       ii.copyTo(instrApp);
       instrApp.initForUser(UserHandle.myUserId());
       final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
               appContext.getClassLoader(), false, true, false);
       final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
       //
       try {
           final 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);
       }

       final ComponentName component = new ComponentName(ii.packageName, ii.name);
       mInstrumentation.init(this, instrContext, appContext, component,
               data.instrumentationWatcher, data.instrumentationUiAutomationConnection);

       if (mProfiler.profileFile != null && !ii.handleProfiling
               && mProfiler.profileFd == null) {
           mProfiler.handlingProfiling = true;
           final File file = new File(mProfiler.profileFile);
           file.getParentFile().mkdirs();
           Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
       }
   } else {
       mInstrumentation = new Instrumentation();
   }

   //Application中指定了big heap,清除限制
   if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
       dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
   } else {
       dalvik.system.VMRuntime.getRuntime().clampGrowthLimit();
   }

   final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
   try {
       //生成Application物件
       Application app = data.info.makeApplication(data.restrictedBackupMode, null);
       mInitialApplication = app;
           .....

       try {
           //呼叫Instrumentation的onCreate方法
           mInstrumentation.onCreate(data.instrumentationArgs);
       }
       catch (Exception e) {

       }

       try {
           //呼叫Application的onCreate方法
           mInstrumentation.callApplicationOnCreate(app);
       } catch (Exception e) {

       }
   } finally {
       StrictMode.setThreadPolicy(savedPolicy);
   }
}

至此一個應用程序被開啟,同時其Instrumentation和Application的onCreate方法也被呼叫了,接下來就是Activity的執行。

ActivityThread mian函式執行

Activity啟動到顯示

從上面的程序啟動可以得知每一個程序對應一個Application,對應一個ActivityThread,也對應這一個Instrumentation。對於Activity的啟動會呼叫到其handleLaunchActivity方法。

handleLaunchActivity

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
       ....
    WindowManagerGlobal.initialize();
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
       handleResumeActivity(r.token, false, r.isForward,
                   !r.activity.mFinished && !r.startsNotResumed,           r.lastProcessedSeq, reason);
                   ...
    } else {
       ActivityManagerNative.getDefault()
                   .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                           Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
   }
}

該方法首先對WindowManagerGlobal做了初始化操作,然後呼叫了performLaunchActivity方法,返回一個Activity物件後,返回物件偽非空,則呼叫handleResumeActivity。如果為空呼叫ActivityManager的finishActivity方法。對於啟動,這裡performLaunchActivity和handleResumeActivity兩個方法是核心。接下來將針對這兩個方法來進行分析。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

  //獲取該Activity的包資訊,這裡為LoadedApk型別
  ActivityInfo aInfo = r.activityInfo;
  if (r.packageInfo == null) {
      r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                   Context.CONTEXT_INCLUDE_CODE);
  }
       ...
     //建立Activity例項
   java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
   activity = mInstrumentation.newActivity(    
   cl, component.getClassName(), r.intent);
   //獲取Application例項
   Application app = r.packageInfo.makeApplication(false, mInstrumentation);

  //建立視窗例項,並呼叫activity的attch方法,attach該視窗    
   Window window = null;
   if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
      window = r.mPendingRemoveWindow;
      r.mPendingRemoveWindow = null;
      r.mPendingRemoveWindowManager = null;
    }

    activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,
                       r.referrer, r.voiceInteractor, window);

                   ....
    //為Activity設定主題
   int theme = r.activityInfo.getThemeResource();
   if (theme != 0) {
        activity.setTheme(theme);
    }
    ....
  //呼叫該Activity的onCreate方法
   mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
   ....
   mActivities.put(r.token, r);
   ...
}

首先根據Activity的資訊來獲取相關的包資訊,這裡呼叫了getPackInfo來獲得相關的包資訊。得到一個LoadedApk型別來表示當前的Activity的包資訊。

public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
           int flags)
{
   return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId());
}

其中包含了Activity相關的資訊Application資訊,資源目錄等等。在獲得了LoadedApk例項之後,呼叫其makApplication方法,我們會疑問,在啟動一個Activity的時候,難道每次都要建立一個Application物件嗎?跟進原始碼。

public Application makeApplication(boolean forceDefaultAppClass,
           Instrumentation instrumentation)
{
 if (mApplication != null) {
     return mApplication;
 }
   ....
//建立Application例項
 java.lang.ClassLoader cl = getClassLoader();
 if (!mPackageName.equals("android")) {
      initializeJavaContextClassLoader();
  }
  ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
  app = mActivityThread.mInstrumentation.newApplication(
                   cl, appClass, appContext);
  appContext.setOuterContext(app);

  mActivityThread.mAllApplications.add(app);
  mApplication = app;
   //呼叫Application的onCreate方法
  instrumentation.callApplicationOnCreate(app);
}

通過makeApplication的方法實現,我們可以看到其首先判斷Application物件是否建立,如果沒有建立,則初始化類裝載器,然後建立Application物件,建立完成,則呼叫Application物件的onCreate方法。這裡也就是我們所熟知的在我們自定義Application的時候重寫的onCreate方法將會被呼叫。
繼續回到上面程式碼的分析,這裡的Activity通過類裝載器被裝載出來,然後例項化出一個物件,然後呼叫了其attach方法,進行了一系列資訊的配置。然後呼叫了mInstrumentation,呼叫了callActivityOnCreate。同時也會將我們的Activity新增到mActivitys中,這裡其定義如下。

final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

通過這段程式碼可以看到,呼叫了acticity的attach方法,跟進attach方法。

final void attach(Context context, ....,Window window) {
    mWindow = new PhoneWindow(this, window);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);

       .....
    mWindow.setWindowManager(                    (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
         .....);
      .....
   mWindowManager = mWindow.getWindowManager();
}

至此,我們Activity中的Window已經被創建出來了。Window實際型別為PhoneWindow。同時為該Window設定了WindowManager。至此,我們雖然不瞭解Window是個什麼東西,但是至少,我們可以知道的一點就是每一個Activity的建立是會有持有一個Window物件的。然後Instrumentation的callActivityOnCreate方法被呼叫。

callActivityOnCreate

public void callActivityOnCreate(Activity activity, Bundle icicle) {
   prePerformCreate(activity);
   activity.performCreate(icicle);
   postPerformCreate(activity);
}

這裡對於Activity的具體啟動細節,我們不做關心,具體細節,接下來的原始碼分析會做介紹,這裡先看一下Activity的performCreate方法。

final void performCreate(Bundle icicle) {
  restoreHasCurrentPermissionRequest(icicle);
  onCreate(icicle);
  mActivityTransitionState.readState(icicle);
  performCreateCommon();
}

這個時候呼叫了Activity的performCreate函式,呼叫了Activity的onCreate,我們一般會在onCreate中呼叫setContentView,進行我們佈局檔案的設定。這也是比較奇怪的一點,為什麼,我們呼叫了該方法,傳遞一個xml佈局檔案,我們的View就顯示出來了呢?這便是這次程式碼分析的核心,所有圍繞的相關知識點也會在此被引出。接下來,讓我們剝繭抽絲,逐層遞進。

public void setContentView(@LayoutRes int layoutResID) {
   getWindow().setContentView(layoutResID);
   initWindowDecorActionBar();
}

此處的Window即為在attach中得到的PhoneWindow的例項。

public void setContentView(int layoutResID) {
   if (mContentParent  ==  null) {
         installDecor();
      } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
         mContentParent.removeAllViews();
      }
   if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
         final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                   getContext());
         transitionTo(newScene);
     } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
   }
       ...
}

首先判斷mContentParent是否為空,mContentParent是用來放置contentView的,如果不為空則要清理掉所有的view,如果為null,呼叫installDecor(),其中,我們可以看到有呼叫對於transitions這個feature的判斷,這個是在Android系統5.0之後新增的一個功能,可以用來實現Activity的過渡,同時還是實現Activity之間的元素共享,使得Activity間切換更加的絲滑流暢。這裡對於該場景不做分析,我們跳過看其具體的View裝載,然後呼叫了

mLayoutInflater.inflate(layoutResID, mContentParent);

private void installDecor() {
   mDecor = generateDecor(-1);
   ....
   mContentParent = generateLayout(mDecor);
   ...
    //過渡動畫,標題,logo,UI選項的顯示處理
}

在installDecor中,首先呼叫了generateDecor方法,然後根據建立的DecorView,來生成ContentView。

protected DecorView generateDecor(int featureId) {
   Context context;
   if (mUseDecorContext) {
       Context applicationContext = getContext().getApplicationContext();
         if (applicationContext == null) {
             context = getContext();
         } else {
           context = new DecorContext(applicationContext, getContext().getResources());
            if (mTheme != -1) {
               context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
   }
    return new DecorView(context, featureId, this, getAttributes());
   }

根據是否使用DecorContext來建立相應的context,然後利用該Context來建立DecorView。那麼這個DecorView到底是個什麼呢?

public class DecorView extends FrameLayout

DecorView其實就是一個FrameLayout。這個FrameLayout則是我們所看到的Activity試圖的根View,在建立了一個DecorView之後,又根據這個DecorView例項來建立了ContentView。

這裡建立的DecorView其實是一個FrameLayout,

由上面函式可以看出,mContentParent是和mDecor有關的,下面來看一下ContentParent的建立過程。

protected ViewGroup generateLayout(DecorView decor) {
...

相關推薦

Android程序啟動Activity顯示

code小生,一個專注於 Android 領域的技術分享平臺作者:Jensen95地址:http

【iOS程序啟動運轉】- RunLoop個人小結

source ons splay pmo 過渡 它的 端口 launch code 學習iOS開發一般都是從UI開始的,從只知道從IB拖控件,到知道怎麽在方法裏寫代碼,然後會顯示什麽樣的視圖,產生什麽樣的事件,等等。其實程序從啟動開始,一直都是按照蘋果封裝好的代碼運行著,暴

Android實現FragmentActivity之間的資料互動

1概念 1 為什麼 因為Fragment和Activity一樣是具有生命週期,不是一般的bean通過建構函式傳值,會造成異常。 2 參考連結 Activity和Fragment傳遞資料的兩種方式 【Fragment精深系列4】Frag

【架構分析】Android鎖屏Activity生命週期的變化

概述 Android開機啟動後預設會先進入鎖屏然後滅屏休眠, 本文旨在介紹這個過程中最先啟動的Home Launch Activity (其實在Android N上最早被啟動的是Settings中的FallbackHome Activity) 如何發生相關的生命週期變化, 以及AMS

mac守護程序啟動停止

Mac下的啟動服務主要有三個地方可配置: 1,系統偏好設定-&gt;帳戶-&gt;登陸項 2,/System/Library/StartupItems 和 /Library/StartupItems/ 3,launchd 系統初始化程序配置。 前兩種優化比較

Android中ServiceActivity資料互動的簡單理解

Service跟Activity是最相似的元件,都代表可執行的程式,區別在於:Service一直在後臺執行,沒有跟使用者互動的介面。 啟動與停止Service有兩種方法: 第一種通過startService()與stopService()啟動和停止服務,Se

android中fragmentactivity之間通訊原理以及例子

首先,如果你想在android3.0及以下版本使用fragment,你必須引用android-support-v4.jar這個包 然後你寫的activity不能再繼承自Activity類了,而是要繼承android.support.v4.app.FragmentA

Android中ServiceActivity的通訊---回撥介面方式

最近在技術交流群中有人問到:要實現service與activity的高強度通訊用什麼方法? 群友回答的是用handler,但面試官好像不太滿意,後來本人查找了下資料,得到個人的結論:service與activity之前的通訊方式有很多,回撥介面方式、觀察者模式、廣播、還有h

Android View系統分析之三Activity啟動顯示

前言在Android View系統分析之從setContentView說開來(一)與Android View系統分析之二View與ViewGroup中我們已經簡單介紹了一個Activity的UI內容與檢視樹的組成關係,即View與ViewGroup組成了Activity的視覺

Android APK打包安裝、應用程序啟動過程、Activity啟動流程

目錄 一、Android APK的構建過程 通過IDE可以生成可以在android裝置中安裝的apk檔案,Google官方提供的構建APK的過程流程圖如下: 打包APK流程總結如下: AAPT(Android Asset Packaging Tool)工

Android程序啟動Activity黑屏(白屏)的三種解決方案

當Android跨程序啟動Activity時,過程介面很黑屏(白屏)短暫時間(幾百毫秒?)。當然從桌面Lunacher啟動一個App時也會出現相同情況,那是因為App冷啟動也屬於跨程序啟動Activity。為什麼沒會出現這種情況呢?真正元凶就是Android建立

Android複習-任務棧Activity啟動標記(使用標記啟動Activity時的坑)

任務棧,我們說過任務棧的概念,也知道了它的作用,但是在使用過程中會有一些坑。 指定任務棧: <activity android:name=".Main2Activity" android:launchMo

android 音樂播放 啟動方式 (3)服務通過傳送廣播來控制activity顯示進度等

播放列表存放在裡application中,這個地方可以優化 1 PlayService中:播放路徑從intent中獲取 @Override public void onCreate() { super.onCreate(); mPlayer = ne

Android程序系統:程序的建立、啟動排程流程

Android程序框架:程序的建立、啟動與排程流程 文章目錄 一 程序的建立與啟動流程 二 程序的優先順序 三 程序的排程流程 Android系統的啟動流程如下圖(點選檢視大圖)所示: Loader層 當手機處於關機狀態時,長按電源鍵開機,引

Android學習路線(十二)Activity生命周期——啟動一個Activity

-c out 方式 下載地址 生命 後臺線程 ring lan debug DEMO下載地址:http://download.csdn.net/detail/sweetvvck/7728735 不像其他的編程模式那樣應用是通過main()函數啟動的。Android系

如何找到Android app啟動activity和頁面元素信息

dump ref adg 按鈕 配置環境變量 好的 too 啟動app ace 在實施app自動化的時候,我們需要知道app 的啟動activity和頁面元素信息,以此啟動app和定位頁面元素,那麽如何在沒有源碼的情況下找打他們呢?當然是有好的工具啦,有Android sd

android FragmentActivity交互,互相發數據(附圖具體解釋)

oncreate @+ targe save inflate find enter 提交 ransac 筆者最近看官方training。發現了非常多實用又好玩的知識。當中。fragment與Activity通信就是一個。 fragment與Activity通信主要

Intent顯示啟動隱式啟動

example tar tro android 不同 strong main oid 方式 Android的Acitivity啟動大致有兩種方式:顯式啟動與隱式啟動。下面分別介紹: 1.顯示啟動: 清單文件註冊Activity <activi

Android查缺補漏--Activity生命周期和啟動模式

oncreate 交互 odin 生命周期方法 ont state cnblogs blog 開發 一、生命周期 onCreate():啟動Activity時,首次創建Activity時回調。 onRestart():再次啟動Activity時回調。 onStart():

Android自定義viewactivity的傳值

重復 轉動 自定義 activit 廣播 內部 代碼 view 等待 昨晚在寫團隊項目的時候,遇到一個問題,直到今天早上才解決。。。即在自定義view“轉盤”結束轉動後獲取結果的處理中,我是想吧值傳到activity中的一個textview中的,但我的自定義view類不是a