KICAD TUTORIAL-Make Your First Printed Circuit Board
Debian10升级11(buster升级bullseye)
2021年8月14日,Debian11正式发布了,代号“bullseye” Debian 11 (bullseye), 64-bit PC 的发行说明
下面演示下Debian10升级到Debian11
Jdk 6260652 Bug
最近在看JDK的源码:CopyOnWriteArrayList.java和ArrayList.java,这2个类的构造函数,注释中有一句话看不懂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); setArray(elements); } |
上网查了一下资料,才知道see 6260652 这个编号代表JDK bug库中的编号。可以去官网查看bug详情
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6515694
6260652 和6515694这2个bug,貌似是同一个问题。这个bug是什么意思呢?我们先来看看一些测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public static void test1() { SubClass[] subArray = {new SubClass(), new SubClass()}; System.out.println(subArray.getClass()); // class [Lcollection.SubClass; BaseClass[] baseArray = subArray; System.out.println(baseArray.getClass()); // java.lang.ArrayStoreException baseArray[0] = new BaseClass(); } public static void test2() { List<String> list = Arrays.asList("abc"); // class java.util.Arrays$ArrayList System.out.println(list.getClass()); // class [Ljava.lang.String; Object[] objArray = list.toArray(); System.out.println(objArray.getClass()); objArray[0] = new Object(); // cause ArrayStoreException } public static void test3() { List<String> dataList = new ArrayList<String>(); dataList.add("one"); dataList.add("two"); Object[] listToArray = dataList.toArray(); // class [Ljava.lang.Object;返回的是Object数组 System.out.println(listToArray.getClass()); listToArray[0] = ""; listToArray[0] = 123; listToArray[0] = new Object(); } |
1、关于test1()
SubClass 继承自BaseClass,由于SubClass数组中每一个元素都是SubClass对象,所以
1 |
BaseClass[] baseArray = subArray; |
这种强制类型转换不会报错。这其实就是java对象的向上转型,子类数组转换成父类数组是允许的。但是由于数组中元素类型都是SubClass类型的,所以
1 |
baseArray[0] = new BaseClass(); |
会报错
1 |
java.lang.ArrayStoreException |
这也就是说假如我们有1个Object[]数组,并不代表着我们可以将Object对象存进去,这取决于数组中元素实际的类型。
2、关于test2()
1 |
List<String> list = Arrays.asList("abc"); |
需要注意,可以知道返回的实际类型是
1 |
java.util.Arrays$ArrayList |
而不是
1 |
ArrayList |
我们调用
1 |
Object[] objArray = list.toArray(); |
返回是String[]数组,所以我们不能将Object对象,放到objArray数组中。
3、关于test3()
ArrayList对象的toArray()返回就是Object[]数组,所以我们可以将任意对象存放到返回的Object[]数组中。
通过test2和test3可以看出,如果我们有1个
1 |
List<String> stringList |
对象,当我么调用
1 |
Object[] objectArray = stringList.toArray(); |
的时候,objectArray 并不一定能够放置Object对象。这就是源码中的注释:
1 |
//c.toArray might (incorrectly) not return Object[] (see 6260652) |
为了考虑这种情况,所以源码中进行了if判断,来防止错误的数组对象导致异常。
1 |
Arrays.copyOf(elementData, size, Object[].class); |
这个方法就是用来创建1个Object[]数组,这样数组中就可以存放任意对象了。
参考链接
Android跳转权限设置页面
最近项目上有个需求,读取通讯录。当用户点了拒绝访问通讯录或者其他权限,导致无法使用,这时候我想重新打开权限设置,但是对于很多小白用户不知道怎么设置,这就会导致用户体验不友好的一面。
之前已经有人写过类似的文章,不过都比较分散,经实测将这些方法总结了一下。
要跳转的权限设置界面如图:目前手上只有华为和小米作为测试
国产手机Android R(Android 11)系统执行Espresso UI(AndroidX)遇到的问题总结
最近在 Android R(Android 11)系统上执行以前写的Espresso UI(AndroidX)单元测试用例的时候,发现在 联想拯救者电竞手机Pro(128GB) 系统版本 12.5.225上,如果测试用例申请了通讯录写入权限,如下:
1 |
<uses-permission android:name="android.permission.WRITE_CONTACTS" /> |
那么测试用例执行的时候,会报告如下错误:
1 |
Test running failed:Process crashed. |
详细的logcat日志如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
D/Zygote: Forked child process 10664 I/ActivityManager: Start proc 10664:xx.test/u0a274 for added application xx.test I/AutoRun: AutoRunServices: ProcessStateHandler: sendKillAppRequest: [uid = 10274, pid = 10664, package name = xx.test mOtherPids = null, mStartupTime = 0, mDelay = 0, mCount = 0] I/adbd: jdwp connection from 10664 D/ApplicationLoaders: Returning zygote-cached class loader: /system/framework/android.test.base.jar D/ApplicationLoaders: Returning zygote-cached class loader: /system/framework/android.test.base.jar I/yboard.biz.tes: The ClassLoaderContext is a special shared library. D/ApplicationLoaders: Returning zygote-cached class loader: /system/framework/android.test.base.jar D/ApplicationLoaders: Returning zygote-cached class loader: /system/framework/android.test.base.jar I/Perf: Connecting to perf service. D/NetworkSecurityConfig: No Network Security Config specified, using platform default D/NetworkSecurityConfig: No Network Security Config specified, using platform default I/MonitoringInstr: Instrumentation started! I/AndroidJUnitRunner: Waiting for debugger to connect... I/System.out: Sending WAIT chunk I/ActivityManager: Killing 10664:xx.test/u0a274 (adj 0): permissions revoked I/Zygote: Process 10664 exited due to signal 9 (Killed) I/libprocessgroup: Successfully killed process cgroup uid 10274 pid 10664 in 44ms |
目前测试发现,即使通过GrantPermissionRule授予权限,也是没用的,代码根本来不及执行,应用就一下被系统杀死了。
1 2 |
@Rule public final GrantPermissionRule rule = GrantPermissionRule.grant(android.Manifest.permission.WRITE_CONTACTS); |
在OPPO/VIVO/小米等手机上,需要手工开启 后台弹出界面权限 才能执行单元测试。有个思路是通过UI Automator来自动开启这个权限,暂时还需要测试一下。
参考链接
- No Network Security Config specified, using platform default
- No Network Security Config specified, using platform default - Android Log
- Android revoke permission at start of each test
- Permissions updates in Android 11
- Android revoke permission at start of each test
- Android Marshmallow: Test permissions with Espresso?
- AndroidTestMockPermissionUtils
- Android 权限适配 从此第三方系统新增的权限无法判断状态的问题得到解决! 如MIUI自启动, 后台弹出界面权限等
- 实战|Android后台启动Activity实践之路
- Espresso和UIAutomator - 完美的结合
- UI Automator
Android R(11) ContentResolver报错java.lang.IllegalArgumentException: Invalid token limit
在Android 11执行如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/** * 保存拍照的图片是否已经完成 * * @param context Context 对象 * @return 查询成功返回 true ,否则返回false */ private static boolean isSavePictureComplete(@NonNull final Context context, long takeTime) { //扫描图片 final Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; final ContentResolver resolver = context.getContentResolver(); final Cursor cursor = resolver.query(uri, new String[]{ MediaStore.Images.Media.DATE_ADDED, MediaStore.Images.Media._ID, MediaStore.Images.Media.SIZE }, MediaStore.MediaColumns.SIZE + ">0", null, MediaStore.Files.FileColumns._ID + " DESC limit 1 offset 0"); //读取扫描到的图片 if ((null != cursor) && (cursor.getCount() > 0) && cursor.moveToFirst()) { //获取图片时间 long time = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED)); int id = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media._ID)); if (String.valueOf(time).length() < 13) { time *= 1000; } cursor.close(); // 如果照片的插入时间大于相机的拍照时间,就认为是拍照图片已插入 return time + 1000 < takeTime; } return true; } |
会出现如下错误:
1 2 3 4 5 6 7 |
java.lang.IllegalArgumentException: Invalid token limit at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:172) at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142) at android.content.ContentProviderProxy.query(ContentProviderNative.java:472) at android.content.ContentResolver.query(ContentResolver.java:1197) at android.content.ContentResolver.query(ContentResolver.java:1128) at android.content.ContentResolver.query(ContentResolver.java:1084) |
原因为 Android 11 上使用 ContentResolver 的时候,已经不允许进行 SQL语句拼接,主要是为了防止 SQL注入攻击。
代码参考如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
/** * 保存拍照的图片是否已经完成 * * @param context Context 对象 * @return 查询成功返回 true ,否则返回false */ private static boolean isSavePictureComplete(@NonNull final Context context, long takeTime) { final Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; final String[] columns = new String[]{ MediaStore.Images.Media.DATE_ADDED, MediaStore.Images.Media._ID, MediaStore.Images.Media.SIZE}; //扫描图片 final ContentResolver contentResolver = context.getContentResolver(); Cursor cursor = null; try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q/*29*/) { final Bundle bundle = new Bundle(); bundle.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, MediaStore.MediaColumns.SIZE + " > ?"); bundle.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, new String[]{"0"}); bundle.putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, new String[]{MediaStore.Files.FileColumns._ID}); bundle.putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION, ContentResolver.QUERY_SORT_DIRECTION_DESCENDING); bundle.putInt(ContentResolver.QUERY_ARG_LIMIT, 1); bundle.putInt(ContentResolver.QUERY_ARG_OFFSET, 0); cursor = contentResolver.query(uri, columns, bundle, null); } else { cursor = contentResolver.query(uri, columns, MediaStore.MediaColumns.SIZE + ">0", null, MediaStore.Files.FileColumns._ID + " DESC limit 1 offset 0"); } //读取扫描到的图片 if ((null != cursor) && (cursor.getCount() > 0) && cursor.moveToFirst()) { //获取图片时间 long time = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED)); if (String.valueOf(time).length() < 13) { time *= 1000; } // 如果照片的插入时间大于相机的拍照时间,就认为是拍照图片已插入 return time + 1000 < takeTime; } } finally { if (null != cursor) { cursor.close(); } } return true; } |
参考链接
limiting number of rows in a ContentResolver.query() function
Robolectric/PowerMock测试AIDL相关类
在编写单元测试用例的时候,如果被测试类是与AIDL相关的类,不管是直接调用,还是被测试类继承了AIDL生成的类,由于AIDL类是在编译期间动态生成的,并且每次构建都会重新生成一次(类名相同,但是Hash可能会变,文件修改日期必然变更)。
然而,由于PowerMock调用Objenesis库为了加快编译速度,会缓存同名的类,这样导致在运行时进行类型转换的时候无法通过校验,报告如下错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
org.mockito.exceptions.base.MockitoException: ClassCastException occurred while creating the mockito proxy : class to mock : 'com.squareup.otto.Bus', loaded by classloader : 'org.robolectric.internal.bytecode.InstrumentingClassLoader@1593948d' created class : 'com.squareup.otto.Bus$$EnhancerByMockitoWithCGLIB$$82a3b196', loaded by classloader : 'org.robolectric.internal.bytecode.InstrumentingClassLoader@1593948d' proxy instance class : 'com.squareup.otto.Bus$$EnhancerByMockitoWithCGLIB$$82a3b196', loaded by classloader : 'org.mockito.internal.creation.util.SearchingClassLoader@618ff5c2' instance creation by : ObjenesisInstantiator You might experience classloading issues, disabling the Objenesis cache *might* help (see MockitoConfiguration) at org.powermock.api.mockito.repackaged.ClassImposterizer.imposterise(ClassImposterizer.java:61) at org.powermock.api.mockito.repackaged.ClassImposterizer.imposterise(ClassImposterizer.java:49) at org.powermock.api.mockito.repackaged.CglibMockMaker.createMock(CglibMockMaker.java:24) at org.powermock.api.mockito.internal.mockmaker.PowerMockMaker.createMock(PowerMockMaker.java:45) at com.acme.android.myapp.services.gcm.handlers.RequestLogoutHandlerTest.setup(RequestLogoutHandlerTest.java:39) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24) at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:251) at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188) at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runners.Suite.runChild(Suite.java:128) at org.junit.runners.Suite.runChild(Suite.java:27) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140) Caused by: java.lang.ClassCastException: Cannot cast com.squareup.otto.Bus$$EnhancerByMockitoWithCGLIB$$82a3b196 to com.squareup.otto.Bus at java.lang.Class.cast(Class.java:3369) at org.powermock.api.mockito.repackaged.ClassImposterizer.imposterise(ClassImposterizer.java:59) at org.powermock.api.mockito.repackaged.ClassImposterizer.imposterise(ClassImposterizer.java:49) at org.powermock.api.mockito.repackaged.CglibMockMaker.createMock(CglibMockMaker.java:24) at org.powermock.api.mockito.internal.mockmaker.PowerMockMaker.createMock(PowerMockMaker.java:45) at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:33) at org.mockito.internal.MockitoCore.mock(MockitoCore.java:59) at org.mockito.Mockito.mock(Mockito.java:1285) at org.mockito.Mockito.mock(Mockito.java:1163) ... 36 more org.mockito.exceptions.base.MockitoException: ClassCastException occurred while creating the mockito proxy : class to mock : 'com.squareup.otto.Bus', loaded by classloader : 'org.robolectric.internal.bytecode.InstrumentingClassLoader@1593948d' created class : 'com.squareup.otto.Bus$$EnhancerByMockitoWithCGLIB$$82a3b196', loaded by classloader : 'org.robolectric.internal.bytecode.InstrumentingClassLoader@1593948d' proxy instance class : 'com.squareup.otto.Bus$$EnhancerByMockitoWithCGLIB$$82a3b196', loaded by classloader : 'org.mockito.internal.creation.util.SearchingClassLoader@618ff5c2' instance creation by : ObjenesisInstantiator You might experience classloading issues, disabling the Objenesis cache *might* help (see MockitoConfiguration) at org.powermock.api.mockito.repackaged.ClassImposterizer.imposterise(ClassImposterizer.java:61) at org.powermock.api.mockito.repackaged.ClassImposterizer.imposterise(ClassImposterizer.java:49) at org.powermock.api.mockito.repackaged.CglibMockMaker.createMock(CglibMockMaker.java:24) at org.powermock.api.mockito.internal.mockmaker.PowerMockMaker.createMock(PowerMockMaker.java:45) at com.acme.android.myapp.services.gcm.handlers.RequestLogoutHandlerTest.setup(RequestLogoutHandlerTest.java:39) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24) at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:251) at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188) at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runners.Suite.runChild(Suite.java:128) at org.junit.runners.Suite.runChild(Suite.java:27) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:6 |
解决方法就是阻止Objenesis的缓存功能。在测试用例的根目录下新建文件,如下:
1 2 3 4 5 6 7 8 9 |
package org.mockito.configuration; public class MockitoConfiguration extends DefaultMockitoConfiguration { @Override public boolean enableClassCache() { return false; } } |
参考链接
- How can I test class extends auto-generated Stub class from AIDL file
- How to test remote android aidl service
- Mockito ClassCastException after using PowerMock
- ClassCastException exception when running Robolectric test with Power Mock on multiple files
- Mockito ClassCastException after using PowerMock
Robolectric 3.8/PowerMock无法Mock由Executors线程运行的类
参照 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单元测试环境 进行单元测试,不过,由于工程的限制(不能依赖 AndroidX),我们只能在 Robolectric 3.8 。
Android Studio 4.1.3,JDK使用 Java 1.8
在执行如下测试用例的时候,发现当被测试代码使用 Executors 在子线程执行的时候,如果使用 Powermock 的 WhenNew 对被测试对象进行仿真的时候,无法正确的被 Mock。
被测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import android.support.annotation.NonNull; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CustomExecutor { private final static String TAG = "CustomExecutor"; private static volatile CustomExecutor instance; private CustomMocker custom; private final ExecutorService executor = Executors.newSingleThreadExecutor(); private CustomExecutor() { executor.submit(new Runnable() { @Override public void run() { custom = new CustomMocker(); } }); } @NonNull public static CustomExecutor getInstance() { if (null == instance) { synchronized (CustomExecutor.class) { if (null == instance) { instance = new CustomExecutor(); } } } return instance; } public String getMessage() throws Exception { final Future<String> future = executor.submit(new Callable<String>() { @Override public String call() { return custom.getMessage(); } }); return future.get(); } } |
1 2 3 4 5 |
public class CustomMocker { public String getMessage() { return "I am not mocked"; } } |
单元测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) @PrepareForTest({CustomMocker.class, CustomExecutor.class}) @Config(manifest = Config.NONE, constants = BuildConfig.class, sdk = 26) public class CustomExecutorTest { @Rule public final PowerMockRule rule = new PowerMockRule(); @Before public void setup() throws Exception { final CustomMocker clz = PowerMockito.mock(CustomMocker.class); PowerMockito.when(clz.getMessage()).thenReturn("I am mocked!"); PowerMockito.whenNew(CustomMocker.class).withAnyArguments().thenReturn(clz); } @Test public void testCustomExecutors() { final CustomExecutor executor = CustomExecutor.getInstance(); Assert.assertNotNull(executor); Assert.assertEquals(expStr, executor.getMessage()); } } |
上述的测试代码在执行的时候,完全不会生效。
网上查询了一下,根据 Unable to get mocked instance of Executor in separate class 的介绍,我们需要在测试用例里面的@PrepareForTest
里面增加 Executors.class
,于是修改测试用例为如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.modules.junit4.PowerMockRunnerDelegate; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(RobolectricTestRunner.class) @PowerMockIgnore({"jdk.internal.reflect.*", "org.apache.commons.*", "org.mockito.*", "org.robolectric.*", "android.*"}) @PrepareForTest({ExecutorService.class, Executors.class, CustomMocker.class, CustomExecutor.class}) @Config(sdk = 26) public class CustomExecutorTest { private final static String expStr = "I am mocked!"; @Rule public final PowerMockRule rule = new PowerMockRule(); @Before public void setup() throws Exception { PowerMockito.mockStatic(Executors.class); PowerMockito.mockStatic(ExecutorService.class); PowerMockito.when(Executors.newSingleThreadExecutor()).thenCallRealMethod(); PowerMockito.mockStatic(CustomExecutor.class); final CustomMocker clz = PowerMockito.mock(CustomMocker.class); PowerMockito.when(clz.getMessage()).thenReturn(expStr); PowerMockito.whenNew(CustomMocker.class).withAnyArguments().thenReturn(clz); PowerMockito.mockStatic(CustomExecutor.class); PowerMockito.when(CustomExecutor.getInstance()).thenCallRealMethod(); } @Test public void testExecutorsMock() throws Exception { final CustomExecutor executor = CustomExecutor.getInstance(); Assert.assertNotNull(executor); Assert.assertEquals(expStr, executor.getMessage()); } } |
结果出现一个诡异的现象,如果在创建对象的地方设置断点进行调试跟踪,就是正确的,去掉断点,就会失败。
这个问题卡住很久,结果突然想起以前写的一个差不多功能的被测试类,是能正常工作的,于是进行了代码对比,结果发现一个奇怪的现象,只要内部定义一个类来中继一下,就可以完美解决问题。于是修改被测试类,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
import android.support.annotation.NonNull; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CustomExecutor { private static volatile CustomExecutor instance; @NonNull private final ExecutorService executor = Executors.newSingleThreadExecutor(); private Delegate delegate; private CustomExecutor() { executor.submit(new Runnable() { @Override public void run() { delegate = new Delegate(); } }); } @NonNull public static CustomExecutor getInstance() { if (null == instance) { synchronized (CustomExecutor.class) { if (null == instance) { instance = new CustomExecutor(); } } } return instance; } public String getMessage() throws Exception { final Future<String> future = executor.submit(new Callable<String>() { @Override public String call() { return delegate.getMessage(); } }); return future.get(); } private static class Delegate { private final CustomMocker mocker; public Delegate() { mocker = new CustomMocker(); } public String getMessage() { return mocker.getMessage(); } } } |
也就是内部定义来一个代理子类,初始化的时候,通过内部子类进行初始化。
这个现象比较奇怪,目前能解决问题,具体原因还是不详。
参考链接
Android与JavaScript交互数据上限(Base64图片传输问题)
使用webview.loadUrl
给JavaScript
传Base64
格式的较大图片时,有的Android版本上不反馈任何错误信息,只是页面接收不到任何信息,传入的脚本不执行任何操作。
有的则报错,如下:
1 |
Refusing to load URL as it exceeds 2097152 characters |
Missing essential plugin: org.jetbrains.android
今天升级到 Android Studio Arctic Fox | 2020.3.1 出现了如下错误:
1 2 3 4 5 6 7 |
Corrputed Installation Missing essential plugin: org.jetbrains.android Please reinstall Android Studio from scratch. |