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。
注意里面不要有空格,否则虽然浏览器可以请求成功,客户端使用volley或okhttp请求都会失败。
node.js代码如下:
这里的host地址请求时可以填入自己的ipv4地址。
接下来就是使用okhttp请求该地址,获取JSON,然后本地业务逻辑。android代码见附录。
在浏览器中打开http://ipv4:8081/download/G:/learnNodeJS/download/app.apk就可以下载了。
android客户端下载apk的代码见附录。
以上就是简单的apk版本更新demo。
Activity代码如下:
因为okhttp的callback不在主UI线程中,所以需要更新UI时,需要借助Handler或runOnUiThread()等。
最后需要的权限
根据一般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)); } } } }; }
最后需要的权限
相关文章推荐
- 小心服务器内存居高不下的元凶--WebAPI服务
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件