android实验
Ⅰ android怎么用流 读取云端图片
在java 直接用BitmapFactory.decodeFile(pathName);就可以读取。
或者用你的方式,BitmapFactory.decodeStream(fosfrom);
SE的学习过程中,我们就接触到了I/O流的概念,并且使用了一些文件输入输出流的API来将数据保存到文件或者是从文件中读取数据。同样,这个手段也适用于Android系统,我们可以在应用开发过程中使用文件相关的API方法。
何谓内部存储呢?实际上就是指将程序的私有数据以文件的形式存储在Android设备的内存中。同理,如果是存放在SD卡等地方,就称之为外部存储。
我们在,你需要建立文件的输入输出流,并在流中操作数据。
首先是写文件。
为了使用内部存储向文件中写入一些数据,你可以调用openFileOutput()方法,需要的参数为文件名和写入模式。写入模式实际上就是该文件的权限归属,可以是公有的,也可以是私有的。如何具体地创建一个文件输出流呢,具体方式如下:
FileOutputStream fileOut =
openFileOutput("file_name",MODE_WORLD_READABLE);
//第一个参数即文件名,第二个参数表示文件存储的模式
对于第二个参数,需要补充的是:文件存储的模式公有四种:
MODE_PRIVATE:该模式是默认的,通过该模式创建的文件是私有的,即只能被应用本身访问。注意:在该模式下写入的内容会覆盖原有的内容。
MODE_APPEND:与上个模式类似,但在写入内容时会检查待创建的文件是否存在。若文件已被创建,则往该文件中追加内容,而不是覆盖原来的内容;若文件未被创建,则创建新的文件。
MODE_WORLD_READABLE:表示当前创建的这个文件允许被其他应用读取。
MODE_WORLD_WRITEABLE:表示当前创建的这个文件允许被其他应用写入。
默认情况下,不仅是应用创建的文件,包括Shared
Preferences和数据库,都是私有的。那么问题来了,如果希望其他应用既能读又能写,应该怎么办呢?参考下面这种用法:
FileOutputStream fileOut = openFileOutput("file_name",MODE_WORLD_READABLE +
MODE_WORLD_WRITEABLE);
而对于openFileOutput()方法,它返回的是一个FileOutputStream实例。换言之,你通过文件输出流对象中得到了这个实例,随后你便可以调用写相关的方法来向文件写入数据。具体的用法如下:
String str = "shiyanlou";
//待写入的数据
fOut.write(str.getBytes());
//实际上写入文件的是一个字节数组,我们用gettBytes()方法将其转换成目标格式
fOut.close();
//记得写入完毕后要用close()结束
接下来是读文件。
怎样读取我们刚刚创建的这个文件呢?你应该猜到了,是使用openFileInput()方法。该方法需要填入待读取文件的文件名,同样,它返回的是一个FileInputStream的实例。具体用法如下:
FileInputStream fileIn = openFileInput(file);
在打开输入流后,你可以调用read()方法每次从文件中读出一个字符出来,然后装入一个字符串变量中。具体做法如下:
int ch;
String temp="";
while( (ch = fin.read()) != -1){
temp = temp + Character.toString((char)ch);
//不断地追加读取的内容
}
//通过一个for循环,你最后得到的temp字符串包含了文件中的所有数据
fileIn.close();
//记得要关闭输入流
除了上面我们用到的这些方法(比如write()、read()和close()),FileOutputStream和FileInputStream类中还有很多常用的方法,我们在下面作一简要介绍。
FileOutputStream类中的其它常用方法:
write(byte[] b, int off, int len):该方法从指定的字节数组开始到该文件输出流关闭写入len字节。
getChannel():该方法返回一个只读的、与文件输出流关联的文件通道对象(FileChannel)
getFD():该方法返回与此流有关的文件描述符对象(FileDescriptor)
FileInputStream类中的其它常用方法:
read(byte[] b, int off, int len):该方法从输入流中数据读取最多len个字节到字节数组
getChannel():该方法返回一个只读的、与文件输出流关联的文件通道对象(FileChannel)
getFD():该方法返回与此流有关的文件描述符对象(FileDescriptor)
available():该方法可通过一个方法的调用者从输入流中阻止这个输入流返回可以读取(或跳过)的字节数的估计值。
四、在Android上实现文件读写
下面我们通过一个实例来学习如何将Java中常用的文件读写沿用到Android中去。
实验步骤主要有:
(若你已在第二小节完成,请跳至下一步)使用Android
Studio创建应用项目FileIOPractice,包名为com.shiyanlou.android.fileiopractice,基于Android
5.1制作。同时添加MainActivity及其布局资源文件。
创建并打开AVD模拟器(镜像选择API22:Android 5.1.1)。
在res/layout/activity_main.xml资源文件放入一些文本框和按钮。
在MainActivity.java中,初始化要用到的控件,添加文件读写相关的代码。
编译并运行这个应用,等待应用安装至模拟器,在模拟器中使用该应用。
现在你可以按照上述步骤自己完成该实验。下面也给出了本实验涉及到的源代码和资源文件详情,在必要时你可以使用它们。在注释中我们也对一些模块进行了详细的讲解。
以下是MainActivity的布局文件res/layout/activity_main.xml:
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textview_domain"
android:text="Shiyanlou.com"
android:textSize="40dp"
android:textColor="#11AA8C"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView_appName"
android:text="File I/O Practice"
android:layout_below="@+id/textview_domain"
android:layout_centerHorizontal="true"
android:textColor="#000000"
android:textSize="40dp" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/editText"
android:hint="Input some text here."
android:focusable="true"
android:layout_marginTop="20dp"
android:layout_below="@+id/textView_appName"
android:layout_alignEnd="@+id/textView_appName"
android:layout_alignLeft="@+id/textView_appName"
android:layout_alignStart="@+id/textView_appName"
android:layout_alignRight="@+id/textView_appName"
android:inputType="textMultiLine"
android:minLines="5"
android:background="#DDDDDD" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView_data"
android:text="Text would be shown here."
android:layout_below="@+id/button_save"
android:layout_marginTop="10dp"
android:layout_centerHorizontal="true" />
android:layout_width="120dp"
android:layout_height="wrap_content"
android:id="@+id/button_save"
android:text="Save File"
android:textColor="#FFFFFF"
android:background="#11AA8C"
android:layout_alignLeft="@+id/textView_appName"
android:layout_alignStart="@+id/textView_appName"
android:layout_below="@+id/editText"
android:layout_marginTop="20dp"/>
android:layout_width="120dp"
android:layout_height="wrap_content"
android:id="@+id/button_load"
android:text="Load File"
android:textColor="#FFFFFF"
android:background="#11AA8C"
android:layout_alignTop="@+id/button_save"
android:layout_alignRight="@+id/editText"
android:layout_alignEnd="@+id/editText" />
以下是src/MainActivity.java中的源代码:
package com.shiyanlou.android.fileiopractice;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class MainActivity extends Activity {
private Button button_save;
private Button button_load;
private TextView textView_status;
private EditText editText;
//声明各个控件
private String data;
private String file = "shiyanlou_file";
//声明用于存放数据的变量data和文件名常量file
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button_save =(Button)findViewById(R.id.button_save);
button_load =(Button)findViewById(R.id.button_load);
editText =(EditText)findViewById(R.id.editText);
textView_status =(TextView)findViewById(R.id.textView_data);
//实例化各个控件
//为按钮设置点击事件监听器
//首先是保存按钮(写操作)
button_save.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
data = editText.getText().toString();
//调用getText()方法从文本框中读取内容,使用toString()方法将其转化为String类型数据,并存放到data变量中
try {
//对于文件读写操作应使用异常处理流程
FileOutputStream fileOut = openFileOutput(file, MODE_APPEND);
// 此处用到了MODE_PRIVATE,在新版本的Android系统中,不推荐使用MODE_WORLD_READABLE等模式
// 这是因为Android中为应用间的数据交互提供了更好的支持,如Content Privoder等,你可一进步了解
fileOut.write(data.getBytes());
//用write()方法保存数据
fileOut.close();
//关闭输出流
Toast.makeText(getBaseContext(), "File saving completed.",
Toast.LENGTH_SHORT).show();
//用一个Toast来告诉用户已经写入完毕了
} catch (Exception e) {
e.printStackTrace();
}
}
});
//其次是读取按钮(读操作)
button_load.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//同理,建立文件输入流
FileInputStream fileIn = openFileInput(file);
int ch;
//声明一个整型变量ch用于接收数据
String tempText = "";
//这里的tempText用于临时存放读取的数据(字符串)
while ((ch = fileIn.read()) != -1) {
//这里的-1是特定的状态码,表示文件读取结束
//在while循环中逐个读取数据,并追加到tempText中
tempText = tempText + Character.toString((char) ch);
//将ch转化为字符
}
textView_status.setText(tempText);
//读取完毕后将文字显示出来
Toast.makeText(getBaseContext(), "File reading completed.",
Toast.LENGTH_SHORT).show();
//同样用一个Toast告知用户已经读取完成
} catch (Exception e) {
}
}
});
}
}
检查一下代码,编译并运行该应用。在模拟器中尝试输入一些信息,保存一下,再试着读取它们。
Ⅱ android推送保活实验到结论
(以极光推送为例)
操作:从后台应用列表划除应用
结果:只干掉了UI进程,remote进程没有干掉。
所以推送服务正常运作。
重启手机,推送服务正常运作。
判断是,由于能够捕获到开机监听,其他带有极光SDK的应用做了开机自启动,然后极光SDK再互相启动手机里所有带有极光SDK的服务。
于是自己的应用即使没有做开机自启动推送服务,推送服务也可以正常运作。极光SDK互相拉起。
操作:从后台应用列表划除应用
结果:UI进程,remote进程都被干掉了,所有包名下的服务都被干掉,包括前台服务。干干净净。
开机监听无法检测到。
微信那些主流APP已经在小米白名单里,跟系统进程一样开机就存在了。
操作:从后台应用列表划除应用
结果:UI进程,remote进程都被干掉了,所有包名下的服务都被干掉,包括前台服务。干干净净。
开机监听无法检测到。
微信那些主流APP已经在小米白名单里,跟系统进程一样开机就存在了。
除非能像微信、QQ等大牌应用获取厂商支持,默认添加进白名单,否则其他应用在用户主动杀死应用后(在后台应用列表中,滑动删除应用),都无法存活,包括推送子进程。
当然,像NEXUS,LG,索尼这类不是本土品牌的手机,则可以存活,原因是本土厂商对手机系统做了严格的限制。你懂的,本土应用太过流氓,后台服务,互相保活,开机唤醒等各种骚操作使得手机性能急剧下降,为了提高用户体验,让手机更具性价比,而为之。
在用户没有主动杀死应用的情况下,提高进程的优先级,让应用不被系统主动回收。进程参数oom_score_adj(oom_adj)标记了进程优先级,数字越小优先级越高,越难被系统回收。
前台进程>可见进程>服务进程>后台进程>空进程
如何提高进程优先级可自行google
使用厂商自家的推送服务。
也就是说,你要支持华为用户,那么就接入华为推送;你要支持小米用户,那么就接入小米推送。
信鸽推送可以减少接入多个厂商的工作量,可以了解其SDK文档。(截止目前,信鸽支持的第三方厂商,有华为、小米、魅族;OPPO刚出了自家的推送服务,信鸽还没有;VIVO压根没有自家的推送服务)
Ⅲ Android 屏幕分辨率适配
Android屏幕分辨率千奇百怪,怎么让app在不同的分辨率的设备上“看起来一样”呢?
你也许还有以下疑惑:
这篇文章将会针对以上问题一一解答。
Pixels 我们看到屏幕上的图像由一个个像素组成,像素里包含色彩信息。
如常说的手机分辨率:1080 x 1920 指的是手机宽度可展示1080像素,高度可展示1920像素。
Pixels Per Inch 每英寸长度所具有的像素个数,单位面积内像素越多,图像显示越清晰。
ppi一般用在显示器、手机、平板等描述屏幕精细度。
Dots Per Inch 每英寸长度所具有的点数。
dpi一般用来描述打印(书本、杂志、电报)的精细度
density-independent pixels (device-independent pixels 我查了一下,官网更多时候使用前者,有的时候也显示后者),dip是缩写,也可以更简单些称作dp。该单位的目的是屏蔽不同设备密度差异,后面细说。
Scalable pixels 用于设置字体,在用户更改字体大小时候会适配。
澄清了基本概念,我们现在从一个例子开始说明以上单位之间的区别与联系。
布局文件里有个View,长宽都是200px,分别在分辨率为480(宽)x800(高)简称A设备、1080(宽)x1920(高)简称B设备,效果如下:
左边是A设备,右边是B设备。问题出来了,同样长宽都是200px,为啥A设备显示很大,B设备显示很小呢?你可能会说B设备的横向分辨率1080比A设备的480大,所以在B设备上看起来比较小。来看看A、B设备横向到底是多少英寸,怎么来计算呢?这时候就需要用到ppi了,既然知道横向的像素点个数,也知道每英寸能容纳的像素点,当然可以得知横向的尺寸了。
其中一种方式获取DisplayMetrics对象:
A设备宽度尺寸:480(px)/240(ppi)=2inch
B设备宽度尺寸:1080(px)/420(ppi)=2.5inch
可以看出,A、B设备尺寸差别不大。A设备ppi=240 B设备ppi=420,明显地看出B设备单位长度上比A设备能够容纳更多的像素,因此同样的200px,B设备只需要较小的尺寸就能够显示,因此在B设备上的view看起来比A设备小很多。
知道了问题的原因,然而显示的效果却不能接受。
我们总不能自己判断每个设备的ppi,然后计算实际需要多少像素,再动态设置view的大小吧,那layout里的静态布局大小就无法动态更改适应了。想当然的能有一个统一的地方替我们转换,没错!Android系统已经帮我们实现了转换。接下来就是dpi、dp出场了。
Android系统使用dpi来描述屏幕的密度,使用dp来描述密度与像素的关系。
A设备dpi=240
B设备dpi=420
Android系统最终识别的单位是px,怎么将dpi和px关联起来呢?,答案是dp。
Android规定当dpi=160时,1dp=1px,当dpi=240时,1dp=1.5px,依此类推,并且给各个范围的dpi取了简易的名字加以直观的识别,如120<dpi<=160,称作为mdpi,120<dpi<=240 称作hdpi,最终形成如下规则:
现在知道了dp能够在不同dpi设备上对应不同px,相当于中间转换层,我们只需要将view长宽单位设置为合适的dp,就无需关注设备之间密度差异,系统会帮我们完成dp-px转换。将我们之前的例子稍微更改,再看看效果验证一下:
通过上面对dp的了解,我们知道在设定view大小、间距时使用dp能最大限度地屏蔽设备密度之间的差异。可能你就会问了,那bitmap展示的时候如何适配不同密度的设备呢?
自定义view从磁盘上加载一张图片,并将之显示在view上,view的大小决定于bitmap大小。依旧以上述A、B设备为例,展示结果如下:
左边是A设备,右边是B设备。
明显地看出,在A设备显示比B设备大很多,实际上和我们之前用px来描述view的大小原理是一样的,bitmap的宽、高都是px在描述,而bitmap决定了view的宽、高,最终导致A设备和B设备上的view大小(宽、高像素)是一样的,而它们屏幕密度又不相同,因此产生了差异。
那不会每次都需要我们自己根据屏幕密度来转换bitmap大小吧?幸运的是,Android已经为我们考虑到了。
生成不同密度的目录有什么作用?
A设备dpi=240,根据dpi范围,属于hdpi
B设备dpi=420,根据dpi范围,属于xxhdpi
图片原始尺寸:photo1.jpg(宽高 172px-172px)
当我们想要在不同密度设备上显示同一张图片并且想要“看起来一样大时”。假设设计的时候以hdpi为准,放置photo1.jpg为172*172,那么根据计算规则在xxhdpi上需要设置photo1.jpg为:
现在hdpi和xxhdpi目录下分别存放了同名图片:photo1.jpg,只是大小不同。当程序运行的时候:
来看看效果:
左边A设备,右边B设备
针对不同的密度设计不同的图片大小,最大限度保证了同一图片在不同密度设备上表现“看起来差不多大”。
来看看A、B设备上图片占内存大小:
说明在B设备上显示photo1.jpg需要更多的内存。
上边只是列举了hdpi、xxhdipi,同理对于mdpi、xhdpi、xxxhdpi根据规则放入相应大小的图片,程序会根据不同的设备密度从对应的mipmap文件夹下加载资源。如此一来,我们无需关注bitmap在不同密度设备上显示问题了。
在mipmap各个文件夹下都放置同一套资源的不同尺寸文件似乎有点太占apk大小,能否只放某个密度下图片,其余的靠系统自己适配呢?
现在只保留hdpi下的photo1.jpg图片,看看在A、B设备上运行情况如何:
看起来和上张图差不多,说明系统会帮我们适配B设备上的图片。
再来看看A、B设备上图片占内存大小:
先看A设备:
对比photo1.jpg 分别放在hdpi、xxhdpi和只放在hdpi下可以看出:B设备上图片所占内存变小了。为什么呢?接下来从源码里寻找答案。
A、B设备同样加载hdpi/photo1.jpg,返回的bitmap大小不相同,我们从这方法开始一探究竟。
上面涉及到的关键点是density,分别是TypedValue的density和Options的density。
先来看看TypedValue density:
再来看看Options density
现在分析B设备加载hdpi/photo1.jpg如何做的:
和我们之前调试的结果一致。
B设备是怎么决定使用hdpi下的图片资源呢?
根据实验(尝试找了源码,没怎么看懂,因此只是做了实验,可能在不同密度设备上找寻规则不一样):B设备先找属于自己密度范围文件夹下的图片,B设备属于xxhdpi,先查看xxhdpi有没有photo1.jpg,如果没有则往更高的密度找,比它高的密度是xxxhdpi,还是没有,则往低密度找,找xhdpi,没有再找hdpi,找到了则返回构造好的TypedValue,剩下的就是我们前面分析的。
既然我们只想放某个密度下的一份切图,该放哪个密度下呢?从系统寻找规则看,更推荐放置在更高密度下的,因为如果放在低密度下,那么当运行在高密度设备上时,图片会进行放大,可能导致不清晰。我一般习惯放在xxhdpi下。
Android Studio默认创建了不同密度的mipmap文件夹,默认放置了ic_launcher.png。我们普通的切图该放drawable还是mipmap下呢?对于这个问题网上也是众说纷纭,实际上对于我们来说,关注的重点是图片放在drawable或者mipmap,加载出来bitmap是否有差异,如果没有差异放在哪就看习惯了。通过实践,普通的切图放drawable和mipmap下加载出来的bitmap是没有差异的,只不过用drawable的话需要自己创建不同密度的文件夹。我习惯于放在drawable下(启动图标logo还是放在mipmap下)。
前边 [注1] 留了个问题,我们使用dp来表示view的大小了,为啥两个看起来还是有些差距?下面我们更加直观地看一个例子。
A设备dpi=240 密度1.5 分辨率(宽高px):480 * 800
B设备dpi=420 密度2.625 分辨率(宽高px):1080 * 1794
换算成dp
A设备分辨率:320dp * 533dp
B设备分辨率:411dp * 683dp
依旧是上边的例子:
将view宽高分别设置为320dp,看看效果:
左边A设备,右边B设备
可以看出同样的320dp大小,A设备铺满了屏幕,而B设备没有。这效果显然是不能接受的,Android考虑到不同设备宽高不同,推出了"宽高限定符"。以A、B设备为例:
在res文件夹下创建文件夹:
假设设计师出图是按照800x480,那么我们创建dimen文件的时候
该文件放在values-800x480文件夹下。
根据分辨率比例算出1794x1080的dimen值
这样子,A、B设备加载资源的时候使用对应分辨率限定符下的px,如果找不到再找默认值,可以在一定程度上解决屏幕宽高碎片化适配问题。
但是这样子的限定比较严格,需要测试各种分辨率,后来Android又推出了"smallest-width"简称最小宽度限制。
A设备宽320dp
B设备宽411dp
假设设计师切图标准屏幕宽是320dp(A设备),那么可以定义如下dimen.xml文件
该文件放在values-sw320dp文件夹下
根据规则,计算B设备dimen.xml
现在我们继续来看之前的view
通过对dimen引用,A设备寻找和自己宽度一样的dimen文件,找到values-sw320dp,dp320=320dp。B设备寻找和自己宽度一样的dimen文件,找到values-sw411dp,dp320=410dp。这样子同样的dp320,得出不同的值,就适配了屏幕宽度不同的问题。
看看效果:
这次B设备也铺满了屏宽。
综上,为了适配不同屏幕大小,推荐使用dp+smallest-width。
获取设备dpi最终都是从这方法获取的,实际上就是读取系统的配置文件。因此我们也可以通过adb shell 获取:
可以看出dpi是系统配置好的,当然有些手机是可以设置分辨率的,设置之后我们查看分辨率:
分辨率变低了,dpi也变小了。
Ⅳ Android调用系统相机实现拍照和视频录制
(1)申请权限
(2)设置布局
这里做了一个简单的布局:添加了一个按钮和一个ImageView控件用于显示拍摄的图像。
(3)为按钮添加点击事件监听
点击按钮时,调用系统相机进行拍照,并在确定后将图像显示在ImageView控件中。
(1)申请权限
(2)设置布局
添加了一个按钮和一个VideoView控件用于显示录制的视频。
(3)为按钮添加点击事件监听
同前面一样,点击按钮后调用系统相机进行录制视频,录制完成后点击确定即可将录制的视频显示在VideoView控件中。
对于Android11.0的版本,在调用系统相近进行视频录制的时候,即使在AndroidMenifest.xml中申请了CAMERA权限,还是会在程序运行时报错: Permission Denial , . .... .... with revoked permission android.permission.CAMERA
解决方法是在程序中动态申请权限:
写在最后:文章是在学习过程中做的学习笔记,同时与志同道合者分享,文章内容均经过我自己实验证实可行,如有问题欢迎留言,很高兴一起交流讨论,共同进步!
Ⅳ 进行Android开发的时候没有测试机适配怎么办,如何进行屏幕适配
Android项目的res目录下一般加上我们自己创建的,会有6个目录,分别是:drawble drawble-ldpi drawble-mdpi drawble-hdpi drawble-xhdpi drawble-xxhdpi, 这里就不包括更为特殊的drawble目录了,(比如drawlbe-land-hdpi, 表示水平方向的高分辨率的图片,这些都目录不管多么长,它们都是按一丁点规律匹配的, 我们的目的是, 从个别中发现规律,从而应用到整体)。
当一个apk运行起来时,Android系统会根据其所运行的手机的屏幕密度去相对应的图片文件夹里找指定名称的图片。 注意, 先去哪个目录里找,完全是根据这个手机的屏幕密度决定的。
其中注意两点:
1, 中等分辨率,即mdpi的屏幕密度是160,他是标准的参考密度。所以计算比例的时候它的比例值是1. 其他屏幕密度的参考比例都是以这个为依据。
2, 默认的drawble目录(一般是自己建的),和mdpi是一样的。将图片放到这个目录和放到drawble-mdpi目录是一样的效果。不过一般习惯性的放一些自定义selector或者点9的图片在这里。
现在我们来看, HTC one V手机的屏幕密度是252ppi, 那距离哪一个最靠近呢, 就是hdpi了。 所以当apk运行在这个手机上时,首先会去这个目录找图片。
下面是用常见的一些类型的手机总结的一个表格:
注意一点: 上面说的对应关系,都是首选目录, 那如果首选目录里面找不到图片呢?
Android图片选择策略
上面说到, 如果屏幕所对应的文件夹没有要找的图片,怎么办。这是很常见的,我们开发项目时一般不会去为每一个级别的屏幕去切一套图片。那样做只会让apk很大。所以一般性的图片我们只切一两个典型密度屏幕的图片。但是apk是有可能会运行在从ldpi到xxhdpi的各种级别的手机上。这个时候就需要根据一定的策略去寻找图片了。
Android系统寻找图片的步骤是这样的:
1, 去屏幕密度对应的目录去找。如果找到就拿来用。
2, 如果没找到,就去比这个密度高一级的目录里面去找,如果找到就拿来用。
3, 如果没找到就继续往上找。以此类推。
4, 如果到了xxhdpi目录还没有找到的话,就会去比自身屏幕密度低一级的目录去找,如果低一级的目录>=hdpi,找到了就拿来用。
5, 如果没找到, 就去mdpi目录去找, 如果找到了,就拿来用。
6, 如果没找到,就去默认的drawble目录里去找, 如果找到了就拿来用。
7 ,如果没找到,再去最低的ldpi目录里去找。如果找到了,就拿来用。
8, 如果没找到, 那就是没找到了, 图片无法显示。(不过一般不会出现这种现象,因为如果每个目录都没有这个图片的话,你是编译不过的)
这里有两点需要注意:
① 首先会去比自己密度高的目录里去找,这是因为因为系统相信,你在密度更高的目录里会放置分辨率更大的图片,这样的话这个图片会被缩小,但同时显示效果不会有损失,但是如果优先去低一级别的目录去找的话, 找到的图片就会被放大,这样的话这个图片就会被拉扯模糊了。
e.g. 同一张图片,你在mdpi和xxhdpi目录各放了一份, 这个应用你现在运行在hdpi的手机上, 那应用会选择哪张图片呢。答案是xxhdpi目录里的。即便hdpi离mdpi更近一点!
②,如果在mdpi里找不到是不会直接去ldpi里找的, 而是先去默认的drawble目录里找,这是drawble目录和drawble-mdpi是一个级别的。
下面用一张流程图来总结:
(注: 以上流程图是我通过做实验总结出来的,如有谬误还望指出。)
Android系统对图片的缩放规则
上文中提到如果在手机对应的目录没有找到图片,就会按照一定的策略去其他目录找,那找到了以后就原图显示么? 非也。
对于放在不同目录下的图片, 系统会按照一定比例对原始的图片进行放大或者缩小, 具体的放大缩小比例可参考下表, 图片所在目录和对应的屏幕密度是相同时图片缩放比例为1,也就是原图显示,而横向的比例表示分别放在该密度手机上运行时图片被缩放的比例。
对原始图片的缩放倍数。
上表几点值得注意的地方:
①, drawable目录和drawable-mdpi目录和dp到px的转换关系是一样的。
②,当你放一个120px*180px的图片到drawable-hdpi目录,如果此应用运行在一个xhdpi的手机上,则这个图片会被拉扯到160px*240px。
③, 最后一行dp->px, 说明了在代码或者布局文件中声明一个dp值, 这个值在不同屏幕密度的手机中会被乘以不同的倍数。 比如你在布局文件中写了一个宽和高分别为120dp和180dp的LinearLayout, 那么当这个应用运行在xhdpi的手机上时(比如上面那个常见手机表中的中兴U985手机),它的实际像素就会被转换为240px*360px。 如果运行在ldpi的手机上,就变成了90px*135px。 但是在这两个手机中显示的区域大小从肉眼看,是一模一样大的。(这点作为后面内容的一个引子,“看起来”一样大,这就是Android的一个神奇的地方)
我们来做个试验
试验材料:
① 一张120px*180px的图片
② 四部手机, 具体参数参考上面的一张表格。三星 Galaxy win pro 3218 (hdpi)、 HTC one V (hdpi)、 中兴U985 (xhdpi)、Google Nexus 7 (xhdpi)。
③ 我在布局文件里声明了3个View, 第一个位于左上角,是一个线性布局,宽和高指定为120dp*180dp(注意是dp哦), 第二个位于右上角,是一个ImageView,内容就是上面这张120px*180px的图片, 第三个位于左下角也是一个线性布局,固定宽高,是120px*180px。
我将这个图片放到一个Android工程里的drawable-hdpi目录
从上面的那种缩放关系表中我们可以知道,图片从hdpi目录中取, 运行在hdpi手机上宽高保持原始值,,运行在xhdpi手机上,宽高会乘以4/3, 也就是说图片会被拉扯变大, 但是图片的实际显示效果,即“视觉大小”怎么样呢。
下面是运行后的效果:
如图: 黑色区域是120dp*180dp的View, 蓝色区域是120px*180px的图片, 灰色区域是120px*120px的View。
1, 可以看到使用dp的View(黑色区域)在不同分辨率,不同屏幕尺寸,不同屏幕密度的手机下,视觉大小看起来是一模一样的。
但是他们的实际像素值是不一样的: 120dp*180dp -> (hdpi) -> 180px*270px, 而120dp*180dp ->(xhdpi)-> 240px*360px。 由于屏幕密度的不同,缩放以后的像素可以显示出一样的视觉大小。
2, 蓝色图片的视觉大小也是一样的, 由于图片放到了hdpi目录下, 所以前两个手使用的是图片的原始像素120px*180px, 而后两个手机对图片进行了放大, 参考上面的屏幕密度缩放关系表, 放大了4/3倍。 我通过对屏幕的截图,测量下来的结果的确是放大了这么多, 分别为160px*240px。 由于屏幕密度的不同,它们显示出来的视觉大小是相同的。
3, 但是使用固定像素值的View就没那么幸运了, 它在hdpi的手机上看起来要比在xhdpi的手机上大一些。 要是在屏幕密度相差更大的手机上看的话, 这个区域的大小会相差很大。 这就是为什么Android推荐使用dp作为View的尺寸,而不是真实像素的原因了。
4, 经过反复试验,(实验结果就不贴图了,很多),得出一个结论,使用哪个目录下的图片(前提是图片只放在某一个目录中),在所有,不管是分辨率还是屏幕尺寸还是屏幕密度,3个参数都在改变的情况下,图片显示的视觉大小都和运行在这个目录对应屏幕密度手机上时的大小是一样的。
UI给工程师切多大图是合适的。
说说我之前走的冤枉路吧。
在之前, 设计师的交互和视觉设计都是基于480*800的界面, 切图的时候会以480*800为基础切一版, 然后在给所切图片的宽和高乘上个4/3,然后在出一版。
比如同一个120*180的图片, 就会出两个版本, 一个是120*180的一个是160*240的。分别放到hdpi目录和xhdpi目录。
吃到的苦头是,UI很累, apk很大。T^T
这番探究下来, 发现直接基于720*1280的视觉稿切一版图片就可以了。 将图片只放到xhdpi目录中,这样系统会在不同密度屏幕的手机中对图片进行合理的缩放, 而之前这个缩放工作竟然是人工完成的!
另: 如果想在xxhdpi的手机上显示的很好, 也可以基于1080P的屏幕设计, 这样的话就兼容所有低密度屏幕的手机, 而且也不会出现图片被拉扯的现象。
Ⅵ android获取视频每一帧
本周给大家分享如何获取视频的每一帧的信息,说到这个那就得看我们的谷歌官方给我们的提供的api接口类:MediaMetadataRetriever,这个类是提供给我们用来获取视频信息的,
官方文档:
https://developer.android.google.cn/reference/android/media/MediaMetadataRetriever
通过文档我们知道,我们可以通过它提供的extractMetadata()方法获取视频基本信息,
例如:视频宽、高,时长,作者等,还有通过getFrameAtTime()方法获取对应时长位置的视频帧信息,返回的是一个bitmap对象
通过实践知道,这个方法好是好,但是获取视频帧的速度太慢了,我自己做的实验是,获取一个4分多钟的视频,每隔10秒取一帧,开了4个线程去取,时间大概在10秒左右,这样对于我们来说是不可以忍受的,所以最后在github上找到了一个很好的库推荐给大家:
https://github.com/wseemann/FFmpegMediaMetadataRetriever
这个库是基于ffmpeg实现的,同样的实验获取视频帧信息的速度在1秒左右,只开了一个线程,ffmpeg还是依旧的强大啊。
通过本周的学习,自己对于ffmpeg是越来越赶兴趣了,所以买了一本书来研究一下,但是本书讲的都是c语言,所以没办法又把我大学学习的c语言的书找了出来,继续开始着自己的爬坑之旅。