我在前面的一片博客中,介绍了jPBC 2.0.0在PC平台上面的配置和测试。既然jPBC是Java平台上面实现的,那么jPBC能不能在Android这个以Java为主要语言的平台上运行呢?这样一来,各种在jPBC上撰写的有关双线性对的函数就都能够在移动终端上面用了。我个人的想法就是把最新的密码学算法应用到工程里面,而这确实是我想法的一个很好的跨越。因此,我在第一时间公开整个配置的过程以及我测试的方法,以供广大国内密码学研究者们进行尝试。整个配置过程实际上是非常简单的,这也要感谢jPBC库的编写者们的辛勤工作。在整个配置过程中,我几乎可以确定,jPBC的开发者们在2.0.0版本中完全抛离了GMP库和PBC库,而是将整个PBC在Java上进行了完整的实现。而唯一没有实现的部分,也就是椭圆曲线常数产生部分,作者也使用了相同的配置格式,以使得PBC中使用的椭圆曲线常数可以在jPBC中直接使用。

用到的库工具

Android Development Tools

首先用到的当然是Android的开发工具啦。我使用的是Windows下面的ADT工具。这个工具已经被Google统一打包。也就是说,现在大家开发Android的时候,再也不用下载Eclipse,下载Android SDK,下载Android ADT,进行各种复杂的配置后才能使用了。Google将整个开发工具集成在了一起。这个集成工具的下载地址为:http://developer.android.com/sdk/index.html。里面包含了包括Eclipse,Android SDK Manager,Android ADT在内的全套工具。

然而,如果是Linux平台下面的开发者,虽然Google也提供了Linux下面的Android打包开发工具,但是我建议大家不要使用这一打包工具,而是手动进行一步一步地配置。实际上,如果大家在网上进行检索,会发现网上已经有很多很多的人在抱怨Linux下面Android打包开发工具的各种问题。所以,本着不给自己找麻烦的初衷,大家还是老老实实手动配置吧~

jPBC 2.0.0

jPBC 2.0.0也是必不可少的。其官方网站为:http://gas.dia.unisa.it/projects/jpbc/index.html。需要指出的是,虽然网站中专门有一项是Android,但是里面没有给出任何配置或者使用的方法,有的只是下面的一段话:

JPBC runs out of box on Android (2.1+ version).

A Benchmark App

Download the following APK to benchmark JPBC on your terminal. Once the benchmark is finished the results can be found on the external memory in a file called "benchmark.out".

If you don’t mind it sending me the results with a description of the characteristics of the terminal used pleasecontact me.

所以如果大家想使用Android jPBC,确实需要自己摸索一下怎么配置,这也是我写这篇博客的根本目的。同时,这一段话也可以让我们确信jPBC是可以在Android上面运行的,毕竟连APK都已经有了嘛~

jpbc-android运行

我们首先尝试将jPBC中提供的APK文件恢复成工程文件。我们将分三步介绍恢复的方法:整理并准备需要的文件、Android-jpbc工程的建立和配置、运行Benchmark并显示结果。

需要的文件

下载好jPBC 2.0.0的源文件后,解压,观察一下解压的结果。我们可以发现,在解压文件中有个文件夹名字为jpbc-android,这里面存放了一些源文件,同时也存放了打包好的APK文件。原文件中包括了APK文件中使用的resources,有icon、layout、甚至可以找到AndroidManifest.xml。同时,在src文件中可以找到APK工程中需要的三个java源代码:AndroidBenchmark.java、Benchmark.java以及JPBCBenchmarkActivity.java。在asserts文件夹下,存放了4个用于测试的椭圆曲线常数Properties:a.properties、d159.properties、d201.properties,以及d224.properties。这四个文件也是回复APK工程的必要文件。以上这些就是jpbc-android里面所需要的全部文件了。

对于jPBC 2.0.0中的jar文件夹,里面的两个必要jar文件也是我们需要的library:jpbc-api-2.0.0.jar和jpbc-plaf-2.0.0.jar。

好啦,所有必要的文件都准备完毕,开始进行工程的建立。

工程的建立与配置

1. 在Eclipse的Android开发环境中建立一个新的空工程。在我的测试中,我的工程名称为Android-jPBC。

2. 将给出的AndroidManifest.xml引入到工程中。

这一步比较简单,大家可以直接打开工程中的AndroidManifest.xml文件,然后用jpbc-android文件夹下面的同名文件对此文件进行覆盖,当然也可以使用其他任意的方法。修改结果如图。在此跟大家道个歉,我截图的时候正好QQ上有一个好友上线提醒了… 为了保护朋友的隐私我把QQ上面的信息抹掉了,这也是这个图里面我唯一手动改动的部分。

AndroidManifest.xml修改后,我们会发现系统有个报错,说找不到icon文件。这个是Android新版本的问题。在旧的Android版本中,程序的默认图标就为icon。而在新版本的开发环境中,图标名称已经变为了ic_launcher了。因此,我们只需要在源代码中修改这一部分,把名称改为ic_launcher。或者把图标的源文件名称修改为icon,这两种方法都可以。我使用的是第一种方法,修改结果如图。大家可以看到,系统已经不报错了。

3. 引入其他必要的文件

我们还需要引入的文件有:三个源代码文件,layout文件,assert中的四个椭圆曲线常数文件, 以及必要的jar文件。

我们首先将三个源代码文件引入到工程中。引入结果如图。我们可以发现,引入后系统报了很多错误,这是因为其他一些必要的文件还没有引入成功。

随后,我们分别更新layout文件、asserts文件以及jar文件。layout文件的引入和AndroidManifest.xml文件引入方法相同,在此就不再重复说明了。直接看图:

asserts文件引入方法很简单,直接将四个Properties文件复制到asserts目录下,然后在Eclipse工程中刷新即可。需要注意的是jar文件。引入的方法是,在libs文件夹下点击右键->import,然后选择两个文件即可。注意,大家不需要再按照PC上面jPBC的配置方法,在工程的Properties下面进行多余的设置了。因为Android默认会将libs文件夹下面的全部文件作为自己jar库的一部分。配置的结果如图。

至此,所有的配置就全部搞定了。大家注意,与以前的jPBC 1.2.1不同的是,jPBC 2.0.0的配置没有涉及到任何有关Native Library的内容。也就是说,jPBC的编写者们已经把PBC的所有核心功能都写在了Java中,没有涉及到任何原始PBC的调用,这极大地增强了jPBC的可移植性。同时,这也使得jPBC在Windows下面进行开发称为可能。

Benchmark运行

所有内容配置完毕后,就可以运行啦。我们将手机连接电脑后,run这个工程即可。随后,手机端会弹出如下图所示的界面。

中间的iteration是测试的总次数。为了较快地给大家展示测试的效果,我这里面只让他运行一轮。点击Benchmark后,程序将进行测试。等待一段时间后,程序会体制Benchmark已经测试完毕,测试结果已经输出。

那么,测试结果输出到哪里了呢?jpbc-android默认将输出结果放置在Android内置SD卡中的根目录下。实际上,测试完毕后我们可以在SD卡根目录下面找打一个叫做benchmark.out的文件,这个文件存储的就是测试结果。jpbc-android的测试结果输出格式是html格式,因此大家可以用IE浏览器等各种浏览器直接打开这个文件,查询测试的结果,如图所示。

BBG-HIBE在Android的运行

Benchmark的测试结果只能显示时间等信息,那么jpbc-android到底能不能成功运行呢?我现在将以前博客中撰写的BBGHIBE方案也移植到Android中,看看能否得到正确的结果。

BBGHIBE中涉及到的源代码几乎不需要做任何修改,只有两个地方需要特别注意:

1. 在PC中,输出的方法是System.out.println,而在Android中,推荐的输出方法是Log.i。因此,所有的System.out.println都需要改成Log.i的形式,举例:

Log.i(tag, "Infor - encrypt: the generated random message is " + message);  

2. 特别要注意Properties的路径需要修改成Android下面的路径:assets/a.properties。因此,在TestBBGHIBE.java中,需要做如下修改:

BBGHIBEMasterKey msk = bbgHIBE.Setup("assets/a.properties", 7);  

其他地方呢,就是为了让Android成功调用TestBBGHIBE函数所作出的修改啦。后面我会放置所有需要修改的源代码。我们尝试运行一下,运行两次测试,两次都能得到正确的结果,并且两次的消息都不相同,这证明jpbc-android确实能够在Android下面正确的运行。

源代码汇总

BBGHIBE.java

package cn.edu.buaa.crypto;import android.util.Log;
import it.unisa.dia.gas.jpbc.Element;
import it.unisa.dia.gas.jpbc.Pairing;
import it.unisa.dia.gas.plaf.jpbc.pairing.PairingFactory;  public class BBGHIBE {  public static final boolean isDebug = true;  private static final String tag = "BBGHIBE";private Pairing pairing;  private int MAX_DEPTH;  //Public parameters  private Element g;  private Element h;  private Element[] u;  private Element E_g_g;  /** * System setup algorithms, takes the max depth of hierarchy as input, and outputs the master secret key * @param perperties The file name of the elliptic curve parameters * @param D Maximal depth of hierarchy * @return Master Secret Key */  public BBGHIBEMasterKey Setup(String perperties, int D){  // Generate curve parameters  pairing = PairingFactory.getPairing(perperties);  this.MAX_DEPTH = D;  //generate alpha  Element alpha = pairing.getZr().newRandomElement().getImmutable();  // Generate public parameters  this.g = pairing.getG1().newRandomElement().getImmutable();  this.h = pairing.getG1().newRandomElement().getImmutable();  this.u = new Element[this.MAX_DEPTH];  for (int i=0; i<this.u.length; i++){  this.u[i] = pairing.getG1().newRandomElement().getImmutable();  }  this.E_g_g = pairing.pairing(this.g, this.g).powZn(alpha).getImmutable();  //generate master secret key  BBGHIBEMasterKey masterKey = new BBGHIBEMasterKey();  masterKey.alpha = alpha.duplicate().getImmutable();  return masterKey;  }  /** * Key Generation algorithm to generate secret key associated with the given identity vector * @param msk master secret key * @param identityVector the given identity vector * @return secret key associated with the given identity vector */  public BBGHIBESecretKey KeyGen(BBGHIBEMasterKey msk, String[] identityVector){  //Determine the validity of Identity Vector  assert(identityVector.length <= this.MAX_DEPTH);  //generate the secret key  Element r = pairing.getZr().newRandomElement().getImmutable();  BBGHIBESecretKey secretKey = new BBGHIBESecretKey();  secretKey.identityVector = new String[identityVector.length];  System.arraycopy(identityVector, 0, secretKey.identityVector, 0, identityVector.length);  //compute K_1  secretKey.K_1 = this.g.powZn(r).getImmutable();  //compute K_2  secretKey.K_2 = this.h.duplicate();  for (int i=0; i<identityVector.length; i++){  secretKey.K_2 = secretKey.K_2.mul(this.u[i].powZn(Util.hash_id(pairing, identityVector[i])));  }  secretKey.K_2 = secretKey.K_2.powZn(r);  secretKey.K_2 = secretKey.K_2.mul(this.g.powZn(msk.alpha)).getImmutable();  //compute E  secretKey.E = new Element[this.MAX_DEPTH];  for (int i=identityVector.length; i<this.MAX_DEPTH; i++){  secretKey.E[i] = this.u[i].powZn(r).getImmutable();  }  return secretKey;  }  /** * Delegation algorithm to delegate a secret key for the user's subordinate * @param secretkey the secret key for the supervisor * @param identity the identity for the user's subordinate * @return secret key for the user's subordinate */  public BBGHIBESecretKey Delegate(BBGHIBESecretKey secretkey, String identity){  //Determine the validity of Identity Vector  assert(secretkey.identityVector.length < this.MAX_DEPTH);  //delegate the secret key  BBGHIBESecretKey delegateKey = new BBGHIBESecretKey();  String[] delegateIV = new String[secretkey.identityVector.length + 1];  System.arraycopy(secretkey.identityVector, 0, delegateIV, 0, secretkey.identityVector.length);  delegateIV[secretkey.identityVector.length] = identity;  delegateKey.identityVector = delegateIV;  Element r = pairing.getZr().newRandomElement().getImmutable();  //compute K_1  delegateKey.K_1 = secretkey.K_1.duplicate();  delegateKey.K_1 = delegateKey.K_1.mul(this.g.powZn(r)).getImmutable();  //compute K_2  delegateKey.K_2 = this.h.duplicate();  for (int i=0; i<delegateIV.length; i++){  delegateKey.K_2 = delegateKey.K_2.mul(this.u[i].powZn(Util.hash_id(pairing, delegateIV[i])));  }  delegateKey.K_2 = delegateKey.K_2.powZn(r);  delegateKey.K_2 = delegateKey.K_2.mul(secretkey.K_2);  delegateKey.K_2 = delegateKey.K_2.mul(secretkey.E[secretkey.identityVector.length].powZn(Util.hash_id(pairing, identity))).getImmutable();  //compute b  delegateKey.E = new Element[this.MAX_DEPTH];  for (int i=secretkey.identityVector.length; i<this.MAX_DEPTH; i++){  delegateKey.E[i] = this.u[i].powZn(r).mul(secretkey.E[i]).getImmutable();  }  return delegateKey;  }  /** * Encrypt algorithm to an identity vector * @param identityVector the target identity vector * @return the ciphertext */  public BBGHIBECiphertext Encrypt(String[] identityVector){  //Determine the validity of Identity Vector  assert(identityVector.length <= this.MAX_DEPTH);  //Generate a random message  Element message = pairing.getGT().newRandomElement();  if (isDebug){  Log.i(tag, "Infor - encrypt: the generated random message is " + message);  }  //Encrypt that message  Element s = pairing.getZr().newRandomElement().getImmutable();  BBGHIBECiphertext ciphertext = new BBGHIBECiphertext();  //compute C_0  ciphertext.C_0 = this.E_g_g.powZn(s).mul(message).getImmutable();  //compute C_2  ciphertext.C_2 = this.g.powZn(s).getImmutable();  //compute c_1  ciphertext.C_1 = this.h.duplicate();  for (int i=0; i<identityVector.length; i++){  ciphertext.C_1 = ciphertext.C_1.mul(this.u[i].powZn(Util.hash_id(pairing, identityVector[i])));  }  ciphertext.C_1 = ciphertext.C_1.powZn(s).getImmutable();  return ciphertext;  }  /** * Decrypt the ciphertext using a secret key * @param identityVector the receive identity vector * @param ciphertext the ciphertext * @param secretKey the secret key for the receiver or for the receiver's supervisor * @return the message */  public Element decrypt(String[] identityVector, BBGHIBECiphertext ciphertext, BBGHIBESecretKey secretKey){  //Determine the validity of Identity Vector, the ciphertext and the secret key  //Secret Key Identity Vector depth needs to be smaller than ciphertext Identity Vector depth  assert(identityVector.length >= secretKey.identityVector.length);  //the identity vector for the secret key should match the receiver's identity vector  for (int i=0; i<secretKey.identityVector.length; i++){  assert(secretKey.identityVector[i].equals(identityVector[i]));  }  Element K = secretKey.K_2.duplicate();  for (int i=secretKey.identityVector.length; i<identityVector.length; i++){  K.mul(secretKey.E[i].powZn(Util.hash_id(pairing, identityVector[i])));  }  Element message = pairing.pairing(secretKey.K_1, ciphertext.C_1);  message = message.div(pairing.pairing(K, ciphertext.C_2));  message = message.mul(ciphertext.C_0);  Log.i(tag, "Infor - decrypt: the message is " + message);  return message;  }
}

BBGHIBEActivity.java

package cn.edu.buaa.crypto;import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;public class BBGHIBEActivity extends Activity implements View.OnClickListener {private static final String tag = "JPBCBenchmarkActivity";private Button benchmark;/*** Called when the activity is first created.*/public void onCreate(Bundle savedInstanceState) {// Init UIsuper.onCreate(savedInstanceState);setContentView(R.layout.bbghibe);benchmark = (Button) findViewById(R.id.button);benchmark.setOnClickListener(this);}public void onClick(View view) {if (view == benchmark){TestBBGHIBE.testBBEHIBE();}}
}

BBGHIBECiphertext.java

package cn.edu.buaa.crypto;import it.unisa.dia.gas.jpbc.Element;  public class BBGHIBECiphertext {  Element C_0;  Element C_1;  Element C_2;
}

BBGHIBEMasterKey.java

package cn.edu.buaa.crypto;import it.unisa.dia.gas.jpbc.Element;  public class BBGHIBEMasterKey {  public Element alpha;
}

BBGHIBESecretKey.java

package cn.edu.buaa.crypto;import it.unisa.dia.gas.jpbc.Element;  public class BBGHIBESecretKey {  public String[] identityVector;  public Element K_1;  public Element K_2;  public Element[] E;
}

TestBBGHIBE.java

package cn.edu.buaa.crypto;import android.util.Log;public class TestBBGHIBE {  private static final String tag = "TestBBGHIBE";public static void testBBEHIBE() {  BBGHIBE bbgHIBE = new BBGHIBE();  BBGHIBEMasterKey msk = bbgHIBE.Setup("assets/a.properties", 7);  String[] testI1 = {"Depth 1"};  String testI2 = "Depth 2";  String testI3 = "Depth 3";  String testI4 = "Depth 4";  String testI5 = "Depth 5";  String testI6 = "Depth 6";  String testI7 = "Depth 7";  String[] receiver = new String[7];  receiver[0] = testI1[0];  receiver[1] = testI2;  receiver[2] = testI3;  receiver[3] = testI4;  receiver[4] = testI5;  receiver[5] = testI6;  receiver[6] = testI7;  String[] ciphertextIV = new String[7];  System.arraycopy(receiver, 0, ciphertextIV, 0, 7);  //KeyGen for depth 1  if (BBGHIBE.isDebug){  Log.i(tag, "Generate secret key for user at depth 1");  }  BBGHIBESecretKey SKDepth1 = bbgHIBE.KeyGen(msk, testI1);  //Delegation for depth 2  if (BBGHIBE.isDebug){  Log.i(tag, "Generate secret key for user at depth 2");  }  BBGHIBESecretKey SKDepth2 = bbgHIBE.Delegate(SKDepth1, testI2);  //Delegation for depth 3  if (BBGHIBE.isDebug){  Log.i(tag, "Generate secret key for user at depth 3");  }  BBGHIBESecretKey SKDepth3 = bbgHIBE.Delegate(SKDepth2, testI3);  //Delegation for depth 4  if (BBGHIBE.isDebug){  Log.i(tag, "Generate secret key for user at depth 4");  }  BBGHIBESecretKey SKDepth4 = bbgHIBE.Delegate(SKDepth3, testI4);  //Delegation for depth 5  if (BBGHIBE.isDebug){  Log.i(tag, "Generate secret key for user at depth 5");  }  BBGHIBESecretKey SKDepth5 = bbgHIBE.Delegate(SKDepth4, testI5);  //Delegation for depth 6  if (BBGHIBE.isDebug){  Log.i(tag, "Generate secret key for user at depth 6");  }  BBGHIBESecretKey SKDepth6 = bbgHIBE.Delegate(SKDepth5, testI6);  //Delegation for depth 7  if (BBGHIBE.isDebug){  Log.i(tag, "Generate secret key for user at depth 7");  }  BBGHIBESecretKey SKDepth7 = bbgHIBE.Delegate(SKDepth6, testI7);  //encryption  if (BBGHIBE.isDebug){  Log.i(tag, "Encryption");  }  BBGHIBECiphertext ciphertext = bbgHIBE.Encrypt(ciphertextIV);  //Decryption for depth 1  if (BBGHIBE.isDebug){  Log.i(tag, "Dncryption for user at depth 1");  }  bbgHIBE.decrypt(ciphertextIV, ciphertext, SKDepth1);  //Decryption for depth 2  if (BBGHIBE.isDebug){  Log.i(tag, "Dncryption for user at depth 2");  }  bbgHIBE.decrypt(ciphertextIV, ciphertext, SKDepth2);  //Decryption for depth 3  if (BBGHIBE.isDebug){  Log.i(tag, "Dncryption for user at depth 3");  }  bbgHIBE.decrypt(ciphertextIV, ciphertext, SKDepth3);  //Decryption for depth 4  if (BBGHIBE.isDebug){  Log.i(tag, "Dncryption for user at depth 4");  }  bbgHIBE.decrypt(ciphertextIV, ciphertext, SKDepth4);  //Decryption for depth 5  if (BBGHIBE.isDebug){  Log.i(tag, "Dncryption for user at depth 5");  }  bbgHIBE.decrypt(ciphertextIV, ciphertext, SKDepth5);  //Decryption for depth 6  if (BBGHIBE.isDebug){  Log.i(tag, "Dncryption for user at depth 6");  }  bbgHIBE.decrypt(ciphertextIV, ciphertext, SKDepth6);  //Decryption for depth 7  if (BBGHIBE.isDebug){  Log.i(tag, "Dncryption for user at depth 7");  }  bbgHIBE.decrypt(ciphertextIV, ciphertext, SKDepth7);  }  //    public static void main(String[] args){
//        testBBEHIBE();
//    }
}

Util.java

package cn.edu.buaa.crypto;import it.unisa.dia.gas.jpbc.Element;
import it.unisa.dia.gas.jpbc.Pairing;  public class Util {  public static Element hash_id(Pairing pairing, String id){  byte[] byte_identity = id.getBytes();  Element hash = pairing.getZr().newElement().setFromHash(byte_identity, 0, byte_identity.length);  return hash;  }
}

Layout: bbghibe.xml

<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayoutandroid:id="@+id/widget38"android:layout_width="fill_parent"android:layout_height="fill_parent"xmlns:android="http://schemas.android.com/apk/res/android"android:keepScreenOn="true"><Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:text="Benchmark"android:layout_x="226px"android:layout_y="84px"></Button>
</AbsoluteLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="cn.edu.buaa.crypto"><application android:icon="@drawable/ic_launcher" android:label="jpbc-benchmark"><activity android:name=".BBGHIBEActivity"><intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter></activity></application></manifest

Android jPBC 2.0.0配置与测试相关推荐

  1. 【Ubuntu-Opencv】Ubuntu14.04 Opencv3.3.0 安装配置及测试

    Ubuntu14.04 Opencv3.3.0 安装配置及测试 网上有许多的Opencv的安装方法,不过找到一个适合自己的安装路数才最为重要,笔者整理了一下自己的安装配置测试过程,仅供学习参考. ## ...

  2. windows 下配置 apache 2.2.4+php 5.2.5+mysql 5.0.41+zend 3.3.0最新配置

    windows 下配置 apache 2.2.4+php 5.2.5+mysql 5.0.41+zend 3.3.0最新配置 1,php5.2.5 for windows 2, mysql-5.0.4 ...

  3. jPBC 2.0.0配置与测试(补充版)

    题注 随着技术博客中的文章越写越多,越写越有经验,我也越来越喜欢把各种各样自己做的有意思的东西公开给大家了~通过技术博客也认识了全国各地的朋友们,他们涉及到的领域真是包罗万象:有做设计的,有做算法的, ...

  4. Android studio 配置 jPBC 2.0.0

    1.去官网(http://gas.dia.unisa.it/projects/jpbc/index.html)下载 jPBC 2.0.0 2.在压缩包的jars文件夹下找到下面两个文件:jpbc-ap ...

  5. Android 5.0 Termux 配置米家设备环境[Alpine Linux]

    1. 前提 在配置米家设备环境之前我们先分析一下目前市面上能打通小米设备的API 或者开源软件.分别需要做哪些工作. 通过python-miio库实现对米家设备的控制 先例1:https://sspa ...

  6. 配置的android版本,Android SDK配置(V3.0.0及以上版本)

    消息接收Receiver配置 创建消息接收Receiver,继承自com.alibaba.sdk.android.push.MessageReceiver,并在对应回调中添加业务处理逻辑,可参考以下代 ...

  7. android8 测试,Android 8.0 Oreo 国内可用测试平台上线

    原标题:Android 8.0 Oreo 国内可用测试平台上线 Android 8.0 Oreo 已经发布一个月了,无数开发者已经跃跃欲试,想在这个全新版本的 Android 系统上让自己的应用一展身 ...

  8. Java自动化测试系列[v1.0.0][TestNG测试开发环境配置]

    基于之前写的一篇文章Java自动化测试系列[v1.0.0][Maven开发环境]的基础上,阐述如何配置单元测试框架TestNG的测试开发环境 创建Maven项目 启动IDEA,点击Create New ...

  9. Testin云测率先全面支持Android 5.0手游云测试

    Testin云测率先全面支持Android 5.0手游云测试 2014/11/21 · Testin · 业界资讯 谷歌公司于今年10月16日推出全新Android 5.0操作系统,迅速成为全球移动互 ...

最新文章

  1. Python ljust()方法
  2. java中集合的结构(list和map)
  3. django开发个人博客
  4. 【CodeForces - 438D】The Child and Sequence(线段树区间取模操作)
  5. Java中当前的时间的各种写法
  6. 2020年用于前端开发的顶级JavaScript框架
  7. Android 8.0 学习(13)---开发者 FAQ
  8. 如何查看微信页面的源代码
  9. 台达A2/B2伺服电机编码器改功率软件
  10. UML简单介绍(五)——用例图的使用案例与分析
  11. 综艺真人秀势不可挡,互联网金融也要投广告?
  12. 激光SLAM:激光雷达运动畸变补偿--轮速里程计辅助方法
  13. quartz - 宕机后的设置
  14. 测试开发岗 - 常见面试题
  15. linux不支持modprobe命令,Linux中modprobe命令起什么作用呢?
  16. 703n的OpenWrt配置四:把路由器变成下载机
  17. Unity3d C#实现程序的加密狗验证功能(PC)
  18. 一大波大运存来袭,智能手机进入大容量时代
  19. 求介绍一款实用的工作任务管理软件,具有进度管理、自动提醒等功能?谢谢
  20. 小 H 的数字c++

热门文章

  1. Android环境搭建
  2. JetBrains提示“No suitable licenses associated with account ”
  3. 使用ngrok实现内网穿透,免费在本地发布项目
  4. 我哋嘅水墨动剐曾让宫祈骏、糕填勋睇木殳咗2019年6月25日 星期二
  5. 面向对象之多态以及进阶
  6. 归一法的计算方法讲解_归一法
  7. 电分糊涂日记之《电路频率响应》
  8. jQuery源码分析理解
  9. 声音内容识别 linux,语音支付,银行的声纹识别与声纹鉴定
  10. docker的目录挂载