@DslMarker public annotation classViewModelFactoryDsl
public inline fun viewModelFactory( builder: InitializerViewModelFactoryBuilder.() -> Unit ): ViewModelProvider.Factory = InitializerViewModelFactoryBuilder().apply(builder).build()
publicabstractclassViewModel { // Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460) @Nullable privatefinal Map<String, Object> mBagOfTags = newHashMap<>(); @Nullable privatefinal Set<Closeable> mCloseables = newLinkedHashSet<>(); privatevolatilebooleanmCleared=false;
@JvmOverloads constructor( private val store: ViewModelStore, private val factory: Factory, private val defaultCreationExtras: CreationExtras = CreationExtras.Empty, ) {
publicinterfaceFactory {
public fun <T : ViewModel> create(modelClass: Class<T>): T { throw UnsupportedOperationException( "Factory.create(String) is unsupported. This Factory requires " + "`CreationExtras` to be passed into `create` method." ) }
public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T = create(modelClass)
@MainThread public open operator fun <T : ViewModel> get(modelClass: Class<T>): T { valcanonicalName= modelClass.canonicalName ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels") return get("$DEFAULT_KEY:$canonicalName", modelClass) }
@Suppress("UNCHECKED_CAST") @MainThread public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T { valviewModel= store[key] if (modelClass.isInstance(viewModel)) { (factory as? OnRequeryFactory)?.onRequery(viewModel) return viewModel as T } else { @Suppress("ControlFlowWithEmptyBody") if (viewModel != null) { // TODO: log a warning. } } valextras= MutableCreationExtras(defaultCreationExtras) extras[VIEW_MODEL_KEY] = key // AGP has some desugaring issues associated with compileOnly dependencies so we need to // fall back to the other create method to keep from crashing. returntry { factory.create(modelClass, extras) } catch (e: AbstractMethodError) { factory.create(modelClass) }.also { store.put(key, it) } }
// actually there is getInstance() @Suppress("SingletonConstructor") public open classNewInstanceFactory : Factory { @Suppress("DocumentExceptions") override fun <T : ViewModel> create(modelClass: Class<T>): T { returntry { modelClass.newInstance() } catch (e: InstantiationException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } catch (e: IllegalAccessException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } }
public companion object { privatevar sInstance: NewInstanceFactory? = null @JvmStatic public val instance: NewInstanceFactory @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) get() { if (sInstance == null) { sInstance = NewInstanceFactory() } return sInstance!! }
private object ViewModelKeyImpl : Key<String>
@JvmField val VIEW_MODEL_KEY: Key<String> = ViewModelKeyImpl } }
public open classAndroidViewModelFactory privateconstructor( private val application: Application?, @Suppress("UNUSED_PARAMETER") unused: Int, ) : NewInstanceFactory() {
@Suppress("DocumentExceptions") override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T { returnif (application != null) { create(modelClass) } else { valapplication= extras[APPLICATION_KEY] if (application != null) { create(modelClass, application) } else { // For AndroidViewModels, CreationExtras must have an application set if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) { throw IllegalArgumentException( "CreationExtras must have an application by `APPLICATION_KEY`" ) } super.create(modelClass) } } }
@Suppress("DocumentExceptions") override fun <T : ViewModel> create(modelClass: Class<T>): T { returnif (application == null) { throw UnsupportedOperationException( "AndroidViewModelFactory constructed " + "with empty constructor works only with " + "create(modelClass: Class<T>, extras: CreationExtras)." ) } else { create(modelClass, application) } }
@Suppress("DocumentExceptions") private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T { returnif (AndroidViewModel::class.java.isAssignableFrom(modelClass)) { try { modelClass.getConstructor(Application::class.java).newInstance(app) } catch (e: NoSuchMethodException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } catch (e: IllegalAccessException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } catch (e: InstantiationException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } catch (e: InvocationTargetException) { throw RuntimeException("Cannot create an instance of $modelClass", e) } } elsesuper.create(modelClass) }
public companion object { internal fun defaultFactory(owner: ViewModelStoreOwner): Factory = if (owner is HasDefaultViewModelProviderFactory) owner.defaultViewModelProviderFactory else instance
The formulation of the problem is often more essential than its solution, which may be merely a matter of mathematical or experimental skill. ― Albert Einstein
Q:一个线程有几个 Handler?
Q: 线程间的通信的原理是怎样的?
Q: Handler 内存泄漏的原因?为什么其他的内部类没有说过这个问题?
Q: 为何主线程可以 new Handler ?如果想要在子线程中 new Handler 要做些什么准备?
/** * Pushes a message onto the end of the message queue after all pending messages * before the current time. It will be received in {@link #handleMessage}, * in the thread attached to this handler. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ /** * 在所有挂起的消息之后将消息推送到消息队列的末尾 * 当前时间之前。它将在 {@link #handleMessage} 中收到, * 在附加到该处理程序的线程中。 * * @return 如果消息成功放入则返回 true * 消息队列。失败时返回 false,通常是因为 * 处理消息队列的 looper 正在退出。 */ publicfinalbooleansendMessage(@NonNull Message msg) { return sendMessageDelayed(msg, 0); }
/** * Enqueue a message into the message queue after all pending messages * before (current time + delayMillis). You will receive it in * {@link #handleMessage}, in the thread attached to this handler. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. Note that a * result of true does not mean the message will be processed -- if * the looper is quit before the delivery time of the message * occurs then the message will be dropped. */ /** * 将一条消息放入消息队列中,位于所有挂起的消息之后 * 之前(当前时间+delayMillis)。您将在以下时间收到它: * {@link #handleMessage},在附加到该处理程序的线程中。 * * @return 如果消息成功放入则返回 true * 消息队列。失败时返回 false,通常是因为 * 处理消息队列的 looper 正在退出。请注意,一个 * true 的结果并不意味着该消息将被处理 -- 如果 * Looper 在消息发送之前退出 * 发生则消息将被丢弃。 */ publicfinalbooleansendMessageDelayed(@NonNull Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
/** * Enqueue a message into the message queue after all pending messages * before the absolute time (in milliseconds) <var>uptimeMillis</var>. * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> * Time spent in deep sleep will add an additional delay to execution. * You will receive it in {@link #handleMessage}, in the thread attached * to this handler. * * @param uptimeMillis The absolute time at which the message should be * delivered, using the * {@link android.os.SystemClock#uptimeMillis} time-base. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. Note that a * result of true does not mean the message will be processed -- if * the looper is quit before the delivery time of the message * occurs then the message will be dropped. */ /** * 将消息排队到消息队列中,在绝对时间(以毫秒为单位)<var>uptimeMillis</var>之后的所有挂起消息之后。 * <b>时间基准是 {@link android.os.SystemClock#uptimeMillis}。</b> * 在深度睡眠期间花费的时间将会额外延迟执行。 * 您将在{@link #handleMessage}中接收它,该方法会在与此处理程序连接的线程中执行。 * * @param uptimeMillis 消息应该传递的绝对时间,使用{@link android.os.SystemClock#uptimeMillis}作为时间基准。 * * @return 如果消息成功放置到消息队列中,则返回 true 。如果失败,则返回 false ,通常是因为处理消息队列的消息循环正在退出。 * 请注意,返回true并不意味着消息将被处理 - 如果消息传递时间之前消息循环被退出,则消息将被丢弃。 */ publicbooleansendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueuequeue= mQueue; if (queue == null) { RuntimeExceptione=newRuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); returnfalse; } return enqueueMessage(queue, msg, uptimeMillis); }
booleanenqueueMessage(Message msg, long when) { if (msg.target == null) { thrownewIllegalArgumentException("Message must have a target."); }
synchronized (this) { if (msg.isInUse()) { thrownewIllegalStateException(msg + " This message is already in use."); }
if (mQuitting) { IllegalStateExceptione=newIllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); returnfalse; }
msg.markInUse(); msg.when = when; Messagep= mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; }
// We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } returntrue; }
if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { ... }
if(p == null || when == 0 || when < p.when) { ... } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; }
// CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Make sure TrustedCertificateStore looks in the right place for CA certificates finalFileconfigDir= Environment.getUserConfigDirectory(UserHandle.myUserId()); TrustedCertificateStore.setDefaultUserDirectory(configDir);
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line. // It will be in the format "seq=114" longstartSeq=0; if (args != null) { for (inti= args.length - 1; i >= 0; --i) { if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) { startSeq = Long.parseLong( args[i].substring(PROC_START_SEQ_IDENT.length())); } } } ActivityThreadthread=newActivityThread(); thread.attach(false, startSeq);
if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); }
if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); }
// End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop();
/** * Initialize the current thread as a looper, marking it as an * application's main looper. See also: {@link #prepare()} * * @deprecated The main looper for your application is created by the Android environment, * so you should never need to call this function yourself. */ @Deprecated publicstaticvoidprepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { thrownewIllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
android.os.Looper#prepare(boolean)
1 2 3 4 5 6
privatestaticvoidprepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { thrownewRuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(newLooper(quitAllowed)); }
准备 Handler
1 2 3
if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); }
android.app.ActivityThread#getHandler
1 2 3 4
@UnsupportedAppUsage public Handler getHandler() { return mH; }
publicstaticvoidloop() { finalLooperme= myLooper(); if (me == null) { thrownewRuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } if (me.mInLoop) { Slog.w(TAG, "Loop again would have the queued messages be executed" + " before this one completed."); }
me.mInLoop = true;
// Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); finallongident= Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g. // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start' finalintthresholdOverride= SystemProperties.getInt("log.looper." + Process.myUid() + "." + Thread.currentThread().getName() + ".slow", 0);
me.mSlowDeliveryDetected = false;
for (;;) { if (!loopOnce(me, ident, thresholdOverride)) { return; } } }
privatestaticbooleanloopOnce(final Looper me, finallong ident, finalint thresholdOverride) { Messagemsg= me.mQueue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. returnfalse; }
// This must be in a local variable, in case a UI event sets the logger finalPrinterlogging= me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } // Make sure the observer won't change while processing a transaction. finalObserverobserver= sObserver;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); }
finallongdispatchStart= needStartTime ? SystemClock.uptimeMillis() : 0; finallong dispatchEnd; Objecttoken=null; if (observer != null) { token = observer.messageDispatchStarting(); } longorigWorkSource= ThreadLocalWorkSource.setUid(msg.workSourceUid); try { msg.target.dispatchMessage(msg); if (observer != null) { observer.messageDispatched(token, msg); } dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } catch (Exception exception) { if (observer != null) { observer.dispatchingThrewException(token, msg, exception); } throw exception; } finally { ThreadLocalWorkSource.restore(origWorkSource); if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (logSlowDelivery) { if (me.mSlowDeliveryDetected) { if ((dispatchStart - msg.when) <= 10) { Slog.w(TAG, "Drained"); me.mSlowDeliveryDetected = false; } } else { if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery", msg)) { // Once we write a slow delivery log, suppress until the queue drains. me.mSlowDeliveryDetected = true; } } } if (logSlowDispatch) { showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg); }
if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); }
// Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. finallongnewIdent= Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); }
@UnsupportedAppUsage Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. finallongptr= mPtr; if (ptr == 0) { returnnull; }
intpendingIdleHandlerCount= -1; // -1 only during first iteration intnextPollTimeoutMillis=0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); }
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) { // Try to retrieve the next message. Return if found. finallongnow= SystemClock.uptimeMillis(); MessageprevMsg=null; Messagemsg= mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; }
// Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); returnnull; }
// If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; }
// Run the idle handlers. // We only ever reach this code block during the first iteration. for (inti=0; i < pendingIdleHandlerCount; i++) { finalIdleHandleridler= mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler
if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } }
// Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } }
/** * This method returns the Looper associated with this thread. If this thread not been started * or for any reason isAlive() returns false, this method will return null. If this thread * has been started, this method will block until the looper has been initialized. * @return The looper. */ public Looper getLooper() { if (!isAlive()) { returnnull; }
booleanwasInterrupted=false;
// If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { wasInterrupted = true; } } }
/* * We may need to restore the thread's interrupted flag, because it may * have been cleared above since we eat InterruptedExceptions */ if (wasInterrupted) { Thread.currentThread().interrupt(); }
The formulation of the problem is often more essential than its solution, which may be merely a matter of mathematical or experimental skill. ― Albert Einstein
/** * Private constructor that must received an already allocated native bitmap * int (pointer). */ // called from JNI Bitmap(long nativeBitmap, int width, int height, int density, boolean isMutable, boolean requestPremultiplied, byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) { if (nativeBitmap == 0) { thrownewRuntimeException("internal error: native bitmap is 0"); }
publicstatic Bitmap decodeResource(Resources res, int id, Options opts) { validate(opts); Bitmapbm=null; InputStreamis=null; try { finalTypedValuevalue=newTypedValue(); is = res.openRawResource(id, value);
bm = decodeResourceStream(res, value, is, null, opts); } catch (Exception e) { /* do nothing. If the exception happened on open, bm will be null. If it happened on close, bm is still valid. */ } finally { try { if (is != null) is.close(); } catch (IOException e) { // Ignore } }
if (bm == null && opts != null && opts.inBitmap != null) { thrownewIllegalArgumentException("Problem decoding into existing bitmap"); }
return bm; }
publicstatic Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value, @Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) { validate(opts); if (opts == null) { opts = newOptions(); } if (opts.inDensity == 0 && value != null) { finalintdensity= value.density; if (density == TypedValue.DENSITY_DEFAULT) { opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; } elseif (density != TypedValue.DENSITY_NONE) { opts.inDensity = density; } } // 获取当前手机设备的 dpi if (opts.inTargetDensity == 0 && res != null) { opts.inTargetDensity = res.getDisplayMetrics().densityDpi; } return decodeStream(is, pad, opts); }
// 省略部分跟踪代码 ......
privatestaticnative Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts);
static jobject doDecode(JNIEnv *env, SkStreamRewindable *stream, jobject padding, jobject options){ // This function takes ownership of the input stream. Since the SkAndroidCodec // will take ownership of the stream, we don't necessarily need to take ownership // here. This is a precaution - if we were to return before creating the codec, // we need to make sure that we delete the stream. std::unique_ptr<SkStreamRewindable> streamDeleter(stream);
// Set default values for the options parameters. int sampleSize = 1; // 是否只是获取图片的大小 bool onlyDecodeSize = false; SkColorType prefColorType = kN32_SkColorType; bool isMutable = false; float scale = 1.0f; bool requireUnpremultiplied = false; jobject javaBitmap = NULL;
// Update with options supplied by the client. // 解析 options 参数 if (options != NULL) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); // Correct a non-positive sampleSize. sampleSize defaults to zero within the // options object, which is strange. if (sampleSize <= 0) { sampleSize = 1; }
if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) { onlyDecodeSize = true; }
// initialize these, in case we fail later on env->SetIntField(options, gOptions_widthFieldID, -1); env->SetIntField(options, gOptions_heightFieldID, -1); env->SetObjectField(options, gOptions_mimeFieldID, 0); // 解析 ColorType ,复用参数等等 jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); isMutable = env->GetBooleanField(options, gOptions_mutableFieldID); requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID); javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); // 计算缩放的比例 if (env->GetBooleanField(options, gOptions_scaledFieldID)) { // 获取图片当前 xhdpi 的 density constint density = env->GetIntField(options, gOptions_densityFieldID); // 获取当前设备的 dpi constint targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID); constint screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID); if (density != 0 && targetDensity != 0 && density != screenDensity) { // scale = 当前设备的 dpi / xhdpi 的 density // scale = 420/320 = 1.3125 scale = (float) targetDensity / density; } } }
// Create the codec. NinePatchPeeker peeker; std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(streamDeleter.release(), 280 & peeker)); if (!codec.get()) { returnnullObjectReturn("SkAndroidCodec::NewFromStream returned null"); }
// Do not allow ninepatch decodes to 565. In the past, decodes to 565 // would dither, and we do not want to pre-dither ninepatches, since we // know that they will be stretched. We no longer dither 565 decodes, // but we continue to prevent ninepatches from decoding to 565, in order // to maintain the old behavior. if (peeker.mPatch && kRGB_565_SkColorType == prefColorType) { prefColorType = kN32_SkColorType; } // 获取当前图片的大小 // Determine the output size. SkISize size = codec->getSampledDimensions(sampleSize);
int scaledWidth = size.width(); int scaledHeight = size.height(); bool willScale = false; // 处理 simpleSize 压缩,我们这里没穿,上面默认是 1 // Apply a fine scaling step if necessary. if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) { willScale = true; scaledWidth = codec->getInfo().width() / sampleSize; scaledHeight = codec->getInfo().height() / sampleSize; }
// Set the options and return if the client only wants the size. if (options != NULL) { jstring mimeType = encodedFormatToString(env, codec->getEncodedFormat()); if (env->ExceptionCheck()) { returnnullObjectReturn("OOM in encodedFormatToString()"); } // 设置 options 对象中的 outWidth 和 outHeight env->SetIntField(options, gOptions_widthFieldID, scaledWidth); env->SetIntField(options, gOptions_heightFieldID, scaledHeight); env->SetObjectField(options, gOptions_mimeFieldID, mimeType); // 如果只是获取大小直接 return null 这里是 nullptr 而不是 NULL if (onlyDecodeSize) { returnnullptr; } }
// Scale is necessary due to density differences. if (scale != 1.0f) { willScale = true; // 计算 scaledWidth 和 scaledHeight // scaledWidth = 864 * 1.3125 + 0.5f = 1134 + 0.5f = 1134 scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f); // scaledHeight = 582 * 1.3125 + 0.5f = 763.875 + 0.5f = 764 scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f); } // 判断是否有复用的 Bitmap android::Bitmap *reuseBitmap = nullptr; unsignedint existingBufferSize = 0; if (javaBitmap != NULL) { reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap); if (reuseBitmap->peekAtPixelRef()->isImmutable()) { // 无法重用一个不变的位图图像解码器的目标。 ALOGW("Unable to reuse an immutable bitmap as an image decoder target."); javaBitmap = NULL; reuseBitmap = nullptr; } else { existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap); } } JavaPixelAllocator javaAllocator(env); RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize); ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize); SkBitmap::HeapAllocator heapAllocator; SkBitmap::Allocator *decodeAllocator; if (javaBitmap != nullptr && willScale) { // This will allocate pixels using a HeapAllocator, since there will be an extra // scaling step that copies these pixels into Java memory. This allocator // also checks that the recycled javaBitmap is large enough. decodeAllocator = &scaleCheckingAllocator; } elseif (javaBitmap != nullptr) { decodeAllocator = &recyclingAllocator; } elseif (willScale) { // This will allocate pixels using a HeapAllocator, since there will be an extra // scaling step that copies these pixels into Java memory. decodeAllocator = &heapAllocator; } else { decodeAllocator = &javaAllocator; }
// Set the decode colorType. This is necessary because we can't always support // the requested colorType. SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
// Construct a color table for the decode if necessary SkAutoTUnref <SkColorTable> colorTable(nullptr); SkPMColor *colorPtr = nullptr; int *colorCount = nullptr; int maxColors = 256; SkPMColor colors[256]; if (kIndex_8_SkColorType == decodeColorType) { colorTable.reset(newSkColorTable(colors, maxColors));
// SkColorTable expects us to initialize all of the colors before creating an // SkColorTable. However, we are using SkBitmap with an Allocator to allocate // memory for the decode, so we need to create the SkColorTable before decoding. // It is safe for SkAndroidCodec to modify the colors because this SkBitmap is // not being used elsewhere. colorPtr = const_cast<SkPMColor *>(colorTable->readColors()); colorCount = &maxColors; }
// Set the alpha type for the decode. SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied); // 创建 SkImageInfo 信息,宽,高,ColorType,alphaType const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType, alphaType); SkImageInfo bitmapInfo = decodeInfo; if (decodeColorType == kGray_8_SkColorType) { // The legacy implementation of BitmapFactory used kAlpha8 for // grayscale images (before kGray8 existed). While the codec // recognizes kGray8, we need to decode into a kAlpha8 bitmap // in order to avoid a behavior change. bitmapInfo = SkImageInfo::MakeA8(size.width(), size.height()); } // 解析 SkBitmap 设置 bitmapInfo,tryAllocPixels 开辟内存,具体分析在后面 SkBitmap decodingBitmap; if (!decodingBitmap.setInfo(bitmapInfo) || !decodingBitmap.tryAllocPixels(decodeAllocator, colorTable)) { // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo() // should only only fail if the calculated value for rowBytes is too // large. // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the // native heap, or the recycled javaBitmap being too small to reuse. returnnullptr; }
// Use SkAndroidCodec to perform the decode. SkAndroidCodec::AndroidOptions codecOptions; codecOptions.fZeroInitialized = (decodeAllocator == &javaAllocator) ? SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized; codecOptions.fColorPtr = colorPtr; codecOptions.fColorCount = colorCount; codecOptions.fSampleSize = sampleSize; // 解析获取像素值 SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodingBitmap.getPixels(), decodingBitmap.rowBytes(), &codecOptions); switch (result) { case SkCodec::kSuccess: case SkCodec::kIncompleteInput: break; default: returnnullObjectReturn("codec->getAndroidPixels() failed."); }
jbyteArray ninePatchChunk = NULL; if (peeker.mPatch != NULL) { if (willScale) { scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight); }
jobject ninePatchInsets = NULL; if (peeker.mHasInsets) { ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID, peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3], peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3], peeker.mOutlineRadius, peeker.mOutlineAlpha, scale); if (ninePatchInsets == NULL) { returnnullObjectReturn("nine patch insets == null"); } if (javaBitmap != NULL) { env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets); } } // 构建 SkBitmap 这个才是最终的 SkBitmap outputBitmap; if (willScale) { // 如果需要缩放,那需要重新创建一张图片,上面加载的是图片的本身大小 // This is weird so let me explain: we could use the scale parameter // directly, but for historical reasons this is how the corresponding // Dalvik code has always behaved. We simply recreate the behavior here. // The result is slightly different from simply using scale because of // the 0.5f rounding bias applied when computing the target image size constfloat sx = scaledWidth / float(decodingBitmap.width()); constfloat sy = scaledHeight / float(decodingBitmap.height());
// Set the allocator for the outputBitmap. SkBitmap::Allocator *outputAllocator; if (javaBitmap != nullptr) { outputAllocator = &recyclingAllocator; } else { outputAllocator = &javaAllocator; }
SkColorType scaledColorType = colorTypeForScaledOutput(decodingBitmap.colorType()); // FIXME: If the alphaType is kUnpremul and the image has alpha, the // colors may not be correct, since Skia does not yet support drawing // to/from unpremultiplied bitmaps. // 设置 SkImageInfo ,注意这里是 scaledWidth ,scaledHeight outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight, scaledColorType, decodingBitmap.alphaType())); // 开辟当前 Bitmap 图片的内存 if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) { // This should only fail on OOM. The recyclingAllocator should have // enough memory since we check this before decoding using the // scaleCheckingAllocator. returnnullObjectReturn("allocation failed for scaled bitmap"); }
SkPaint paint; // kSrc_Mode instructs us to overwrite the unininitialized pixels in // outputBitmap. Otherwise we would blend by default, which is not // what we want. paint.setXfermodeMode(SkXfermode::kSrc_Mode); paint.setFilterQuality(kLow_SkFilterQuality); // decodingBitmap -> 画到 outputBitmap SkCanvas canvas(outputBitmap); canvas.scale(sx, sy); canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint); } else { outputBitmap.swap(decodingBitmap); }
// If we get here, the outputBitmap should have an installed pixelref. if (outputBitmap.pixelRef() == NULL) { returnnullObjectReturn("Got null SkPixelRef"); } if (!isMutable && javaBitmap == NULL) { // promise we will never change our pixels (great for sharing and pictures) outputBitmap.setImmutable(); } // 如果有复用返回原来的 javaBitmap bool isPremultiplied = !requireUnpremultiplied; if (javaBitmap != nullptr) { GraphicsJNI::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied); outputBitmap.notifyPixelsChanged(); // If a java bitmap was passed in for reuse, pass it back return javaBitmap; }
int bitmapCreateFlags = 0x0; if (isMutable) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Mutable; if (isPremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied; // 没有复用的 Bitmap 创建一个新的 Bitmap // now create the java bitmap return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); }
jobject GraphicsJNI::createBitmap(JNIEnv *env, android::Bitmap *bitmap, int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets, int density){ bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable; bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied; // The caller needs to have already set the alpha type properly, so the // native SkBitmap stays in sync with the Java Bitmap. assert_premultiplied(bitmap->info(), isPremultiplied);
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID, reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(), bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied, ninePatchChunk, ninePatchInsets); hasException(env); // For the side effect of logging. return obj; }
size_t size; if (!computeAllocationSize(*bitmap, &size)) { returnNULL; }
// we must respect the rowBytes value already set on the bitmap instead of // attempting to compute our own. constsize_t rowBytes = bitmap->rowBytes();
jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime, gVMRuntime_newNonMovableArray, gByte_class, size); if (env->ExceptionCheck() != 0) { returnNULL; } SkASSERT(arrayObj); jbyte *addr = (jbyte *) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj); if (env->ExceptionCheck() != 0) { returnNULL; } SkASSERT(addr); android::Bitmap *wrapper = new android::Bitmap(env, arrayObj, (void *) addr, info, rowBytes, ctable); wrapper->getSkBitmap(bitmap); // since we're already allocated, we lockPixels right away // HeapAllocator behaves this way too bitmap->lockPixels();
return wrapper; }
从上面就可以看到, new android::Bitmap 见: frameworks/base/core/jni/android/graphics/Bitmap.cpp
1 2 3 4 5 6 7 8 9 10 11
Bitmap::Bitmap(JNIEnv *env, jbyteArray storageObj, void *address, const SkImageInfo &info, size_t rowBytes, SkColorTable *ctable) : mPixelStorageType(PixelStorageType::Java) { env->GetJavaVM(&mPixelStorage.java.jvm); mPixelStorage.java.jweakRef = env->NewWeakGlobalRef(storageObj); mPixelStorage.java.jstrongRef = nullptr; mPixelRef.reset(newWrappedPixelRef(this, address, info, rowBytes, ctable)); // Note: this will trigger a call to onStrongRefDestroyed(), but // we want the pixel ref to have a ref count of 0 at this point mPixelRef->unref(); }
// we must respect the rowBytes value already set on the bitmap instead of // attempting to compute our own. constsize_t rowBytes = bitmap->rowBytes(); if (!computeAllocationSize(rowBytes, bitmap->height(), &size)) { returnnullptr; }
auto wrapper = alloc(size, info, rowBytes, ctable); if (wrapper) { wrapper->getSkBitmap(bitmap); // since we're already allocated, we lockPixels right away // HeapAllocator behaves this way too bitmap->lockPixels(); } return wrapper; }
On Android 2.3.3 (API level 10) and lower, usingrecycle() is recommended. If you're displaying large amounts of bitmap data in your app, you're likely to run into OutOfMemoryError errors. The recycle()method allows an app to reclaim memory as soon as possible. Caution: You should use recycle() only when you are sure that the bitmap is no longer being used. If you call recycle() and later attempt to draw the bitmap, you will get the error: "Canvas: trying to use a recycled bitmap". The following code snippet gives an example of calling recycle(). It uses reference counting (in the variables mDisplayRefCount and mCacheRefCount) to track whether a bitmap is currently being displayed orin the cache. The code recycles the bitmap when these conditions are met: The reference count for both mDisplayRefCount and mCacheRefCount is 0. The bitmap isnotnull, and it hasn't been recycled yet.
/** * Free the native object associated with this bitmap, and clear the * reference to the pixel data. This will not free the pixel data synchronously; * it simply allows it to be garbage collected if there are no other references. * The bitmap is marked as "dead", meaning it will throw an exception if * getPixels() or setPixels() is called, and will draw nothing. This operation * cannot be reversed, so it should only be called if you are sure there are no * further uses for the bitmap. This is an advanced call, and normally need * not be called, since the normal GC process will free up this memory when * there are no more references to this bitmap. */
/* * 释放与此位图关联的本机对象,并清除对像素数据的引用。这不会立即释放像素数据; * 它只是允许在没有其他引用时进行垃圾回收。该位图被标记为"无效",这意味着如果调用 * getPixels()或setPixels(),它将抛出异常, * 并且不会绘制任何内容。此操作无法撤销,因此只有在确保不再需要位图时才应调用此方法。 * 这是一个高级调用,通常不需要调用,因为正常的垃圾回收过程将在不再引用该位图时释放此内 * 存。 */ publicvoidrecycle() { if (!mRecycled && mNativePtr != 0) { if (nativeRecycle(mNativePtr)) { // return value indicates whether native pixel object was actually recycled. // false indicates that it is still in use at the native level and these // objects should not be collected now. They will be collected later when the // Bitmap itself is collected. // 返回值指示本机像素对象是否实际已被回收。 // false 表示它仍在本机级别上使用,现在不应收集这些对象。它们将在位图本身被回收时稍后收集。 mNinePatchChunk = null; } mRecycled = true; } } privatestaticnativebooleannativeRecycle(long nativeBitmap);
// Native memory allocated for the duration of the Bitmap, // if pixel data allocated into native memory, instead of java byte[] privateint mNativeAllocationByteCount;
/** * Private constructor that must received an already allocated native bitmap * int (pointer). */ // called from JNI Bitmap(long nativeBitmap, int width, int height, int density, boolean isMutable, boolean requestPremultiplied, byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) { if (nativeBitmap == 0) { thrownewRuntimeException("internal error: native bitmap is 0"); }
public Runnable registerNativeAllocation(Object referent, long nativePtr) { if (referent == null) { thrownew IllegalArgumentException("referent is null"); } if (nativePtr == 0) { thrownew IllegalArgumentException("nativePtr is null"); }
CleanerThunk thunk; CleanerRunner result; try { thunk = new CleanerThunk(); Cleaner cleaner = Cleaner.create(referent, thunk); result = new CleanerRunner(cleaner); registerNativeAllocation(this.size); } catch (VirtualMachineError vme /* probably OutOfMemoryError */) { applyFreeFunction(freeFunction, nativePtr); throw vme; // Other exceptions are impossible. // Enable the cleaner only after we can no longer throw anything, including OOME. thunk.setNativePtr(nativePtr); return result; }
/** * An {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation that uses an * {@link com.bumptech.glide.load.engine.bitmap_recycle.LruPoolStrategy} to bucket {@link Bitmap}s * and then uses an LRU eviction policy to evict {@link android.graphics.Bitmap}s from the least * recently used bucket in order to keep the pool below a given maximum size limit. */ publicclassLruBitmapPoolimplementsBitmapPool { privatestaticfinalStringTAG="LruBitmapPool"; privatestaticfinal Bitmap.ConfigDEFAULT_CONFIG= Bitmap.Config.ARGB_8888;
/** * Constructor for LruBitmapPool. * * @param maxSize The initial maximum size of the pool in bytes. */ publicLruBitmapPool(long maxSize) { this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs()); }
/** * Constructor for LruBitmapPool. * * @param maxSize The initial maximum size of the pool in bytes. * @param allowedConfigs A white listed put of {@link android.graphics.Bitmap.Config} that are * allowed to be put into the pool. Configs not in the allowed put will be rejected. */ // Public API. @SuppressWarnings("unused") publicLruBitmapPool(long maxSize, Set<Bitmap.Config> allowedConfigs) { this(maxSize, getDefaultStrategy(), allowedConfigs); }
/** Returns the number of cache hits for bitmaps in the pool. */ publiclonghitCount() { return hits; }
/** Returns the number of cache misses for bitmaps in the pool. */ publiclongmissCount() { return misses; }
/** Returns the number of bitmaps that have been evicted from the pool. */ publiclongevictionCount() { return evictions; }
/** Returns the current size of the pool in bytes. */ publiclonggetCurrentSize() { return currentSize; }
if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap)); } dump();
evict(); }
privatevoidevict() { trimToSize(maxSize); }
@Override @NonNull public Bitmap get(int width, int height, Bitmap.Config config) { Bitmapresult= getDirtyOrNull(width, height, config); if (result != null) { // Bitmaps in the pool contain random data that in some cases must be cleared for an image // to be rendered correctly. we shouldn't force all consumers to independently erase the // contents individually, so we do so here. See issue #131. result.eraseColor(Color.TRANSPARENT); } else { result = createBitmap(width, height, config); }
return result; }
@NonNull @Override public Bitmap getDirty(int width, int height, Bitmap.Config config) { Bitmapresult= getDirtyOrNull(width, height, config); if (result == null) { result = createBitmap(width, height, config); } return result; }
@TargetApi(Build.VERSION_CODES.O) privatestaticvoidassertNotHardwareConfig(Bitmap.Config config) { // Avoid short circuiting on sdk int since it breaks on some versions of Android. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { return; }
if (config == Bitmap.Config.HARDWARE) { thrownewIllegalArgumentException( "Cannot create a mutable Bitmap with config: " + config + ". Consider setting Downsampler#ALLOW_HARDWARE_CONFIG to false in your" + " RequestOptions and/or in GlideBuilder.setDefaultRequestOptions"); } }
@Nullable privatesynchronized Bitmap getDirtyOrNull( int width, int height, @Nullable Bitmap.Config config) { assertNotHardwareConfig(config); // Config will be null for non public config types, which can lead to transformations naively // passing in null as the requested config here. See issue #194. finalBitmapresult= strategy.get(width, height, config != null ? config : DEFAULT_CONFIG); if (result == null) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Missing bitmap=" + strategy.logBitmap(width, height, config)); } misses++; } else { hits++; currentSize -= strategy.getSize(result); tracker.remove(result); normalize(result); } if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Get bitmap=" + strategy.logBitmap(width, height, config)); } dump();
return result; }
// Setting these two values provides Bitmaps that are essentially equivalent to those returned // from Bitmap.createBitmap. privatestaticvoidnormalize(Bitmap bitmap) { bitmap.setHasAlpha(true); maybeSetPreMultiplied(bitmap); }
@TargetApi(Build.VERSION_CODES.O) privatestatic Set<Bitmap.Config> getDefaultAllowedConfigs() { Set<Bitmap.Config> configs = newHashSet<>(Arrays.asList(Bitmap.Config.values())); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // GIFs, among other types, end up with a native Bitmap config that doesn't map to a java // config and is treated as null in java code. On KitKat+ these Bitmaps can be reconfigured // and are suitable for re-use. configs.add(null); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { configs.remove(Bitmap.Config.HARDWARE); } return Collections.unmodifiableSet(configs); }
@SuppressWarnings("unused") // Only used for debugging privatestaticclassThrowingBitmapTrackerimplementsBitmapTracker { privatefinal Set<Bitmap> bitmaps = Collections.synchronizedSet(newHashSet<Bitmap>());
/** * Clears internal storage and notifies ViewModels that they are no longer used. */ publicfinalvoidclear() { for (ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); } }
@NonNull @Override public ViewModelStore getViewModelStore() { if (getApplication() == null) { thrownewIllegalStateException("Your activity is not yet attached to the " + "Application instance. You can't request ViewModel before onCreate call."); } ensureViewModelStore(); return mViewModelStore; }
ViewModelStoreOwner - Fragment
1 2 3 4 5 6 7 8 9 10 11 12 13
@NonNull @Override public ViewModelStore getViewModelStore() { if (mFragmentManager == null) { thrownewIllegalStateException("Can't access ViewModels from detached fragment"); } if (getMinimumMaxLifecycleState() == Lifecycle.State.INITIALIZED.ordinal()) { thrownewIllegalStateException("Calling getViewModelStore() before a Fragment " + "reaches onCreate() when using setMaxLifecycle(INITIALIZED) is not " + "supported"); } return mFragmentManager.getViewModelStore(this); }
privatevoidinitViewTreeOwners() { // Set the view tree owners before setting the content view so that the inflation process // and attach listeners will see them already present ViewTreeLifecycleOwner.set(getWindow().getDecorView(), this); ViewTreeViewModelStoreOwner.set(getWindow().getDecorView(), this); ViewTreeSavedStateRegistryOwner.set(getWindow().getDecorView(), this); ViewTreeOnBackPressedDispatcherOwner.set(getWindow().getDecorView(), this); }
// We're already stopped but we've been asked to retain. // Our fragments are taken care of but we need to mark the loaders for retention. // In order to do this correctly we need to restart the loaders first before // handing them off to the next activity. mFragments.doLoaderStart(); mFragments.doLoaderStop(true); ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
/** * Retain all appropriate non-config state. You can NOT * override this yourself! Use a {@link androidx.lifecycle.ViewModel} if you want to * retain your own non config state. */ @Override @Nullable @SuppressWarnings("deprecation") publicfinal Object onRetainNonConfigurationInstance() { // Maintain backward compatibility. Objectcustom= onRetainCustomNonConfigurationInstance();
ViewModelStoreviewModelStore= mViewModelStore; if (viewModelStore == null) { // No one called getViewModelStore(), so see if there was an existing // ViewModelStore from our last NonConfigurationInstance NonConfigurationInstancesnc= (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { viewModelStore = nc.viewModelStore; } }
publicstaticfinalclassActivityClientRecord { @UnsupportedAppUsage public IBinder token; public IBinder assistToken; // A reusable token for other purposes, e.g. content capture, translation. It shouldn't be // used without security checks public IBinder shareableActivityToken; // The token of the initial TaskFragment that embedded this activity. Do not rely on it // after creation because the activity could be reparented. @Nullablepublic IBinder mInitialTaskFragmentToken; int ident; @UnsupportedAppUsage Intent intent; String referrer; IVoiceInteractor voiceInteractor; Bundle state; PersistableBundle persistentState; @UnsupportedAppUsage Activity activity; Window window; Activity parent; String embeddedID; Activity.NonConfigurationInstances lastNonConfigurationInstances; // TODO(lifecycler): Use mLifecycleState instead. @UnsupportedAppUsage boolean paused; @UnsupportedAppUsage boolean stopped; boolean hideForNow; Configuration createdConfig; Configuration overrideConfig; // Used for consolidating configs before sending on to Activity. privateConfigurationtmpConfig=newConfiguration(); // Callback used for updating activity override config and camera compat control state. ViewRootImpl.ActivityConfigCallback activityConfigCallback; ActivityClientRecord nextIdle;
// Indicates whether this activity is currently the topmost resumed one in the system. // This holds the last reported value from server. boolean isTopResumedActivity; // This holds the value last sent to the activity. This is needed, because an update from // server may come at random time, but we always need to report changes between ON_RESUME // and ON_PAUSE to the app. boolean lastReportedTopResumedState;
ProfilerInfo profilerInfo;
@UnsupportedAppUsage ActivityInfo activityInfo; @UnsupportedAppUsage CompatibilityInfo compatInfo; @UnsupportedAppUsage public LoadedApk packageInfo;
boolean startsNotResumed; publicfinalboolean isForward; int pendingConfigChanges; // Whether we are in the process of performing on user leaving. boolean mIsUserLeaving;
init { var result = arrayOf( Bitmap.Config.ARGB_8888, // The value returned by Bitmaps with the hidden Bitmap config. null ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { result = result.copyOf(result.size + 1) result[result.size - 1] = Bitmap.Config.RGBA_F16 } ARGB_8888_IN_CONFIGS = result }
overridefunhashCode(): Int { var result = width result = 31 * result + height result = 31 * result + if (config != null) config.hashCode() else0 return result }