Activity singleInstance/singleTop启动模式的理解

Android有四种启动模式,分别是standard,singleTop,singleTask,singleInstance。下面分别简单的介绍下这四种启动模式的作用。

standard

Android 默认的一种启动模式。不需要为activity设置launchMode。这种启动模式简单的来说就是当你startActivity的时候,他就创建一个。

singleTop

这种模式模式从字面意思就能看得出来,就是当前的activity处于栈顶的时候,当你startActivity当前的activity的时候,它不会创建新的activity,而是会复用之前的activity。举个例子,startActivity了一个ActivityA,ActivityA又startActivity了ActivityB,当在ActivityB再次startActivity一个ActivityB的时候,它不会创建一个新的ActivityB,而是复用之前的ActivityB。
这里需要注意的是,只有当前的activity处于栈顶的时候才管用。举个例子:startActivity了一个ActivityA,ActivityA又startActivity了ActivityB,ActivityB又startActivity了ActivityA,那么ActivityA还是会重新创建,而不是复用之前的ActivityA。

singleTask

单一任务。意思就是说当前的activity只有一个实例,无论在任何地方startActivity出来这个activity,它都只存在一个实例。并且,它会将在他之上的所有activity都销毁。通常这个activity都是用来作为MainActivity。因为主页只需要存在一个,然后回到主页的时候可以将所有的activity都销毁起到退出应用的作用。举个例子,startActivity了一个ActivityA,ActivityA的启动模式为singleTask,那么在ActivityA里startActivity了一个ActivityB,在ActivityB里startActivity了一个ActivityC。此时在当前的任务栈中的顺序是,ActivityA->ActivityB->ActivityC。然后在ActivityC里重新startActivity了一个ActivityA,此时ActivityA会将存在于它之上的所有activity都销毁。所以此时任务栈中就只剩下ActivityA了。

singleInstance

这个模式才是重点,也是比较容易入坑的一种启动模式。字面上理解为单一实例。它具备所有singleTask的特点,唯一不同的是,它是存在于另一个任务栈中。上面的三种模式都存在于同一个任务栈中,而这种模式则是存在于另一个任务栈中。

注意事项

以往的理解中,只要在`AndroidManifest.xml`中声明`singleTop`或者`singleInstance`,那么在使用`startActivity`或者`startActivityForResult`的时候,自动就会保证`singleInstance`的情况下只有一个对象,`singleTop`的情况下不会两个相同的`Activity`叠加在一起。

但是现实是让人崩溃的。

使用如下的代码:

package com.mobibrw.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private final static int SINGLE_INSTANCE = 1;
    private final static int SINGLE_TOP = 2;

    private void startSingleInstanceActivity() {
        final Intent intent = new Intent(this, SingleInstanceActivity.class);
        //intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        startActivityForResult(intent, SINGLE_INSTANCE);
    }

    private void startSingleTopActivity() {
        final Intent intent = new Intent(this, SingleTopActivity.class);
        //intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        startActivityForResult(intent, SINGLE_TOP);
    }

    private void startSingleInstanceWithFlagsActivity() {
        final Intent intent = new Intent(this, SingleInstanceActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        startActivityForResult(intent, SINGLE_INSTANCE);
    }

    private void startSingleTopWithFlagsActivity() {
        final Intent intent = new Intent(this, SingleTopActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        startActivityForResult(intent, SINGLE_TOP);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnSingleTop = findViewById(R.id.SingleTop);
        btnSingleTop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startSingleTopActivity();
                startSingleTopActivity();
            }
        });
        Button btnSingleInstance = findViewById(R.id.SingleInstance);
        btnSingleInstance.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startSingleInstanceActivity();
                startSingleInstanceActivity();
            }
        });

        Button btnSingleTopWithFlags = findViewById(R.id.SingleTopWithFlags);
        btnSingleTopWithFlags.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startSingleTopWithFlagsActivity();
                startSingleTopWithFlagsActivity();
            }
        });
        Button btnSingleInstanceWithFlags = findViewById(R.id.SingleInstanceWithFlags);
        btnSingleInstanceWithFlags.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startSingleInstanceWithFlagsActivity();
                startSingleInstanceWithFlagsActivity();
            }
        });
    }
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.mobibrw.myapplication">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".SingleInstanceActivity"
            android:launchMode="singleInstance"/>
        <activity
            android:name=".SingleTopActivity"
            android:launchMode="singleTop"/>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

在两次触发`startSingleTopActivity`/`startSingleInstanceActivity`的时候,出现了两个叠加的`Activity`,如下:

$ adb shell dumpsys activity | grep SingleTopActivity 
        Hist #2: ActivityRecord{6f4fc4 u0 com.mobibrw.myapplication/.SingleTopActivity t457}
          Intent { cmp=com.mobibrw.myapplication/.SingleTopActivity }
        Hist #1: ActivityRecord{4a4ec71 u0 com.mobibrw.myapplication/.SingleTopActivity t457}
          Intent { cmp=com.mobibrw.myapplication/.SingleTopActivity }
        Run #1: ActivityRecord{6f4fc4 u0 com.mobibrw.myapplication/.SingleTopActivity t457}
    mResumedActivity: ActivityRecord{6f4fc4 u0 com.mobibrw.myapplication/.SingleTopActivity t457}
  mFocusedActivity: ActivityRecord{6f4fc4 u0 com.mobibrw.myapplication/.SingleTopActivity t457}

可以看到出现两个单独的实例 Hist #2/Hist #1,而不是预期的忽略第二次调用。

在两次触发`startSingleTopWithFlagsActivity`/`startSingleInstanceWithFlagsActivity`的时候,只会出现了一个`Activity`,如下:

$ adb shell dumpsys activity | grep SingleTopActivity
        Hist #1: ActivityRecord{933a335 u0 com.mobibrw.myapplication/.SingleTopActivity t457}
          Intent { flg=0x420000 cmp=com.mobibrw.myapplication/.SingleTopActivity }
        Run #1: ActivityRecord{933a335 u0 com.mobibrw.myapplication/.SingleTopActivity t457}
    mResumedActivity: ActivityRecord{933a335 u0 com.mobibrw.myapplication/.SingleTopActivity t457}
  mFocusedActivity: ActivityRecord{933a335 u0 com.mobibrw.myapplication/.SingleTopActivity t457}

两者的区别就是

intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);

这句代码。

还是学艺不精啊。

完整例子代码,点击此处下载 MyApplication

参考链接


Gradle: 一个诡异的问题(ERROR: Failed to parse XML AndroidManifest.xml ParseError at [row,col]:[5,5] Message: expected start or end tag)

今天同事说他下了一个老版本的Android Studio项目死活编不过,我心想不就是一个项目么,编不过要么就是代码有问题,要么就是依赖库不完整这能有什么问题,于是自己在自己电脑试了下,结果自己也中招了:

继续阅读Gradle: 一个诡异的问题(ERROR: Failed to parse XML AndroidManifest.xml ParseError at [row,col]:[5,5] Message: expected start or end tag)

error: cannot find symbol import android.support.v7.widget.ListViewCompat;

项目中,当`com.android.support:appcompat-v7`升级到`28`之后,出现如下错误

~/Source/TimeFlow/lib-ui/src/main/java/com/mobibrw/lib/ui/SwipeListView/SwipeListView.java:194: 错误: 找不到符号
import android.support.v7.widget.ListViewCompat;
                                ^
  符号:   类 ListViewCompat
  位置: 程序包 android.support.v7.widget

这个原因是由于`com.android.support:appcompat-v7`升级到`28`之后

implementation 'com.android.support:appcompat-v7:28.+'

已经不包含 `android.support.v7.widget.ListViewCompat;`这个类了。

我们要么使用`android.support.v4.widget.ListViewCompat;`替代,要么直接使用`android.widget.ListView`。

目前的解决方法是直接使用`android.widget.ListView`

参考链接


加快Android Studio 3.6.3的编译速度

1. 加大给Android Studio 3.6.3分配的内存

打开`Android Studio`的安装目录,找到虚拟机的配置文件,进行修改,如下:

Windows
编辑安装目录下的`bin\studio64.exe.vmoptions`

macOS Catalina(10.15.4)

$ sudo vim /Applications/Android\ Studio.app/Contents/bin/studio.vmoptions

继续阅读加快Android Studio 3.6.3的编译速度

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单元测试环境

基础配置以及常见错误


目前版本的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 ,尽管软件源中的版本已经更新升级到2.0.7版本了,但是Robolectric并没有及时更新依赖。尝试过使用最新版本的PowerMock,结果很多莫名的报错。

    // 单元测试
    testImplementation "com.android.support.test:runner:1.0.2"
    testImplementation "junit:junit:4.13.2"
    testImplementation "org.robolectric:robolectric:4.5.1"

    // 注意AndroidX环境下,不能引入shadows-supportv4,Jetifier转换的时候报错
    testImplementation "org.robolectric:shadows-supportv4:4.5.1"

    // MultiDex支持
    testImplementation "org.robolectric:shadows-multidex:4.5.1"
    
    // 此处版本更新需要特别小心,目前 robolectric 跟 powermock 存在兼容问题,
    // 需要固定的某些版本才能正常配合
    // 依赖关系参考 
    // https://github.com/robolectric/robolectric/wiki/Using-PowerMock
    testImplementation "org.powermock:powermock-module-junit4:1.6.6"
    testImplementation "org.powermock:powermock-module-junit4-rule:1.6.6"
    testImplementation "org.powermock:powermock-classloading-xstream:1.6.6"
    testImplementation "org.powermock:powermock-api-mockito:1.6.6"

如果 Windows 系统下出现如下提示,受限于Windows系统路径不能超过260个字符的限制

13:36 Error running 'xxxTest': Command line is too long. Shorten command line for xxxTest or also for Android JUnit default configuration.

貌似Windows 10 Build 14352版中已经可以通过修改注册表去除260字符的路径长度限制了。

在项目`/.idea/workspace.xml`文件中添加一行代码如下

<component name="PropertiesComponent">
    ...
    <property name="dynamic.classpath" value="true" />
</component>

对于使用MultiDex的应用,如果报告如下错误:

java.lang.RuntimeException: MultiDex installation failed (/var/folders/h6/4__l0pyn32j1mkkysynnxydr0000gn/T/robolectric-Method_xxxxxxxxxxxx_isCorrect4035485067136008322/com.xxxx.xxxx.app-sourceDir (Is a directory)).

	at android.support.multidex.MultiDex.install(MultiDex.java:121)
	at com.xxx.xx.app.xx.application.xxxxxx.attachBaseContext(MyApplication.java:212)
	at android.app.Application.attach(Application.java:212)
	at org.robolectric.util.ReflectionHelpers.callInstanceMethod(ReflectionHelpers.java:283)
	at org.robolectric.shadows.ShadowApplication.callAttach(ShadowApplication.java:79)
	at org.robolectric.android.internal.AndroidTestEnvironment.installAndCreateApplication(AndroidTestEnvironment.java:250)
	at org.robolectric.android.internal.AndroidTestEnvironment.setUpApplicationState(AndroidTestEnvironment.java:169)
	at org.robolectric.RobolectricTestRunner.beforeTest(RobolectricTestRunner.java:301)
	at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:243)
	at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

需要在`build.gradle`中引入

 
    // MultiDex支持
    testImplementation "org.robolectric:shadows-multidex:4.5.1"

例子测试代码:

package com.mobibrw.example;

import org.junit.Test;
import static org.junit.Assert.*;

import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.multidex.ShadowMultiDex;

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(RobolectricTestRunner.class)
@PowerMockIgnore("jdk.internal.reflect.*")
@Config(shadows = ShadowMultiDex.class)
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
}

注意,需要

@Config(shadows = ShadowMultiDex.class)

才能使得配置生效。

如果发生如下错误:

java.lang.VerifyError: Expecting a stackmap frame at branch target 43
Exception Details:
  Location:
    com/meizu/cloud/pushsdk/base/IntentReceiver.onReceive(Landroid/content/Context;Landroid/content/Intent;)V @6: ifne
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0x0000000: b200 08b6 0013 9a00 25bb 0004 5912 0110
    0x0000010: 0ab7 000e 59b3 000a b600 10bb 0003 59b2
    0x0000020: 000a b600 0fb7 000c b300 09b2 0009 bb00
    0x0000030: 0659 2a2b 2cb7 0011 b600 0d57 b1       


	at java.lang.Class.getDeclaredConstructors0(Native Method)
	at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
	at java.lang.Class.getConstructor0(Class.java:3075)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at org.robolectric.util.ReflectionHelpers.callConstructor(ReflectionHelpers.java:396)
	at org.robolectric.internal.bytecode.ShadowImpl.newInstanceOf(ShadowImpl.java:18)
	at org.robolectric.shadow.api.Shadow.newInstanceOf(Shadow.java:35)
	at org.robolectric.android.internal.AndroidTestEnvironment.registerBroadcastReceivers(AndroidTestEnvironment.java:541)
	at org.robolectric.android.internal.AndroidTestEnvironment.installAndCreateApplication(AndroidTestEnvironment.java:265)
	at org.robolectric.android.internal.AndroidTestEnvironment.setUpApplicationState(AndroidTestEnvironment.java:169)
	at org.robolectric.RobolectricTestRunner.beforeTest(RobolectricTestRunner.java:301)
	at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:243)
	at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

继续阅读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单元测试环境

Android Studio 3.6.2编译报错“GC overhead limit exceeded”

`Android Studio 3.6.2` 编译报错 “GC overhead limit exceeded”,详细的报错信息如下:

Problem in daemon expiration check
java.lang.OutOfMemoryError: GC overhead limit exceeded
	at org.gradle.cache.internal.DefaultFileLockManager$DefaultFileLock.<init>(DefaultFileLockManager.java:158)
	at org.gradle.cache.internal.DefaultFileLockManager.lock(DefaultFileLockManager.java:110)
	at org.gradle.cache.internal.DefaultFileLockManager.lock(DefaultFileLockManager.java:96)
	at org.gradle.cache.internal.DefaultFileLockManager.lock(DefaultFileLockManager.java:91)
	at org.gradle.cache.internal.OnDemandFileAccess.readFile(OnDemandFileAccess.java:41)
	at org.gradle.cache.internal.SimpleStateCache.get(SimpleStateCache.java:49)
	at org.gradle.cache.internal.FileIntegrityViolationSuppressingPersistentStateCacheDecorator.get(FileIntegrityViolationSuppressingPersistentStateCacheDecorator.java:34)
	at org.gradle.launcher.daemon.registry.PersistentDaemonRegistry.getAll(PersistentDaemonRegistry.java:70)
	at org.gradle.launcher.daemon.registry.PersistentDaemonRegistry.getDaemonsMatching(PersistentDaemonRegistry.java:115)
	at org.gradle.launcher.daemon.registry.PersistentDaemonRegistry.getIdle(PersistentDaemonRegistry.java:83)
	at org.gradle.launcher.daemon.server.CompatibleDaemonExpirationStrategy.checkExpiration(CompatibleDaemonExpirationStrategy.java:54)
	at org.gradle.launcher.daemon.server.expiry.AllDaemonExpirationStrategy.checkExpiration(AllDaemonExpirationStrategy.java:46)
	at org.gradle.launcher.daemon.server.expiry.AnyDaemonExpirationStrategy.checkExpiration(AnyDaemonExpirationStrategy.java:43)
	at org.gradle.launcher.daemon.server.MasterExpirationStrategy.checkExpiration(MasterExpirationStrategy.java:73)
	at org.gradle.launcher.daemon.server.Daemon$DaemonExpirationPeriodicCheck.run(Daemon.java:269)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.lang.Thread.run(Thread.java:748)
org.gradle.internal.event.ListenerNotificationException: Failed to notify output event listener.
	at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:86)
	at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:325)
	at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:235)
	at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:141)
	at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:37)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy0.onOutput(Unknown Source)
	at org.gradle.internal.logging.sink.OutputEventTransformer.onOutput(OutputEventTransformer.java:104)
	at org.gradle.internal.logging.sink.OutputEventRenderer.onOutput(OutputEventRenderer.java:431)
	at org.gradle.internal.logging.sink.OutputEventListenerManager$1.onOutput(OutputEventListenerManager.java:36)
	at org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger.log(OutputEventListenerBackedLogger.java:160)
	at org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger.log(OutputEventListenerBackedLogger.java:152)
	at org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger.error(OutputEventListenerBackedLogger.java:504)
	at org.gradle.launcher.daemon.server.Daemon$DaemonExpirationPeriodicCheck.run(Daemon.java:274)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
Expiring Daemon because JVM heap space is exhausted
Daemon will be stopped at the end of the build after running out of JVM memory
Expiring Daemon because JVM heap space is exhausted

> Task :app:compileDebugJavaWithJavac FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:compileDebugJavaWithJavac'.
> GC overhead limit exceeded

这个问题是由于编译过程中的Java的内存开销太大超过了默认限制导致的,解决方法就是增大Java的内存限制。

具体操作为在项目的 `gradle.properties` 中修改内存限制 `org.gradle.jvmargs` 默认情况下,这个限制在 `1536m` 我们需要拉大到 `4096m`。

修改后的内容如下:

# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx4096m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

如果这个文件不存在,则在工程文件 `build.gradle` ,`gradlew` , `local.properties` 等相同的目录下新建 `gradle.properties` 这个文件即可。

参考链接


ubuntu 18.04 Android Studio运行模拟器时提示“/dev/kvm device: permission denied”

升级 `ubuntu` 系统, 从 `16.04.5` 升级到 `18.04.1` ,接着又开始配置各种软件环境。

当配置好 `Android` 开发环境,准备创建一个模拟器并运行程序环境看是否OK时,问题出现了。

创建和运行时都提示:`/dev/kvm device: permission denied` 或者 `/dev/kvm device: open failed`,而且模拟器跑不起来。

执行命令查看:

$ ls -al /dev/kvm
crw------- 1 root root 10, 232 11月 17 22:37 /dev/kvm

需要安装 `qemu-kvm` 并把当前用户加入到 `kvm` 用户组即可:

$ sudo apt install qemu-kvm

$ sudo adduser `whoami` kvm

$ ls -al /dev/kvm
crw-rw---- 1 root kvm 10, 232 11月 18 14:40 /dev/kvm

然后运行模拟器。

如果依旧报错,则需要修改 /dev/kvm 的所有者为当前用户,如下:

$ sudo chown `whoami` /dev/kvm

参考链接


Android Studio 3.5.2最小化接入weex 0.28.0实践

目前在尝试使用weex,但是在搭建基础的最小化项目的时候,官方文档描述的相当佛系,导致搭建出来的项目没办法正常运行。

下面我们探讨一下,使用`Android Studio 3.5.2`新建一个项目,实现最小化接入`weex 0.28.0`实践过程。

官方文档要求在项目的`build.gradle` 中增加如下内容:

implementation 'org.apache.weex:sdk:0.28.0@aar'
implementation 'com.alibaba:fastjson:1.2.62'

但是实际上,由于`weex 0.28.0`的调整,以前版本自动引入的`facebook`提供的`JS`引擎js-android,现在被修改为需要手工引入,但是文档没有清晰的指出这个问题,导致运行的时候,会由于找不到`libjsc.so`而导致`WXSDKEngine`初始化失败。

官方提供了一个`download_jsc.gradle`的脚本解决这个问题(这个脚本的功能仅仅是下载`libjsc.so` ),需要在项目的 `build.gradle` 的头部增加这个脚本文件的引用:

apply from: 'https://raw.githubusercontent.com/apache/incubator-weex/release/0.28/android/sdk/buildSrc/download_jsc.gradle'

如果下载不成功,也可从本站下载

apply from: 'http://www.mobibrw.com/wp-content/uploads/2019/11/download_jsc.gradle'

完成后的`build.gradle`中完整内容如下:

apply plugin: 'com.android.application'
apply from: 'https://raw.githubusercontent.com/apache/incubator-weex/release/0.28/android/sdk/buildSrc/download_jsc.gradle'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.mobibrw.weex"
        minSdkVersion 22
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        ndk {
            abiFilters "armeabi","armeabi-v7a", "arm64-v8a", "x86"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'org.apache.weex:sdk:0.28.0@aar'
    implementation 'com.alibaba:fastjson:1.2.62'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

接下来,就是具体的代码部分了,如下,需要自定义一个`Application`类,在`Application`的初始化部分初始化`WXSDKEngine`,代码如下:

package com.mobibrw.weex;

import android.app.Application;
import org.apache.weex.InitConfig;
import org.apache.weex.WXSDKEngine;

public class WeexApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        final InitConfig config = new InitConfig.Builder().build();
        /**
         * 底层的初始化是异步执行的,尤其是初始化JS引擎部分的代码(WXBridgeManager),是相当耗时的
         * 因此,在调用完初始化之后,Activity第一次调用的时候,一定要增加是否已经初始化完成的判断
         * 如果没有完成初始化,适当的增加延迟等待的代码
         **/
        WXSDKEngine.initialize(this, config);
    }

}

接下来,就是具体的`Activity`内容展现代码部分了,代码如下:

package com.mobibrw.weex;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import org.apache.weex.IWXRenderListener;
import org.apache.weex.WXSDKEngine;
import org.apache.weex.WXSDKInstance;
import org.apache.weex.common.WXRenderStrategy;

public class MainActivity extends AppCompatActivity implements IWXRenderListener {

    private WXSDKInstance wXSDKInstance;

    private static final String TAG = "WXSampleTAG";

    private void loadPages() {
        /**
         * bundleUrl source http://dotwe.org/vue/38e202c16bdfefbdb88a8754f975454c
         */
        String pageName = "WXSample";
        String bundleUrl = "http://dotwe.org/raw/dist/38e202c16bdfefbdb88a8754f975454c.bundle.wx";
        wXSDKInstance.renderByUrl(pageName, bundleUrl, null, null, WXRenderStrategy.APPEND_ASYNC);
    }

    private void asyncLoadPages(final View view) {
        /**
         * 此处一定要判断WXSDKEngine是否已经成功初始化了,由于WXSDKEngine底层初始化的库非常多
         * 导致整个的初始化非常的耗时,并且这个初始化是异步执行的,尤其是初始化JS引擎部分的代码(WXBridgeManager)。
         * 因此有非常大的概率导致当第一次使用Week的API的时候,底层还没有完成初始化
         * 导致出现错信息 "degradeToH5|createInstance fail|wx_create_instance_error isJSFrameworkInit==false reInitCount == 1"
         * 这段耗时可以通过在程序启动的时候增加启动等待页面来人性化的忽略这部分耗时。
         **/
        if(!WXSDKEngine.isInitialized()) {
            view.postDelayed(new Runnable() {
                @Override
                public void run() {
                    asyncLoadPages(view);
                }
            }, 1);
        } else {
            loadPages();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final View view = findViewById(R.id.activity_main);

        wXSDKInstance = new WXSDKInstance(this);
        wXSDKInstance.registerRenderListener(this);
        asyncLoadPages(view);
    }

    @Override
    public void onPointerCaptureChanged(boolean hasCapture) {

    }

    @Override
    public void onViewCreated(WXSDKInstance instance, View view) {
        setContentView(view);
    }

    @Override
    public void onRenderSuccess(WXSDKInstance instance, int width, int height) {
        Log.i(TAG, "onRenderSuccess");
    }

    @Override
    public void onRefreshSuccess(WXSDKInstance instance, int width, int height) {
        Log.i(TAG, "onRefreshSuccess");
    }

    @Override
    public void onException(WXSDKInstance instance, String errCode, String msg) {
        Log.e(TAG, "onException:" + msg);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if(wXSDKInstance!=null){
            wXSDKInstance.onActivityResume();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(wXSDKInstance!=null){
            wXSDKInstance.onActivityPause();
        }
    }
    @Override
    protected void onStop() {
        super.onStop();
        if(wXSDKInstance!=null){
            wXSDKInstance.onActivityStop();
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(wXSDKInstance!=null){
            wXSDKInstance.onActivityDestroy();
        }
    }
}

需要注意的是`WXSDKEngine`是异步初始化的,导致在首次调用的时候,可能会因为没有正常初始化而出现异常,因此需要等待初始化完成。

具体的例子项目在这里下载 Weex

鉴于开源项目经常性找不到文件,因此记录下来 http://dotwe.org/raw/dist/38e202c16bdfefbdb88a8754f975454c.bundle.wx 这个文件里面的内容:

// { "framework": "Vue" }

/******/
(function(modules) { // webpackBootstrap
    /******/ // The module cache
    /******/
    var installedModules = {};

    /******/ // The require function
    /******/
    function __webpack_require__(moduleId) {

        /******/ // Check if module is in cache
        /******/
        if (installedModules[moduleId])
        /******/
            return installedModules[moduleId].exports;

        /******/ // Create a new module (and put it into the cache)
        /******/
        var module = installedModules[moduleId] = {
            /******/
            exports: {},
            /******/
            id: moduleId,
            /******/
            loaded: false
                /******/
        };

        /******/ // Execute the module function
        /******/
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

        /******/ // Flag the module as loaded
        /******/
        module.loaded = true;

        /******/ // Return the exports of the module
        /******/
        return module.exports;
        /******/
    }


    /******/ // expose the modules object (__webpack_modules__)
    /******/
    __webpack_require__.m = modules;

    /******/ // expose the module cache
    /******/
    __webpack_require__.c = installedModules;

    /******/ // __webpack_public_path__
    /******/
    __webpack_require__.p = "";

    /******/ // Load entry module and return exports
    /******/
    return __webpack_require__(0);
    /******/
})
/************************************************************************/
/******/
([
    /* 0 */
    /***/
    function(module, exports, __webpack_require__) {

        'use strict';

        var _e202c16bdfefbdb88a8754f975454c = __webpack_require__(1);

        var _e202c16bdfefbdb88a8754f975454c2 = _interopRequireDefault(_e202c16bdfefbdb88a8754f975454c);

        function _interopRequireDefault(obj) {
            return obj && obj.__esModule ? obj : {
                default: obj
            };
        }

        _e202c16bdfefbdb88a8754f975454c2.default.el = '#root';
        new Vue(_e202c16bdfefbdb88a8754f975454c2.default);

        /***/
    },
    /* 1 */
    /***/
    function(module, exports, __webpack_require__) {

        var __vue_exports__, __vue_options__
        var __vue_styles__ = []

        /* styles */
        __vue_styles__.push(__webpack_require__(2))

        /* script */
        __vue_exports__ = __webpack_require__(3)

        /* template */
        var __vue_template__ = __webpack_require__(4)
        __vue_options__ = __vue_exports__ = __vue_exports__ || {}
        if (
            typeof __vue_exports__.default === "object" ||
            typeof __vue_exports__.default === "function"
        ) {
            if (Object.keys(__vue_exports__).some(function(key) {
                    return key !== "default" && key !== "__esModule"
                })) {
                console.error("named exports are not supported in *.vue files.")
            }
            __vue_options__ = __vue_exports__ = __vue_exports__.default
        }
        if (typeof __vue_options__ === "function") {
            __vue_options__ = __vue_options__.options
        }
        __vue_options__.__file = "/usr/src/app/raw/38e202c16bdfefbdb88a8754f975454c.vue"
        __vue_options__.render = __vue_template__.render
        __vue_options__.staticRenderFns = __vue_template__.staticRenderFns
        __vue_options__._scopeId = "data-v-02e1e786"
        __vue_options__.style = __vue_options__.style || {}
        __vue_styles__.forEach(function(module) {
            for (var name in module) {
                __vue_options__.style[name] = module[name]
            }
        })
        if (typeof __register_static_styles__ === "function") {
            __register_static_styles__(__vue_options__._scopeId, __vue_styles__)
        }

        module.exports = __vue_exports__


        /***/
    },
    /* 2 */
    /***/
    function(module, exports) {

        module.exports = {
            "text": {
                "fontSize": 50
            }
        }

        /***/
    },
    /* 3 */
    /***/
    function(module, exports) {

        'use strict';

        Object.defineProperty(exports, "__esModule", {
            value: true
        });
        //
        //
        //
        //
        //
        //
        //
        //
        //
        //

        exports.default = {
            data: function data() {
                return {
                    text: 'Hello World.'
                };
            }
        };

        /***/
    },
    /* 4 */
    /***/
    function(module, exports) {

        module.exports = {
            render: function() {
                var _vm = this;
                var _h = _vm.$createElement;
                var _c = _vm._self._c || _h;
                return _c('div', [_c('text', {
                    staticClass: ["text"]
                }, [_vm._v(_vm._s(_vm.text))])])
            },
            staticRenderFns: []
        }
        module.exports.render._withStripped = true

        /***/
    }
    /******/
]);

参考链接


Android Studio 3.5.1配置NDK路径

早期版本的Android Studio在全局配置NDK的路径信息,但是从Android Studio 3.4版本开始,NDK的路径信息被转移到Project Structure部分去配置了,这变成了一个工程相关的配置,每个工程可以单独配置独立的NDKSDK版本。

具体操作如下图:

继续阅读Android Studio 3.5.1配置NDK路径

Android Gradle Plugin源码解析之externalNativeBuild

在Android Studio 2.2开始的Android Gradle Plugin版本中,Google集成了对cmake的完美支持,而原先的ndkBuild的方式支持也变得更加良好。这篇文章就来说说Android Gradle Plugin与交叉编译之间的一些事,即externalNativeBuild相关的task,主要是解读一下gradle构建系统相关的源码。

继续阅读Android Gradle Plugin源码解析之externalNativeBuild