Android 10 适配攻略小结

相比较去年写的Android 9适配,这次Android 10的内容有点多。没想到写了我整整两天,吐血中。。。

准备工作

老规矩,首先将我们项目中的 targetSdkVersion 改为 29。

1.Scoped Storage(分区存储) 说明

在Android 10之前的版本上,我们在做文件的操作时都会申请存储空间的读写权限。但是这些权限完全被滥用,造成的问题就是手机的存储空间中充斥着大量不明作用的文件,并且应用卸载后它也没有删除掉。为了解决这个问题,Android 10 中引入了 Scoped Storage 的概念,通过添加外部存储访问限制来实现更好的文件管理。

首先明确一个概念,外部储存和内部储存。

  • 内部储存: /data 目录。一般我们使用 getFilesDir()getCacheDir() 方法获取本应用的内部储存路径,读写该路径下的文件不需要申请储存空间读写权限,且卸载应用时会自动删除。
  • 外部储存: /storage/mnt 目录。一般我们使用 getExternalStorageDirectory() 方法获取的路径来存取文件。

因为不同厂商、系统版本的原因,所以上述的方法并没有一个固定的文件路径。了解了上面的概念,那我们所说的外部储存访问限制,可以认为是针对 getExternalStorageDirectory() 路径下的文件。具体的规则如下表:

上图将外部存储空间分为了三部分:

  • 特定目录(App-specific),使用 getExternalFilesDir()getExternalCacheDir() 方法访问。无需权限,且卸载应用时会自动删除。
  • 照片、视频、音频这类媒体文件。使用 MediaStore 访问,访问其他应用的媒体文件时需要 READ_EXTERNAL_STORAGE 权限。
  • 其他目录,使用 存储访问框架SAF(Storage Access Framwork)

所以在Android 10上即使你拥有了储存空间的读写权限,也无法保证可以正常的进行文件的读写操作。

适配

最简单粗暴的方法就是在 AndroidManifest.xml 中添加 android:requestLegacyExternalStorage="true" 来请求使用旧的存储模式。

但是我不推荐此方法。因为在下一个版本的Android中,此条配置将会失效,将强制采用外部储存限制。其实早在Android Q Beta 3之前都是强制的,但为了给开发者适配的时间才没有强制执行。所以如果你不抓住这段时间去适配,那么今年下半年出了Android 11。。。直接开花~~

如果你已经适配Android 10,这里有个现象要 注意一下

如果应用通过升级安装,那么还会使用以前的储存模式(Legacy View)。只有通过首次安装或是卸载重新安装才能启用新模式(Filtered View)。

所以在适配时,我们的判断代码如下:

 // 使用Environment.isExternalStorageLegacy()来检查APP的运行模式
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
    !Environment.isExternalStorageLegacy()) {
  }

这样的好处是你可以在用户升级后,能方便的将用户的数据移动至应用的特定目录。否则你只能通过SAF去移动,这样会非常麻烦。如果你要移动数据注意只适用于Android 10下,所以现在适配反而是一个好时机。当然如果你不需要迁移数据,那适配会更省事。

下面就说说推荐适配方案:

对于应用中涉及的文件操作,修改一下你的文件路径。

以前我们习惯使用 Environment.getExternalStorageDirectory() 方法,那么现在可以使用 getExternalFilesDir() 方法(包括下载的安装包这类的文件)。如果是缓存类型文件,可以放到 getExternalCacheDir() 路径下。

或者使用 MediaStore ,将文件存至对应的媒体类型中(图片: MediaStore.Images ,视频: MediaStore.Video ,音频: MediaStore.Audio ),不过仅限于多媒体文件。

下面代码将图片保存到公共目录下,返回Uri:

public static Uri createImageUri(Context context) {
    ContentValues values = new ContentValues();
    // 需要指定文件信息时,非必须
    values.put(MediaStore.Images.Media.DESCRIPTION, "This is an image");
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "Image.png");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
    values.put(MediaStore.Images.Media.TITLE, "Image.png");
    values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/test");

    return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
  }

对于媒体资源的访问:比如图片选择器这类的场景。无法直接使用File,而应使用Uri。否则报错如下:

java.io.FileNotFoundException: open failed: EACCES (Permission denied)

比如我在适配项目中使用的图片选择器时,首先修改了 Glide 通过加载File的方式显示图片。改为加载Uri的方式,否则图片无法显示出来。

Uri的获取方式还是使用 MediaStore

String id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));

Uri uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);

其次为了便于不影响之前选择图片返回File的逻辑(因为一般都是上传File,没有直接上传Uri的操作),所以我将最终选择的文件又转存进了 getExternalFilesDir() ,主要代码如下:

  File imgFile = this.getExternalFilesDir("image");
  if (!imgFile.exists()){
    imgFile.mkdir();
  }
  try {
    File file = new File(imgFile.getAbsolutePath() + File.separator +
    	System.currentTimeMillis() + ".jpg");
    // 使用openInputStream(uri)方法获取字节输入流
    InputStream fileInputStream = getContentResolver().openInputStream(uri);
    FileOutputStream fileOutputStream = new FileOutputStream(file);
    byte[] buffer = new byte[1024];
    int byteRead;
    while (-1 != (byteRead = fileInputStream.read(buffer))) {
      fileOutputStream.write(buffer, 0, byteRead);
    }
    fileInputStream.close();
    fileOutputStream.flush();
    fileOutputStream.close();
    // 文件可用新路径 file.getAbsolutePath()
  } catch (Exception e) {
    e.printStackTrace();
  }

如果你要获取图片中的地理位置信息,需要申请 ACCESS_MEDIA_LOCATION 权限,并使用MediaStore.setRequireOriginal()获取。下面是官方的示例代码:

 Uri photoUri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
		 cursor.getString(idColumnIndex));

  final double[] latLong;

  // 从ExifInterface类获取位置信息
  photoUri = MediaStore.setRequireOriginal(photoUri);
  InputStream stream = getContentResolver().openInputStream(photoUri);
  if (stream != null) {
    ExifInterface exifInterface = new ExifInterface(stream);
    double[] returnedLatLong = exifInterface.getLatLong();

    // If lat/long is null, fall back to the coordinates (0, 0).
    latLong = returnedLatLong != null ? returnedLatLong : new double[2];

    // Don't reuse the stream associated with the instance of "ExifInterface".
    stream.close();
  } else {
    // Failed to load the stream, so return the coordinates (0, 0).
    latLong = new double[2];
  }

这样下来,一个图片选择器就基本适配完了。

补充

应用在卸载后,会将 App-specific 目录下的数据删除,如果在 AndroidManifest.xml 中声明: android:hasFragileUserData="true" 用户可以选择是否保留。

对于 SAF 的使用,可以查看我之前写的 SAF使用攻略 ,这里就不展开说了。

最后这里有一个介绍Scoped Storage的视频,推荐 观看

2.权限变化

从6.0开始,基本每次都会有权限方面变动,这次也不例外。(前几天发布了Android 11的预览版,看来也有权限方面的变化。。。单次权限即将到来)

1.在后台运行时访问设备位置信息需要权限

Android 10 引入了 ACCESS_BACKGROUND_LOCATION 权限(危险权限)。

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>

该权限允许应用程序在后台访问位置。如果请求此权限,则还必须请求 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 权限。只请求此权限无效果。

在Android 10的设备上,如果你的应用的 targetSdkVersion < 29,则在请求 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 权限时,系统会自动同时请求 ACCESS_BACKGROUND_LOCATION 。在请求弹框中,选择“始终允许”表示同意后台获取位置信息,选择“仅在应用使用过程中允许”或"拒绝"选项表示拒绝授权。

如果你的应用的 targetSdkVersion >= 29,则请求 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 权限表示在前台时拥有访问设备位置信息的权。在请求弹框中,选择“始终允许”表示前后台都可以获取位置信息,选择“仅在应用使用过程中允许”只表示拥有前台的权限。

总结一下就是下图:

其实官方 不推荐你使用申请后台访问权的方式 ,因为这样的结果无非就是多请求一个权限,那么这像变更还有什么意义?申请过多的权限,也会造成用户的反感。所以官方推荐使用 前台服务

来实现,在前台服务中获取位置信息。

首先在清单中对应的 service 中添加 android:foregroundServiceType="location"

  <service
    android:name="MyNavigationService"
    android:foregroundServiceType="location" ... >
    ...
  </service>

启动前台服务前检查是否具有前台的访问权限:

  boolean permissionApproved = ActivityCompat.checkSelfPermission(this,
		Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;

  if (permissionApproved) {
    // 启动前台服务
  } else {
    // 请求前台访问位置权限
  }

如此一来就可以在 Service 中获取位置信息。

2.一些电话、蓝牙和WLAN的API需要精确位置权限

下面列举了Android 10中必须具有 ACCESS_FINE_LOCATION 权限才能使用类和方法:

电话

  • TelephonyManager

    • getCellLocation()
    • getAllCellInfo()
    • requestNetworkScan()
    • requestCellInfoUpdate()
    • getAvailableNetworks()
    • getServiceState()
  • TelephonyScanManager
    • requestNetworkScan()
  • TelephonyScanManager.NetworkScanCallback
    • onResults()
  • PhoneStateListener
    • onCellLocationChanged()
    • onCellInfoChanged()
    • onServiceStateChanged()

WLAN

  • WifiManager

    • startScan()
    • getScanResults()
    • getConnectionInfo()
    • getConfiguredNetworks()
  • WifiAwareManager
  • WifiP2pManager
  • WifiRttManager

蓝牙

  • BluetoothAdapter

    • startDiscovery()
    • startLeScan()
  • BluetoothAdapter.LeScanCallback
  • BluetoothLeScanner
    • startScan()

我们可以根据上面提供的具体类和方法,在适配项目中检查是否有使用到并及时处理。

3.ACCESS_MEDIA_LOCATION
Android 10新增权限,上面有提到,不赘述了。

4.PROCESS_OUTGOING_CALLS
Android 10上该权限已废弃。

3.后台启动 Activity 的限制

简单解释就是 应用处于后台时,无法启动Activity 。比如点开一个应用会进入启动页或者广告页,一般会有几秒的延时再跳转至首页。如果这期间你退到后台,那么你将无法看到跳转过程。而在之前的版本中,会强制弹出页面至前台。

既然是限制,那么肯定有不受限的情况,主要有以下几点:

  • 应用具有可见窗口,例如前台 Activity。
  • 应用在前台任务的返回栈中已有的 Activity。
  • 应用在 Recents 上现有任务的返回栈中已有的 Activity。 Recents 就是我们的任务管理列表。
  • 应用收到系统的 PendingIntent 通知。
  • 应用收到它应该在其中启动界面的系统广播。示例包括 ACTION_NEW_OUTGOING_CALLSECRET_CODE_ACTION 。应用可在广播发送几秒钟后启动 Activity。
  • 用户已向应用授予 SYSTEM_ALERT_WINDOW 权限,或是在应用权限页开启 后台弹出页面 的开关。

因为此项行为变更适用于在 Android 10 上运行的所有应用,所以这一限制导致最明显的问题就是点击推送信息时,有些应用无法进行正常的跳转(具体的实现问题导致)。所以针对这类问题,可以采取 PendingIntent 的方式,发送通知时使用 setContentIntent 方法。

当然你也可以申请相应权限或者白名单:

不过申请白名单这种方法受各种手机厂商所限,很麻烦。感觉还不如引导用户手动开启权限。。。

对于全屏 intent,注意设置最高优先级和添加 USE_FULL_SCREEN_INTENT 权限,这是一个普通权限。比如微信来语音或者视频通话时,弹出的接听页面就是使用这一功能。

 <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
Intent fullScreenIntent = new Intent(this, CallActivity.class);
  PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(this, 0,
      fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);

  NotificationCompat.Builder notificationBuilder =
      new NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("Incoming call")
    .setContentText("(919) 555-1234")
    .setPriority(NotificationCompat.PRIORITY_HIGH) // <--- 高优先级
    .setCategory(NotificationCompat.CATEGORY_CALL)

    // Use a full-screen intent only for the highest-priority alerts where you
    // have an associated activity that you would like to launch after the user
    // interacts with the notification. Also, if your app targets Android 10
    // or higher, you need to request the USE_FULL_SCREEN_INTENT permission in
    // order for the platform to invoke this notification.
    .setFullScreenIntent(fullScreenPendingIntent, true); // <--- 全屏 intent

  Notification incomingCallNotification = notificationBuilder.build();

注意:在部分手机上,直接设置 setPriority 无效(或者说以渠道优先级为准)。所以需要创建通知渠道时将重要性设置为 IMPORTANCE_HIGH

NotificationChannel channel = new NotificationChannel(channelId, "xxx", NotificationManager.IMPORTANCE_HIGH);

后台启动 Activity 的限制的目的是为了减少对用户操作的中断。如果你有要弹出的页面,推荐你先弹出通知,让用户自己选择接下来的操作,而不是一股脑的强制弹出。(如果你的全屏intent都让用户反感,那他也可以关掉你的通知,不至于任你摆布。)

4.深色主题

Android 10 新增了一个系统级的深色主题(在系统设置中开启)。虽然深色主题并不是强制适配项,但是它可以带给用户更好的体验:

  • 可大幅减少耗电量。 OLED 屏幕中每个像素都是自主发光,所以在显示深色元素时像素所消耗的电流更低,尤其在纯黑颜色时像素点可以完全关闭来达到省电的效果。
  • 为弱视以及对强光敏感的用户提高可视性。深色可以降低屏幕的整体视觉亮度,减少对眼睛的视觉压力。
  • 让所有人都可以在光线较暗的环境中更轻松地使用设备。

适配方法有两种:

1.手动适配(资源替换)

官方文档中提到的继承 Theme.AppCompat.DayNight 或者 Theme.MaterialComponents.DayNight 的方法,但这只是将我们使用的各种View的默认样式进行了适配,并不太适用于实际项目的适配。因为具体的项目中的View都按照设计的风格进行了重定义。

其实适配的方法很简单,类似屏幕适配、国际化的操作,并不需要继承上面的主题。比如你要修改颜色,就在 res 下新建 values-night 目录,创建对应的 colors.xml 文件。将具体要修改的色值定义在里面。图标之类的也是一个思路,创建对应的 drawable-night 目录。

只要你之前的代码不是硬编码且代码规范,那么适配起来还是很轻松。

2.自动适配(Force Dark)

Android 10 提供 Force Dark 功能。一如其名,此功能可让开发者快速实现深色主题背景,而无需明确设置 DayNight 主题背景。

如果您的应用采用浅色主题背景,则 Force Dark 会分析应用的每个视图,并在相应视图在屏幕上显示之前,自动应用深色主题背景。有些开发者会混合使用 Force Dark 和本机实现,以缩短实现深色主题背景所需的时间。

应用必须选择启用 Force Dark,方法是在其主题背景中设置 android:forceDarkAllowed="true" 。此属性会在所有系统及 AndroidX 提供的浅色主题背景(例如 Theme.Material.Light)上设置。使用 Force Dark 时,您应确保全面测试应用,并根据需要排除视图。

如果您的应用使用 Dark Theme 主题(例如Theme.Material),则系统不会应用 Force Dark。同样,如果应用的主题背景继承自 DayNight 主题(例如Theme.AppCompat.DayNight),则系统不会应用 Force Dark,因为会自动切换主题背景。

您可以通过 android:forceDarkAllowed 布局属性或 setForceDarkAllowed(boolean) 在特定视图上控制 Force Dark。

上述内容我直接照搬文档的说明。总结一下,使用 Force Dark 需要注意几点:

  • 如果使用的是 DayNightDark Theme 主题,则设置 forceDarkAllowed 不生效。
  • 如果有需要排除适配的部分,可以在对应的View上设置 forceDarkAllowed 为false。

这里说说我实际使用此方法的感受: 整体还是不错的,设置的色值会自动取反。但也因此颜色不受控制,能否达到预期效果是个需要注意的问题。追求快速适配可以采取此方案。

手动切换主题

使用 AppCompatDelegate.setDefaultNightMode(@NightMode int mode) 方法,其中参数 mode 有以下几种:

  • 浅色 - MODE_NIGHT_NO
  • 深色 - MODE_NIGHT_YES
  • 由省电模式设置 - MODE_NIGHT_AUTO_BATTERY
  • 系统默认 - MODE_NIGHT_FOLLOW_SYSTEM

下面的代码是官方Demo中的使用示例:

public class ThemeHelper {

  public static final String LIGHT_MODE = "light";
  public static final String DARK_MODE = "dark";
  public static final String DEFAULT_MODE = "default";

  public static void applyTheme(@NonNull String themePref) {
    switch (themePref) {
      case LIGHT_MODE: {
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
        break;
      }
      case DARK_MODE: {
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
        break;
      }
      default: {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
          AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
        } else {
          AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
        }
        break;
      }
    }
  }
}

通过 AppCompatDelegate.getDefaultNightMode() 方法,可以获取到当前的模式,这样便于代码中去适配。

监听深色主题是否开启

首先在清单文件中给对应的Activity配置 android:configChanges="uiMode"

 <activity
  	android:name=".MyActivity"
  	android:configChanges="uiMode" />

这样在 onConfigurationChanged 方法中就可以获取:

	@Override
  public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
    switch (currentNightMode) {
      case Configuration.UI_MODE_NIGHT_NO:
        // 关闭
        break;
      case Configuration.UI_MODE_NIGHT_YES:
        // 开启
        break;
      default:
        break;
    }
  }

详细的内容你可以参看官方文档官方Demo

判断深色主题是否开启

其实和上面 onConfigurationChanged 方法同理:

  public static boolean isNightMode(Context context) {
    int currentNightMode = context.getResources().getConfiguration().uiMode &
    	Configuration.UI_MODE_NIGHT_MASK;
    return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
  }

5.标识符和数据

 对不可重置的设备标识符实施了限制

受影响的方法包括:

从 Android 10 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 特许权限才能正常使用以上这些方法。

如果你的应用没有该权限,却仍然使用了以上的方法,则返回的结果会因目标 SDK 版本而异:

  • 如果应用以 Android 10 或更高版本为目标平台 ,则会发生 SecurityException
  • 如果应用以 Android 9(API 级别 28)或更低版本为目标平台 ,则相应方法会返回 null 或占位符数据(如果应用具有 READ_PHONE_STATE 权限)。否则,会发生 SecurityException

这项改动表示第三方应用无法获取 Device ID 这类唯一标识。如果你需要唯一标识符,请参阅文档: 唯一标识符的最佳做法

当然你也可以试试移动安全联盟(MSA)联合多家厂商共同开发的 统一补充设备标识调用SDK 。据说还有点不稳定,因为我暂时还没有尝试过,所以不做评价。

限制了对剪贴板数据的访问权限

除非您的应用是默认输入法 (IME) 或是目前处于焦点的应用,否则它无法访问 Android 10 或更高版本平台上的剪贴板数据。

对启用和停用 WLAN 实施了限制

以 Android 10 或更高版本为目标平台的应用无法启用或停用 WLAN。

WifiManager.setWifiEnabled()方法始终返回 false。

如果您需要提示用户启用或停用 WLAN,请使用设置面板

6.其他

Android10上对折叠屏设备有了更好的支持,对于有折叠屏适配的需求,可以参看为可折叠设备构建应用华为折叠屏应用开发指导。

以上内容只是Android 10中比较大的几项变化,完整的内容可以查看官方文档

参考

OPPO - Android Q版本应用兼容性适配指导

面向开发者的 Android 10

用阿里巴巴APP的案例,教你如何快速适配「深色模式」

到此这篇关于Android 10 适配攻略小结的文章就介绍到这了,更多相关Android 10 适配内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • AndroidQ(10)分区存储完美适配方法

    前言 最近时间在做AndroidQ的适配,截止到今天AndroidQ分区存储适配完成,期间出现很多坑,目前网上的帖子大部分都是概述变更内容,接下来的几篇帖子都是对分区存储实际经验代码总结,填坑经验,特此记录一下,也为大家提供帮助. 本篇主要是对AndroidQ(10)分区存储适配具体实现 要点: Android Q文件存储机制修改成了沙盒模式 APP只能访问自己目录下的文件和公共媒体文件 对于AndroidQ以下,还是使用老的文件存储方式 这里需要注意:在适配AndroidQ的时候还要兼容Q系统

  • Android10填坑适配指南(实际经验代码)

    今天看到一篇好的文章,分享给大家,膜拜大佬. Android10填坑适配指南,包含实际经验代码,绝不照搬翻译文档 1.Region.Op相关异常:java.lang.IllegalArgumentException: Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed 当 targetSdkVersion >= Build.VERSION_CODES.P 时调用 canvas.clipPath(path, Region.Op

  • Android 10 适配攻略小结

    相比较去年写的Android 9适配,这次Android 10的内容有点多.没想到写了我整整两天,吐血中... 准备工作 老规矩,首先将我们项目中的 targetSdkVersion 改为 29. 1.Scoped Storage(分区存储) 说明 在Android 10之前的版本上,我们在做文件的操作时都会申请存储空间的读写权限.但是这些权限完全被滥用,造成的问题就是手机的存储空间中充斥着大量不明作用的文件,并且应用卸载后它也没有删除掉.为了解决这个问题,Android 10 中引入了 Sco

  • 安卓11适配攻略抢先看

    引言 近期,Google 发布了 Android 11的平台稳定版本.Android 11 将在确保用户隐私安全的前提下,更好地让用户畅享最新科技. 下图是 Android 11 发布整体流程的时间线: 个推深耕开发者领域,一直紧跟行业发展趋势,对Android 11的有关更新展开了深入研究.本文将从分区存储.应用包可见性.权限变化和新功能四个方面来谈谈 Android 11 新特性,以帮助开发者更快速.更便捷地上手适配Android新系统. 作者| 个推高级安卓开发工程师 一七 正文 分区存储

  • 详解Android 折叠屏适配攻略

    随着三星 Galaxy Fold 和 华为 Mate X 的发布,折叠屏手机开始进入大家的视线.在改变手机体验的同时,也给我们开发人员在适配方面带来了更多的挑战.本文给大家介绍一下 Android 开发中和折叠屏相关的一些概念,以及如何进行折叠屏的适配. 折叠屏适配 折叠屏之所以需要适配,是因为我们的应用有可能在运行的过程中,所在的屏幕尺寸发生了变化,这种情况对现有项目多少都会产生一些问题. 所以折叠屏适配的本质是: 当应用运行时,屏幕的尺寸.密度或比例发生了变化,应用能够继续在变化后的屏幕上正

  • iOS13原生端适配攻略(推荐)

    随着iOS 13的发布,公司的项目也势必要着手适配了.现汇总一下iOS 13的各种坑 1. KVC访问私有属性 这次iOS 13系统升级,影响范围最广的应属KVC访问修改私有属性了,直接禁止开发者获取或直接设置私有属性.而KVC的初衷是允许开发者通过Key名直接访问修改对象的属性值,为其中最典型的 UITextField 的 _placeholderLabel.UISearchBar 的 _searchField. 造成影响:在iOS 13下App闪退 错误代码: // placeholderL

  • Android FTP服务器上传文件攻略(代码详解)

    1.前言 在开发中,会遇到向FTP服务器上传文件的需求,首先要导入 commons-net-3.3.jar 然后利用api进行相关操作,具体功能如下: Ftp相关代码 import android.util.Log; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPReply; import java.i

  • 驱除威胁—无线网络防黑完全攻略

    由于无线局域网以无线电波作为数据传输媒介,因此在安全问题上总会带给家庭用户或多或少的威胁,接下来我们选用支持WPA协议的中怡数宽IP806LM无线路由器,作为例子,手把手地教你进行无线局域网的安全设置. 更改预设的无线网络名称 每个无线网络都有一个识别名称,称为无线网络名称(Network Name)或服务集标志符(Service Set IDentifier, SSID).所有无线路由器或无线AP,都有自身的无线网络名称,而且所有无线网络的客户端,都必须提供与之对应的无线网络名称,方能成功建立

  • 生成PDF全攻略之在已有PDF上添加内容的实现方法

    项目在变,需求在变,不变的永远是敲击键盘的程序员..... PDF 生成后,有时候需要在PDF上面添加一些其他的内容,比如文字,图片.... 经历几次失败的尝试,终于获取到了正确的代码书写方式. 在此记录总结,方便下次以不变应万变,需要的 jar 请移步:生成PDF全攻略 PdfReader reader = new PdfReader("E:\\A.pdf"); PdfStamper stamper = new PdfStamper(reader, new FileOutputStr

  • 微信公众帐号开发教程之图文消息全攻略

    引言及内容概要 已经有几位读者抱怨"柳峰只用到文本消息作为示例,从来不提图文消息,都不知道图文消息该如何使用",好吧,我错了,原本以为把基础API封装完.框架搭建好,再给出一个文本消息的使用示例,大家就能够照猫画虎的,或许是因为我的绘画功底太差,画出的那只猫本来就不像猫吧-- 本篇主要介绍微信公众帐号开发中图文消息的使用,以及图文消息的几种表现形式.标题取名为"图文消息全攻略",这绝对不是标题党,是想借此机会把大家对图文消息相关的问题.疑虑.障碍全部清除掉. 图文消

  • phpMyAdmin 安装教程全攻略

    管理MYSQL数据库的最好工具是PHPmyAdmin,现在最新版本是phpMyAdmin 2.9.0.2,这是一个国际上开源的软件,一直在更新版本,你可以从 http://www.phpmyadmin.net 官方网站上下载到,安装后可以远程更新数据库(其实是在服务器上安装).  安装办法请参考:  phpMyAdmin  安装攻略  1.先下载 phpMyAdmin 安装包 ,http://www.phpmyadmin.net  2.解压后一个单独目录中(你可以自定义目录名称)  3.找到 /

  • Python基础篇之初识Python必看攻略

    Python简介 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言的一种继承. Python和其他语言的对比: C 和 Python.Java.C#等 C语言: 代码编译得到 机器码 ,机器码在处理器上直接执行,每一条指令控制CPU工作 其他语言: 代码编译得到 字节码 ,虚拟机执行字节码并转换成机器码再后在处理器上执行 Python 和 C  Python这门语

随机推荐