每次启动微信,都会时不时得看到应用更新的信息。我们在开发完成一款应用时,需要后期维护,时不时更新程序。
这段时间做一个项目,最后收尾的一个功能就是应用更新,现在这个功能已实现,在此分享下开发经验。
首先讲下大体思路:
1.判断网络可用否;
2.如果网络已连接,则比较当前应用的版本号与服务器上最新的应用版本号;
3.如果服务器上的版本号大于当前应用的版本号,则通知用户是否要更新应用;
4.用户确定更新应用,则下载应用,安装应用。(必须要注意的是:更新版本与原版本的数字签名要一致)
第一步:定义网络连接这个类,用于判断网络,如果网络连接不可用则设置网络。
public class NetWorkSetting {
private Context context ;
public NetWorkSetting( Context con ) {
// TODO Auto-generated constructor stub
context = con;
}
private boolean isOpenNetwork( ) {
if( context == null )
{
return false;
}
ConnectivityManager connManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connManager.getActiveNetworkInfo() != null )
{
return connManager.getActiveNetworkInfo().isAvailable();
}
return false;
}
/*网络可用就调用下一步需要进行的方法, 网络不可用则需设置
*/
public boolean initInternet( )
{ // 判断网络是否可用
if (isOpenNetwork( ) == true) {
// 网络可用,则开始加载。
return true;
}
else
{
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("没有可用的网络").setMessage("是否对网络进行设置?");
builder.setPositiveButton("是", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = null;
try {
String sdkVersion = android.os.Build.VERSION.SDK;
if (Integer.valueOf(sdkVersion) > 10)
{
intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
}
else
{
intent = new Intent();
ComponentName comp = new ComponentName("com.android.settings", "com.android.settings.WirelessSettings");
intent.setComponent(comp);
intent.setAction("android.intent.action.VIEW");
}
context.startActivity(intent);
SplashActivity activity = new SplashActivity( );
UpdateSetting setting = new UpdateSetting(context, activity.getDataFilePath( ) );//检测是否更新数据
setting.checkUpdate( );
} catch (Exception e) {
e.printStackTrace();
}
}
}).setNegativeButton("否", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
Toast.makeText(context, "网络异常,加载失败!", Toast.LENGTH_SHORT).show( );
}
}).show();
}
return false;
}
}
第二步:定义解析版本信息的类,可以使用xml文件保存版本信息,也 可以使用json保存。
在此建议大家,使用json保存数据,我开始时用xml来保存数据的,在网络状态好时,下载数据情况正常;一旦网络不好,则程序直接卡死。通常情况下,是把更新应用这个功能放在启动界面之后进入的主界面中,如果网络连接不好,就会导致连主界面都进入不的。
不过还是先介绍下解析xml文件:
首先定义一个version.xml,其中保存新版本的一些信息,如版本名称,版本号,更新的功能,以及更新的apk的地址等。
<?xml version="1.0" encoding="utf-8"?>
<update>
<version>2</version>
<name>*****系统</name>
<content>版本信息:\n ******系统2.0.0新版本特性:\n 可以设置字体属性\n兼容Android版本2.3以上\n安装包大小:7.85MB</content>
<url>http://101.69.244.66/guzhang/jackDoctor.apk</url>
</update>
然后定义ParseXml这个类,来解析xml文件:
public class ParseXmlService {
public HashMap<String ,String > ParseXml ( InputStream inputstream )
{
HashMap <String,String >hashmap = new HashMap<String ,String >( );
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();//建立DocumentFactory ,以用于取得Documentbuilder
DocumentBuilder builder = null;
try {
builder = factory.newDocumentBuilder();
} catch (ParserConfigurationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Document doc = null;
try {
doc = builder.parse( inputstream );
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Element root = doc.getDocumentElement( );
NodeList nl = root.getChildNodes();//查找nl的结点
//得到NodeList中第一个子节点中文本节点的内容
for( int i=0 ;i < nl.getLength();i++ )
{
Node childnode = ( Node )nl.item(i);//取得每一个元素
if( childnode.getNodeType()==Node.ELEMENT_NODE)
{
Element elementnode = (Element)childnode;
if("version".equals( elementnode.getNodeName() ))
{
hashmap.put("version", elementnode.getFirstChild().getNodeValue());
}
else if("name".equals( elementnode.getNodeName() ))
{
hashmap.put("name", elementnode.getFirstChild().getNodeValue());
}
else if("content".equals(elementnode.getNodeName() ) )
{
hashmap.put("content", elementnode.getFirstChild().getNodeValue());
}
else if("url".equals(elementnode.getNodeName()))
{
hashmap.put("url", elementnode.getFirstChild().getNodeValue());
}
}
}
return hashmap;
}
}
下面再来介绍下json文件保存数据,以及如何解析json数据。
首先,定义version.json。
[{"appname":"jackDoctor","verName":"1.0.1","verCode":"2","apkurl":"http://************/guzhang/jackDoctor.apk",
"content":"版本信息:\n *********系统2.0.0新版本特性:\n 可以设置字体属性\n兼容Android版本2.3以上\n安装包大小:7.85MB"}]
然后再定义一个类ParseJSONService来解析json文件:
public class ParseJSONService {
public static String parsejson( )
{
String url = "http://**********/guzhang/version.json";//此处为服务器上version.json的地址
InputStream inputstream = null;
try{
HttpClient httpclient = new DefaultHttpClient();//取得HttpClient的实例
HttpParams params = httpclient.getParams();
HttpConnectionParams.setConnectionTimeout(params, 3000);
HttpConnectionParams.setSoTimeout(params, 5000);
HttpResponse response = httpclient.execute( new HttpGet( url ));//连接到服务器
HttpEntity entity = response.getEntity();
StringBuilder total = new StringBuilder();
if( entity!=null )
{
inputstream = entity.getContent();//取得数据对象
BufferedReader buffer = new BufferedReader(new InputStreamReader( inputstream,"GB2312"),8192);
String line = "";
while( (line = buffer.readLine())!=null )
{
total.append(line+"\n");
}
buffer.close();//关闭
return total.toString();
}
}catch( Exception e )
{
e.printStackTrace();
}
return null;
}
}
下面才是重点:
更新应用主程序
public class UpdateSetting {
private Context context;
private ProgressBar progressbar;
private static int value;
private static final int DOWLANDER =1;
private static final int DOWLANDER_OVER =2;
private HashMap<String ,String >hashmap ;
private String DirectoryName;
private Dialog builder ;
private Thread mythread;
Boolean cancelbar = false ;
private int version_num = 1 ;
private String version_url;
private String version_content;
public UpdateSetting( Context context,String directoryName ) {
// TODO Auto-generated constructor stub
this.context = context;
DirectoryName = directoryName;//下载文件保存目录
}
/*检测是否要更新,如果需要更新,且计数为1时,才更新*/
public void checkUpdate( )
{
SharedPreferences share = context.getSharedPreferences("VersionUpdate", Activity.MODE_PRIVATE);
int count = share.getInt("loadapp_count", 1);
if( isupdate()&&(count==1 ) )
{
Toast.makeText(context,"有新版本可以更新", Toast.LENGTH_LONG).show();
UpdateAPK( );
}
SharedPreferences.Editor edit = share.edit();//否则不检测更新几次的话,在主界面中,可能每次都得检测是否要更新
edit.putInt("loadapp_count", ++count);
edit.clear().commit();
}
/*得到当前包的版本号*/
public int getVersionCode( Context context )
{
int version = 0;
try {
version = context.getPackageManager( ).getPackageInfo("com.example.mytabhostdemo", 0).versionCode;
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return version ;
}
/*通过解析xml文件或json文件,比较版本号,判断是否要更新应用*/
public boolean isupdate( )
{
int current_version = getVersionCode( context );
URL url = null ;
try{
//外网"http://101.69.244.66/guzhang/version.xml"
//内网192.168.13.101
url = new URL("http://192.68.13.101/guzhang/version2.xml"); //版本信息地址
}catch( Exception e )
{
e.printStackTrace( );
}
HttpURLConnection connection = null;
InputStream inputstream = null;
try {
connection = (HttpURLConnection)url.openConnection( );
inputstream = connection.getInputStream( );
ParseXmlService parsexmlservice = new ParseXmlService( );
hashmap = parsexmlservice.ParseXml(inputstream );//得到hashmap,后面要用到里面的version信息和content信息
if( hashmap != null )
{
int update_version = Integer.valueOf(hashmap.get("version"));
if( update_version >current_version )
{
return true;
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
public void UpdateAPK()
{
Dialog dialog = new AlertDialog.Builder( context )
.setIcon(R.drawable.sc_icon_label_press)
.setTitle("更新软件包")
.setMessage( hashmap.get("content").toString().replace("\\n", "\n") )
//.setMessage( version_content.toString().replace("\\n", "\n"))
.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
DownloadAPK();
dialog.dismiss();
}
}).setNegativeButton("以后再说",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
dialog.dismiss();
}
}).create();
dialog.show();
}
/*下载应用程序过程中,弹出窗口并显示下载进度条*/
public void DownloadAPK()
{
LayoutInflater inflater = LayoutInflater.from( context );
View view = inflater.inflate(R.layout.updateapp , null );
progressbar = (ProgressBar )view.findViewById(R.id.progressBar1);
builder = new AlertDialog.Builder( context )
.setIcon(R.drawable.sc_icon_label_press)
.setTitle("正在下载安装包,安装后需要手动重启程序")
.setMessage("请稍等...")
.setNegativeButton("取消",
new DialogInterface.OnClickListener( ) {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
dialog.dismiss( );
cancelbar = true;
}
}).setView(view)
.create();
builder.show( );
downloaderApk( );
}
/*下载Apk*/
public void downloaderApk( )
{
mythread = new Thread( downloadrunnable );
mythread.start( );
}
/*开启线程下载apk*/
private Handler handler = new Handler ( ){
public void handleMessage( Message msg ){
switch( msg.what ){
case DOWLANDER:
progressbar.setProgress(value);//设置进度条的进度
break;
case DOWLANDER_OVER:
builder.dismiss();
setupfile( );
break;
}
}
};
private Runnable downloadrunnable = new Runnable( )
{
@Override
public void run( )
{
try{
URL url = new URL( hashmap.get("url") );//取得网络文件资源
//URL url = new URL(version_url );
HttpURLConnection connection = (HttpURLConnection)url.openConnection( );//打开
connection.connect();
InputStream inputstream = connection.getInputStream( );
int length = connection.getContentLength( );
File directory = new File ( DirectoryName );
if( !directory.exists( ) )
{
directory.mkdir( );
}
String filename = DirectoryName +"jackDoctor.apk";
File apkfile = new File( filename );
FileOutputStream filestream = new FileOutputStream(apkfile);
int len = 0;
int count = 0;
byte buffer [] = new byte[1024];
while( !cancelbar )
{
len = inputstream.read( buffer );//问题在这里
count += len;
value = (int )(((float)count/length)*100);
handler.sendEmptyMessage(DOWLANDER);
if( len<=0 )
{
handler.sendEmptyMessage(DOWLANDER_OVER); //下载完毕
break;
}
filestream.write( buffer ,0,len );//保存字节数组
}
filestream.close();//关闭文件流
inputstream.close();//关闭数据流
}catch( Exception e )
{
e.printStackTrace( );
}
}
};
/*安装文件*/
public void setupfile( )
{
String filename = DirectoryName +"jackDoctor.apk";
File directory = new File ( DirectoryName );
if( !directory.exists( ) )
{
directory.mkdir();
}
File apkfile = new File( filename );
if( !apkfile.exists( ) )
{
return ;
}
Intent intent = new Intent( Intent.ACTION_VIEW);//Uri.parse("file://"+apkfile.toString()),解析包出现问题
intent.setDataAndType(Uri.fromFile(apkfile),"application/vnd.android.package-archive" );//file:///sdcard
context.startActivity(intent);
}
}
如果是需要使用json保存版本信息,则需要做些简单的修改:
public boolean isupdate( )
{
int current_version = getVersionCode( context );
try {
ParseJSONService service = new ParseJSONService() ;
String jsonstring = ParseJSONService.parsejson( );
JSONArray jsonArr = new JSONArray( jsonstring );
if(jsonArr.length()>0 )
{
JSONObject jsonObj = jsonArr.getJSONObject(0);
version_num = jsonObj.getInt("verCode");
version_url = jsonObj.getString("apkurl");
version_content = jsonObj.getString("content");
}
if(version_num > current_version )
{
return true;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
private Runnable downloadrunnable = new Runnable( )
{
@Override
public void run( )
{
try{
//URL url = new URL( hashmap.get("url") );//取得网络文件资源
URL url = new URL(version_url );//url修改
HttpURLConnection connection = (HttpURLConnection)url.openConnection( );//打开
connection.connect();
InputStream inputstream = connection.getInputStream( );
int length = connection.getContentLength( );
File directory = new File ( DirectoryName );
if( !directory.exists( ) )
{
directory.mkdir( );
}
String filename = DirectoryName +"jackDoctor.apk";
File apkfile = new File( filename );
FileOutputStream filestream = new FileOutputStream(apkfile);
int len = 0;
int count = 0;
byte buffer [] = new byte[1024];
while( !cancelbar )
{
len = inputstream.read( buffer );
count += len;
value = (int )(((float)count/length)*100);
handler.sendEmptyMessage(DOWLANDER);
if( len<=0 )
{
handler.sendEmptyMessage(DOWLANDER_OVER); //下载完毕
break;
}
filestream.write( buffer ,0,len );//保存字节数组
}
filestream.close();//关闭文件流
inputstream.close();//关闭数据流
}catch( Exception e )
{
e.printStackTrace( );
}
}
};
public void UpdateAPK()
{
Dialog dialog = new AlertDialog.Builder( context )
.setIcon(R.drawable.sc_icon_label_press)
.setTitle("更新软件包")
//.setMessage( hashmap.get("content").toString().replace("\\n", "\n") )
.setMessage( version_content.toString().replace("\\n", "\n"))//content修改
.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
DownloadAPK();
dialog.dismiss();
}
}).setNegativeButton("以后再说",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
dialog.dismiss();
}
}).create();
dialog.show();
}
其中在checkUpdate()函数中,有段SharePreferences取数据的代码段。
在启动界面的程序中,保存一个字段loadapp_count来记录启动程序次数,这样可以保证每一次启动程序都检测更新。
在启动界面需要加这段:
SharedPreferences share = getSharedPreferences("VersionUpdate",Activity.MODE_PRIVATE);
SharedPreferences.Editor edit = share.edit( );
edit.putInt("loadapp_count", 1);
edit.clear().commit();
这样我们可以在checkUpdate()函数中,才能得到load_count.。
在AndroidMainfest.xml需要配置网络权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
本想上传图片,但考虑到其他问题,在此就不上传了。
最后,我们可以在主程序中onCreate()方法中调用:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature( Window.FEATURE_NO_TITLE);
setContentView(R.layout.searchinform);
NetWorkSetting networksetting = new NetWorkSetting( this );
if( networksetting.initInternet( ) )
{
//Toast.makeText(this, "网络连接可用,查看是否有新版本", Toast.LENGTH_LONG).show( );
SplashActivity activity = new SplashActivity( );
UpdateSetting setting = new UpdateSetting( eControlSettingTabActivity.this, activity.getDataFilePath( ) );//检测是否更新数据
setting.checkUpdate( );
}
}