您的位置:首页 > 移动开发 > Android开发

Node.js实现android的apk版本更新服务器

2016-07-13 15:33 525 查看
以下内容仅为android程序员自己测试时搭建的简单测试服务器。

根据一般apk升级步骤:

1. 请求服务器versionCode和本地apk的versionCode比对,如果服务器versionCode大于apk的versionCode,则执行2),否则结束;

2. 弹出对话框,告知用户有新的版本可以更新,用户点击更新,则执行3)开始下载apk,否则结束;

3. 后台下载最新版本的apk。

第一步:判断versionCode

目前的数据请求基本上都是JSON格式的,服务器端设置一个JSON文件:updateApp.json.具体内容如下:

{"versionCode":"201607121111","versionName":"2.1.5","apkSize":"8MB","updateContent":"具体的更新内容如下:\n1、修复bug;\n2、更新UI"}


注意里面不要有空格,否则虽然浏览器可以请求成功,客户端使用volley或okhttp请求都会失败。

node.js代码如下:

var express = require('express');
var app = express();
var fs = require("fs");

app.get('/', function(req, res) {
fs.readFile(__dirname + "/" + "appUpdate.json", 'utf8', function(err, data) {
console.log( data );
res.end( data );
});
})

var server = app.listen(8081, function() {
var host = server.address().address
var port = server.address().port

console.log("访问地址为http://%s:%s", host, port);
})


这里的host地址请求时可以填入自己的ipv4地址。

接下来就是使用okhttp请求该地址,获取JSON,然后本地业务逻辑。android代码见附录。

第二步:apk文件的下载

filesystem.js文件:

var express = require('express');
var http = require('http');
var path = require('path');

var app = express();

app.get('/download/*', function(req, res, next) {
var f = req.params[0];
f = path.resolve(f);
console.log('Download file: %s', f);
res.download(f);
});

http.createServer(app).listen(8081);


在浏览器中打开http://ipv4:8081/download/G:/learnNodeJS/download/app.apk就可以下载了。

android客户端下载apk的代码见附录。

以上就是简单的apk版本更新demo。

附录

请求版本比较的JSON请求

这里使用的是okhttp,需要导入两个jar包:okhttp.jar和okio.jar。

Activity代码如下:

weakReference;

public UpdateAppHandler(AOPActivity activity) {
this.weakReference = new WeakReference(activity);
}

@Override
public void handleMessage(Message msg) {
AOPActivity aopActivity = weakReference.get();
if (msg.what == DownloadFile.UPDATEAPP_OK) {
Bundle bundle = msg.getData();
AppUpdateVersionBean bean = bundle.getParcelable(DownloadFile.UPDATEAPP);
Log.i(TAG, "handleMessage: " + bean.getApkSize());
Log.i(TAG, "handleMessage: " + bean.getUpdateContent());
Log.i(TAG, "handleMessage: " + bean.getVersionCode());
Log.i(TAG, "handleMessage: " + bean.getVersionName());
aopTV.setText(bean.getUpdateContent());
} else if (msg.what == DownloadFile.UPDATEAPP_FAIL) {
//
} else if (msg.what == DownloadFile.UPDATEAPP_DOWNLOAD) {
int progress = msg.arg1;
Log.i(TAG, "handleMessage:progress= " + progress);
}
super.handleMessage(msg);
}
}

private Button aopBtn;
private Button aopDownloadBtn;
private TextView aopTV;
private DownloadFile downloadFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aop);
updateHandler = new UpdateAppHandler(this);
downloadFile = new DownloadFile(AOPActivity.this, updateHandler);

aopTV          = (TextView) findViewById(R.id.aopTV);
aopBtn         = (Button) findViewById(R.id.aopBtn);
aopDownloadBtn = (Button) findViewById(R.id.aopDownloadBtn);
aopBtn.setOnClickListener(myOnClickLisntener);
aopDownloadBtn.setOnClickListener(myOnClickLisntener);

//// 注册广播, 设置只接受下载完成的广播
registerReceiver(downloadFile.receiver, new    IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}

View.OnClickListener myOnClickLisntener = new View.OnClickListener() {

@Override
public void onClick(View v) {
if (v == aopBtn) {
checkApkVersion();
} else if (v == aopDownloadBtn) {
downAPkFile();
}
}
};

private void checkApkVersion() {
new Thread(new Runnable() {
@Override
public void run() {
try {
downloadFile.isUpdateAppVersion();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}

private void downAPkFile() {
new Thread(new Runnable() {
@Override
public void run() {
downloadFile.downloadAppFile();
}
}).start();

}

@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(downloadFile.receiver);
}
}

" data-snippet-id="ext.b195dda70d17300b6513821c2a83d655" data-snippet-saved="false" data-codota-status="done">[code]package com.testandroid;

import android.app.DownloadManager;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.testandroid.R;
import com.testandroid.util.AppUpdateVersionBean;

import java.lang.ref.WeakReference;

public class AOPActivity extends AppCompatActivity {
private final String TAG = AOPActivity.class.getSimpleName();

private UpdateAppHandler updateHandler;

private class UpdateAppHandler extends Handler {
private WeakReference<AOPActivity> weakReference;

public UpdateAppHandler(AOPActivity activity) {
this.weakReference = new WeakReference<>(activity);
}

@Override
public void handleMessage(Message msg) {
AOPActivity aopActivity = weakReference.get();
if (msg.what == DownloadFile.UPDATEAPP_OK) {
Bundle bundle = msg.getData();
AppUpdateVersionBean bean = bundle.getParcelable(DownloadFile.UPDATEAPP);
Log.i(TAG, "handleMessage: " + bean.getApkSize());
Log.i(TAG, "handleMessage: " + bean.getUpdateContent());
Log.i(TAG, "handleMessage: " + bean.getVersionCode());
Log.i(TAG, "handleMessage: " + bean.getVersionName());
aopTV.setText(bean.getUpdateContent());
} else if (msg.what == DownloadFile.UPDATEAPP_FAIL) {
//
} else if (msg.what == DownloadFile.UPDATEAPP_DOWNLOAD) {
int progress = msg.arg1;
Log.i(TAG, "handleMessage:progress= " + progress);
}
super.handleMessage(msg);
}
}

private Button aopBtn;
private Button aopDownloadBtn;
private TextView aopTV;
private DownloadFile downloadFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aop);
updateHandler = new UpdateAppHandler(this);
downloadFile = new DownloadFile(AOPActivity.this, updateHandler);

aopTV          = (TextView) findViewById(R.id.aopTV);
aopBtn         = (Button) findViewById(R.id.aopBtn);
aopDownloadBtn = (Button) findViewById(R.id.aopDownloadBtn);
aopBtn.setOnClickListener(myOnClickLisntener);
aopDownloadBtn.setOnClickListener(myOnClickLisntener);

//// 注册广播, 设置只接受下载完成的广播
registerReceiver(downloadFile.receiver, new    IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}

View.OnClickListener myOnClickLisntener = new View.OnClickListener() {

@Override
public void onClick(View v) {
if (v == aopBtn) {
checkApkVersion();
} else if (v == aopDownloadBtn) {
downAPkFile();
}
}
};

private void checkApkVersion() {
new Thread(new Runnable() {
@Override
public void run() {
try {
downloadFile.isUpdateAppVersion();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}

private void downAPkFile() {
new Thread(new Runnable() {
@Override
public void run() {
downloadFile.downloadAppFile();
}
}).start();

}

@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(downloadFile.receiver);
}
}


因为okhttp的callback不在主UI线程中,所以需要更新UI时,需要借助Handler或runOnUiThread()等。

apk下载

package com.testandroid.classloader;

import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;

import com.testandroid.util.AppUpdateVersionBean;
import com.google.gson.Gson;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
* Created by Administrator on 2016-7-13.
*/
public class DownloadFile {
private final String TAG = DownloadFile.class.getSimpleName();
private Context context;
public static final String ApkVersionURL = "http://xx.xx.xx.xx:xxxx/";
public static final String ApkDownloadURL = "http://xxxx.xxx.xxx.xxx:xxxx/download/download/xx.apk";

public static final String UPDATEAPP = "updateApp";
public static final int UPDATEAPP_OK = 0x0001;
public static final int UPDATEAPP_FAIL = 0x0002;
public static final int UPDATEAPP_DOWNLOAD = 0x0003;

private OkHttpClient mOkHttpClient = new OkHttpClient();
private Handler updateHandler;
private DownloadManager downloadManager;
public DownloadFile(Context context, Handler mHandler) {
this.context = context;
this.updateHandler = mHandler;
downloadManager = (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);
}

/**
* 判断是否有新的app版本
*/
public void isUpdateAppVersion() {
final Request request = new Request.Builder().url(ApkVersionURL).build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i(TAG, "onFailure: ");
}

@Override
public void onResponse(Call call, Response response) throws IOException {
String res = response.body().string();
Log.i(TAG, "onResponse: 版本比较结果:" + res);
Message msg = updateHandler.obtainMessage();
if (response.isSuccessful()) {
Gson gson = new Gson();
AppUpdateVersionBean bean = gson.fromJson(res, AppUpdateVersionBean.class);
if(compareAppVersion(bean.getVersionCode())) {
msg.what = UPDATEAPP_OK;
Bundle bundle = new Bundle();
bundle.putParcelable(UPDATEAPP, bean);
msg.setData(bundle);
}
} else {
msg.what = UPDATEAPP_FAIL;
}
msg.sendToTarget();
}
});
}

private boolean compareAppVersion(String serverVersionCode) {
PackageManager manager = context.getPackageManager();
PackageInfo info = null;
try {
info = manager.getPackageInfo(context.getPackageName(), 0);
}catch (Exception e) {
e.printStackTrace();
}
int localVersion = info.versionCode;
int serverVCode = Integer.parseInt(serverVersionCode);
return localVersion < serverVCode;
}
public
void downloadAppFile() {
DownloadManager.Request down = new DownloadManager.Request(Uri.parse(ApkDownloadURL));
down.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
down.setNotificationVisibility(View.VISIBLE);
down.setTitle("apk_title");
down.setDescription("apk_description");
down.setVisibleInDownloadsUi(true);
// 设置下载后文件存放的位置,该位置下只能使用root explore打开。
//      down.setDestinationInExternalFilesDir(MainActivity.this, null,"downtest.png");
// sdcard的目录下的download文件夹
down.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, getFileName(ApkDownloadURL));
//在这里返回的reference变量是系统为当前的下载请求分配的一个唯一的ID,
//我们可以通过这个ID重新获得这个下载任务,进行一些自己想要进行的操作或者
//查询下载的状态以及取消下载等等。
enqueueId = downloadManager.enqueue(down);
// 将下载请求放入队列
//queryDownloadStatus();
}
long enqueueId = 0L;

private String getFileName(String url) {
return url.substring(url.lastIndexOf("/"), url.length());
}

private void promptInstall(Uri file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(file, "application/vnd.android.package-archive");
context.startActivity(intent);
}

public BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
long downloadCompletedId = intent.getLongExtra(
DownloadManager.EXTRA_DOWNLOAD_ID, 0);
// 检查是否是自己的下载队列 id, 有可能是其他应用的
if (enqueueId != downloadCompletedId) {
return;
}
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(enqueueId);
Cursor c = downloadManager.query(query);
if (c.moveToFirst()) {
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
// 下载失败也会返回这个广播,所以要判断下是否真的下载成功
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
// 获取下载好的 apk 路径
String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
// 提示用户安装
promptInstall(Uri.parse("file://" + uriString));
}
}
}
};
}


最后需要的权限
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息