用android和用iPhone的区别,背景
- 因为实在受不了Android的电量问题,所以决定更换机器。
- 然后还维护着两款Android软件,一款需要通过手机IMEI计算一个注册码,另一款,需要解析一些Excel文件,然后上传的另外一个服务器。
那么问题来了
-
因为为换机问题,平时外出只会带一台手机,那就需要Android机变为服务器,把iPhone机变为遥控器。那两个手机的通信方式又有哪些呢?
- 通过主动拉的方式,让Android手机轮询一个网址,获取一些数据和变化,产生变化时,执行相应的Action, iPhone端只要可以操作网站端就可以驱动服务器的改变。Android因为支持闹钟,所以后台轮询查询服务端,记录相应的变化即可。
- 被动接受的方式,通过短信,iPhone发送指令短信给Android,然后Android端去处理短信指令,执行操作。因为上面方案还要引入网站端,增加成本,所以我采用了短信控制的方式。
- 上面把Android手机变为服务器,又带来了什么问题呢?
- 问题一,还是电量问题,万一我可怜的Android机,又没有电的时候怎么办?为了解决这个问题,增加电量监测功能如下代码,当电量低的时候,主动发一条短信进行提醒,同时,停止一些耗电的轮询操作。
public class BatteryLevelReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Utils.BATTERY_LOW_ACTION)) {
Utils.stopMonitorAlarm(context.getApplicationContext());
sendingSms(context, "186xxxxxxx", "手机电量低,请尽快给手机充电,定时监控已关闭!", "0");
}
}
}
其他积极的推动
- 简化操作,把简单留给用户,用户提供IMEI给我后,通过QQ再返回给对方。略复杂。
- 读取剪切板,采用了淘宝的方案,在onResume中,去读剪切板内容,序列号是否正确,如果正确,激活软件。
- 直接省掉和用户交互的过程,客户端在onResume时,请求 xxx.com/imei.txt 文件,激活设备。那这样的话,就需要服务端了,下面说一说服务端的实现方案。
- NOSQL时代,肯定不会去写复杂的DB管理系统,直接文件搞定,普通的ASP,PHP空间,一年几百块钱,然后直接通过FTP上传上去。没错,Android也是可以访问FTP的。FTP上传
public class FtpUtil {
private FTPClient ftpClient;
private String strIp;
private int intPort;
private String user;
private String password;
/* *
* Ftp构造函数
*/
public FtpUtil(String strIp, int intPort, String user, String Password) {
this.strIp = strIp;
this.intPort = intPort;
this.user = user;
this.password = Password;
this.ftpClient = new FTPClient();
}
/**
* @return 判断是否登入成功
*/
public boolean ftpLogin() {
boolean isLogin = false;
FTPClientConfig ftpClientConfig = new FTPClientConfig();
ftpClientConfig.setServerTimeZoneId(TimeZone.getDefault().getID());
this.ftpClient.setControlEncoding("GBK");
this.ftpClient.configure(ftpClientConfig);
try {
if (this.intPort > 0) {
this.ftpClient.connect(this.strIp, this.intPort);
} else {
this.ftpClient.connect(this.strIp);
}
// FTP服务器连接回答
int reply = this.ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
this.ftpClient.disconnect();
L.e("登录FTP服务失败!");
return isLogin;
}
this.ftpClient.login(this.user, this.password);
// 设置传输协议
this.ftpClient.enterLocalPassiveMode();
this.ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
L.i("恭喜" + this.user + "成功登陆FTP服务器");
isLogin = true;
} catch (Exception e) {
e.printStackTrace();
L.e(this.user + "登录FTP服务失败!" + e.getMessage());
}
this.ftpClient.setBufferSize(1024 * 2);
this.ftpClient.setDataTimeout(30 * 1000);
return isLogin;
}
/**
* @退出关闭服务器链接
*/
public void ftpLogOut() {
if (null != this.ftpClient && this.ftpClient.isConnected()) {
try {
boolean reuslt = this.ftpClient.logout();// 退出FTP服务器
if (reuslt) {
L.i("成功退出服务器");
}
} catch (IOException e) {
e.printStackTrace();
L.d("退出FTP服务器异常!" + e.getMessage());
} finally {
try {
this.ftpClient.disconnect();// 关闭FTP服务器的连接
} catch (IOException e) {
e.printStackTrace();
L.d("关闭FTP服务器的连接异常!");
}
}
}
}
/***
* 上传Ftp文件
*
* @param localFile 当地文件
* @param remoteUploadPath 上传服务器路径 - 应该以/结束
*/
public boolean uploadFile(File localFile, String remoteUploadPath) {
BufferedInputStream inStream = null;
boolean success = false;
try {
this.ftpClient.changeWorkingDirectory(remoteUploadPath);// 改变工作路径
inStream = new BufferedInputStream(new FileInputStream(localFile));
L.i(localFile.getName() + "开始上传.....");
success = this.ftpClient.storeFile(localFile.getName(), inStream);
if (success == true) {
L.i(localFile.getName() + "上传成功");
return success;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
L.e(localFile + "未找到");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inStream != null) {
try {
inStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return success;
}
/***
* 下载文件
*
* @param remoteFileName 待下载文件名称
* @param localDires 下载到当地那个路径下
* @param remoteDownLoadPath remoteFileName所在的路径
*/
public boolean downloadFile(String remoteFileName, String localDires,
String remoteDownLoadPath) {
String strFilePath = localDires + remoteFileName;
BufferedOutputStream outStream = null;
boolean success = false;
try {
this.ftpClient.changeWorkingDirectory(remoteDownLoadPath);
outStream = new BufferedOutputStream(new FileOutputStream(
strFilePath));
L.i(remoteFileName + "开始下载....");
success = this.ftpClient.retrieveFile(remoteFileName, outStream);
if (success == true) {
L.i(remoteFileName + "成功下载到" + strFilePath);
return success;
}
} catch (Exception e) {
e.printStackTrace();
L.e(remoteFileName + "下载失败");
} finally {
if (null != outStream) {
try {
outStream.flush();
outStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (success == false) {
L.e(remoteFileName + "下载失败!!!");
}
return success;
}
/***
* @param localDirectory 当地文件夹
* @param remoteDirectoryPath Ftp 服务器路径 以目录"/"结束
* @上传文件夹
*/
public boolean uploadDirectory(String localDirectory,
String remoteDirectoryPath) {
File src = new File(localDirectory);
try {
remoteDirectoryPath = remoteDirectoryPath + src.getName() + "/";
this.ftpClient.makeDirectory(remoteDirectoryPath);
// ftpClient.listDirectories();
} catch (IOException e) {
e.printStackTrace();
L.i(remoteDirectoryPath + "目录创建失败");
}
File[] allFile = src.listFiles();
for (int currentFile = 0; currentFile < allFile.length; currentFile++) {
if (!allFile[currentFile].isDirectory()) {
String srcName = allFile[currentFile].getPath().toString();
uploadFile(new File(srcName), remoteDirectoryPath);
}
}
for (int currentFile = 0; currentFile < allFile.length; currentFile++) {
if (allFile[currentFile].isDirectory()) {
// 递归
uploadDirectory(allFile[currentFile].getPath().toString(),
remoteDirectoryPath);
}
}
return true;
}
/***
* @param localDirectoryPath 本地地址
* @param remoteDirectory 远程文件夹
* @下载文件夹
*/
public boolean downLoadDirectory(String localDirectoryPath, String remoteDirectory) {
try {
String fileName = new File(remoteDirectory).getName();
localDirectoryPath = localDirectoryPath + fileName + "//";
new File(localDirectoryPath).mkdirs();
FTPFile[] allFile = this.ftpClient.listFiles(remoteDirectory);
for (int currentFile = 0; currentFile < allFile.length; currentFile++) {
if (!allFile[currentFile].isDirectory()) {
downloadFile(allFile[currentFile].getName(), localDirectoryPath, remoteDirectory);
}
}
for (int currentFile = 0; currentFile < allFile.length; currentFile++) {
if (allFile[currentFile].isDirectory()) {
String strremoteDirectoryPath = remoteDirectory + "/" + allFile[currentFile].getName();
downLoadDirectory(localDirectoryPath, strremoteDirectoryPath);
}
}
} catch (IOException e) {
e.printStackTrace();
L.i("下载文件夹失败");
return false;
}
return true;
}
// FtpClient的Set 和 Get 函数
public FTPClient getFtpClient() {
return ftpClient;
}
public void setFtpClient(FTPClient ftpClient) {
this.ftpClient = ftpClient;
}
public static void main(String[] args) throws IOException {
// Ftp ftp = new Ftp("10.3.15.1", 21, "ghips", "ghipsteam");
// ftp.ftpLogin();
// //上传文件夹
// ftp.uploadDirectory("d://DataProtemp", "/home/data/");
// //下载文件夹
// ftp.downLoadDirectory("d://tmp//", "/home/data/DataProtemp");
// ftp.ftpLogOut();
}
}
- 但这些共用的小空间,存在不稳定的可能,自己搭建服务器,太弱了,稳定压倒一切的原则下,再寻求更优的方案:OSS,阿里云存储, 现在都流行应用上云,阿里云的稳定性,比空间类的要稳定不少吧?JAVA语言的易用性,所以也是可以通过SDK直接上传文件的。
public class AliYunOSSUtils {
/**
* 整个文件夹上传文件
*
* @param endpoint
* @param baseFilePath
* @param subFilePath
* @param bucket
*/
public static void uploadDirectory(String endpoint, String baseFilePath, String subFilePath, String bucket) {
L.d("开始上传:" + baseFilePath + File.separator + subFilePath);
File file = new File(baseFilePath + File.separator + subFilePath);
if (file.isDirectory()) {
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
File childFile = files[i];
String targetFile = subFilePath + File.separator + childFile.getName();
uploadFile(endpoint, childFile.getAbsolutePath(), targetFile, bucket);
}
}
}
/**
* 单个上传文件
*
* @param endpoint
* @param uploadFilePath
* @param remoteFilePath
* @param bucket
*/
public static void uploadFile(String endpoint, String uploadFilePath, final String remoteFilePath, final String bucket) {
// 明文设置secret的方式建议只在测试时使用,更多鉴权模式请参考后面的`访问控制`章节
OSSCredentialProvider credentialProvider = new OSSPlainTextAKSKCredentialProvider("xxx", "xxx");
;
OSS oss = new OSSClient(SmsApp.getApp(), endpoint, credentialProvider);
// 构造上传请求
PutObjectRequest put = new PutObjectRequest(bucket, remoteFilePath, uploadFilePath);
// 异步上传时可以设置进度回调
put.setProgressCallback(new OSSProgressCallback<PutObjectRequest>() {
@Override
public void onProgress(PutObjectRequest request, long currentSize, long totalSize) {
// L.d("PutObject", "进度: 当前大小: " + currentSize + " 总大小: " + totalSize);
}
});
OSSAsyncTask task = oss.asyncPutObject(put, new OSSCompletedCallback<PutObjectRequest, PutObjectResult>() {
@Override
public void onSuccess(PutObjectRequest request, PutObjectResult result) {
L.d(bucket + " 下: " + remoteFilePath + " 上传成功");
}
@Override
public void onFailure(PutObjectRequest request, ClientException clientExcepion, ServiceException serviceException) {
// 请求异常
if (clientExcepion != null) {
// 本地异常如网络异常等
clientExcepion.printStackTrace();
}
if (serviceException != null) {
// 服务异常
L.e("ErrorCode", serviceException.getErrorCode());
L.e("RequestId", serviceException.getRequestId());
L.e("HostId", serviceException.getHostId());
L.e("RawMessage", serviceException.getRawMessage());
}
}
});
}
}
通过上面的一系列优化后,形成如下序列图:
所以,一个奇葩Android开发,换了Iphone后,就产生了这些在Android应用上的想法,如果你也有类似的经历,也欢迎提出你玩出了哪些新的花样。
二、让你的应用更酷炫
- 开源库:Lottie,实现一系列的牛X动画。如下图:
- 下面说一下接入方式:
增加依赖:
dependencies {
compile 'com.airbnb.android:lottie:2.0.0-beta4'
}
在Asset中放好 json文件。然后最简单的使用如下:
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lottie_fileName="hello-world.json"
app:lottie_loop="true"
app:lottie_autoPlay="true" />
至此,基本的应用就算完成了,但这些东西,你会觉得太简单了,能不能举个实际应用粟子?我看到了,但接入的过程中,还是费了点力气。
要解决的问题如下:
- 怎么控制动画的大小?
- 能不能控制一下播放完成时的一些操作?
针对第一个问题,我一开始设计了各种wrap_content, fill_parent,都未生效,然后想到去调整json文件,因为里面有w 和 h 两个属性。然而,也没有作用。千思万解之际,我去http://www.lottiefiles.com/ 下载了一些其他的模板动画文件,发现这里的动画又太大了,又有缩小的需求,妹的,看了LottieAnimationView 继承自ImageView,然后设置scaleType = fitCenter,也无效。最后想到Demo中的 iv_lav_like.setScale(0.4f); 方法。一试,果然有效,所以关于动画的大小,可以通过如下代码,需要延迟设置才会生效。
handler.postDelayed(new Runnable() {
@Override
public void run() {
iv_lav_like.setScale(0.4f);
iv_lav_share.setScale(0.4f);
}
}, 1000);
第二个问题,动画播放前和播放完成后的一些Action。在结束时,把自己的动画UI给隐藏掉。
iv_lav_like = (LottieAnimationView) v.findViewById(R.id.iv_lav_like);
iv_lav_like.setAnimation("heart.json");
iv_lav_like.loop(false);
iv_lav_like.setVisibility(View.GONE);
iv_lav_like.addAnimatorListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
iv_lav_like.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
iv_lav_like.playAnimation();
这样就可以解决大部分的需求了。
赛文市场营销