1. 程式人生 > >Android App啟動時Apk資源載入機制原始碼分析

Android App啟動時Apk資源載入機制原始碼分析

在Andorid開發中我們要設定文字或圖片顯示,都直接通過Api一步呼叫就完成了,不僅是我們工程下res資源以及系統自帶的framwork資源也可以,那這些資源打包成Apk之後是如何被系統載入從而顯示出來的呢。

這裡我要從Apk安裝之後啟動流程開始講起,在桌面應用click事件之後
會通過Binder機制通知ActivityManagerService啟動,具體由ActivityManagerNative.getDefault返回ActivityManagerService的遠端介面代理物件ActivityManagerProxy,通知ActivityManagerService執行startActivity進入啟動流程. 該Service會進行些獲取目標內容,檢查許可權之後,再檢查對應程序的ProcessRecord是否存在了.如果ProcessRecord是null, ActivityManagerService會建立新的程序來例項化目標activity從而把App啟動了起來,詳細的App啟動流程可以參考老羅的文章:

http://blog.csdn.net/luoshengyang/article/details/6689748

程序的建立及繫結Application

  • 建立程序
    ActivityManagerService呼叫startProcessLocked()方法來建立新的程序, 該方法會通過前面講到的socket通道傳遞引數給Zygote程序. Zygote孵化自身, 並呼叫ZygoteInit.main()方法來例項化ActivityThread物件並最終返回新程序的pid。程式碼如下
 public final class ActivityManagerService extends
ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
...... private final void startProcessLocked(ProcessRecord app, String hostingType, String hostingNameStr) { ...... try { int uid = app.info.uid; int
[] gids = null; try { gids = mContext.getPackageManager().getPackageGids( app.info.packageName); } catch (PackageManager.NameNotFoundException e) { ...... } int debugFlags = 0; ...... int pid = Process.start("android.app.ActivityThread", mSimpleProcessManagement ? app.processName : null, uid, uid, gids, debugFlags, null); ...... } catch (RuntimeException e) { ...... } } ...... }

這裡主要是呼叫Process.start介面來建立一個新的程序,新的程序會匯入android.app.ActivityThread類,並且執行它的main函式。

  • 繫結Application
    這個是通過呼叫上文的ActivityThread物件中呼叫handleBindApplication方法完成的. 我們可以通過Thread.dumpStack()來檢視流程:
 at java.lang.Thread.dumpStack(Thread.java:505)
06-21 17:42:18.448 8620-8620/cn.terminal.egame.myphone W/System.err:     at cn.terminal.egame.myphone.MyApplication.onCreate(MyApplication.java:13)
06-21 17:42:18.448 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1015)
06-21 17:42:18.448 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4793)
06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.app.ActivityThread.access$1600(ActivityThread.java:165)
06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1437)
06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.os.Looper.loop(Looper.java:150)
06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5621)
06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)
06-21 17:42:18.449 8620-8620/cn.terminal.egame.myphone W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684)

那麼ActivityThread的handleBindApplication又是誰來呼叫的(這裡就簡單介紹下,不能偏離主題太遠,深入太遠,最後不知所云,沒有了方向):

在ActivityThread的main方法中會執行ActivityThread物件的attach方法,會呼叫用了ActivityManagerService的遠端介面本地代理物件ActivityManagerProxy的attachApplication函式通知attachApplication,並傳入引數是mAppThread,這是ApplicationThread型別的Binder物件,用來接受ActivityManagerService的程序間訊息。

public final class ActivityThread {

 ......

 public static void main(String[] args) {
  .....
  ActivityThread thread = new ActivityThread();
        thread.attach(false);
   .....

 }

 private void attach(boolean system) {
   .......
    final IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }

            ....
    }
}

ActivityManagerService在接受到attachApplication函式呼叫遠端訊息之後,一系列處理之後,會有兩個重要Binder通訊,一個就是通過傳來的引數Binder引數ApplicationThread來通知ActivityThread中mAppThread遠端中呼叫bindApplication(),另一個是scheduleLaunchActivity。在Ams中收到attachApplication時程式碼如下:

AMS
 @Override
    public final void attachApplication(IApplicationThread thread) {
        synchronized (this) {
            //獲取applicationThread的程序id(也就是淘寶應用程序)
            int callingPid = Binder.getCallingPid();
            final long origId = Binder.clearCallingIdentity();
            attachApplicationLocked(thread, callingPid);
            Binder.restoreCallingIdentity(origId);
        }
    }


 private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid) {

        // Find the application record that is being attached...  either via
        // the pid if we are running in multiple processes, or just pull the
        // next app record if we are emulating process with anonymous threads.
        ProcessRecord app;
        if (pid != MY_PID && pid >= 0) {
            synchronized (mPidsSelfLocked) {
                app = mPidsSelfLocked.get(pid);
            }
        } else {
            app = null;
        }

        //因為程序由AMS啟動,所以在AMS中一定會有ProcessRecord(程序記錄)
        //如果沒有ProcessRecord,則需要殺死該程序並退出
        if (app == null) {
            ``````
            return false;
        }

        // If this application record is still attached to a previous
        // process, clean it up now.
        if (app.thread != null) {
            //如果從ProcessRecord中獲取的IApplicationThread不為空,則需要處理該IApplicationThread
            //因為有可能此Pid為複用,舊應用程序剛釋放,內部IApplicationThread尚未清空,
            //同時新程序又剛好使用了此Pid
            handleAppDiedLocked(app, true, true);
        }


        //建立死亡代理(程序kill後通知AMS)
        AppDeathRecipient adr = new AppDeathRecipient(app, pid, thread);

        //程序註冊成功,移除超時通知
        mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);

        ``````
        try {
            //******繫結Application******
            thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                    profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                    app.instrumentationUiAutomationConnection, testMode,
                    mBinderTransactionTrackingEnabled, enableTrackAllocation,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    new Configuration(mConfiguration), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked());

            updateLruProcessLocked(app, false, null);
        } catch (Exception e) {

            ``````
            //bindApplication失敗後,重啟程序
            startProcessLocked(app, "bind fail", processName);
            return false;
        }

        try {
            //******啟動Activity(啟動應用MainActivity)******
            if (mStackSupervisor.attachApplicationLocked(app)) {
                didSomething = true;//didSomething表示是否有啟動四大元件
            }
        } catch (Exception e) {
            badApp = true;
        }

        ``````
        //繫結service和Broadcast的Application


        if (badApp) {
            //如果以上元件啟動出錯,則需要殺死程序並移除記錄
            app.kill("error during init", true);
            handleAppDiedLocked(app, false, true);
            return false;
        }

        //如果以上沒有啟動任何元件,那麼didSomething為false
        if (!didSomething) {
            //調整程序的oom_adj值, oom_adj相當於一種優先順序
            //如果應用程序沒有執行任何元件,那麼當記憶體出現不足時,該程序是最先被系統“殺死”
            updateOomAdjLocked();
        }
        return true;
    }

從上午可以看到在attachApplicationLocked中有兩個比較重要的方法函式:


thread.bindApplication(…) : 繫結Application到ActivityThread
mStackSupervisor.attachApplicationLocked(app) : 啟動Activity(7.0前為mMainStack.realStartActivityLocked())

bindApplication

通過AIDL介面IApplicationThread遠端通知到ApplicationThreadNative的onTransact方法指定執行BIND_APPLICATION_TRANSACTION方法,而ActivityThread的內部類ApplicationThread實現ApplicationThreadNative抽象類bindApplication(),由於bindApplication()是執行在服務端Binder的執行緒池中,所以bindApplication會通過Handler傳送BIND_APPLICATION的Message訊息,ActivityThread中handler接受到之後呼叫handleBindApplication。

public final class ActivityThread {


private class ApplicationThread extends ApplicationThreadNative {
       .....
          public final void bindApplication(String processName, ApplicationInfo appInfo,
                List<ProviderInfo> providers, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableOpenGlTrace, boolean trackAllocation, boolean isRestrictedBackupMode,
                boolean persistent, Configuration config, CompatibilityInfo compatInfo,
                Map<String, IBinder> services, Bundle coreSettings) {

           .........

            AppBindData data = new AppBindData();
            ......
            sendMessage(H.BIND_APPLICATION, data);
        }
    .....
    }

 private class H extends Handler {
      .....
         public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                ....
               case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;

                    .....

            }

            ....
        }
   }

初始化ContextImpl載入Apk資源

在handleBindApplication的具體實現中就可以看到資源載入:

private void handleBindApplication(AppBindData data) {
      //..........
      // Context初始化(ContextImpl)
      final ContextImpl appContext = ContextImpl.createAppContext(this/*ActivityThread*/, data.info/*LoadedApk*/);
      //........
  }

最終會呼叫到ContextImpl這個建構函式:

 private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
            //.......
            // LoadedApk賦值 
            mPackageInfo = packageInfo;
            mResourcesManager = ResourcesManager.getInstance();
            // resources初始化:通過LoadedApk.getResources來建立一個Resources例項
            Resources resources = packageInfo.getResources(mainThread);
            if (resources != null) {
            if (displayId != Display.DEFAULT_DISPLAY
                    || overrideConfiguration != null
                    || (compatInfo != null && compatInfo.applicationScale
                            != resources.getCompatibilityInfo().applicationScale)) {
                resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
                        packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
                        packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
                        overrideConfiguration, compatInfo);
            }
        }
            mResources = resources;// 賦值
            //......
            mContentResolver = new ApplicationContentResolver(this, mainThread, user);
    }

其中 packageInfo.getResources(mainThread)是指 LoadedApk.getResources():

 public Resources getResources(ActivityThread mainThread) {
        if (mResources == null) {
            // ActivityThread.getTopLevelResources()
            mResources = mainThread.getTopLevelResources(mResDir/*APK檔案位置*/, mSplitResDirs, mOverlayDirs,
                    mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
        }
        return mResources;
    }

即而又呼叫到ActivityThread.getTopLevelResources():

 /**
     * Creates the top level resources for the given package.
     */
    Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
            String[] libDirs, int displayId, Configuration overrideConfiguration,
            LoadedApk pkgInfo) {
        return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
                displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo());
    }

mResourcesManager是ResourcesManager的例項,最後資源載入是交給ResourcesManager來完成。
ResourcesManager.getTopLevelResources:

Resources getTopLevelResources(String resDir, String[] splitResDirs,
            String[] overlayDirs, String[] libDirs, int displayId,
            Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
        final float scale = compatInfo.applicationScale;
        Configuration overrideConfigCopy = (overrideConfiguration != null)
                ? new Configuration(overrideConfiguration) : null;
        ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
        Resources r;
        synchronized (this) {
            // Resources is app scale dependent.
            if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
            // Resources是以ResourcesKey為key以弱應用的方式儲存在mActiveResources這個Map中
            WeakReference<Resources> wr = mActiveResources.get(key);
            r = wr != null ? wr.get() : null;
            //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
            if (r != null && r.getAssets().isUpToDate()) {/
                // 快取裡面有,並且是最新的
                if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
                        + ": appScale=" + r.getCompatibilityInfo().applicationScale
                        + " key=" + key + " overrideConfig=" + overrideConfiguration);
                return r;
            }
        }

        //if (r != null) {
        //    Log.w(TAG, "Throwing away out-of-date resources!!!! "
        //            + r + " " + resDir);
        //}
        // AssetManager建立
        AssetManager assets = new AssetManager();
        // resDir can be null if the 'android' package is creating a new Resources object.
        // This is fine, since each AssetManager automatically loads the 'android' package
        // already.

        //載入apk資源
        if (resDir != null) {
            if (assets.addAssetPath(resDir) == 0) {
                return null;
            }
        }

       ......

        if (libDirs != null) {
            for (String libDir : libDirs) {
                if (libDir.endsWith(".apk")) {
                    // Avoid opening files we know do not have resources,
                    // like code-only .jar files.
                    if (assets.addAssetPath(libDir) == 0) {
                        Log.w(TAG, "Asset path '" + libDir +
                                "' does not exist or contains no resources.");
                    }
                }
            }
        }

        //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
        DisplayMetrics dm = getDisplayMetricsLocked(displayId);
        Configuration config;

        ......
          //config初始化賦值
        .....

        // 建立Resources
        r = new Resources(assets, dm, config, compatInfo);

           //快取Resources
        synchronized (this) {
            // 可能其他執行緒已經建立好了,則直接返回
            WeakReference<Resources> wr = mActiveResources.get(key);
            Resources existing = wr != null ? wr.get() : null;
            if (existing != null && existing.getAssets().isUpToDate()) {
                // Someone else already created the resources while we were
                // unlocked; go ahead and use theirs.
                r.getAssets().close();
                return existing;
            }

            // XXX need to remove entries when weak references go away
            // 把最新的物件儲存到快取中
            mActiveResources.put(key, new WeakReference<>(r));
            if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size());
            return r;
        }
    }

可以看到ResourcesManager先從快取找已經載入好的資源Resource,如果沒有就重新載入,通過初始化AssetManager和Resources來完成,並快取。
其中關鍵函式就是AssetManager的addAssetPath(resDir)來完成載入並交給Resource來暴露介面。

先看下AssetManager初始化其,建構函式:

/**
     * Create a new AssetManager containing only the basic system assets.
     * Applications will not generally use this method, instead retrieving the
     * appropriate asset manager with {@link Resources#getAssets}.    Not for
     * use by applications.
     * {@hide}
     */
    public AssetManager() {
        synchronized (this) {
            //......
            init(false);
            // 確保有能夠訪問系統資源的AssetManager物件
            ensureSystemAssets();
        }
    }

init是個native方法,實現如下:
android_util_AssetManager.android_content_AssetManager_init()

static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
    if (isSystem) {// false
        verifySystemIdmaps();
    }
    AssetManager* am = new AssetManager();
    if (am == NULL) {
        jniThrowException(env, "java/lang/OutOfMemoryError", "");
        return;
    }

    am->addDefaultAssets();

    ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
    env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}

AssetManager.cpp:addDefaultAssets()是新增系統預設資源路徑:/system/framework/framework-res.apk

bool AssetManager::addDefaultAssets()
{
    // root = /system/
    const char* root = getenv("ANDROID_ROOT");
    LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");

    String8 path(root);
    // path = /system/framework/framework-res.apk
    path.appendPath(kSystemAssets);

    return addAssetPath(path, NULL);
}

再通過addAssetPath新增資源路徑到mAssetPaths並載入openNonAssetInPathLocked:

bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
    AutoMutex _l(mLock);

    asset_path ap;

    String8 realPath(path);
    if (kAppZipName) {
        // 如果kAppZipName不為NULL(classes.jar),這裡這個值是為NULL的
        realPath.appendPath(kAppZipName);
    }
    ap.type = ::getFileType(realPath.string());
    if (ap.type == kFileTypeRegular) {// kAppZipName不為NULL
        ap.path = realPath;
    } else {
    // kAppZipName為NULL
        ap.path = path;//ap.path指向APK檔案
        ap.type = ::getFileType(path.string());
        if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
            ALOGW("Asset path %s is neither a directory nor file (type=%d).",
                 path.string(), (int)ap.type);
            return false;
        }
    }

    // Skip if we have it already.
    for (size_t i=0; i<mAssetPaths.size(); i++) {
        if (mAssetPaths[i].path == ap.path) {
            if (cookie) {
                *cookie = static_cast<int32_t>(i+1);
            }
            return true;
        }
    }

    ALOGV("In %p Asset %s path: %s", this,
         ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());

    // Check that the path has an AndroidManifest.xml
    Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
            kAndroidManifest, Asset::ACCESS_BUFFER, ap);
    if (manifestAsset == NULL) {
        // This asset path does not contain any resources.
        delete manifestAsset;
        return false;
    }
    delete manifestAsset;

    mAssetPaths.add(ap);

    // new paths are always added at the end
    if (cookie) {
        *cookie = static_cast<int32_t>(mAssetPaths.size());
    }

#ifdef __ANDROID__
    // Load overlays, if any
    asset_path oap;
    for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
        mAssetPaths.add(oap);
    }
#endif

   ......

    return true;
}

初始化完成AssetManager之後就是初始Resource,其作用就是快取mAssets,並暴露介面對外載入資源,實際都是通過AssetManager來完成的:

public class Resources {
.....
   public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
            CompatibilityInfo compatInfo) {
        mAssets = assets;
        mMetrics.setToDefaults();
        if (compatInfo != null) {
            mCompatibilityInfo = compatInfo;
        }
        // 裝置相關配置資訊更新處理
        updateConfiguration(config, metrics);
        // 建立字串資源池
        assets.ensureStringBlocks();
    }

    //實際載入都是轉給mAssets即AssetManager
    public CharSequence getText(@StringRes int id) throws NotFoundException {
        CharSequence res = mAssets.getResourceText(id);
        if (res != null) {
            return res;
        }
        throw new NotFoundException("String resource ID #0x"
                                    + Integer.toHexString(id));
    }
    //載入圖片
     private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {

        final Drawable dr;

        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
        try {
            if (file.endsWith(".xml")) {
                final XmlResourceParser rp = loadXmlResourceParser(
                        file, id, value.assetCookie, "drawable");
                dr = Drawable.createFromXml(this, rp, theme);
                rp.close();
            } else {
                final InputStream is = mAssets.openNonAsset(
                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                dr = Drawable.createFromResourceStream(this, value, is, file, null);
                is.close();
            }
        } catch (Exception e) {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
            final NotFoundException rnf = new NotFoundException(
                    "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
            rnf.initCause(e);
            throw rnf;
        }
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);

        return dr;
    }
    ......
}

從上那麼多可以看到開發中不論是設啥資源都是如此AssetManager完成的,到這裡就講完了整個Apk資源載入。

總結

通過上文就發現資源apk(resDir)通過AssetManager.addAssetPath(resDir)來完成載入,並初始化resource來處理暴露資源載入的流程。那麼我們就可以通過自定義資源的載入,使用AssetManager來載入我們的單獨資源apk不就可以了麼,請看我的另一篇文章:打造自己的框架-實現動態載入兩種方式 以及Resource是如何暴露出資源載入的流程,系統如何載入顯示res下資源。

相關推薦

Android App啟動Apk資源載入機制原始碼分析

在Andorid開發中我們要設定文字或圖片顯示,都直接通過Api一步呼叫就完成了,不僅是我們工程下res資源以及系統自帶的framwork資源也可以,那這些資源打包成Apk之後是如何被系統載入從而顯示出來的呢。 這裡我要從Apk安裝之後啟動流程開始講起,在桌面

Android Apk資源載入機制原始碼分析以及資源動態載入實現系列文章

Android系統中執行Apk時是如何對包內的資源進行載入以及我們開發中設定相關資源後又是如何被加載出來,這個系列我們可以學習系統載入資源的機制原理,然後我們再巧妙的利用學習系統載入技巧來打造我們自己的動態資源載入機制實現。 這個系列主要分為如下3部分內容來講

Android App 啟動顯示正在載入圖片(講解+原始碼)

原始碼下載地址: http://download.csdn.net/download/tangcheng_ok/7616001 微信、QQ、天天動聽等程式,在開啟時顯示了一張圖片,然後跳轉到相關介面。本文實現這個功能,其實很簡單.... 新建兩個Activity

Android APP啟動出現白屏或者黑屏怎麼辦?

1、為什麼APP啟動時會出現白屏或者黑屏? 當開啟一個Activity時,如果這個Activity所屬的應用還沒有在執行,系統會為這個Activity所屬的應用建立一個程序,但程序的建立與初始化都需要時間,在這個動作完成之前系統要做什麼呢?如果沒有任何反應的話

Android APP啟動出現白屏或者黑屏

問題描述: 啟動APP時會有短暫的白屏或者黑屏,大概1~2秒後,才會顯示主介面或者顯示啟動頁。 問題原因: 開啟一個Activity時,如果這個Activity所屬的應用還沒有在執行,系統會為這個Activity所屬的應 用建立

JDK類載入機制原始碼分析原始碼分析

JVM的類載入機制主要有如下三種機制: 1.全盤負責:所謂全盤負責,就是說當一個類載入器載入個個Class的時候,該Class所依賴和引用的其他Class也將由該類載入 器負責載入,除非使用另外一個類載入器來載入。 2.雙親委託:所謂雙親委託則是先讓parent(父)類載入器

資源排程機制原始碼分析(schedule方法,兩種排程演算法)

sparkContext初始化後會註冊Application,然後會呼叫schedule方法,如何為Application在worker上啟動Executor,Executor啟動後,DAGScheduler和TaskScheduler才能分配task給Executor來進行

Master原理剖析與原始碼分析資源排程機制原始碼分析(schedule(),兩種資源排程演算法)

1、主備切換機制原理剖析與原始碼分析 2、註冊機制原理剖析與原始碼分析 3、狀態改變處理機制原始碼分析 4、資源排程機制原始碼分析(schedule(),兩種資源排程演算法) * Dri

Android中ViewGroup、View事件分發機制原始碼分析總結(雷驚風)

1.概述         很長時間沒有回想Android中的事件分發機制了,開啟目前的原始碼發現與兩三年前的實現程式碼已經不一樣了,5.0以後發生了變化,更加複雜了,但是萬變不離其宗,實現原理還是一樣的,在這裡將5.0以前的時間分發機制做一下原始碼剖析及總結。會涉及到幾個方

Android apk動態載入機制的研究(二) 資源載入和activity生命週期管理

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android app啟動activity並調用onCreate()方法都默默地幹了什麽?

AR 其中 保存狀態 位置 mod con 會同 語句 Go Android app啟動activity並調用onCreate() 方法時都默默地幹了什麽? 在AndroidManifest.xml文件中的<intent-filter>元素

App啟動三種效果(黑屏白屏、背景圖片、延遲載入

最近做專案時,發現了一個問題: (1)有些應用點選後需要反應一會然後進入主頁如:微信、中國工商銀行 (2)有些應用點選後出來一個黑屏或白屏,閃一下然後進入主頁如:銀聯錢包 (3)有些應用點選後立刻出來

eclipse中tomcat啟動專案重複載入,導致資源初始化兩次的問題

      在eclise中啟動tomcat發現同一個專案被重複載入了兩次,一直很納悶哪裡出了問題,網上大家各種要去修改appBase之類的方法也不起作用,最後偶然間發現是eclipse中tomcat設定的問題,見圖中: 勾上標紅的選項,就OK了!!!

Android 解決APP啟動出現白屏問題

1、問題描述 當我們首次啟動APP時,再到APP的第一個頁面展示出來之前,這段時間會有幾秒的白屏或者是黑屏出現。這樣給使用者的體驗是十分不好的。 2、問題出現的原因: 當我們在啟動一個應用時,系統會檢查是否已經存在這樣一個程序,如果沒有,Android系統

Android apk動態載入機制的研究

背景問題是這樣的:我們知道,apk必須安裝才能執行,如果不安裝要是也能執行該多好啊,事實上,這不是完全不可能的,儘管它比較難實現。在理論層面上,我們可以通過一個宿主程式來執行一些未安裝的apk,當然,實踐層面上也能實現,不過這對未安裝的apk有要求。我們的想法是這樣的,首先要

Android app執行按HOME鍵,再次點選圖表後從新呼叫啟動頁問題

如標題所述,最近被重複例項化launcher activity這個問題搞得很慘,這個問題有哪些表現呢?如下: 1. 在package installers 安裝介面安裝完一個應用後,直接開啟app,然後進入了 Activity_1, 此時再通過此activity用star

Android 關於APP啟動白(黑)屏解決辦法

APP啟動時,在點選APP到APP啟動頁加載出來會有一段時間的白屏或黑屏,很醜陋。。。其實,黑屏或者白屏這裡並不是不正常,而是還沒載入到佈局檔案,就已經顯示了window視窗背景,黑屏白屏就是window視窗背景。 那window視窗背景在那裡提供呢?在提供t

Android 解決APP啟動出現短暫的白屏或者黑屏

很多時候,我們啟動APP時會有短暫的白屏或者黑屏,大概1~2秒後,才會顯示主介面或者顯示啟動頁,這是為什麼呢?本篇文章來介紹一下,並給出APP啟動秒開的解決辦法。 1、為什麼APP啟動時會出現白屏或者黑屏? 當開啟一個Activity時,如果這個Activity所屬的應用還

[轉]Android Studio啟動出現unable to access android sdk add-on list

上大 ras server tails dea tar ext ida 錯誤信息 轉載請標明出處:http://blog.csdn.net/xx326664162/article/details/50563122 文章出自:薛瑄的博客 你也可以查看我的其他同類文章,也會讓你

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

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