安卓Q | 诸多本地文件找不到?应用文件存储空间沙箱化适配指导
上期我们针对Android Q 版本中对设备存储空间进行的限制、新特性变更引发的兼容性问题及原因分析推出了《安卓 Q | 8大场景全面解析应用存储沙箱化》文章,本期文章我们将手把手指导各位应用开发者如何针对以上特性进行适配。
文件共享适配指导 1、使用FileProvider的Content Uri替换File Uri
2、参考谷歌提供的适配指导链接:
https://developer.android.com/training/secure-file-sharing
3、大致的适配流程总结:
■ 指定应用的FileProvider
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.huawei.qappcompatissues.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
■ 指定应用分享的文件路径,在res/xml/目录增加文件file_paths.xml文件
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external" path="" />
</paths>
■ 获得分享文件的Content Uri fileUri = FileProvider.getUriForFile( this, "com.huawei.qappcompatissues.fileprovider", picFile); ■ 临时授予文件接收方的文件读写权限
// Grant temporary read permission to the content URI
intent.addFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION);
■ 分享文件完整代码
private void sharePicFile(File picFile) {
try {
// Use the FileProvider to get a content URI
fileUri = FileProvider.getUriForFile(
this,
"com.huawei.qappcompatissues.fileprovider",
picFile);
Log.e("test", "fileUri:" + fileUri);
if (fileUri != null) {
Intent intent = new Intent(Intent.ACTION_SEND);
// Grant temporary read permission to the content URI
intent.addFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION);
// Put the Uri and MIME type in the result Intent
intent.setDataAndType(
fileUri,
getContentResolver().getType(fileUri));
startActivity(Intent.createChooser(intent, "test file share"));
} else {
Toast.makeText(this, "share file error", Toast.LENGTH_SHORT).show();
}
} catch (IllegalArgumentException e) {
Log.e("File Selector",
"The selected file can't be shared: " + picFile.toString());
}
■ 接收方读取文件,比如接收图片文件:
AndroidManifest.xml文件中添加intent过滤器:
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
通过Intent读取图片,content uri:content://com.huawei.qappcompatissues.fileprovider/external/test.jpg
ImageView imageView = findViewById(R.id.imageView);
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
// Get the file's content URI from the incoming Intent
Uri returnUri = intent.getData();
if (type.startsWith("image/")) {
Log.e("test", "open image file:" + returnUri);
try {
Bitmap bmp = getBitmapFromUri(returnUri);
imageView.setImageBitmap(bmp);
} catch (IOException e) {
e.printStackTrace();
}
}
} 通过Content Uri读取图片:
public static Bitmap getBitmapFromUri(Context context, Uri uri) throws IOException {
ParcelFileDescriptor parcelFileDescriptor =
context.getContentResolver().openFileDescriptor(uri, "r");
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close();
return image;
}
权限适配指导
1、应用读写自己生成的文件不需要申请任何权限
2、应用如果需要读取其他应用保存的多媒体公共集合文件,就需要申请对应的权限:
■ 音乐文件:
android.permission.READ_MEDIA_AUDIO
■ 照片文件:
android.permission.READ_MEDIA_IMAGES
■ 视频文件:
android.permission.READ_MEDIA_VIDEO
3、谷歌提供的兼容性方案:
■ 应用的targetSdkVersion<Q,只要应用动态申请了READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE权限,系统会自动将该权限转成新增的:android.permission.READ_MEDIA_AUDIO、android.permission.READ_MEDIA_IMAGES和android.permission.READ_MEDIA_VIDEO权限
4、适配指导:
TargetSdkVersion<Q的应用不适配也不会有问题,只有TargetSdkVersion>=Q的应用需要适配,否则会导致没有权限访问多媒体文件:
■ 需要在 AndroidManifest.xml 中新增 uses-permissions 声明 (不一定全要,请根据实际业务需要访问音频,图片还是视频选择必须的; 如果完全不需要访问媒体类的文件,只是访问普通下载文件,下列权限都是不需要申请的)
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
■ 在代码中使用权限前调用checkSelfPermission检查权限是否授权,未授权情况下调用requestPermission动态申请上述权限,让用户通过弹框确认。
■ 同时兼容Q和Q之前的安卓版本:
◆ 在AndroidManifest.xml 中同时uses-permission声明新老权限;
◆ 在代码中通过API level来区分,当API level低于Q时,运行P版本旧的权限的动态授权代码;大于等于Q时运行新的权限的动态授权代码;
private void requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_MEDIA_IMAGES)
!= PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_MEDIA_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_AUDIO},
MY_PERMISSIONS_REQUEST_READ_MEDIA_IMAGES);
}
} else {
// request old storage permission
}
}
本地多媒体文件读写适配指导
1、多媒体文件读取
■ 多媒体文件和下载文件读取接口
■ 通过ContentProvider查询文件,获得需要读取的文件Uri:
public static List<Uri> loadPhotoFiles(Context context) {
Log.e(TAG, "loadPhotoFiles");
List<Uri> photoUris = new ArrayList<Uri>();
Cursor cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Images.Media._ID}, null, null, null);
Log.e(TAG, "cursor size:" + cursor.getCount());
while (cursor.moveToNext()) {
int id = cursor.getInt(cursor
.getColumnIndex(MediaStore.Images.Media._ID));
Uri photoUri = Uri.parse(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + File.separator + id);
Log.e(TAG, "photoUri:" + photoUri);
photoUris.add(photoUri);
}
return photoUris;
}
■ 通过Uri读取文件:
public static Bitmap getBitmapFromUri(Context context, Uri uri) throws IOException {
ParcelFileDescriptor parcelFileDescriptor =
context.getContentResolver().openFileDescriptor(uri, "r");
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close();
return image;
}
■ MediaProvider变更适配指导
MediaProvider中的“_data”字段已经废弃掉了,开发者不能再认为该字段保存的是文件的真实路径,Q版本因为存储空间限制的变更,应用已经无法直接通过文件路径读取文件,需要使用文件的Content URI读取文件,目前发现有很多应用通过“_data”值作为文件的真实路径在加载显示图片之前判断文件是否存在,这样的做法在Q版本是有问题的,应用需要整改。
2、多媒体文件保存
应用只能在沙箱内通过文件路径的方式保存文件,如果需要保存文件到沙箱目录外,需要使用特定的接口实现,具体可参考:
■ 方式1:
通过MediaStore.Images.Media.insertImage接口可以将图片文件保存到/sdcard/Pictures/,但是只有图片文件保存可以通过MediaStore的接口保存,其他类型文件无法通过该接口保存;
public static void saveBitmapToFile(Context context, Bitmap bitmap, String title, String discription) {
MediaStore.Images.Media.insertImage(context.getContentResolver(), bitmap, title, discription);
}
■ 方式2:
通过ContentResolver的insert方法将多媒体文件保存到多媒体的公共集合目录;
/**
* 保存多媒体文件到公共集合目录
* [@param](https://my.oschina.net/u/2303379) uri:多媒体数据库的Uri
* [@param](https://my.oschina.net/u/2303379) context
* [@param](https://my.oschina.net/u/2303379) mimeType:需要保存文件的mimeType
* [@param](https://my.oschina.net/u/2303379) displayName:显示的文件名字
* @param description:文件描述信息
* @param saveFileName:需要保存的文件名字
* @param saveSecondaryDir:保存的二级目录
* @param savePrimaryDir:保存的一级目录
* @return 返回插入数据对应的uri
*/
public static String insertMediaFile(Uri uri, Context context, String mimeType,
String displayName, String description, String saveFileName, String saveSecondaryDir, String savePrimaryDir) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, displayName);
values.put(MediaStore.Images.Media.DESCRIPTION, description);
values.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
values.put(MediaStore.Images.Media.PRIMARY_DIRECTORY, savePrimaryDir);
values.put(MediaStore.Images.Media.SECONDARY_DIRECTORY, saveSecondaryDir);
Uri url = null;
String stringUrl = null; /* value to be returned */
ContentResolver cr = context.getContentResolver();
try {
url = cr.insert(uri, values);
if (url == null) {
return null;
}
byte[] buffer = new byte[BUFFER_SIZE];
ParcelFileDescriptor parcelFileDescriptor = cr.openFileDescriptor(url, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
InputStream inputStream = context.getResources().getAssets().open(saveFileName);
while (true) {
int numRead = inputStream.read(buffer);
if (numRead == -1) {
break;
}
fileOutputStream.write(buffer, 0, numRead);
}
fileOutputStream.flush();
} catch (Exception e) {
Log.e(TAG, "Failed to insert media file", e);
if (url != null) {
cr.delete(url, null, null);
url = null;
}
}
if (url != null) {
stringUrl = url.toString();
}
return stringUrl;
}
比如你需要把一个图片文件保存到/sdcard/dcim/test/下面,可以这样调用:
SandboxTestUtils.insertMediaFile(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, this, "image/jpeg", "insert_test_img", "test img save use insert", "if_apple_2003193.png", "test", Environment.DIRECTORY_DCIM);
音频和视频文件也是可以通过这个方式进行保存,比如音频文件保存到/sdcard/Music/test/:
SandboxTestUtils.insertMediaFile(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, this, "audio/mpeg", "insert_test_music", "test audio save use insert", "Never Forget You.mp3", "test", Environment.DIRECTORY_MUSIC);
可以通过PRIMARY_DIRECTORY和SECONDARY_DIRECTORY字段来设置一级目录和二级目录:
■ 一级目录必须是和MIME type的匹配的根目录下的Public目录,一级目录可以不设置,不设置时会放到默认的路径;
■ 二级目录可以不设置,不设置时直接保存在一级目录下
■ 应用生成的文档类文件,代码里面默认不设置时,一级是Downloads目录,也可以设置为Documents目录,建议推荐三方应用把文档类的文件一级目录设置为Documents目录。
■ 一级目录MIME type,默认目录、允许的目录映射以及对应的读取权限如下表所示:
3、多媒体文件的编辑和修改
应用只有自己插入的多媒体文件的写权限,没有别的应用插入的多媒体文件的写权限,比如使用下面的代码删除别的应用的多媒体文件会因为权限问题导致删除失败:
context.getContentResolver().delete(uri, null, null))
对于需要修改和删除别的应用保存的多媒体文件的适配建议:
■ 方式1:
如果应用需要修改其他应用插入的多媒体文件,需要作为系统默认应用,比如作为系统默认图库,可以删除和修改其他应用的图片和视频文件;作为系统的默认音乐播放软件,可以删除和修改其他应用的音乐文件。
参考谷歌提供的适配指导:https://developer.android.google.cn/preview/features/roles#check-default-app
在AndroidManifest文件增加对应的权限和默认应用intent过滤器的申明
<activity
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.APP_GALLERY" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
在启动应用页面里面增加是不是默认应用的判断:
//设置默认应用
RoleManager roleManager = getSystemService(RoleManager.class);
if (roleManager.isRoleAvailable(RoleManager.ROLE_GALLERY)) {
if (roleManager.isRoleHeld(RoleManager.ROLE_GALLERY)) {
// This app is the default gallery app.
Log.e(TAG, "This app is the default gallery app");
} else {
// This app isn't the default gallery app, but the role is available,
// so request it.
Log.e(TAG, "This app isn't the default gallery app");
Intent roleRequestIntent = roleManager.createRequestRoleIntent(
RoleManager.ROLE_GALLERY);
startActivityForResult(roleRequestIntent, ROLE_REQUEST_CODE);
}
}
用户设置您的App为默认音乐应用之后,就有权限写其他应用保存的音乐文件了。另外其他的类型多媒体文件也可以按照同样的方式处理:
■ 方式2:
使用ContentResolver对象查找文件并进行修改或者删除。执行修改或删除操作时,捕获RecoverableSecurityException,以便您可以请求用户授予您对多媒体文件的写入权限。(备注:目前这一块代码还未完全ready,当前版本无法通过这个方式完成多媒体文件删除。)
在任意的指定目录读写文件适配指导
1、使用SAF方式适配
2、参考谷歌提供的适配指导:
https://developer.android.com/guide/topics/providers/document-provider
3、参考实现代码:
■ 读取和修改文件
通过Intent传入ACTION_OPEN_DOCUMENT拉起DocumentUI:
public void performFileSearch() {
// ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
// browser.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
// Filter to only show results that can be "opened", such as a
// file (as opposed to a list of contacts or timezones)
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Filter to show only images, using the image MIME data type.
// If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
// To search for all documents available via installed storage providers,
// it would be "*/*".
intent.setType("image/*");
startActivityForResult(intent, READ_REQUEST_CODE);
}
在拉起的DocumentUI中用户可以选择需要打开的图片文件:
DocumentUI会把用户选择的图片文件的Content Uri通过intent传回给应用,应用在onActivityResult回调中就可以拿到这个Uri,通过Uri读取或者修改文件,比如打开文件:
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter.
// Pull that URI using resultData.getData().
Uri uri = null;
if (resultData != null) {
uri = resultData.getData();
Log.i(TAG, "Uri: " + uri.toString());
showImage(uri);
}
}
private void showImage(Uri uri) {
try {
((ImageView) findViewById(R.id.imageView)).setImageBitmap(getBitmapFromUri(uri));
} catch (IOException e) {
e.printStackTrace();
}
}
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
ParcelFileDescriptor parcelFileDescriptor =
getContentResolver().openFileDescriptor(uri, "r"www.qwert888.com/);
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close();
return image;
}
修改文件:
try {
ParcelFileDescriptor pfd = getContentResolver().
openFileDescriptor(uri, www.jrgjze.com"w");
FileOutputStream fileOutputStream =
new FileOutputStream(pfd.getFileDescriptor());
fileOutputStream.write((
System.currentTimeMillis() + " edit file by saf\n").getBytes());
// Let the document provider know you're done by closing the stream.
fileOutputStream.close();
pfd.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
删除文件:
Uri uri = null;
if (resultData != null) {
uri = resultData.getData();
Log.i(TAG, "delete Uri: " + uri.toString());
// showImage(uri);
final int takeFlags =www.njdxtm.com getIntent().getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(uri, takeFlags);
try {
DocumentsContract.deleteDocument(getContentResolver(), uri);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
■ 新建文件
private void createFile(String mimeType, String fileName) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
// Filter to only show results that can be "opened", such as
// a file (as opposed to a list of contacts or timezones).
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Create a file with the requested MIME type.
intent.setType(mimeType);
intent.putExtra(Intent.EXTRA_TITLE, fileName);
startActivityForResult(intent, WRITE_REQUEST_CODE);
}
用户通过拉起的DocumentUI选择文件保存的目录,点击保存:
用户点击保存之后,DocumentUI就会把需要保存的文件的Content Uri通过intent传回给应用:
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter.
// Pull that URI using resultData.getData().
Uri uri = null;
if (resultData != null) {
uri = resultData.getData();
Log.i(TAG, "Uri: " + uri.toString());
// showImage(uri);
final int takeFlags = getIntent(www.zhongyiyul.cn).getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver(www.tiaotiaoylzc.com).takePersistableUriPermission(uri, takeFlags);
writeFile(uri);
}
}
写文件:
private void writeFile(Uri uri) {
try {
ParcelFileDescriptor pfd = getContentResolver().
openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(pfd.getFileDescriptor());
fileOutputStream.write(("Overwritten by MyCloud at " +
System.currentTimeMillis(www.yunshengyule178.com) + "\n").getBytes());
// Let the document provider know you're done by closing the stream.
fileOutputStream.close(www.yongshiyule178.com);
pfd.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
应用卸载之后应用文件删除适配指导
应用通过路径生成的文件都是存放在应用的沙箱目录下面,应用卸载的时候,应用不做适配,应用的整个沙箱目录都会被直接删除,如果应用有一些用户主动保存的文件不希望在应用被卸载的时候删除需要如何做呢?有两个方法:
■ 推荐方法:
把不希望删除的文件通过MediaProvider或者SAF的接口保存在公共集合目录下面,具体可以参考前面的适配章节内容,保存在公共集合目录的文件在应用卸载的时候默认会弹框提示用户是否删除这些文件,对应下面弹框的第一个勾选,默认保留,勾选删除,谷歌后续的版本计划把这个勾选去掉,意味着应用保存到公共集合目录的文件卸载的时候不会提示用户删除。
■ 方法2:
在应用的AndroidManifest.xml文件增加:<application android:fragileUserData=”true” />,应用不增加该属性的话,应用能卸载的时候应用保存在沙箱目录的文件会直接被删除,不会弹框提示。只有应用增加了这个属性,并且设置的值是为true,在应用被卸载的时候,才会弹框提示,对应的是上面图中的第2个勾选,默认删除,勾选保留。
以上就是我们关于Android Q 版本对设备存储空间进行的限制、新特性变更引发的兼容性问题及原因分析以及各应用厂商该如何适配这些变动点进行的重点分享与讲解。目前Android Q beta 2测试版本已经默认开启沙箱化特性,请广大开发者尽快适配!
安卓Q | 诸多本地文件找不到?应用文件存储空间沙箱化适配指导的更多相关文章
- SpringBoot 无法显示html文件 找不到html文件 如果显示html文件
两种情况: 1.如果使用了 thymeleaf 模板引擎,html文件可以放在 template文件夹中,如果不是一定不要放进去,否则找不到,因为html是静态页面,所以放在把此类文件放在了stati ...
- 本地上传文件至服务器的技巧(linux文件压缩及解压文件)
linux(ubuntu)文件解压及压缩文件 ubuntu支持文件的解压及压缩功能, 如果ubuntu上面没有安装过unzip工具的话,可以通过下面命令安装: sudo apt-get install ...
- 报错解决——Failed to load resource: the server responded with a status of 404 (Not Found) favicon.ico文件找不到
Django项目开发完成后在本地运行没问题,但在推到服务器上后出现报错Failed to load resource: the server responded with a status of 40 ...
- js调用本地office打开服务器的office文件预览
本来是想做成直接在网页上在线预览office文件的,但是找了好多,要不是收费,要不就是要调用别人的API不安全,所以纠结了好久还是用调用本地的office预览office文件. 废话不多说,那么怎么调 ...
- [CentOS7] Segmentation fault (core dumped),但是在主机上找不到core文件
1.问题描述 程序执行报:Segmentation fault (core dumped),但是在主机上找不到core文件 2.如何让系统生成core file /home>ulimit -ac ...
- 【Android】安卓Q适配指南-相册
碎碎念 本来每次安卓版本升级都是非常期待的事情,但是开发者就吃苦了!!! 尤其是从Q开始,应用采用沙盒模式,即各种公共文件的访问都会受到限制... 所以适配Q成了当务之急,然鹅网上关于适配的资料少之又 ...
- 项目集成seata和mybatis-plus冲突问题解决方案:(分页插件失效, 自动填充失效, 自己注入的id生成器失效 找不到mapper文件解决方案)
项目集成seata和mybatis-plus,seata与mybatis-plus冲突问题(所有插件失效,自动填充失效,找不到mapper文件解决方案) 自动填充代码: package com.fro ...
- Hive实战UDF 外部依赖文件找不到的问题
目录 关于外部依赖文件找不到的问题 为什么要使用外部依赖 为什么idea 里面可以运行上线之后不行 依赖文件直接打包在jar 包里面不香吗 学会独立思考并且解决问题 继承DbSearcher 读取文件 ...
- iOS开发中遇到的错误整理 - 集成第三方框架时,编译后XXX头文件找不到
iOS编译报错-XXX头文件找不到 错误出现的情况: 自己在继承第三方的SDK的时候,明明导入了头文件,但是系统报错,提示头文件找不到 解决方法 既然系统找不到,给他个具体路径,继续找去! 路径就填写 ...
随机推荐
- 用友云开放平台之API网关
本文介绍选择API网关应考虑的几方面内容,API网关在微服务框架中的作用,API网关如何选型,用友云开放平台的API网关可以做什么. 随着互联网的快速发展,当前已步入移动互联.物联网时代.企业内部系统 ...
- MySQL导出数据,并转存到Excel表格中
从数据库中导出数据的方法,这里就不提了,网上有很多方法,如果闲麻烦,可以看一下这个:mysql导出数据 其实使用最简单的下面这个语句: mysql > select * from demo in ...
- PHP优化与提升
一.十个不错的建议 1.使用 ip2long() 和 long2ip() 函数来把 IP 地址转化成整型存储到数据库里.这种方法把存储空间降到了接近四分之一(char(15) 的 15 个字节对整形的 ...
- 关于Fatal error: Paletter image not supported by webp 报错
报错提示 Fatal error: Paletter image not supported by webp 原因是由于图片被非法编辑过(相对PHP来说)造成, 有可能是某些编辑图片的软件的格式与PH ...
- Git SSH公钥配置
https://www.cnblogs.com/smuxiaolei/p/7484678.html https://blog.csdn.net/weixin_42063071/article/deta ...
- 剑指offer(20)二叉搜索树与双向表
题目: 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表.要求不能创建任何新的结点,只能调整树中结点指针的指向. 思路一:递归法 1.将左子树构造成双链表,并返回链表头节点. 2.定位至左子 ...
- 将Vue移动端项目打包成手机app---HBuilder
将移动端页面打包成app 1.使用 HBuilder 直接编译打包 点击左上角 文件>打开目录>选择目录 选择用Webpack打包好的dist文件目录 由于我添加到项目了,所以会显示该项 ...
- python设计模式第二十三天【状态模式】
1.应用场景 (1)通过改变对象的内部状态从而改变对象的行为,一般表现为状态的顺序执行 2.代码实现 #!/usr/bin/env python #!_*_ coding:UTF-8 _*_ from ...
- Python图形用户界面
1.使用Tkinter创建图形用户界面的步骤 (1)导入Tkinter模块,使用import Tkinter或from Tkinter import * (2)创建顶层窗口对象容器,使用top = T ...
- TP5上传图片
模板: <form action="{:url('Temp/addTempDo')}" enctype="multipart/form-data" met ...