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

以下内容仅为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代码如下:

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));
                }
            }
        }
    };
}

最后需要的权限

你可能感兴趣的:(android,服务器,node.js,apk版本更新)