androidstudio怎么用base64encoder jar包

Android签字机制之-签名过程详解 - Android当前位置:& &&&Android签字机制之-签名过程详解Android签字机制之-签名过程详解&&网友分享于:&&浏览:0次Android签名机制之---签名过程详解一、前言又是过了好长时间,没写文章的双手都有点难受了。今天是圣诞节,还是得上班。因为前几天有一个之前的同事,在申请微信SDK的时候,遇到签名的问题,问了我一下,结果把我难倒了。。我说Android中的签名大家都会熟悉的,就是为了安全,不让别人修改你的apk,但是我们真正的有了解多少呢?所以准备两篇文章好好介绍一下Android中签名机制。在说道Android签名之前,我们需要了解的几个知识点1、数据摘要(数据指纹)、签名文件,证书文件2、jarsign工具签名和signapk工具签名3、keystore文件和pk8文件,x509.pem文件的关系4、如何手动的签名apk上面介绍的四个知识点,就是今天介绍的核心,我们来一一看这些问题。二、准备知识首先来看一下数据摘要,签名文件,证书文件的知识点1、数据摘要这个知识点很好理解,百度百科即可,其实他也是一种算法,就是对一个数据源进行一个算法之后得到一个摘要,也叫作数据指纹,不同的数据源,数据指纹肯定不一样,就和人一样。消息摘要算法(Message Digest Algorithm)是一种能产生特殊输出格式的算法,其原理是根据一定的运算规则对原始数据进行某种形式的信息提取,被提取出的信息就被称作原始数据的消息摘要。著名的摘要算法有RSA公司的MD5算法和SHA-1算法及其大量的变体。消息摘要的主要特点有:1)无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出。2)一般来说(不考虑碰撞的情况下),只要输入的原始数据不同,对其进行摘要以后产生的消息摘要也必不相同,即使原始数据稍有改变,输出的消息摘要便完全不同。但是,相同的输入必会产生相同的输出。3)具有不可逆性,即只能进行正向的信息摘要,而无法从摘要中恢复出任何的原始消息。2、签名文件和证书签名文件和证书是成对出现了,二者不可分离,而且我们后面通过源码可以看到,这两个文件的名字也是一样的,只是后缀名不一样。其实数字签名的概念很简单。大家知道,要确保可靠通信,必须要解决两个问题:首先,要确定消息的来源确实是其申明的那个人;其次,要保证信息在传递的过程中不被第三方篡改,即使被篡改了,也可以发觉出来。所谓数字签名,就是为了解决这两个问题而产生的,它是对前面提到的非对称加密技术与数字摘要技术的一个具体的应用。对于消息的发送者来说,先要生成一对公私钥对,将公钥给消息的接收者。如果消息的发送者有一天想给消息接收者发消息,在发送的信息中,除了要包含原始的消息外,还要加上另外一段消息。这段消息通过如下两步生成:1)对要发送的原始消息提取消息摘要;2)对提取的信息摘要用自己的私钥加密。通过这两步得出的消息,就是所谓的原始信息的数字签名。而对于信息的接收者来说,他所收到的信息,将包含两个部分,一是原始的消息内容,二是附加的那段数字签名。他将通过以下三步来验证消息的真伪:1)对原始消息部分提取消息摘要,注意这里使用的消息摘要算法要和发送方使用的一致;2)对附加上的那段数字签名,使用预先得到的公钥解密;3)比较前两步所得到的两段消息是否一致。如果一致,则表明消息确实是期望的发送者发的,且内容没有被篡改过;相反,如果不一致,则表明传送的过程中一定出了问题,消息不可信。通过这种所谓的数字签名技术,确实可以有效解决可靠通信的问题。如果原始消息在传送的过程中被篡改了,那么在消息接收者那里,对被篡改的消息提取的摘要肯定和原始的不一样。并且,由于篡改者没有消息发送方的私钥,即使他可以重新算出被篡改消息的摘要,也不能伪造出数字签名。所以,综上所述,数字签名其实就是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。不知道大家有没有注意,前面讲的这种数字签名方法,有一个前提,就是消息的接收者必须要事先得到正确的公钥。如果一开始公钥就被别人篡改了,那坏人就会被你当成好人,而真正的消息发送者给你发的消息会被你视作无效的。而且,很多时候根本就不具备事先沟通公钥的信息通道。那么如何保证公钥的安全可信呢?这就要靠数字证书来解决了。所谓数字证书,一般包含以下一些内容:证书的发布机构(Issuer)证书的有效期(Validity)消息发送方的公钥证书所有者(Subject)数字签名所使用的算法数字签名可以看出,数字证书其实也用到了数字签名技术。只不过要签名的内容是消息发送方的公钥,以及一些其它信息。但与普通数字签名不同的是,数字证书中签名者不是随随便便一个普通的机构,而是要有一定公信力的机构。这就好像你的大学毕业证书上签名的一般都是德高望重的校长一样。一般来说,这些有公信力机构的根证书已经在设备出厂前预先安装到了你的设备上了。所以,数字证书可以保证数字证书里的公钥确实是这个证书的所有者的,或者证书可以用来确认对方的身份。数字证书主要是用来解决公钥的安全发放问题。综上所述,总结一下,数字签名和签名验证的大体流程如下图所示:3、jarsign和signapk工具了解到完了签名中的三个文件的知识点之后,下面继续来看看Android中签名的两个工具:jarsign和signapk关于这两个工具开始的时候很容易混淆,感觉他们两到底有什么区别吗?其实这两个工具很好理解,jarsign是Java本生自带的一个工具,他可以对jar进行签名的。而signapk是后面专门为了Android应用程序apk进行签名的工具,他们两的签名算法没什么区别,主要是签名时使用的文件不一样,这个就要引出第三个问题了。4、keystore文件和pk8,x509.pem文件的区别我们上面了解到了jarsign和signapk两个工具都可以进行Android中的签名,那么他们的区别在于签名时使用的文件不一样jarsign工具签名时使用的是keystore文件signapk工具签名时使用的是pk8,x509.pem文件其中我们在使用Eclipse工具写程序的时候,出Debug包的时候,默认用的是jarsign工具进行签名的,而且Eclipse中有一个默认签名文件:我们可以看到这个默认签名的keystore文件,当然我们可以选择我们自己指定的keystore文件。这里还有一个知识点:我们看到上面有MD5和SHA1的摘要,这个就是keystore文件中私钥的数据摘要,这个信息也是我们在申请很多开发平台账号的时候需要填入的信息,比如申请百度地图,微信SDK等,会需要填写应用的MD5或者是SHA1信息。5、手动的签名Apk包1》使用keytool和jarsigner来进行签名当然,我们在正式签名处release包的时候,我们需要创建一个自己的keystore文件:这里我们可以对keystore文件起自己的名字,而且后缀名也是无关紧要的。创建完文件之后,也会生成MD5和SHA1的值,这个值可以不用记录的,可以通过命令查看keystore文件的MD5和SHA1的值。keytool -list -keystore debug.keystore当然我们都知道这个keytstore文件的重要性,说白了就相当于你的银行卡密码。你懂得。这里我们看到用Eclipse自动签名和生成一个keystore文件,我们也可以使用keytool工具生成一个keystore文件。这个方法网上有,这里就不做太多的介绍了。然后我们可以使用jarsign来对apk包进行签名了。我们可以手动的生成一个keystore文件:keytool -genkeypair -v -keyalg DSA -keysize 1024 -sigalg SHA1withDSA -validity 20000 -keystore D:\jiangwei.keystore -alias jiangwei -keypass jiangwei -storepass jiangwei这个命令有点长,有几个重要的参数需要说明:-alias是定义别名,这里为debug-keyalg是规定签名算法,这里是DSA,这里的算法直接关系到后面apk中签名文件的后缀名,到后面会详细说明在用jarsigner工具进行签名jarsigner -verbose -sigalg SHA1withDSA -digestalg SHA1 &-keystore D:\jiangwei.keystore -storepass jiangwei D:\123.apk jiangwei这样我们就成功的对apk进行签名了。签名的过程中遇到的问题:1》证书链找不到的问题这个是因为最后一个参数alias,是keystore的别名输错了。2》生成keystore文件的时候提示密码错误这个原因是因为在当前目录已经有debug.ketystore了,在生成一个debug.keystore的话,就会报错3》找不到别名的问题这个问题的原因是因为我们在使用keytool生成keystore的时候,起了debug的别名,这个问题困扰了我很久,最后做了很多例子才发现的,就是只要我们的keystore文件的别名是debug的话,就会报这样的错误。这个应该和系统默认的签名debug.keystore中的别名是debug有关系吧?没有找到jarsigner的源码,所以只能猜测了,但是这三个问题在这里标注一下,以防以后在遇到。注意:Android中是允许使用多个keystore对apk进行签名的,这里我就不在粘贴命令了,我又创建了几个keystore对apk进行签名:这里我把签名之后的apk进行解压之后,发现有三个签名文件和证书(.SF/.DSA)这里我也可以注意到,我们签名时用的是DSA算法,这里的文件后缀名就是DSA而且文件名是keystore的别名哎,这里算是理清楚了我们上面的如何使用keytool产生keystore以及,用jarsigner来进行签名。2》使用signapk来进行签名下面我们再来看看signapk工具进行签名:java -jar signapk.jar .testkey.x509.pem testkey.pk8 debug.apk debug.sig.apk这里需要两个文件:.pk8和.x509.pem这两个文件pk8是私钥文件x509.pem是含有公钥的文件这里签名的话就不在演示了,这里没什么问题的。但是这里需要注意的是:signapk签名之后的apk中的META-INF文件夹中的三个文件的名字是这样的,因为signapk在前面的时候不像jarsigner会自动使用别名来命名文件,这里就是写死了是CERT的名字,不过文件名不影响的,后面分析Android中的Apk校验过程中会说道,只会通过后缀名来查找文件。3》两种的签名方式有什么区别那么问题来了,jarsigner签名时用的是keystore文件,signapk签名时用的是pk8和x509.pem文件,而且都是给apk进行签名的,那么keystore文件和pk8,x509.pem他们之间是不是有什么联系呢?答案是肯定的,网上搜了一下,果然他们之间是可以转化的,这里就不在分析如何进行转化的,网上的例子貌似很多,有专门的的工具可以进行转化:那么到这里我们就弄清楚了这两个签名工具的区别和联系。三、分析Android中签名流程机制下面我们开始从源码的角度去看看Android中的签名机制和原理流程因为网上没有找到jarsigner的源码,但是找到了signapk的源码,那么下面我们就来看看signapk的源码吧:源码位置:com/android/signapk/sign.java通过上面的签名时我们可以看到,Android签名apk之后,会有一个META-INF文件夹,这里有三个文件:MANIFEST.MFCERT.RSACERT.SF下面来看看这三个文件到底是干啥的?1、MANIFEST.MF我们来看看源码:public static void main(String[] args) {
if (args.length != 4) {
System.err.println(&Usage: signapk & +
&publickey.x509[.pem] privatekey.pk8 & +
&input.jar output.jar&);
System.exit(2);
JarFile inputJar =
JarOutputStream outputJar =
X509Certificate publicKey = readPublicKey(new File(args[0]));
// Assume the certificate is valid for at least an hour.
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
PrivateKey privateKey = readPrivateKey(new File(args[1]));
inputJar = new JarFile(new File(args[2]), false);
// Don't verify.
outputJar = new JarOutputStream(new FileOutputStream(args[3]));
outputJar.setLevel(9);
// MANIFEST.MF
Manifest manifest = addDigestsToManifest(inputJar);
je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);
// CERT.SF
Signature signature = Signature.getInstance(&SHA1withRSA&);
signature.initSign(privateKey);
je = new JarEntry(CERT_SF_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureFile(manifest,
new SignatureOutputStream(outputJar, signature));
// CERT.RSA
je = new JarEntry(CERT_RSA_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(signature, publicKey, outputJar);
// Everything else
copyFiles(manifest, inputJar, outputJar, timestamp);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
} finally {
if (inputJar != null) inputJar.close();
if (outputJar != null) outputJar.close();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}在main函数中,我们看到需要输入四个参数,然后就做了三件事:写MANIFEST.MF//MANIFEST.MF
Manifest manifest = addDigestsToManifest(inputJar);
je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);在进入方法看看:/** Add the SHA1 of every file to the manifest, creating it if necessary. */
private static Manifest addDigestsToManifest(JarFile jar)
throws IOException, GeneralSecurityException {
Manifest input = jar.getManifest();
Manifest output = new Manifest();
Attributes main = output.getMainAttributes();
if (input != null) {
main.putAll(input.getMainAttributes());
main.putValue(&Manifest-Version&, &1.0&);
main.putValue(&Created-By&, &1.0 (Android SignApk)&);
BASE64Encoder base64 = new BASE64Encoder();
MessageDigest md = MessageDigest.getInstance(&SHA1&);
byte[] buffer = new byte[4096];
// We sort the input entries by name, and add them to the
// output manifest in sorted order.
We expect that the output
// map will be deterministic.
TreeMap&String, JarEntry& byName = new TreeMap&String, JarEntry&();
for (Enumeration&JarEntry& e = jar.entries(); e.hasMoreElements(); ) {
JarEntry entry = e.nextElement();
byName.put(entry.getName(), entry);
for (JarEntry entry: byName.values()) {
String name = entry.getName();
if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
!name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
(stripPattern == null ||
!stripPattern.matcher(name).matches())) {
InputStream data = jar.getInputStream(entry);
while ((num = data.read(buffer)) & 0) {
md.update(buffer, 0, num);
Attributes attr =
if (input != null) attr = input.getAttributes(name);
attr = attr != null ? new Attributes(attr) : new Attributes();
attr.putValue(&SHA1-Digest&, base64.encode(md.digest()));
output.getEntries().put(name, attr);
}代码逻辑还是很简单的,主要看那个循环的意思:除了三个文件(MANIFEST.MF,CERT.RSA,CERT.SF),其他的文件都会对文件内容做一次SHA1算法,就是计算出文件的摘要信息,然后用Base64进行编码即可,下面我们用工具来做个案例看看是不是这样:首先安装工具:HashTab下载地址:/s?wd=hashtab&rsv_spt=1&issp=1&f=8&rsv_bp=0&ie=utf-8&tn=baiduhome_pg&bs=hashtable然后还有一个网站就是在线计算Base64:http://tomeko.net/online_tools/hex_to_base64.php?lang=en那下面就开始我们的验证工作吧:我们就来验证一下AndroidManifest.xml文件,首先在MANIFEST.MF文件中找到这个条目,记录SHA1的值然后我们安装HashTab之后,找到AndroidManifest.xml文件,右击,选择Hashtab:复制SHA-1的值:9C6B201CA3697FD73C,到上面的那个Base64转化网站,转化一下:nGSBLec3OyAcKUEBRzY2o2l/1zw=和MANIFEST.MF中的条目内容一模一样啦啦那么从上面的分析我们就知道了,其实MANIFEST.MF中存储的是:逐一遍历里面的所有条目,如果是目录就跳过,如果是一个文件,就用SHA1(或者SHA256)消息摘要算法提取出该文件的摘要然后进行BASE64编码后,作为“SHA1-Digest”属性的值写入到MANIFEST.MF文件中的一个块中。该块有一个“Name”属性,其值就是该文件在apk包中的路径。2、下面再来看一下CERT.SF文件内容这里的内容感觉和MANIFEST.MF的内容差不多,来看看代码吧://CERT.SF
Signature signature = Signature.getInstance(&SHA1withRSA&);
signature.initSign(privateKey);
je = new JarEntry(CERT_SF_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureFile(manifest,new SignatureOutputStream(outputJar, signature));进入到writeSignatureFile方法中:/** Write a .SF file with a digest the specified manifest. */
private static void writeSignatureFile(Manifest manifest, OutputStream out)
throws IOException, GeneralSecurityException {
Manifest sf = new Manifest();
Attributes main = sf.getMainAttributes();
main.putValue(&Signature-Version&, &1.0&);
main.putValue(&Created-By&, &1.0 (Android SignApk)&);
BASE64Encoder base64 = new BASE64Encoder();
MessageDigest md = MessageDigest.getInstance(&SHA1&);
PrintStream print = new PrintStream(
new DigestOutputStream(new ByteArrayOutputStream(), md),
true, &UTF-8&);
// Digest of the entire manifest
manifest.write(print);
print.flush();
main.putValue(&SHA1-Digest-Manifest&, base64.encode(md.digest()));
Map&String, Attributes& entries = manifest.getEntries();
for (Map.Entry&String, Attributes& entry : entries.entrySet()) {
// Digest of the manifest stanza for this entry.
print.print(&Name: & + entry.getKey() + &\r\n&);
for (Map.Entry&Object, Object& att : entry.getValue().entrySet()) {
print.print(att.getKey() + &: & + att.getValue() + &\r\n&);
print.print(&\r\n&);
print.flush();
Attributes sfAttr = new Attributes();
sfAttr.putValue(&SHA1-Digest&, base64.encode(md.digest()));
sf.getEntries().put(entry.getKey(), sfAttr);
sf.write(out);
}首先我们可以看到,需要对之前的MANIFEST.MF文件整个内容做一个SHA1放到SHA1-Digest-Manifest字段中:我们看看出入的manifest变量就是刚刚写入了MANIFEST.MF文件的这个我们可以验证一下:然后转化一下看到了吧,和文件中的值是一样的啦啦下面我们继续看代码,有一个循环:Map&String, Attributes& entries = manifest.getEntries();
for (Map.Entry&String, Attributes& entry : entries.entrySet()) {
// Digest of the manifest stanza for this entry.
print.print(&Name: & + entry.getKey() + &\r\n&);
for (Map.Entry&Object, Object& att : entry.getValue().entrySet()) {
print.print(att.getKey() + &: & + att.getValue() + &\r\n&);
print.print(&\r\n&);
print.flush();
Attributes sfAttr = new Attributes();
sfAttr.putValue(&SHA1-Digest&, base64.encode(md.digest()));
sf.getEntries().put(entry.getKey(), sfAttr);
sf.write(out);这里还是用到了刚刚传入的mainfest变量,遍历他的条目内容,然后进行SHA算法计算在Base64一下:其实就是对MANIFEST.MF文件中的每个条目内容做一次SHA,在保存一下即可,做个例子验证一下:用AndroidManifest.xml为例,我们把MANIFEST.MF文件中的条目拷贝保存到txt文档中:这里需要注意的是,我们保存之后,需要添加两个换行,我们可以在代码中看到逻辑:然后我们计算txt文档的SHA值:看到了吧,这里计算的值是一样的啦啦到这里我们就知道CERT.SF文件做了什么:1》计算这个MANIFEST.MF文件的整体SHA1值,再经过BASE64编码后,记录在CERT.SF主属性块(在文件头上)的“SHA1-Digest-Manifest”属性值值下2》逐条计算MANIFEST.MF文件中每一个块的SHA1,并经过BASE64编码后,记录在CERT.SF中的同名块中,属性的名字是“SHA1-Digest3、最后我们在来看一下CERT.RSA文件这里我们看到的都是二进制文件,因为RSA文件加密了,所以我们需要用openssl命令才能查看其内容openssl pkcs7 -inform DER -in CERT.RSA -noout -print_certs –text关于这些信息,可以看下面这张图:我们来看一下代码:/** Write a .RSA file with a digital signature. */
private static void writeSignatureBlock(
Signature signature, X509Certificate publicKey, OutputStream out)
throws IOException, GeneralSecurityException {
SignerInfo signerInfo = new SignerInfo(
new X500Name(publicKey.getIssuerX500Principal().getName()),
publicKey.getSerialNumber(),
AlgorithmId.get(&SHA1&),
AlgorithmId.get(&RSA&),
signature.sign());
PKCS7 pkcs7 = new PKCS7(
new AlgorithmId[] { AlgorithmId.get(&SHA1&) },
new ContentInfo(ContentInfo.DATA_OID, null),
new X509Certificate[] { publicKey },
new SignerInfo[] { signerInfo });
pkcs7.encodeSignedData(out);
}我们看到,这里会把之前生成的 CERT.SF文件, 用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入 &CERT.RSA &中保存。CERT.RSA是一个满足PKCS7格式的文件。四、为何要这么来签名上面我们就介绍了签名apk之后的三个文件的详细内容,那么下面来总结一下,Android中为何要用这种方式进行加密签名,这种方加密是不是最安全的呢?下面我们来分析一下,如果apk文件被篡改后会发生什么。首先,如果你改变了apk包中的任何文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是验证失败,程序就不能成功安装。其次,如果你对更改的过的文件相应的算出新的摘要值,然后更改MANIFEST.MF文件里面对应的属性值,那么必定与CERT.SF文件中算出的摘要值不一样,照样验证失败。最后,如果你还不死心,继续计算MANIFEST.MF的摘要值,相应的更改CERT.SF里面的值,那么数字签名值必定与CERT.RSA文件中记录的不一样,还是失败。那么能不能继续伪造数字签名呢?不可能,因为没有数字证书对应的私钥。所以,如果要重新打包后的应用程序能再Android设备上安装,必须对其进行重签名。从上面的分析可以得出,只要修改了Apk中的任何内容,就必须重新签名,不然会提示安装失败,当然这里不会分析,后面一篇文章会注重分析为何会提示安装失败。五、知识点梳理1、数据指纹,签名文件,证书文件的含义1》数据指纹就是对一个数据源做SHA/MD5算法,这个值是唯一的2》签名文件技术就是:数据指纹+RSA算法3》证书文件中包含了公钥信息和其他信息4》在Android签名之后,其中SF就是签名文件,RSA就是证书文件我们可以使用openssl来查看RSA文件中的证书信息和公钥信息2、我们了解了Android中的签名有两种方式:jarsigner和signapk&这两种方式的区别是:1》jarsigner签名时,需要的是keystore文件,而signapk签名的时候是pk8,x509.pem文件2》jarsigner签名之后的SF和RSA文件名默认是keystore的别名,而signapk签名之后文件名是固定的:CERT3》Eclipse中我们在跑Debug程序的时候,默认用的是jarsigner方式签名的,用的也是系统默认的debug.keystore签名文件4》keystore文件和pk8,x509.pem文件之间可以互相转化六、思考我们在分析了签名技术之后,无意中发现一个问题,就是CERT.SF,MANIFEST.MF,这两个文件中的内容的name字段都是apk中的资源名,那么就有一个问题了,如果资源名很长,而且apk中的资源很多,那么这两个文件就会很大,那么这里我们是不是可以优化呢?后面在分析如何减小apk大小的文章中会继续讲解,这里先提出这个问题。资源下载:http://download.csdn.net/detail/jiangwei/9377046总结上面我们就通过源码来介绍了Android中的签名过程,整个过程还是很清楚的,文章写得有点长,如果大家看的有问题的话,记得给我留言,后面我还会再写一篇姊妹篇文章:Android中的签名校验过程详解,期待中~~PS: 关注微信,最新Android技术实时推送
12345678910
12345678910
12345678910 上一篇:下一篇:文章评论相关解决方案 1234567891011 Copyright & &&版权所有getDrawingCache()和Android中的截图方法简介
getDrawingCache()方法截取部分屏幕:
view.setDrawingCacheEnabled(true);//设置能否缓存图片信息(drawing cache)
view.buildDrawingCache();//如果能够缓存图片,则创建图片缓存
Bitmap bitmap = view.getDrawingCache();//如果图片已经缓存,返回一个bitmap
view.destroyDrawingCache();//释放缓存占用的资源
如果在一个界面中,重复截取图片,在每次截屏之前,都应该清除缓存;
假如图片不符合我们的要求,可以使用Bitmap.createBitmap( )方法处理图片(图片压缩过度,会致使不能显示);
图片本地存储
File f = new File(& &);
f.createNewFile();
} catch (IOException e1) {
e1.printStackTrace();
FileOutputStream fOut =
fOut = new FileOutputStream(f);
} catch (FileNotFoundException e) {
e.printStackTrace();
pressFormat.PNG, 100, fOut);
fOut.flush();
} catch (IOException e) {
e.printStackTrace();
fOut.close();
} catch (IOException e) {
e.printStackTrace();
其他截屏方法
1、在APK中调用&adb shell screencap -pfilepath& 命令(基于Android SDK的截屏方法)
该命令读取系统的framebuffer,需要获得系统权限:
(1). 在AndroidManifest.xml文件中添加
(2). 修改APK为系统权限,将APK放到中编译, 修改Android.mk
LOCAL_CERTIFICATE := platform
publicvoid takeScreenShot(){
String mSavedPath = Environment.getExternalStorageDirectory()+File. separator + &screenshot.png& ;
Runtime. getRuntime().exec(&screencap -p & + mSavedPath);
} catch (Exception e) {
e.printStackTrace();
2、利用系统的API,实现Screenshot,这部分代码是系统隐藏的,需要在源码下编译(基于Android SDK的截屏方法)
(1). 在AndroidManifest.xml文件中添加
(2). 修改APK为系统权限,将APK放到源码中编译, 修改Android.mk
LOCAL_CERTIFICATE := platform
public boolean takeScreenShot(String imagePath){
if(imagePath.equals(&& )){
imagePath = Environment.getExternalStorageDirectory()+File. separator+&Screenshot.png& ;
Bitmap mScreenB
WindowManager mWindowM
DisplayMetrics mDisplayM
Display mD
mWindowManager = (WindowManager) mcontext.getSystemService(Context.WINDOW_SERVICE);
mDisplay = mWindowManager.getDefaultDisplay();
mDisplayMetrics = new DisplayMetrics();
mDisplay.getRealMetrics(mDisplayMetrics);
float[] dims = {mDisplayMetrics.widthPixels , mDisplayMetrics.heightPixels };
mScreenBitmap = Surface. screenshot((int) dims[0], ( int) dims[1]);
if (mScreenBitmap == null) {
FileOutputStream out = new FileOutputStream(imagePath);
pressFormat. PNG, 100, out);
} catch (Exception e) {
基于Android ddmlib进行截屏
public class ScreenShot {
private BufferedImage image =
* @param args
public static void main(String[] args) {
// TODO Auto-generated method stub
AndroidDebugBridge.init(false); //
ScreenShot screenshot = new ScreenShot();
IDevice device = screenshot.getDevice();
for (int i = 0; i & 10; i++) {
Date date=new Date();
SimpleDateFormat df=new SimpleDateFormat(&MM-dd-HH-mm-ss&);
String nowTime = df.format(date);
screenshot.getScreenShot(device, &Robotium& + nowTime);
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
public void getScreenShot(IDevice device,String filename) {
RawImage rawScreen =
rawScreen = device.getScreenshot();
} catch (TimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (AdbCommandRejectedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
if (rawScreen != null) {
Boolean landscape =
int width2 = landscape ? rawScreen.height : rawScreen.
int height2 = landscape ? rawScreen.width : rawScreen.
if (image == null) {
image = new BufferedImage(width2, height2,
BufferedImage.TYPE_INT_RGB);
if (image.getHeight() != height2 || image.getWidth() != width2) {
image = new BufferedImage(width2, height2,
BufferedImage.TYPE_INT_RGB);
int index = 0;
int indexInc = rawScreen.bpp && 3;
for (int y = 0; y & rawScreen. y++) {
for (int x = 0; x & rawScreen. x++, index += indexInc) {
int value = rawScreen.getARGB(index);
if (landscape)
image.setRGB(y, rawScreen.width - x - 1, value);
image.setRGB(x, y, value);
ImageIO.write((RenderedImage) image, &PNG&, new File(&D:/&
+ filename + &.jpg&));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
* 获取得到device对象
private IDevice getDevice(){
AndroidDebugBridge bridge = AndroidDebugBridge
.createBridge(&adb&, true);//如果代码有问题请查看API,修改此处的参数值试一下
waitDevicesList(bridge);
IDevice devices[] = bridge.getDevices();
device = devices[0];
* 等待查找device
* @param bridge
private void waitDevicesList(AndroidDebugBridge bridge) {
int count = 0;
while (bridge.hasInitialDeviceList() == false) {
Thread.sleep(500);
} catch (InterruptedException e) {
if (count & 240) {
System.err.print(&等待获取设备超时&);
Android本地(Native Programming)读取framebuffer
(1)命令行,框架的截屏功能是通过framebuffer来实现的,所以我们先来介绍一下framebuffer。
framebuffer介绍
帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行 读写操作。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。
Linux FrameBuffer 本质上只是提供了对图形设备的硬件抽象,在开发者看来,FrameBuffer 是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说FrameBuffer就是一块白板。例如对于初始化为16 位色的FrameBuffer 来说, FrameBuffer中的两个字节代表屏幕上一个点,从上到下,从左至右,屏幕位置与内存地址是顺序的线性关系。
帧缓存有个地址,是在内存里。我们通过不停的向frame buffer中写入数据, 显示控制器就自动的从frame buffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。
Android截屏实现思路
Android系统是基于Linux内核的,所以也存在framebuffer这个设备,我们要实现截屏的话只要能获取到framebuffer中的数据,然后把数据转换成图片就可以了,android中的framebuffer数据是存放在 /dev/graphics/fb0 文件中的,所以我们只需要来获取这个文件的数据就可以得到当前屏幕的内容。
现在我们的测试代码运行时候是通过RC(remote controller)方式来运行被测应用的,那就需要在PC机上来访问模拟器或者真机上的framebuffer数据,这个的话可以通过android的ADB命令来实现。
public class ScreenShot {
* @param args
* @throws InterruptedException
public static void main(String[] args) throws InterruptedException {
//分辨率大小,后续可以通过代码来获取到当前的分辨率
int xResolution = 320;
int yResolution = 480;
//执行adb命令,把framebuffer中内容保存到fb1文件中
Runtime.getRuntime().exec(&adb pull /dev/graphics/fb0 C:/fb1&);
//等待几秒保证framebuffer中的数据都被保存下来,如果没有保存完成进行读取操作会有IO异常
Thread.sleep(15000);
//读取文件中的数据
InputStream in = (InputStream)new FileInputStream(&C:/fb1&);
DataInput frameBuffer = new LittleEndianDataInputStream(in);
BufferedImage screenImage = new BufferedImage(
xResolution, yResolution, BufferedImage.TYPE_INT_ARGB);
int[] oneLine = new int[xResolution];
for (int y = 0; y & yR y++) {
//从frameBuffer中计算出rgb值
convertToRgba32(frameBuffer, oneLine);
//把rgb值设置到image对象中
screenImage.setRGB(0, y, xResolution, 1, oneLine, 0, xResolution);
Closeables.closeQuietly(in);
ByteArrayOutputStream rawPngStream = new ByteArrayOutputStream();
if (!ImageIO.write(screenImage, &png&, rawPngStream)) {
throw new RuntimeException(
environment does not support converting to PNG.&);
} catch (IOException exception) {
// This should never happen because rawPngStream is an in-memory stream.
System.out.println(&IOException=& + exception);
byte[] rawPngBytes = rawPngStream.toByteArray();
String base64Png = new Base64Encoder().encode(rawPngBytes);
File screenshot = OutputType.FILE.convertFromBase64Png(base64Png);
System.out.println(&screenshot==& + screenshot.toString());
screenshot.renameTo(new File(&C:\\screenshottemp.png&));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e);
public static void convertToRgba32(DataInput frameBuffer, int[] into) {
for (int x = 0; x & into. x++) {
int rgb = frameBuffer.readShort() & 0
int red = rgb && 11;
red = (red && 3) | (red && 2);
int green = (rgb && 5) & 63;
green = (green && 2) | (green && 4);
int blue = rgb & 31;
blue = (blue && 3) | (blue && 2);
into[x] = 0xff000000 | (red && 16) | (green && 8) |
}catch (EOFException e){
System.out.println(&EOFException=& + e);
} catch (IOException exception) {
System.out.println(&convertToRgba32Exception=& + exception);
public class SimpleScreenshotActivity extends Activity {
private Display mD
private WindowManager mWindowM
private DisplayMetrics mDisplayM
private Bitmap mScreenB
private Matrix mDisplayM
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new Thread(new Runnable() {
public void run() {
takeScreenshot();
}).start();
private float getDegreesForRotation(int value) {
switch (value) {
case Surface.ROTATION_90:
return 360f - 90f;
case Surface.ROTATION_180:
return 360f - 180f;
case Surface.ROTATION_270:
return 360f - 270f;
return 0f;
private void takeScreenshot() {
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
mDisplay = mWindowManager.getDefaultDisplay();
mDisplayMetrics = new DisplayMetrics();
mDisplay.getRealMetrics(mDisplayMetrics);
mDisplayMatrix = new Matrix();
float[] dims = { mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels };
int value = mDisplay.getRotation();
String hwRotation = SystemProperties.get(&ro.sf.hwrotation&, &0&);
if (hwRotation.equals(&270&) || hwRotation.equals(&90&)) {
value = (value + 3) % 4;
float degrees = getDegreesForRotation(value);
boolean requiresRotation = (degrees & 0);
if (requiresRotation) {
// Get the dimensions of the device in its native orientation
mDisplayMatrix.reset();
mDisplayMatrix.preRotate(-degrees);
mDisplayMatrix.mapPoints(dims);
dims[0] = Math.abs(dims[0]);
dims[1] = Math.abs(dims[1]);
mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
if (requiresRotation) {
// Rotate the screenshot to the current orientation
Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(ss);
c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
c.rotate(degrees);
c.translate(-dims[0] / 2, -dims[1] / 2);
c.drawBitmap(mScreenBitmap, 0, 0, null);
c.setBitmap(null);
mScreenBitmap =
// If we couldn't take the screenshot, notify the user
if (mScreenBitmap == null) {
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
saveBitmap(mScreenBitmap);
} catch (IOException e) {
System.out.println(e.getMessage());
public void saveBitmap(Bitmap bitmap) throws IOException {
String imageDate = new SimpleDateFormat(&yyyy-MM-dd-HH-mm-ss&)
.format(new Date(System.currentTimeMillis()));
File file = new File(&/mnt/sdcard/Pictures/&+imageDate+&.png&);
if(!file.exists()){
file.createNewFile();
FileOutputS
out = new FileOutputStream(file);
if (pressFormat.PNG, 70, out)) {
out.flush();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
package org.winplus.
import java.io.F
import java.io.FileNotFoundE
import java.io.FileOutputS
import java.io.IOE
import java.text.SimpleDateF
import java.util.D
import android.app.A
import android.content.C
import android.graphics.B
import android.graphics.C
import android.graphics.M
import android.os.B
import android.util.DisplayM
import android.util.L
import android.view.D
import android.view.S
import android.view.WindowM
import android.os.SystemP
public class SimpleScreenshotActivity extends Activity {
private Display mD
private WindowManager mWindowM
private DisplayMetrics mDisplayM
private Bitmap mScreenB
private Matrix mDisplayM
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new Thread(new Runnable() {
public void run() {
takeScreenshot();
}).start();
private float getDegreesForRotation(int value) {
switch (value) {
case Surface.ROTATION_90:
return 360f - 90f;
case Surface.ROTATION_180:
return 360f - 180f;
case Surface.ROTATION_270:
return 360f - 270f;
return 0f;
private void takeScreenshot() {
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
mDisplay = mWindowManager.getDefaultDisplay();
mDisplayMetrics = new DisplayMetrics();
mDisplay.getRealMetrics(mDisplayMetrics);
mDisplayMatrix = new Matrix();
float[] dims = { mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels };
int value = mDisplay.getRotation();
String hwRotation = SystemProperties.get(&ro.sf.hwrotation&, &0&);
if (hwRotation.equals(&270&) || hwRotation.equals(&90&)) {
value = (value + 3) % 4;
float degrees = getDegreesForRotation(value);
boolean requiresRotation = (degrees & 0);
if (requiresRotation) {
// Get the dimensions of the device in its native orientation
mDisplayMatrix.reset();
mDisplayMatrix.preRotate(-degrees);
mDisplayMatrix.mapPoints(dims);
dims[0] = Math.abs(dims[0]);
dims[1] = Math.abs(dims[1]);
mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
if (requiresRotation) {
// Rotate the screenshot to the current orientation
Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(ss);
c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
c.rotate(degrees);
c.translate(-dims[0] / 2, -dims[1] / 2);
c.drawBitmap(mScreenBitmap, 0, 0, null);
c.setBitmap(null);
mScreenBitmap =
// If we couldn't take the screenshot, notify the user
if (mScreenBitmap == null) {
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
saveBitmap(mScreenBitmap);
} catch (IOException e) {
System.out.println(e.getMessage());
public void saveBitmap(Bitmap bitmap) throws IOException {
String imageDate = new SimpleDateFormat(&yyyy-MM-dd-HH-mm-ss&)
.format(new Date(System.currentTimeMillis()));
File file = new File(&/mnt/sdcard/Pictures/&+imageDate+&.png&);
if(!file.exists()){
file.createNewFile();
FileOutputS
out = new FileOutputStream(file);
if (pressFormat.PNG, 70, out)) {
out.flush();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
PS:1、需要在Manifest.xml中加入代码:android:sharedUserId=&android.uid.system&
2、由于调用了@hide的API,所以编译得时候请使用makefile编译。或者通过在Eclipse中添加Jar文件通过编译。
3、此代码只在Android4.0中使用过,2.3的就没去做测试了。
利用TakeScreenShotService截图
Android手机一般都自带有手机屏幕截图的功能:在手机任何界面(当然手机要是开机点亮状态),通过按组合键,屏幕闪一下,然后咔嚓一声,截图的照片会保存到当前手机的图库中,真是一个不错的功能!
以我手头的测试手机为例,是同时按电源键+音量下键来实现截屏,苹果手机则是电源键 + HOME键,小米手机是菜单键+音量下键,而HTC一般是按住电源键再按左下角的&主页&键。那么Android源码中使用组合键是如何实现屏幕截图功能呢?前段时间由于工作的原因仔细看了一下,这两天不忙,便把相关的知识点串联起来整理一下,分下面两部分简单分析下实现流程:
Android源码中对组合键的捕获。
Android源码中对按键的捕获位于文件PhoneWindowManager.java(alps\frameworks\base\policy\src\com\android\internal
\policy\impl)中,这个类处理所有的键盘输入事件,其中函数interceptKeyBeforeQueueing()会对常用的按键做特殊处理。以我手头的测试机为例,是同时按电源键和音量下键来截屏,那么在这个函数中我们会看到这么两段代码:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (down) {
if (isScreenOn && !mVolumeDownKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mVolumeDownKeyTriggered =
mVolumeDownKeyTime = event.getDownTime();
mVolumeDownKeyConsumedByScreenshotChord =
cancelPendingPowerKeyAction();
interceptScreenshotChord();
mVolumeDownKeyTriggered =
cancelPendingScreenshotChordAction();
case KeyEvent.KEYCODE_POWER: {
result &= ~ACTION_PASS_TO_USER;
if (down) {
if (isScreenOn && !mPowerKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mPowerKeyTriggered =
mPowerKeyTime = event.getDownTime();
interceptScreenshotChord();
可以看到正是在这里(响应Down事件)捕获是否按了音量下键和电源键的,而且两个地方都会进入函数interceptScreenshotChord()中,那么接下来看看这个函数干了什么工作:
private void interceptScreenshotChord() {
if (mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {
final long now = SystemClock.uptimeMillis();
if (now &= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
&& now &= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
mVolumeDownKeyConsumedByScreenshotChord =
cancelPendingPowerKeyAction();
mHandler.postDelayed(mScreenshotChordLongPress,
ViewConfiguration.getGlobalActionKeyTimeout());
在这个函数中,用两个布尔变量判断是否同时按了音量下键和电源键后,再计算两个按键响应Down事件之间的时间差不超过150毫秒,也就认为是同时按了这两个键后,算是真正的捕获到屏幕截屏的组合键。
附言:文件PhoneWindowManager.java类是拦截键盘消息的处理类,在此类中还有对home键、返回键等好多按键的处理。
Android源码中调用屏幕截图的接口
捕获到组合键后,我们再看看android源码中是如何调用屏幕截图的函数接口。在上面的函数interceptScreenshotChord中我们看到用handler判断长按组合键500毫秒之后,会进入如下函数:
private final Runnable mScreenshotChordLongPress = new Runnable() {
public void run() {
takeScreenshot();
在这里启动了一个线程来完成截屏的功能,接着看函数takeScreenshot():
private void takeScreenshot() {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
ComponentName cn = new ComponentName(&com.android.systemui&,
&com.android.systemui.screenshot.TakeScreenshotService&);
Intent intent = new Intent();
intent.setComponent(cn);
ServiceConnection conn = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != this) {
Messenger messenger = new Messenger(service);
Message msg = Message.obtain(null, 1);
final ServiceConnection myConn =
Handler h = new Handler(mHandler.getLooper()) {
public void handleMessage(Message msg) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection == myConn) {
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection =
mHandler.removeCallbacks(mScreenshotTimeout);
msg.replyTo = new Messenger(h);
msg.arg1 = msg.arg2 = 0;
if (mStatusBar != null && mStatusBar.isVisibleLw())
msg.arg1 = 1;
if (mNavigationBar != null && mNavigationBar.isVisibleLw())
msg.arg2 = 1;
messenger.send(msg);
} catch (RemoteException e) {
public void onServiceDisconnected(ComponentName name) {}
if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
mScreenshotConnection =
mHandler.postDelayed(mScreenshotTimeout, 10000);
可以看到这个函数使用AIDL绑定了service服务到&com.android.systemui.screenshot.TakeScreenshotService&,注意在service连接成功时,对message的msg.arg1和msg.arg2两个参数的赋值。其中在mScreenshotTimeout中对服务service做了超时处理。接着我们找到实现这个服务service的类TakeScreenshotService,看看其实现的流程:
public class TakeScreenshotService extends Service {
private static final String TAG = &TakeScreenshotService&;
private static GlobalScreenshot mS
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
final Messenger callback = msg.replyTo;
if (mScreenshot == null) {
mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
mScreenshot.takeScreenshot(new Runnable() {
@Override public void run() {
Message reply = Message.obtain(null, 1);
callback.send(reply);
} catch (RemoteException e) {
}, msg.arg1 & 0, msg.arg2 & 0);
public IBinder onBind(Intent intent) {
return new Messenger(mHandler).getBinder();
在这个类中,我们主要看调用接口,用到了mScreenshot.takeScreenshot()传递了三个参数,第一个是个runnable,第二和第三个是之前message传递的两个参数msg.arg1和msg.arg2。最后我们看看这个函数takeScreenshot(),位于文件GlobalScreenshot.java中(跟之前的函数重名但是文件路径不一样):
* Takes a screenshot of the current display and shows an animation.
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
// We need to orient the screenshot correctly (and the Surface api seems to take screenshots
// only in the natural orientation of the device :!)
mDisplay.getRealMetrics(mDisplayMetrics);
float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
float degrees = getDegreesForRotation(mDisplay.getRotation());
boolean requiresRotation = (degrees & 0);
if (requiresRotation) {
// Get the dimensions of the device in its native orientation
mDisplayMatrix.reset();
mDisplayMatrix.preRotate(-degrees);
mDisplayMatrix.mapPoints(dims);
dims[0] = Math.abs(dims[0]);
dims[1] = Math.abs(dims[1]);
// Take the screenshot
mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
if (mScreenBitmap == null) {
notifyScreenshotError(mContext, mNotificationManager);
finisher.run();
if (requiresRotation) {
// Rotate the screenshot to the current orientation
Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(ss);
c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
c.rotate(degrees);
c.translate(-dims[0] / 2, -dims[1] / 2);
c.drawBitmap(mScreenBitmap, 0, 0, null);
c.setBitmap(null);
mScreenBitmap =
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
// Start the post-screenshot animation
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
statusBarVisible, navBarVisible);
这段代码的注释比较详细,其实看到这里,我们算是真正看到截屏的操作了,具体的工作包括对屏幕大小、旋转角度的获取,然后调用Surface类的screenshot方法截屏保存到bitmap中,之后把这部分位图填充到一个画布上,最后再启动一个延迟的拍照动画效果。如果再往下探究screenshot方法,发现已经是一个native方法了:
* Like {@link #screenshot(int, int, int, int)} but includes all
* Surfaces in the screenshot.
public static native Bitmap screenshot(int width, int height);
使用JNI技术调用底层的代码,如果再往下走,会发现映射这这个jni函数在文件android_view_Surface.cpp中,这个真的已经是底层c++语言了,统一调用的底层函数是:
static jobject doScreenshot(JNIEnv* env, jobject clazz, jint width, jint height,
jint minLayer, jint maxLayer, bool allLayers)
ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);
if (pixels-&update(width, height, minLayer, maxLayer, allLayers) != NO_ERROR) {
uint32_t w = pixels-&getWidth();
uint32_t h = pixels-&getHeight();
uint32_t s = pixels-&getStride();
uint32_t f = pixels-&getFormat();
ssize_t bpr = s * android::bytesPerPixel(f);
SkBitmap* bitmap = new SkBitmap();
bitmap-&setConfig(convertPixelFormat(f), w, h, bpr);
if (f == PIXEL_FORMAT_RGBX_8888) {
bitmap-&setIsOpaque(true);
if (w & 0 && h & 0) {
bitmap-&setPixelRef(pixels)-&unref();
bitmap-&lockPixels();
// be safe with an empty bitmap.
bitmap-&setPixels(NULL);
return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
待补内容:
Bitmap.createBitmap;
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'}

我要回帖

更多关于 c base64encoder 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信