Android13 SplashScreen功能源码分析、移除和客制化

SplashScreen功能从android12开始添加,本文章分析了SplashScreen的启动流程,整体和独立App移除方案以及客制化 获取SplashScreen的window名称 在开发者模式将Animation scale调成10x,adb shell输入命令: dumpsys window windows 获取结果,以下为Splash Screen对应的Window信息 Window #8 Window{9d4659b u0 Splash Screen com.google.android.gm}:     mDisplayId=0 rootTaskId=12 mSession=Session{3fbadb0 1074:u0a10116} mClient=android.os.BinderProxy@d8c05aa     mOwnerUid=10116 showForAllUsers=true package=com.google.android.gm appop=NONE     mAttrs={(0,0)(fillxfill) sim={adjust=pan} ty=APPLICATION_STARTING fmt=TRANSLUCENT wanim=0x7f1603e6       fl=NOT_FOCUSABLE NOT_TOUCHABLE LAYOUT_IN_SCREEN LAYOUT_INSET_DECOR ALT_FOCUSABLE_IM HARDWARE_ACCELERATED DRAWS_SYSTEM_BAR_BACKGROUNDS       pfl=SHOW_FOR_ALL_USERS USE_BLAST APPEARANCE_CONTROLLED FIT_INSETS_CONTROLLED       apr=LIGHT_STATUS_BARS LIGHT_NAVIGATION_BARS       bhv=DEFAULT       fitSides=}     Requested w=3840 h=2160 mLayoutSeq=1595     mBaseLayer=21000 mSubLayer=0    mToken=ActivityRecord{2e6ad9e u0 com.google.android.gm/.ConversationListActivityGmail} t12}     mActivityRecord=ActivityRecord{2e6ad9e u0 com.google.android.gm/.ConversationListActivityGmail} t12}     mAppDied=false    drawnStateEvaluated=true    mightAffectAllDrawn=true     mViewVisibility=0x0 mHaveFrame=true mObscured=false     mGivenContentInsets=[0,0][0,0] mGivenVisibleInsets=[0,0][0,0]     mFullConfiguration={1.0 ?mcc?mnc [en_US] ldltr sw720dp w1280dp h696dp 480dpi lrg long land finger qwerty/v/v dpad/v winConfig={ mBounds=Rect(0, 0 – 3840, 2160) mAppBounds=Rect(0, 0 – 3840, 2160) mMaxBounds=Rect(0, 0 – 3840, 2160) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0}     mLastReportedConfiguration={1.0 ?mcc?mnc [en_US] ldltr sw720dp w1280dp h696dp 480dpi lrg long land finger qwerty/v/v dpad/v winConfig={ mBounds=Rect(0, 0 – 3840, 2160) mAppBounds=Rect(0, 0 – 3840, 2160) mMaxBounds=Rect(0, 0 – 3840, 2160) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0}     mHasSurface=true isReadyForDisplay()=true mWindowRemovalAllowed=false     Frames: parent=[0,0][3840,2160] display=[0,0][3840,2160] frame=[0,0][3840,2160] last=[0,0][3840,2160] insetsChanged=false      surface=[0,0][0,0]     WindowStateAnimator{28bc62d Splash Screen com.google.android.gm}:       mSurface=Surface(name=Splash Screen com.google.android.gm)/@0xfb34762       Surface: shown=true layer=0 alpha=1.0 rect=(0.0,0.0)  transform=(1.0, 0.0, 0.0, 1.0)       mDrawState=HAS_DRAWN       mLastHidden=false       mEnterAnimationPending=false      mSystemDecorRect=[0,0][0,0]     mForceSeamlesslyRotate=false seamlesslyRotate: pending=null    isOnScreen=true     isVisible=true     keepClearAreas: restricted=[], unrestricted=[] 以上获取window名字关键字Splash Screen,在整机代码库framework/base目录下搜索: grep -nr "Splash Screen" 搜索结果:根据搜索结果,定位为framework/base/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StaringSurfaceDrawer.java 311行。  /**       * Called when a task need a splash screen starting window.       *       * @param suggestType The suggestion type to draw the splash screen.       */      void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,              @StartingWindowType int suggestType) { …            params.setTitle("Splash Screen " + activityInfo.packageName);          // TODO(b/173975965) tracking performance          // Prepare the splash screen content view on splash screen worker thread in parallel, so the          // content view won't be blocked by binder call like addWindow and relayout.          // 1. Trigger splash screen worker thread to create SplashScreenView before/while          // Session#addWindow.          // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start … Read more

LineageOS 20/AOSP 生成并替换默认系统签名

LineageOS 20/AOSP 生成并替换默认系统签名 本文介绍如何将Lineageos中的testKey替换成releaseKey,基于LineageOS android13。 生成系统签名 LineageOS编译默认使用test-keys,位于build/target/product/security目录: Android.bp                 networkstack.pk8       sdk_sandbox.x509.pem Android.mk                 networkstack.x509.pem  shared.pk8 bluetooth.pk8              platform.keystore      shared.x509.pem bluetooth.x509.pem         platform.p12           testkey.pk8 cts_uicc_2021.pk8          platform.pem           testkey.x509.pem cts_uicc_2021.x509.pem     platform.pk8           verity_key fsverity-release.x509.der  platform.x509.pem      verity.pk8 media.pk8                  README                 verity.x509.pem media.x509.pem             sdk_sandbox.pk8 test-keys是公开的,任何app拿到testkey都可以使用系统签名,所以对外发布的版本需要使用自己生成的release-keys.生成系统签名在android系统源码根目录执行下面的脚本: subject='/C=CN/ST=Beijing/L=Beijing View/O=Xiaomi/OU=MIUI/CN=MIUI/emailAddress=miui@xiaomi.com'mkdir ~/.android-certsfor x in releasekey platform shared media networkstack; do      ./development/tools/make_key ~/.android-certs/$x "$subject";   done 上面脚本会在用户根目录下创建.android-certs目录,并在该目录下生成 releaseKey、platform、shared、media、networkstack几种key文件。提示输入密码直接回车即可。 lili@adam:~/.android-certs$ lsmedia.pk8         networkstack.x509.pem  releasekey.pk8       shared.x509.pem media.x509.pem    platform.pk8           releasekey.x509.pem networkstack.pk8  platform.x509.pem      shared.pk8 $subject 可以根据自己的公司信息修改。subject每个参数的代表的意思如下: C —> Country Name (2 letter code) ST —> State or Province Name (full name) L —> Locality Name (eg, city) O —> Organization Name (eg, company) OU —> Organizational Unit Name (eg, section) CN —> Common Name (eg, your name or your server’s hostname) emailAddress —> Contact email address 以上每种key分别生成了两个文件,.x509.pem后缀的是证书文件,包含key的公共信息。.pk8后缀的是私钥。android构建使用的5种key都在build/target/product/security目录下: testKey包的默认签名 platform 平台核心包的test key sharedhome/contracts进程共享内容的test key mediamedia/download系统的test key networkstack网络系统的test key. 独立的包可以在其模块的Android.mk里面设置LOCAL_CERTIFICATE设置签名。 LOCAL_CERTIFICATE := platform 也可以指定key路基本: LOCAL_CERTIFICATE := device/xiaomi/security/special 指定系统签名为release-Keys 首先,将上面生成的系统签名文件拷贝到 build/target/product/security目录下: cd build/target/product/security   cp -f ~/.android-certs/* ./ 修改build/make/core/config.mk: ifdef PRODUCT_DEFAULT_DEV_CERTIFICATE   DEFAULT_SYSTEM_DEV_CERTIFICATE := $(PRODUCT_DEFAULT_DEV_CERTIFICATE) else    DEFAULT_SYSTEM_DEV_CERTIFICATE := build/make/target/product/security/testkey … Read more

AOSP System app Android.bp集成Leakcanary检测内存泄漏

本文以Settings模块为例,继承Leakcanary 2.12 确定依赖的aar和jar包。 两种方式确定,一种方式是在android studio中通过在build.gradle中引入leaknacary,然后查看external libraries中新增的以来,到maven center下载对应版本的jar包和aar包。 另一种方式是直接在github release页面下载leakcanary源码自行编译出aar和jar依赖。 Settings集成leakcanary 定位到packages/apps/Settings目录。将依赖的jar包和aar包拷贝到libs目录. 在libs目录下新建Android.bp文件: java_import {     name: "leakcanary-object-watcher",     jars: ["leakcanary-object-watcher-2.12.jar"],     libs: ["shark-log"],     sdk_version: "current", } java_import {     name: "okio-2.2.2",     jars: ["okio-2.2.2.jar"],     sdk_version: "current", } java_import {     name: "shark",     jars: ["shark-2.12.jar"],     libs: ["shark-graph","okio-2.2.2"],     sdk_version: "current", } java_import {     name: "shark-android",     jars: ["shark-android-2.12.jar"],     libs: ["shark"],     sdk_version: "current", } java_import {     name: "shark-graph",     jars: ["shark-graph-2.12.jar"],     libs: ["shark-hprof","okio-2.2.2"],     sdk_version: "current", } java_import {     name: "shark-hprof",     jars: ["shark-hprof-2.12.jar"],     libs: ["shark-log"],     sdk_version: "current", } java_import {     name: "shark-log",     jars: ["shark-log-2.12.jar"],     sdk_version: "current", } android_library_import {     name: "leakcanary-android-utils",     aars: ["leakcanary-android-utils-2.12.aar"], … Read more

Android PiP画中画支持

Android 画中画支持 Android8.0开始支持Activity进入画中画模式。画中画是一种特殊类型的多窗口模式。它允许用户在固定在屏幕一角的小窗口观看视频的同时在应用之间切换或浏览主屏幕上的内容。让App进入画中画,需要将Activity设置成支持画中画。 画中画交互 在触屏设备设备上,用户可以拖拽PiP窗口到另一个位置,从Android S(12)开始,用户也可以: 单击窗口显示PiP菜单。 双击窗口切换PiP窗口最大或者最小。 通过拖动窗口到窗口边缘,隐藏PiP窗口,如果要取消隐藏,可以点击可见部分或直接将拖拽出来。 通过双指缩放调整PiP大小。 App声明PiP支持 默认系统不支持App进入PiP模式,需要在App manifest里设置android:supportsPictureInPicture="true",并指定activity处理configuration改变来防止activity重启。 <activity android:name="VideoActivity"     android:supportsPictureInPicture="true"     android:configChanges=         "screenSize|smallestScreenSize|screenLayout|orientation"     … 进入PiP 在移动设备上从Android12开始,可以通过给activity设置setAutoEnterEnabled flag为true,就不用在onUserLeaveHint时调用enterPictureInPictureMode()方法进入PiP了。 App页面适配 进入和退出PiP时会触发Activity.onPictureInPictureModeChanged()或Fragment.onPictureInPictureModeChanged()方法。需要重写这些回调方法来重新布局activity。当App进入PiP模式的时候,用户无法与app的UI元素交互,小窗口页面不需要呈现太多内容。 @Override public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) {     if (isInPictureInPictureMode) {         // Hide the full-screen UI (controls, etc.) while in PiP mode.     } else {         // Restore the full-screen UI.         …     } } 添加App交互 原生通过PictureInPictureParams.Builder.setActions()方法来设置交互,比如视频暂停、播放等。 生命周期影响 当activity进入PiP模式后,会触发onPause()生命周期,需要在这里做特殊处理,比如全屏播放时进入PiP不需要暂停视频,而其他情况下onPause()后需要暂停,可以这么处理。 @Override public void onPause() {     // If called while in PiP mode, do not pause playback     if (isInPictureInPictureMode()) {         // Continue playback         …     } else {         // Use existing playback logic for paused Activity behavior.         …     } } 在onPause()里通过isInPictureInPictureMode()方法判断当前是否处于PiP模式。当activity退出PiP进入全屏模式的时候,会触发onResume()生命周期。

Android Context Analysis

Android Context 分析 继承结构类图: ContextWrapper是上下文的包装类,ContextImpl是上下文的实现类,Activity是ContextThemeWrapper的直接子类。 getApplicationContext都做了什么   翻看ContextWrapper源码: public class ContextWrapper extends Context {             Context mBase;     public ContextWrapper(Context base) {         mBase = base;     }          /**      * Set the base context for this ContextWrapper.  All calls will then be      * delegated to the base context.  Throws      * IllegalStateException if a base context has already been set.      *       * @param base The new base context for this wrapper.      */     protected void attachBaseContext(Context base) {         if (mBase != null) {             throw new IllegalStateException("Base context already set");         }         mBase = base;     }    @Override     public Context getApplicationContext() {         return mBase.getApplicationContext();     }     ….      }     ContextWrapper的getApplicationContext方法直接调用mBase的getApplicationContext方法,mBase实际上是ContextImpl类实例,通过上面源码看到,在ContextWrapper类的attachBaseContext方法为其赋值。   什么时候调用的Application的attachBaseContext方法?  在ContextWrapper子类Application类的attach方法中: final void attach(Context context) {         attachBaseContext(context);         mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;     } 而Application的attach方法是在Instrumentation类的newApplication方法中调用. static public Application newApplication(Class<?> clazz, Context context)             throws InstantiationException, IllegalAccessException,              ClassNotFoundException {         Application app = (Application)clazz.newInstance();         app.attach(context);         return app;     } 继续向上追溯,Instrumentation的newApplication方法是在LoadApk的makeApplication方法中调用: public Application makeApplication(boolean forceDefaultAppClass,             Instrumentation instrumentation) {         if (mApplication != null) {             return mApplication;         }         Application app = null; … Read more

Android Handler 原理-Handler

Handler允许发送和处理与线程的MessageQueue相关联Message和Runnable对象。每一个Handler实例都与一个单独的线程和该线程的消息队列相关联。当创建Handler的时候,它被它创建的线程绑定。它把Message或Runnable对象传递到消息队列,出队的时候执行它们。Handler有两个主要用处:1.调度消息和Runnable在未来的某个时间点执行。2.入队一个操作并在不同的线程上执行。 调度消息通过post,postAtTime(Runnable,long),postDelayed,sendEmptyMessage,sendMessage,sendMessageAtTime,sendMessageDelayed方法完成。post版本允许入队Runnable对象,sendMessage版本允许入队Message对象,Message对象包含的数据会被Handler的handleMessage方法处理(需要自己实现Handler的子类)。 post或send的时候可以允许item在消息队列准备好的时候立即处理,或者定义一个时间延迟或确切的时间处理。 当一个应用的进程创建完,它的主线程专注运行一个消息队列,负责管理高级别的应用对象(Activity,BroadcastReceiver等)和他们创建的window,我们可以创建自己的子线程,并通过Handler和主线程通信。这样是通过在子线程里调用post或sendMessage方法完成。然后Runnable或Message将在Handler的消息队列里调度,在合适的时候处理。 构造函数 Handler总共有7个构造函数 #无参构造函数,内部调用6,callback传null,async传false 1. public Handler() {         this(null, false);     } #设置消息处理回调,内部调用6,async传false 2. public Handler(@Nullable Callback callback) {         this(callback, false);     }   #设置Looper,这样就不用调用Looper.prepare方法,内部调用7 3. public Handler(@NonNull Looper looper) {         this(looper, null, false);     }     #设置Looper和回调,内部调用7 4. public Handler(@NonNull Looper looper, @Nullable Callback callback) {         this(looper, callback, false);     }     #设置消息是否异步,内部调用6 5. public Handler(boolean async) {         this(null, async);     }     #设置回调和是否异步,内部检测looper是否为空,所以所有内部调用这个方法的构造函数都需要先调用Looper.prepare方法初始化Looper和MessageQueue 6. public Handler(@Nullable Callback callback, boolean async) {         if (FIND_POTENTIAL_LEAKS) {             final Class<? extends Handler> klass = getClass();             if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&                     (klass.getModifiers() & Modifier.STATIC) == 0) {                 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +                     klass.getCanonicalName());             }         }         mLooper = Looper.myLooper();         if (mLooper == null) {             throw new RuntimeException(                 "Can't create handler inside thread " + Thread.currentThread()                         + " that has not called Looper.prepare()");         }         mQueue = mLooper.mQueue;         mCallback = callback;         mAsynchronous = async;     }  #设置Looper,回调,是否异步    7. public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {         mLooper = looper;         mQueue = looper.mQueue;         mCallback = callback;         mAsynchronous = async;     } Handler总共有7个构造函数,分别应用在不同场景下,可以设置消息是否异步,设置Looper,回调。 消息处理 … Read more

Android Handler 原理-Looper

Looper在Handler机制中主要在线程中进行消息循环。线程中默认没有关联的Looper。在线程调用Looper.prepare方法创建关联的looper,调用Looper.loop方法处理消息,直到loop停止。大部分与消息循环的交互是通过Handler类。官方文档给出了一个简单实例实现了一个Looper线程,使用prepare和loop常见一个Handler与Looper通信。 class LooperThread extends Thread {          public Handler mHandler;              public void run() {              Looper.prepare();                  mHandler = new Handler() {                  public void handleMessage(Message msg) {                      // process incoming messages here                  }              };                  Looper.loop();          }      } Looper.prepare 通过ThreadLocal存储当前线程的Looper实例 #线程本地变量存储Looper实例 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); final MessageQueue mQueue;#关联的消息队列 final Thread mThread; #创建Looper的线程  public static void prepare() {         prepare(true);     }     private static void prepare(boolean quitAllowed) {         if (sThreadLocal.get() != null) {          #prepare只允许调用一次              throw new RuntimeException("Only one Looper may be created per thread");         }         sThreadLocal.set(new Looper(quitAllowed)); #新实例创建,存入本地线程变量副本     }       private Looper(boolean quitAllowed) {         mQueue = new MessageQueue(quitAllowed); #消息队列实例化         mThread = Thread.currentThread(); #创建的线程     } preprare参数quitAllowed代表跟Looper关联的MessageQueue是否允许退出,默认允许,Looper实例化的同时,也实例化了关联的MessageQueue,给线程变量mThread赋值,主线程的MessageQueue是不允许退出的,在ActivityThread的main方法中调用Looper.prepareMainLooper方法,内部prepare传参为false,表示不允许退出。 消息循环,Looper.loop public static @Nullable Looper myLooper() {         return sThreadLocal.get();     } public static void loop() {         final Looper me = myLooper();         if (me == null) {             #没有调用prepare方法,线程本地变量获取不到looper实例,直接抛出异常              throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");         }         final MessageQueue queue = me.mQueue;        ….         boolean slowDeliveryDetected = false;         for (;;) {            #从MessageQueue取消息,             Message msg = queue.next(); // might block             if (msg == null) {                 // 没有消息,消息队列正在退出 … Read more

Android Handler原理-MessageQueue

MessageQueue是一个低级别的类,持有一个由Looper传递的消息列表。Message不是直接添加到MessageQueue里的。而是由跟Looper关联的Handler对象添加的。 可以通过Looper.myQueue()方法提取跟当前线程关联的MessageQueue。 MessageQueue的构造函数:  MessageQueue(boolean quitAllowed) {         mQuitAllowed = quitAllowed;         mPtr = nativeInit();     } mQuitAllowed表示是否允许退出,mPtr是navite函数的返回值,nativeInit位于C++层/frameworks/base/core/jni/android_os_MessageQueue.cpp文件内: static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {     NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();     if (!nativeMessageQueue) {         jniThrowRuntimeException(env, "Unable to allocate native queue");         return 0;     }    nativeMessageQueue->incStrong(env);     return reinterpret_cast<jlong>(nativeMessageQueue); }     首先实例化NaviteMessageQue,然后强制转换成long类型返回,那么mPtr就是一个指向NativeMessageQueue的指针,MessageQueue初始化的时候由nativeInit赋值,销毁的时候在finalizelize方法里调用dispose方法置零。  private void dispose() {         if (mPtr != 0) {             nativeDestroy(mPtr);             mPtr = 0;         }     }     回过头看构造函数,形参quitAllowed表示是否允许退出消息队列,主线程这个参数会传递成false,表示不允许退出,在ActivityThread的main方法里调用Looper.prepareMainLooper(),最终调用Lopper的构造函数,在构造函数里初始化MessageQueue,quitAllowed传参false。  private Looper(boolean quitAllowed) {         mQueue = new MessageQueue(quitAllowed);         mThread = Thread.currentThread();     } 我们平时调用Looper.prepare(),方法的时候,构造的MessageQueue默认是允许退出的。 消息入队 MessageQueue的消息入队方法是enqueueMessage,改方法在Handler里的enqueueMessage方法里调用。  boolean enqueueMessage(Message msg, long when) {         #判断入队消息对象的handler是否存在,不存在抛出异常         if (msg.target == null) {             throw new IllegalArgumentException("Message must have a target.");         }         #判断消息是否正在使用(消息队列里或回收池里),使用的话抛出异常         if (msg.isInUse()) {             throw new IllegalStateException(msg + " This message is already in use.");         }         synchronized (this) {             if (mQuitting) { #正在退出,回首消息并返回false                 IllegalStateException e = new IllegalStateException(                         msg.target + " sending message to a Handler on a dead thread");                 Log.w(TAG, e.getMessage(), e);                 msg.recycle();                 return false;             }          #消息标记成使用中状态             msg.markInUse();             msg.when = when;             Message p = mMessages;             boolean needWake;             #当前队列头部消息是空,或者when是0(代表立即执行),或执行的时间小于当前队首执行的时间,则把该消息放到队首 … Read more

Anroid Handler原理-Message

    Message翻译过来是消息,顾名思义,在Android中它定义了一个消息体,消息包含了描述和任意的数据对象,可以被发送给Handler。Message对象包含了两个int类型的额外字段,也包含一个object类型的额外字段,可以在多种情况下不分配。 尽管Message的构造器是public类型的,但最好的获取Message对象的方法是调用Message.obtain()方法或者Handler.obtainMessage()方法,这将会从对象回收池中拉取对象。 享元模式(FlyWeight) 享元模式是23种Gof设计模式结构化模式的一种,它在处理大量具有简单重复元素,单独存储会使用大量内存的时候非常有用,通常做法是把共享的数据存储在外部的数据结构里面,使用的时候把他们传递给对象。 Handler.obtainMessage方法内部也是调用的Message.obtain,所以只看后者:  public static final Object sPoolSync = new Object();     private static Message sPool;     private static int sPoolSize = 0; #当前消息池的大小     ….     ….     private static final int MAX_POOL_SIZE = 50;  #缓存消息池最大容量   public static Message obtain() {         synchronized (sPoolSync) {             if (sPool != null) {                 Message m = sPool;                 sPool = m.next;                 m.next = null;                 m.flags = 0; // clear in-use flag                 sPoolSize–;                 return m;             }         }         return new Message();     }     sPool如果是空,则new一个新的Message,非空的话从缓存池获取对象,并清空获取到的Message对象的flags,消息池当前大小减1。此外Message还有几个重载的obtain方法,分别对应不同场景对Message对象设置不同的属性,本质上都是从缓存池拿对象。 消息回收 Message回收共有两个方法,回收就是把Message对象放在全局的回收池,两个方法分别是: recycleUnchecked() /**      * Recycles a Message that may be in-use.回收一个可能在使用的消息      * Used internally by the MessageQueue and Looper when disposing of queued Messages.处理完队列消息时在MessageQueue和Looper内部使用       */     @UnsupportedAppUsage     void recycleUnchecked() {         // Mark the message as in use while it remains in the recycled object pool.         // Clear out all other details.清空其他的详细信息         flags = FLAG_IN_USE;         what = 0;         arg1 = 0;         arg2 = 0;         obj = null;         replyTo = null;         sendingUid = UID_NONE;         workSourceUid = UID_NONE;         when = 0;         target = null;         callback = null;         data = null;             synchronized (sPoolSync) {             if (sPoolSize < MAX_POOL_SIZE) {              #缓存池对象小于50,当前缓存池后移,当前对象放在链表首部,缓存池大小加一                 next = sPool;                 sPool = this;                 sPoolSize++; … Read more

flutter2接入高德地图定位,工具类封装,打包安卓apk,自定义签名

flutter 接入高德地图,打包安卓apk,自定义签名 引入依赖 在pubspec.yaml引入 amap_flutter_location: ^2.0.0 依赖,然后执行 pub get Android 生成签名文件 在项目android/app 目录下打开终端输入 keytool -genkey -v -keystore xipingflutter.jks -keyalg RSA -keysize 2048 -validity 10000 -alias xiping 然后根据依次设置密钥,姓名,单位,组织,地区,别名密钥等: 输入密钥库口令:再次输入新口令:您的名字与姓氏是什么?  [Unknown]:  adam您的组织单位名称是什么?  [Unknown]:  xiping您的组织名称是什么?  [Unknown]:  xiping您所在的城市或区域名称是什么?  [Unknown]:  cn您所在的省/市/自治区名称是什么?  [Unknown]:  cn该单位的双字母国家/地区代码是什么?  [Unknown]:  CNCN=adam, OU=xiping, O=xiping, L=cn, ST=cn, C=CN是否正确?  [否]:  是正在为以下对象生成 2,048 位RSA密钥对和自签名证书 (SHA256withRSA) (有效期为 10,000 天):         CN=adam, OU=xiping, O=xiping, L=cn, ST=cn, C=CN输入 <xiping> 的密钥口令     … Read more