Android中使用System.exit(0)退出后App又重新启动

System.exit(0):终止当前正在运行的 Java 虚拟机。参数用作状态码;根据惯例,非 0 的状态码表示异常终止。

System.exit(0)正常终止程序,有时候在退出安卓应用会使用到。

使用这个方法如果前面存在没有finish()掉的Activity会重新启动,导致退出失败。

MainActivity代码:

直接启动第二个Activity

@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity_main);  
    Intent intent=new Intent(MainActivity.this,NewActivity.class);  
    startActivity(intent);  
}

NewActivity代码:

public class NewActivity extends Activity {  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    Button button=new Button(NewActivity.this);  
    button.setText("退出测试");  
    button.setOnClickListener(new View.OnClickListener() {  
        @Override  
        public void onClick(View view) {  
            System.exit(0);  
        }  
    });  
    setContentView(button);  
 }  
}

此时点击button退出应用重启,修改MainActivity:启动新的Activity,finish存在MainAcitvity

@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity_main);  
    Intent intent=new Intent(MainActivity.this,NewActivity.class);  
    startActivity(intent);  
    this.finish();  
}
总结:

因为应用栈中还存在别的activity没有finish,导致应用重新启动。

使用System.exit(0)时,确保任务栈中所有activity已经finish。

参考链接


Android中使用System.exit(0)退出后app又重新启动

从Intel版本MacBook Pro 2013迁移到MacBook Pro 2023(Apple M2 Pro)后运行Android模拟器崩溃

MacBook Pro 2023(Apple M2 Pro)Intel 版本 MacBook Pro 2013 通过 TimeMachine 迁移后运行模拟器,启动就崩溃。

崩溃的原因是原来运行的模拟器是通过 Intel HAXM 来加速的,但是 MacBook Pro 2023(Apple M2 Pro) 已经是 ARM 指令集了。这样的组合就诱发了虚拟机的崩溃。

此处,我们需要手工卸载已经安装的 Intel x86 Emulator Accelerator(HAXM installer)Android Emulator 之后重新安装即可解决。

如下图:

继续阅读从Intel版本MacBook Pro 2013迁移到MacBook Pro 2023(Apple M2 Pro)后运行Android模拟器崩溃

Android Studio Flamingo | 2022.2.1 Patch 2 配置Robolectric-3.8/4.3.1/4.5.1/4.6.1单元测试环境

一直通过 Android Studio 3.6.3/4.0/4.1/4.2配置Robolectric-3.8/4.3.1/4.5.1/4.6.1 Powermock-1.6.6单元测试环境 配置 Powermock 进行单元测试。

虽然配置起来稍显复杂,但是也是够用的。

但是当升级到Android Studio Flamingo | 2022.2.1 Patch 2 之后,内置的 Java 版本被升级到 Java 17 ,使用这个 Java 版本执行单元测试,会报错如下:

Failed to instantiate DeepCloner. The DeepCloner implementation must have a one-arg constructor taking a Classloader as parameter.
java.lang.RuntimeException: Failed to instantiate DeepCloner. The DeepCloner implementation must have a one-arg constructor taking a Classloader as parameter.
	at org.powermock.classloading.AbstractClassloaderExecutor.createDeepCloner(AbstractClassloaderExecutor.java:91)
	at org.powermock.classloading.AbstractClassloaderExecutor.executeWithClassLoader(AbstractClassloaderExecutor.java:55)
	at org.powermock.classloading.SingleClassloaderExecutor.execute(SingleClassloaderExecutor.java:67)
	at org.powermock.classloading.AbstractClassloaderExecutor.execute(AbstractClassloaderExecutor.java:43)
	at org.powermock.modules.junit4.rule.PowerMockStatement.evaluate(PowerMockRule.java:75)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:591)
	at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:274)
	at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:88)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at org.powermock.classloading.AbstractClassloaderExecutor.createDeepCloner(AbstractClassloaderExecutor.java:89)
	... 12 more
Caused by: java.lang.ExceptionInInitializerError
	at com.thoughtworks.xstream.XStream.setupConverters(XStream.java:845)
	at com.thoughtworks.xstream.XStream.<init>(XStream.java:574)
	at com.thoughtworks.xstream.XStream.<init>(XStream.java:496)
	at com.thoughtworks.xstream.XStream.<init>(XStream.java:465)
	at com.thoughtworks.xstream.XStream.<init>(XStream.java:411)
	at com.thoughtworks.xstream.XStream.<init>(XStream.java:350)
	at org.powermock.classloading.DeepCloner.<init>(DeepCloner.java:33)
	... 18 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field protected java.lang.reflect.InvocationHandler java.lang.reflect.Proxy.h accessible: module java.base does not "opens java.lang.reflect" to unnamed module @5a69b2d1
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
	at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
	at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
	at com.thoughtworks.xstream.core.util.Fields.locate(Fields.java:40)
	at com.thoughtworks.xstream.converters.extended.DynamicProxyConverter.<clinit>(DynamicProxyConverter.java:42)
	... 25 more

可是 Powermock 已经长时间没有新版本发布,没有及时跟进 Java 版本的更新。

当时引入 Powermock 的原因是为了解决静态函数的测试问题,但是从 Mockito 3.4.0 版本开始,Mockito 已经支持静态函数测试。

因此,完全可以只使用 Mockto 进行测试。

// https://mvnrepository.com/artifact/org.mockito/mockito-core
testImplementation 'org.mockito:mockito-core:5.3.1'

待测试代码如下:

public class StaticUtils {

    public static String name() {
        return "name";
    }

    public static String getString(String s) {
        return s;
    }
}

测试代码如下:

import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

public class StaticUtilsTest {

    @Test
    public void givenStaticMethodWithNoArgs_whenMocked_thenReturnsMockSuccessfully() {

        Assert.assertEquals(StaticUtils.name(), "name");

        try (MockedStatic<StaticUtils> utils = Mockito.mockStatic(StaticUtils.class)) {
            utils.when(StaticUtils::name).thenReturn("uname");
            Assert.assertEquals(StaticUtils.name(), "uname");

            utils.when(() -> StaticUtils.getString(ArgumentMatchers.anyString())).thenReturn("target");
            Assert.assertEquals(StaticUtils.getString("123"), "target");
        }

        Assert.assertEquals(StaticUtils.name(), "name");
        Assert.assertEquals(StaticUtils.getString("123"), "123");
    }
}

参考链接


Android之FileProvider详解

简介

Android 7.0 之前,文件的 Urifile:// 形式提供给其他 app 访问。

Android 7.0 之后,分享文件的 Uri 发生了变化。为了安全起见,file:// 形式的Uri 不能正常访问。官方提供了 FileProviderFileProvider生成的Uri会以content://的形式分享给其他app使用。

content形式的Uri可以让其他app临时获得读取(Read)和写入(Write)权限,只要我们在创建 Intent 时,使用 Intent.setFlags() 添加权限。只要接收Uriapp在接收的Activity任务栈中处于活动状态,添加的权限就会一直有效,直到app被任务栈移除。

目的

7.0 以前,为了访问 file:// 形式的 Uri,我们必须修改文件的权限。修改后的权限对所有 app 都是有效的,这样的行为是不安全的。content://形式的UriAndroid的文件系统更安全,对于分享的文件,接收方 app 只拥有临时的权限,减少了我们app内部的文件被其他 app 恶意操作的行为。

使用

创建FileProvider

manifest文件<application>标签中添加pvodier标签,配置如下。

<manifest>
    ...
    <application>
        <activity>
          .....
      	</activity>
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.example.app.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data 
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
    </application>
</manifest>

android:name指定Provider所在的位置。官方自带的FileProvider已经相当完善,所以我们不需要像ServiceBroadcast一样自定义,直接使用自带的就行,直接输入privoder会自动出现提示。

android:authorities相当于一个用于认证的暗号,在分享文件生成Uri时,会通过它的值生成对应的Uri。值是一个域名,一般格式为<包名>.fileprovider

android:exported设置为falseFileProvider不需要公开。

android:grantUriPermissions设置为true,这样就能授权接收端的app临时访问权限了。

设置共享目录

res/xml中创建一个资源文件(如果xml目录不存在,先创建),名字随便(一般叫file_paths.xml)。

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_logs" path="logs/"/>
    ...
</paths>

<paths>必须有1个或多个子标签,每个子标签代表要请求的私有文件目录。不同的子标签代表不同的目录类型。

<provider>标签中添加<meta-data>子标签。

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.app.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

设置<meta-data>的属性android:name值为android.support.FILE_PROVIDER_PATHS,属性android:resouce的值为刚才我们创建的path文件名。

配置paths

<paths>的每个子标签必须有path属性,代表content Uris的路径。name不需要和path保持一样,只是一个名称。

子标签有以下几种。

files-path
<files-path name="my_files" path="path" />

代表内部存储的files目录,与Context.getFilesDir()获取的路径对应。

最终生成的Uri格式为:authorities/pathname/filename

示例:

content://com.example.app.fileprovider/my_files/filexxx.jpg
cache-path
<cache-path name="name" path="path" />

代表内部存储的cache目录,与Context.getCacheDir()获取的路径对应。

external-path
<external-path name="name" path="path" />

代表外部存储(sdcard)的cache目录,与Environment.getExternalStorageDirectory()获取的路径对应。

external-files-path
<external-files-path name="name" path="path" />

代表app的外部存储的根目录,与Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)获取的路径对应。

external-cache-path
<external-cache-path name="name" path="path" />

代表app外部缓存区域的根目录,与Context.getExternalCacheDir()获取的路径对应。

external-media-path
<external-media-path name="name" path="path" />

代表app外部存储媒体区域的根目录,与Context.getExternalMediaDirs()获取的路径对应。

注意: 这个目录只在API 21(也就是Android 5.0)以上的系统上才存在。

生成Content Uri文件

为了让其他app使用Content Uri,我们的app必须提前生成Uri

File filePath = new File(Context.getFilesDir(), "my_log");
File newFile = new File(filePath, "my_log.log");
// 生成Uri
Uri contentUri = FileProvider.getUriForFile(getContext(), "com.example.app.fileprovider", newFile);

这里注意获取目录,在配置paths时我们讲了,paths的子标签必须和获取目录的代码保持对应。这里我们用的是Context.getFilesDir(),所以paths文件中必须包含files-path子标签,不然别的app获取uri时会出现异常。

最终生成Uri是使用的FileProvider.getUriForFile()。第一个参数就是provider中设置的authorities属性值。

授权临时权限

分享一般只有这读取和写入2种权限,根据需要传入Intent.addFlags()中。

// 这里用的是发送文件。
Intent intent = new Intent(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

Uri传入Intent

有好几种传入的方式,根据不同的场景使用不同的方式。

为邮箱app分享附件文件
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
其他分享

使用Intent.setDateIntent.setClipData()

intent.setClipDataClipData.newRawUri("", contentUri);

最后使用startActivity(intent)启动分享操作。

参考链接


Android之FileProvider详解

Android-Bitmap回收/复用机制

包括 recycle() 方法 bitmap 回收时机。

Android 系统版本的内存分配策略也有所不同,这是我们需要了解的:

系统版本 Bitmap 内存分配策略
Android 3.0 之前

Bitmap 对象存放在 Java Heap,而像素数据是存放在 native 内存中。

缺点:如果不手动调用 bitmap.recycle(),Bitmap native 内存的回收完全依赖 finalize() 回调,但是回调时机是不可控的

Android 3.0~7.0

Bitmap 对象和像素数据统一放到 Java Heap,即使不调用 bitmap.recycle(),Bitmap 像素也会随着对象一起被回收。

缺点:

1、Bitmap 全部放在 Java Heap,Bitmap 是内存消耗的大户,而 Max Heap 一般限制为 256、512MB,Bitmap 过大过多容易导致 OOM。

2、容易引起大量 GC,没有充分利用系统的可用内存

Android 8.0 及以后

1、使用了能够辅助回收 native 内存的 NativeAllocationRegistry 实现将像素数据放到 native 内存,并且可以和 Bitmap 对象一起快速释放,在 GC 的时候还可以考虑到这些 Bitmap 内存以防止被滥用。

2、新增硬件位图 Hardware Bitmap 解决图片内存占用过多和图像绘制效率过慢问题

手动调用recycle()

2.3 及以下版本,保存在 jvm + native 中,手动调用 recycle() 。

7.0 和 8.0 不需要调用 recycle(),下面是该版本下代码。

    /**
     * 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.
     */
    //释放与此位图关联的本机对象,并清除对像素数据的引用。 这不会同步释放像素数据;
    //这是一个高级调用,通常不需要调用,因为当没有更多对此位图的引用时,正常的 GC 过程将释放此内存。
    public void recycle() {
        if (!mRecycled) {
            nativeRecycle(mNativePtr);
            mNinePatchChunk = null;
            mRecycled = true;
        }
    }
  
    private static native void nativeRecycle(long nativeBitmap);

7.0 跟 8.0 nativeRecycle 实现不同。

8.0 版本 Bitmap

static jboolean Bitmap_recycle(JNIEnv* env, jobject, jlong bitmapHandle) {
    LocalScopedBitmap bitmap(bitmapHandle);
    bitmap->freePixels();
    return JNI_TRUE;
}

void freePixels() {
    mInfo = mBitmap->info();
    mHasHardwareMipMap = mBitmap->hasHardwareMipMap();
    mAllocationSize = mBitmap->getAllocationByteCount();
    mRowBytes = mBitmap->rowBytes();
    mGenerationId = mBitmap->getGenerationID();
    mIsHardware = mBitmap->isHardware();
    //清空数据
    mBitmap.reset();
}

7.0 版本 Bitmap

static jboolean Bitmap_recycle(JNIEnv *env, jobject, jlong bitmapHandle) {
    LocalScopedBitmap bitmap(bitmapHandle);
    bitmap->freePixels();
    return JNI_TRUE;
}
//析构函数
Bitmap::~Bitmap() {
    doFreePixels();
}

void Bitmap::doFreePixels() {
    switch (mPixelStorageType) {
        //Invalid表示图片已经失效了,一般图片free掉之后就会是这种状态
        case PixelStorageType::Invalid:
            break;
        //外部存储
        case PixelStorageType::External:
            mPixelStorage.external.freeFunc(mPixelStorage.external.address,
                mPixelStorage.external.context);
            break;
        //匿名共享内存,Fresco 低版本内存优化,通过 BitmapFactory.Options.inPurgeable = true 
        //使得 bitmap 通过匿名共享内存方式保存
        case PixelStorageType::Ashmem:
            munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size);
            close(mPixelStorage.ashmem.fd);
            break;
        //java内存,主要看这种
        case PixelStorageType::Java:
            JNIEnv* env = jniEnv();
            LOG_ALWAYS_FATAL_IF(mPixelStorage.java.jstrongRef,
                "Deleting a bitmap wrapper while there are outstanding strong "
                "references! mPinnedRefCount = %d", mPinnedRefCount);
        //DeleteWeakGlobalRef 该函数删除一个弱全局引用,添加待删除的弱全局引用给 jvm
        env->DeleteWeakGlobalRef(mPixelStorage.java.jweakRef);
            break;
        }

        if (android::uirenderer::Caches::hasInstance()) {
            android::uirenderer::Caches::getInstance().textureCache.releaseTexture(
                mPixelRef->getStableID());
    }
}
  1. 8.0 中,手动调用 recycle() 方法,像素数据会立即释放;

  2. 7.0 版本中,调用 recycle() 方法像素数据不会立即释放,而是通过 DeleteWeakGlobalRef 交由 Jvm GC 处理。Java 层主动调用 recycle() 或者在 Bitmap 析构函数 freePixels() ,移除 Global 对象引用,这个对象是 Heap 内存一堆像素的空间。GC 时释放掉。二是 JNI 不再持有 Global Reference,并 native 函数执行后释放掉,但 Java 层的 Bitmap 对象还在,只是它的 mBuffer 和 mNativePtr 是无效地址,没有像素 Heap 的 Bitmap 也就几乎不消耗内存。Java 层 Bitmap 对象什么时候释放,生命周期结束自然会 free 掉。

回收机制

7.0 通过 java 层 BitmapFinalizer.finalize 实现,8.0 通过 native 层 NativeAllocationRegistry 实现。

7.0 及以下,保存在 jvm 虚拟机中,回收机制通过 BitmapFinalizer. finalize 实现。跟 BitmapFinalizer 而不跟 Bitmap 关联的原因是重写 finalize 方法的对象会被延迟回收,经过两次 gc 才会被回收。原因是重写 finalize 的对象在创建时会创建 FinalizerReference 并引用 Object,并将FR 关联到 ReferenceQueue 中,当第一次执行 gc 时候,会把 FR 放在 queue 中,其中有个守护线程持续读取 queue 中的数据,并执行 finalize 方法。

Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
            boolean isMutable, boolean requestPremultiplied,
            byte[] ninePatchChunk, NinePatchInsetStruct ninePatchInsets) {
        if (nativeBitmap == 0) {
            throw new RuntimeException("internal error: native bitmap is 0");
        }

        ...
        mNativePtr = nativeBitmap;
        // 这个对象对象来回收
        mFinalizer = new BitmapFinalizer(nativeBitmap);
        int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0);
        mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);
    }

    private static class BitmapFinalizer {
        private long mNativeBitmap;

        // Native memory allocated for the duration of the Bitmap,
        // if pixel data allocated into native memory, instead of java byte[]
        private int mNativeAllocationByteCount;

        BitmapFinalizer(long nativeBitmap) {
            mNativeBitmap = nativeBitmap;
        }

        public void setNativeAllocationByteCount(int nativeByteCount) {
            if (mNativeAllocationByteCount != 0) {
                //注销 native 内存
                VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount);
            }
            mNativeAllocationByteCount = nativeByteCount;
            if (mNativeAllocationByteCount != 0) {
                VMRuntime.getRuntime().registerNativeAllocation(mNativeAllocationByteCount);
            }
        }

        @Override
        public void finalize() {
            try {
                super.finalize();
            } catch (Throwable t) {
                // Ignore
            } finally {
                // finalize 这里是 GC 回收该对象时会调用
                setNativeAllocationByteCount(0);
                //本地析构函数,释放资源
                nativeDestructor(mNativeBitmap);
                mNativeBitmap = 0;
            }
        }
    }

    private static native void nativeDestructor(long nativeBitmap);

8.0 以上,保存在 jvm + native 中,NativeAllocationRegistry 中把 native 的文件描述符(句柄)跟 Cleaner 关联,Cleaner 其实是个虚应用,会在没有强应用关联的时候进行内存回收。

    // called from JNI
    Bitmap(long nativeBitmap, int width, int height, int density,
            boolean isMutable, boolean requestPremultiplied,
            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
        ...

        mNativePtr = nativeBitmap;
        long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
        //nativeGetNativeFinalizer() 方法传入 NativeAllocationRegistry
        NativeAllocationRegistry registry = new NativeAllocationRegistry(
                Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
        //注册 Java 层对象引用与 native 层对象的地址
        registry.registerNativeAllocation(this, nativeBitmap);
    }

下面是 NativeAllocationRegistry.java 代码

public class NativeAllocationRegistry {

        private final ClassLoader classLoader;
        private final long freeFunction;
        private final long size;

        public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
            ...
            //外部传入的 nativeGetNativeFinalizer ,有用
            this.freeFunction = freeFunction;
            this.size = size;
        }

        public Runnable registerNativeAllocation(Object referent, long nativePtr) {
            ...

            try {
                //注册 native 内存
                registerNativeAllocation(this.size);
            } catch (OutOfMemoryError oome) {
                applyFreeFunction(freeFunction, nativePtr);
                throw oome;
            }
            // Cleaner 绑定 Java 对象与回收函数
            Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr));
            return new CleanerRunner(cleaner);
        }


        private class CleanerThunk implements Runnable {
            private long nativePtr;

            public CleanerThunk(long nativePtr) {
                this.nativePtr = nativePtr;
            }

            public void run() {
                if (nativePtr != 0) {
                    //freeFunction 即为 nativeGetNativeFinalizer() 方法
                    applyFreeFunction(freeFunction, nativePtr);
                }
                registerNativeFree(size);
            }

        }

        private static class CleanerRunner implements Runnable {
            private final Cleaner cleaner;

            public CleanerRunner(Cleaner cleaner) {
                this.cleaner = cleaner;
            }

            public void run() {
                cleaner.clean();
            }
        }

        public static native void applyFreeFunction(long freeFunction, long nativePtr);
    }

NativeAllocationRegistry 内部是利用了 sun.misc.Cleaner.java 机制,简单来说:使用虚引用得知对象被GC的时机,在GC前执行额外的回收工作。

nativeGetNativeFinalizer()
// Bitmap.java
private static native long nativeGetNativeFinalizer();

// Bitmap.cpp
static jlong Bitmap_getNativeFinalizer(JNIEnv*, jobject) {
    // 转为long
    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Bitmap_destruct));
}

static void Bitmap_destruct(BitmapWrapper* bitmap) {
    delete bitmap;
}

nativeGetNativeFinalizer 是一个 native 函数,返回值是一个 long,这个值其实相当于 Bitmap_destruct() 函数的直接地址,Bitmap_destruct() 就是用来回收 native 层内存的。

故 NativeAllocationRegistry 利用虚引用感知 Java 对象被回收的时机,来回收 native 层内存;在 Android 8.0 (API 27) 之前,Android 通常使用Object#finalize() 调用时机来回收 native 层内存

Tips:Fresco 在 Android 4.4 及以下版本对 bitmap 内存做了优化,通过设置 BitmapFactory.Options.inPurgeable = true 使得 bitmap 保存在匿名共享内存中,减少了 Jvm 虚拟机内存占用。

继续阅读Android-Bitmap回收/复用机制

Android Cuttlefish模拟器(Android Cloud)

macOS Big Sur(11.7.5)编译Android 12.0源码过程总结

Android 11系统映像可直接将 ARM 指令转换成 x86 指令,因此以前很多需要真机测试的情况,现在只需要模拟器就可以进行操作了。

不过,根据官方博客 Run ARM apps on the Android Emulator 尾部的一段注意事项:

Note that the ARM to x86 translation technology enables the execution of intellectual property owned by Arm Limited. It will only be available on Google APIs and Play Store system images, and can only be used for application development and debug purposes on x86 desktop, laptop, customer on-premises servers, and customer-procured cloud-based environments. The technology should not be used in the provision of commercial hosted services.

这段事项说明,自己编译的镜像是没办法用上这个功能的,必须是Google编译好的镜像。

前置条件


  • macOS Big Sur(11.7.5)
  • Homebrew (1.1.9 或更高版本)
  • Xcode (13.2.1或更高版本)
  • Android Studio Electric Eel | 2022.1.1 Patch 2

注意:根据Google官方文档,2021年6月22日之后的Android系统版本不支持在macOS系统上构建,我们只能构建之前的版本,或者之后发布的以前版本的补丁修复。目前测试最高只能编译到Android 12.0,  Android  12.1编译不通过。

继续阅读macOS Big Sur(11.7.5)编译Android 12.0源码过程总结

macOS Big Sur(11.7.5)编译Android 11.0源码过程总结

Android 11系统映像可直接将 ARM 指令转换成 x86 指令,因此以前很多需要真机测试的情况,现在只需要模拟器就可以进行操作了。

不过,根据官方博客 Run ARM apps on the Android Emulator 尾部的一段注意事项:

Note that the ARM to x86 translation technology enables the execution of intellectual property owned by Arm Limited. It will only be available on Google APIs and Play Store system images, and can only be used for application development and debug purposes on x86 desktop, laptop, customer on-premises servers, and customer-procured cloud-based environments. The technology should not be used in the provision of commercial hosted services.

这段事项说明,自己编译的镜像是没办法用上这个功能的,必须是Google编译好的镜像。

前置条件


  • macOS Big Sur(11.7.5)
  • Homebrew (1.1.9 或更高版本)
  • Xcode (13.2.1或更高版本)
  • Android Studio Electric Eel | 2022.1.1 Patch 2

注意:根据Google官方文档,2021年6月22日之后的Android系统版本不支持在macOS系统上构建,我们只能构建之前的版本,或者之后发布的以前版本的补丁修复。目前测试最高只能编译到Android 12.0,  Android  12.1编译不通过。

继续阅读macOS Big Sur(11.7.5)编译Android 11.0源码过程总结

ubuntu 22.04.2编译Android 12.1源代码&模拟器

Android 11系统映像可直接将 ARM 指令转换成 x86 指令,因此以前很多需要真机测试的情况,现在只需要模拟器就可以进行操作了。

不过,根据官方博客 Run ARM apps on the Android Emulator 尾部的一段注意事项:

Note that the ARM to x86 translation technology enables the execution of intellectual property owned by Arm Limited. It will only be available on Google APIs and Play Store system images, and can only be used for application development and debug purposes on x86 desktop, laptop, customer on-premises servers, and customer-procured cloud-based environments. The technology should not be used in the provision of commercial hosted services.

这段事项说明,自己编译的镜像是没办法用上这个功能的,必须是Google编译好的镜像。

由于众所周知的原因,我们是没办法正常下载Android的源代码的,因此只能使用国内的镜像来操作了。

1.安装repo工具以及依赖

$ sudo apt-get install gcc make perl

$ sudo apt-get install ccache

$ sudo apt-get install curl

$ sudo apt-get install python3

# 升级pip
$ pip3 install pip --upgrade

# 清理可能的无效缓存
$ pip cache purge

# 安装repo
$ sudo apt-get install repo

$ sudo apt-get install aria2

$ sudo apt-get install git

$ sudo apt install -y libssl-dev

# 解决 
# prebuilts/clang/host/linux-x86/clang-3289846/bin/clang.real: error while loadin
g shared libraries: libncurses.so.5: cannot open shared object file: No such fi
le or directory

$ sudo apt install libncurses5


# 安装VSCode,方便后续查看代码
$ sudo snap install code --classic

$ git config --global user.email "user@email.com"

$ git config --global user.name "user"

2.在需要存储代码的地方创建文件夹

$ mkdir ~/AndSrc

$ cd ~/AndSrc

3.使用镜像下载Android源代码

清华大学的镜像

# 针对ubuntu 22.04不建议直接下载压缩包,建议直接使用repo更新,原因是ubuntu 22.04的python被升级成了python3,python2被完全移除。
# 而清华镜像压缩包里面的repo还是python2时候创建的,会引起各种初始化异常
# aria2 -c https://mirrors.ustc.edu.cn/aosp-monthly/aosp-latest.tar

$ mkdir aosp

$ cd aosp

$ repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest --repo-url=https://gerrit-googlesource.lug.ustc.edu.cn/git-repo

4.Android 模拟器编译(可选)

# Android 12模拟器对应分支 emu-31-release 

$ repo init -b emu-31-release 

# 会失败多次,不断尝试,直到完全成功,目前测试发现单线程虽然慢点,但是基本不会失败,多线程经常失败
$ repo sync -j4 --fail-fast

$ cd external/qemu/android/

$ ./rebuild.sh --no-tests

编译完成之后,产生的模拟器可执行文件及库文件都位于`external/qemu/objs/android`目录下:

~/AndSrc/aosp/external/qemu/android/objs$ ls
android_emu64_unittests           emulator64-mips
android_emu_metrics64_unittests   emulator64_simg2img
bin64                             emulator64_test_crasher
build                             emulator64-x86
emugl64_common_host_unittests     emulator-check
emulator                          lib
emulator64-arm                    lib64
emulator64_crashreport_unittests  lib64GLcommon_unittests
emulator64-crash-service          lib64OpenglRender_unittests
emulator64_img2simg               qemu
emulator64_libui_unittests        resources
emulator64_make_ext4fs

后面就可以像执行 SDK 中的模拟器那样,执行我们编译的模拟器了:

# 下面是个例子,只有设备上已经通过Android SDK 创建过 Nexus_5_API_30_x86,才可以成功执行

~/AndSrc/aosp/external/qemu/android/objs$ ./emulator -avd Nexus_5_API_30_x86

5.列出android-12全部分支

$ cd ~/AndSrc/aosp

$ cd .repo/manifests && git branch -a | cut -d / -f 3 | grep android-12

6.编译Android 12系统镜像

$ cd ~/AndSrc/aosp

# 前面的编译可能会产生部分垃圾文件,需要清理一下,清理之前,编译好的文件提前备份一下	 
$ rm -rf *

$ repo init -b android-12.1.0_r26

# 会失败多次,不断尝试,直到完全成功
$ repo sync -j4 --fail-fast

# 如果上面的命令失败,可以尝试调整为单线程,目前测试发现单线程虽然慢点,但是基本不会失败,多线程经常失败 
# repo sync -j1 --fail-fast

7.引入编译环境变量

$ source build/envsetup.sh

8.设置编译目标,此处我们指定编译x86_64下的完整调试版本(镜像无法安装ARM应用)

# 纯x86系统镜像,不能安装arm应用
# lunch sdk_phone_x86_64

# eng:代表 engineer,开发工程师的版本,拥有最大的权限(root等),具有额外调试工具的开发配置。 
# 执行 lunch 命令可以输出全部的编译目标列表 
# 更多的编译选项可以从 build/make/target/product/ 下看到 
# lunch aosp_x86_arm-eng 编译后的镜像可以安装 arm 应用, 不过在不进行任何代码调整的情况下,应用启动会崩溃 
# 不能安装 arm 应用

$ lunch sdk_phone_x86_64-eng

9.如果需要跟踪调试代码,建议编译为调试类型

$ export TARGET_BUILD_TYPE=debug

10.编译

$ export USE_CCACHE=1  

# 清理代码,为编译准备干净的环境

$ make clobber

# 由于编译的时候内存压力会非常大,即使我们已经配置了足够大的交换分区
# 如果系统内存在16GB左右,非常容易触发 ubuntu 22.04 自带的 systemd-oomd 杀进程操作
# 导致编译过程莫名中断,如果发现编译的时候,命令行窗口突然消失了,一般都是 systemd-oomd引起的
# 此时我们需要暂时停止 systemd-oomd
# sudo systemctl stop systemd-oomd

$ make -j8

注意此处如果发生编译失败,原因基本上是编译顺序导致的引用出错,也就是某些模块还没有编译完成,其他模块已经开始尝试链接,导致依赖错误,此时只要把多线程并发编译修改成单线程编译即可,即直接执行

$ make

如果由于内存不足导致的编译失败,可以增加物理内存。但是如果内存无法增加的话,那么适当增加交换分区/交换文件的大小(建议配置16GB以上的交换分区)可以解决此问题,只是编译速度会下降。

运行镜像

选择system-qemu.img和vendor-qemu.img,这两个镜像是专门为qemu运行制作的,如果选择system.img 和vendor.img,则avd运行失败。

$ cd ~/AndSrc/aosp

$ export ANDROID_BUILD_TOP=~/AndSrc/aosp

$ export PATH=$PATH:$ANDROID_BUILD_TOP/out/host/linux-x86/bin

$ export ANDROID_SWT=$ANDROID_BUILD_TOP/out/host/linux-x86/framework

$ export ANDROID_PRODUCT_OUT=$ANDROID_BUILD_TOP/out/target/product/emulator_x86_64

$ ./prebuilts/android-emulator/linux-x86_64/emulator -verbose -show-kernel

上面运行起来的镜像是从`~/AndSrc/aosp/out/debug/target/product/generic/hardware-qemu.ini`即可读取配置信息的,但是这个文件直接修改无效,我们如果需要修改参数,只能从启动参数中设置。
比如我们如果需要增大内存,开启`GPU`的支持,则执行如下命令:

$ ./prebuilts/android-emulator/linux-x86_64/emulator -gpu on -memory 4096 -verbose -show-kernel
编译支持ARM应用的镜像

尽管根据

Note that the ARM to x86 translation technology enables the execution of intellectual property owned by Arm Limited. It will only be available on Google APIs and Play Store system images, and can only be used for application development and debug purposes on x86 desktop, laptop, customer on-premises servers, and customer-procured cloud-based environments. The technology should not be used in the provision of commercial hosted services.

我们自己编译的镜像是没办法直接从源代码编译支持安装运行 ARM 应用的。

但是有两个变通方案:

  1. Google官方的 Android 12 镜像中提取需要的文件塞到我们自己编译的镜像里,参考方案: Adding ARM native bridge to the AOSP11 x86 emulatorandroid_vendor_google_emu-x86
  2. 使用 Intel Houdini 实现支持,参考方案:Include Intel Houdini in Android-x86

参考链接


Gradle 都做了哪些缓存?

前言

GradleAndroid的构建工具,它的主要目标就是实现快速的编译构建,而这主要就是通过缓存实现的。本文主要介绍Gradle的缓存机制,具体包括以下内容

  1. Gradle缓存机制
  2. Gradle内存缓存
  3. Gradle项目缓存
  4. Gradle本机缓存
  5. Gradle远程缓存

继续阅读Gradle 都做了哪些缓存?