如果 Synology NAS 出现故障,则可以使用计算机和 Ubuntu live CD 轻松恢复其硬盘上存储的数据。确保 Synology NAS 硬盘上运行的文件系统是 EXT4 或 Btrfs,然后按照以下步骤恢复数据。此处我们以 Ubuntu 18.04 版本为例。
作者: 默默
杭州集体户档案调档流程
如果单位能自己管理档案,并且要求转入,则在手机的 浙里办 上搜索 流动任意人事档案转出, 如下图:
在Android Studio 4.X下查看窗口布局层次Hierarchy Viewer/Layout Inspector
在Android开发的时候,又是需要检查窗口布局层次,观察布局是否显示正确。
早期版本是使用Hierarchy Viewer ,最新的Android Studio 4.X下,我们使用 Layout Inspector,具体操作参考下图:
继续阅读在Android Studio 4.X下查看窗口布局层次Hierarchy Viewer/Layout Inspector
屏幕旋转的适配问题以及遇到的一些坑
在手机APP开发的时候,一般默认会适配竖屏,游戏开发除外。但是在Android平板电脑开发中,屏幕旋转的问题比较退出,可以这样说,平板电脑的最初用意就是横屏使用的,比较方便,用户会经常旋转我们的屏幕。
这里主要针对平板开发中的一些问题做一些总结。
1. 防止屏幕旋转之后,Activity的销毁问题
为了适配屏幕,Activity默认在屏幕旋转之后会销毁并且重建,但是这种情况会造成用户输入数据的丢失(需要开发者手动去保存和恢复,会带来一定的工作量),Activity毕竟是重量级的组件,它的销毁和重建会使得性能的下降,因此我们需要防止Activity的销毁和重建。
做法
在清单文件中为Activity添加一些配置,configChanges属性添加orientation|screenSize:
|
1 2 3 4 5 |
<activity android:name=".ui.activity.MainActivity" android:configChanges="orientation|screenSize" android:launchMode="singleTask" android:windowSoftInputMode="stateAlwaysHidden"> |
通过上述的配置可以防止Activity的销毁和重建。
注意:这里的 screenSize 经常被我们遗漏。一般我们都是设置 orientation , 当屏幕方向改变的时候不要重新创建。那么 screenSize 什么时候发生呢? 目前最常见的就是 系统导航方式 变化的时候,目前Android手机支持 手势导航 / 屏幕内三键导航 两种模式。这两种模式的不同就是在屏幕底部是否出现 Back/ Home / Menu。这样导致应用的显示高度发生变化,从而触发 screenSize。
注意,辅助功能 里的 无障碍 模式下 导航模式 会被切换到 屏幕内三键导航 模式,从而引起窗口的重新绘制。
2. View屏幕旋转适配
除了制作横屏和竖屏两份布局文件的方法之外,如果我们的View是动态添加到Window的,屏幕旋转之后,我们的界面以及View需要做一些变动以适应屏幕。
我们可以复写Activity的onConfigurationChanged方法,并且在里面修改一些东西。
|
1 2 3 4 5 6 |
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); //一些适配操作 } |
这里举个例子,如说我们的ListView右边有一个字母索引控件,这个控件是直接new出来并且是直接覆盖在ListView上面的。当屏幕旋转之后,这个字母索引控件的位置需要刷新(重新布局以及绘制)。因为这个控件是在我们的ListView的右侧,并且竖直方向居中。这时候我们就需要动态获取屏幕上的ListView的宽高,然后才能计算出字母控件应该布局的位置。
但是这里我们会遇到一些坑
直接通过View的getWidth方法或者先measure然后通过getMeasuredWidth方法获取到的宽都是错误的,获取到的是屏幕旋转之前的值,如下面的代码所示。但是我需要的是ListView实时的宽高值,这时候我们只能手动去计算。例如我们要获取ListView的高度,那么我们可以先拿到Window的总高度,然后减去状态栏、Toolbar的高度来获取(这里只是一个例子,具体做法需要具体分析)。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); //一些适配操作 WindowManager wm = getWindowManager();//Activity可以直接获取WindowManager // WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); final int windowWidth = wm.getDefaultDisplay().getWidth(); final int windowHeight = wm.getDefaultDisplay().getHeight(); //手动去计算ListView的宽高 final int mListViewWidth = windowWidth; final int mListViewHeight = windowHeight - statusbarHeight - toolbarHeight; //下面获取ListView的宽高是有问题的 // mListView.measure(0, 0); // mListView.getMeasuredWidth(); // mListView.getMeasuredHeight(); // mListView.getWidth(); // mListView.getHeight(); // 。。。其他适配操作 } |
屏幕旋转的不确定性问题
最近又遇到屏幕旋转相关的新的问题,因此记录下来。我们知道,在Activity、View、Fragment等旋转的时候,如果你在清单文件中配置不重新创建的话,就会调用onConfigurationChanged方法。
但是问题来了,这个问题有一些不确定性因素,比如说当你的页面或者View被遮挡住(Stop)的时候就不会回调onConfigurationChanged这个方法。这比较略坑,会带来一些UI的偶发性问题。
解决办法就是我们自定义一个广播接受者专门用来接受onConfigurationChanged这个广播,这样子就可以确保,无论什么情况下,系统ConfigurationChanged的时候你的代码都会被执行。
自定义的ConfigurationChangeReceiver如下,这里提供一个静态方法方便注册。
|
1 2 3 4 5 6 7 8 9 10 |
public abstract class ConfigurationChangeReceiver extends BroadcastReceiver { private static final String TAG = ConfigurationChangeReceiver.class.getSimpleName(); public static IntentFilter getIntentFilter() { IntentFilter filter = new IntentFilter(); filter.addAction("android.intent.action.CONFIGURATION_CHANGED"); return filter; } } |
接下类在Activity或者Fragment中的正确位置注册(onAttachedToWindow、onDetachedFromWindow):
|
1 2 3 4 5 6 7 8 9 |
private void initConfigurationChangeReceiver() { mConfigurationChangeReceiver = new ConfigurationChangeReceiver() { @Override public void onReceive(Context context, Intent intent) { //你的屏幕适配代码 } }; this.registerReceiver(mConfigurationChangeReceiver, ConfigurationChangeReceiver.getIntentFilter()); } |
记得反注册,防止内存泄漏哦。
|
1 2 3 4 5 |
private void unRegisterConfigurationChangeReceiver() { if (mConfigurationChangeReceiver != null) { this.unregisterReceiver(mConfigurationChangeReceiver); } } |
参考链接
alien:.deb与.rpm包转换工具
deb 与 rpm 是GNU/Linux 流行的软件包格式。我们“alien”可以在rpm\dpkg\slackware tgz\deb\slp 格式见进行转换。
Debian/Ubuntu 可使用下面命令安装alien:
|
1 |
$ sudo apt-get install alien |
从RPM转到DEB
举例:假设在目录/tmp 有myprogram.rpm 文件,使用下面命令转换:
|
1 2 3 |
$ sudo alien /tmp/myprogram-1.10-2.i386.rpm myprogram-1.10-3.i386.deb generated |
这时,在/tmp 目录下就会发现有 myprogram.deb ,这时就可以使用dpkg安装:
|
1 |
$ sudo dpkg -i /tmp/miprogramma.deb |
Android默认调试签名证书
今天遇到了需要手工用调试签名重新签名APK文件的一个需求。
Android默认调试签名证书位置
Windows:
|
1 |
C:\Users\<用户名>\.android\debug.keystore |
macOS Catalina:
|
1 |
~/.android/debug.keystore |
证书密码 :android
签名脚本:
|
1 |
$ bash ~/Android/sdk/build-tools/29.0.2/apksigner sign --ks debug.keystore xxx.apk |
参考链接
使用mitmproxy + python做拦截代理
Vue理解v-model以及监听数据变化
Cannot resolve symbol KeyEventCompat(android.support.v4.view.KeyEventCompat找不到)
今天我把support版本升到了28.0.0 发现V4包 下的KeyEventCompat 类找不到了
com.android.support:appcompat-v4:28.0.0
那是因为KeyEventCompat类被取消了 hasNoModifiers() 方法已经被KeyEvent实现了
修改为:
|
1 2 3 4 5 |
if (event.hasNoModifiers()) { handled = arrowScroll(FOCUS_FORWARD); } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { handled = arrowScroll(FOCUS_BACKWARD); } |
参考链接
Android Q(Android 10)适配
应用读取 Device_ID
Android Q 之前有如下代码,获取设备Id,IMEI等
|
1 2 3 |
TelephonyManager telManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); telManager.getDeviceId(); telManager.getImei(); |
添加下面权限,并且需要动态申请权限
|
1 |
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> |
在 Android Q 上调用上面方法会报错
|
1 |
java.lang.SecurityException: getDeviceId: The user xxxx does not meet the requirements to access device identifiers. |
或者
|
1 2 |
E/Exception: getSubscriberId: The user xxxx does not meet the requirements to access device identifiers. java.lang.SecurityException: getSubscriberId: The user xxxx does not meet the requirements to access device identifiers. |
在 Android Q 上上面方法已经不能使用了,如果获取设备唯一Id,需要使用其他方式了,谷歌提供的获取唯一标识符做法见 文档,也可以用Android_ID,上面这些也不是绝对能得到一个永远不变的Id,可能需要多种方案获取其他Id,比如有谷歌商店的手机可以使用谷歌提供的广告Id,还有其他厂商一般都会提供手机的一个唯一Id。
我们项目现在使用下面这种方式 参考链接。要求生成之后,存储在应用的本地数据库中,读取的时候,优先从本地数据库读取上次存储的设备ID,不要每次都调用下面的接口获取,下面的接口不能保证每次都返回相同结果。
|
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 |
public static String getDeviceId(Context context) { final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; if (targetSdkVersion > Build.VERSION_CODES.P && Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { return getUniqueID(context); } else { return getTelId(context); } } private static String getTelId(Context context) { final TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); return manager.getDeviceId(); } private static String getUniqueID(Context context) { String id = null; final String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); if (!TextUtils.isEmpty(androidId) && !"9774d56d682e549c".equals(androidId)) { try { UUID uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8")); id = uuid.toString(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } if (TextUtils.isEmpty(id)) { id = getUUID(); } return TextUtils.isEmpty(id) ? UUID.randomUUID().toString() : id; } private static String getUUID() { String serial = null; String m_szDevIDShort = "35" + Build.BOARD.length() % 10 + Build.BRAND.length() % 10 + ((null != Build.CPU_ABI) ? Build.CPU_ABI.length() : 0) % 10 + Build.DEVICE.length() % 10 + Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 + Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 + Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 + Build.TAGS.length() % 10 + Build.TYPE.length() % 10 + Build.USER.length() % 10; //13 位 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { serial = android.os.Build.getSerial(); } else { serial = Build.SERIAL; } //API>=9 使用serial号 return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString(); } catch (Exception exception) { serial = "serial" + UUID.randomUUID().toString(); // 随便一个初始化 } } else { serial = android.os.Build.UNKNOWN + UUID.randomUUID().toString(); // 随便一个初始化 } //使用硬件信息拼凑出来的15位号码 return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString(); } |
参考链接