在线升级Android应用程序完善版

 发一个完善版本的 思路还是原来的思路,上一篇文章:http://fengzhizi715.iteye.com/blog/792774
不过结合了线程和ProgressBar

代码如下:
Java代码 复制代码  收藏代码
  1. import java.io.File;   
  2. import java.io.FileOutputStream;   
  3. import java.io.IOException;   
  4. import java.io.InputStream;   
  5. import java.net.HttpURLConnection;   
  6. import java.net.MalformedURLException;   
  7. import java.net.URL;   
  8. import java.util.List;   
  9.   
  10. import javax.xml.parsers.ParserConfigurationException;   
  11. import javax.xml.parsers.SAXParser;   
  12. import javax.xml.parsers.SAXParserFactory;   
  13.   
  14. import org.xml.sax.Attributes;   
  15. import org.xml.sax.SAXException;   
  16. import org.xml.sax.helpers.DefaultHandler;   
  17.   
  18. import android.app.Activity;   
  19. import android.app.ActivityManager;   
  20. import android.app.ActivityManager.RunningAppProcessInfo;   
  21. import android.content.ComponentName;   
  22. import android.content.Context;   
  23. import android.content.Intent;   
  24. import android.content.pm.ResolveInfo;   
  25. import android.net.Uri;   
  26. import android.os.Bundle;   
  27. import android.os.Environment;   
  28. import android.os.Handler;   
  29. import android.os.Message;   
  30. import android.view.View;   
  31. import android.widget.ProgressBar;   
  32. import android.widget.Toast;   
  33.   
  34. import com.decarta.db.MapVersionTable;   
  35.   
  36. /**  
  37.  * @author Tony Shen  
  38.  *  
  39.  */  
  40. public class Main extends Activity {   
  41.        
  42.     private MapVersionTable mDB;   
  43.     private String mapVersion;   
  44.     private String apkUrl;   
  45.        
  46.     private List<RunningAppProcessInfo> process;   
  47.     private ActivityManager activityMan;   
  48.     private ProgressBar progressBar;   
  49.        
  50.     private final int CHECK_NEW_VERSION = 1;   
  51.     private final int DOWNLOAD = 2;   
  52.     private final int INSTALL = 3;   
  53.     private final int CHECK_APP = 4;   
  54.     private final int INVOKE_APP = 5;   
  55.     private final int DOWNLOAD_AGAIN = 6;   
  56.     private final int INSTALL_AGAIN = 7;   
  57.        
  58.     private boolean newVersionFlag = false;   
  59.     private boolean checkAppFlag = false;   
  60.        
  61.     /** Called when the activity is first created. */  
  62.     @Override  
  63.     public void onCreate(Bundle savedInstanceState) {   
  64.         super.onCreate(savedInstanceState);   
  65.         setContentView(R.layout.main);   
  66.   
  67.         mDB = new MapVersionTable(this);   
  68.            
  69.         progressBar = (ProgressBar) findViewById(R.id.progressBar);   
  70.            
  71.         progressBar.setIndeterminate(false);    
  72.         progressBar.setVisibility(View.VISIBLE);   
  73.            
  74.         progressBar.setMax(100);     
  75.         progressBar.setProgress(0);   
  76.   
  77.         checkAppFlag = checkApp();   
  78.            
  79.         new Thread(new Runnable() {   
  80.             Message msg = new Message();   
  81.             public void run() {   
  82.                 try {   
  83.                     Thread.sleep(5000);   
  84.                 } catch (InterruptedException e) {   
  85.                     e.printStackTrace();   
  86.                 }   
  87.                 msg.what = CHECK_NEW_VERSION;   
  88.                 mHandler.sendMessage(msg);   
  89.   
  90.                 try {   
  91.                     Thread.sleep(5000);   
  92.                 } catch (InterruptedException e) {   
  93.                     e.printStackTrace();   
  94.                 }   
  95.                 if (newVersionFlag) {   
  96.                     msg.what = DOWNLOAD;   
  97.                     mHandler.sendMessage(msg);   
  98.   
  99.                     try {   
  100.                         Thread.sleep(5000);   
  101.                     } catch (InterruptedException e) {   
  102.                         e.printStackTrace();   
  103.                     }   
  104.                     msg.what = INSTALL;   
  105.                     mHandler.sendMessage(msg);   
  106.                 } else {   
  107.                     msg.what = CHECK_APP;   
  108.                     mHandler.sendMessage(msg);   
  109.                        
  110.                     try {   
  111.                         Thread.sleep(5000);   
  112.                     } catch (InterruptedException e) {   
  113.                         e.printStackTrace();   
  114.                     }   
  115.                     if (checkAppFlag) {   
  116.                         msg.what = INVOKE_APP;   
  117.                         mHandler.sendMessage(msg);   
  118.                     } else {   
  119.                         msg.what = DOWNLOAD_AGAIN;   
  120.                         mHandler.sendMessage(msg);   
  121.                            
  122.                         try {   
  123.                             Thread.sleep(5000);   
  124.                         } catch (InterruptedException e) {   
  125.                             e.printStackTrace();   
  126.                         }   
  127.                         msg.what = INSTALL_AGAIN;   
  128.                         mHandler.sendMessage(msg);   
  129.                     }   
  130.                 }   
  131.             }   
  132.         }).start();   
  133.            
  134.     }   
  135.        
  136.     private Handler mHandler = new Handler() {     
  137.         public void handleMessage(Message msg) {     
  138.             switch(msg.what){     
  139.   
  140.             case CHECK_NEW_VERSION:     
  141.                 if(!Thread.currentThread().isInterrupted()){//当前线程正在运行    
  142.                     Toast.makeText(Main.this"检查更新", Toast.LENGTH_SHORT).show();   
  143.                     newVersionFlag = checkNewVersion();   
  144.                     progressBar.setProgress(30);   
  145.                 }     
  146.                 break;   
  147.             case DOWNLOAD:     
  148.                 if(!Thread.currentThread().isInterrupted()){//当前线程正在运行     
  149.                     Toast.makeText(Main.this"下载更新", Toast.LENGTH_SHORT).show();   
  150.                     downloadAPK(apkUrl);   
  151.                     progressBar.setProgress(60);   
  152.                 }     
  153.                 break;   
  154.             case INSTALL:     
  155.                 if(!Thread.currentThread().isInterrupted()){//当前线程正在运行     
  156.                     Toast.makeText(Main.this"安装更新", Toast.LENGTH_SHORT).show();   
  157.                     killProcess();   
  158.                     progressBar.setProgress(100);   
  159.                     installAPK();   
  160.                     finish();   
  161.                 }     
  162.                 break;   
  163.             case CHECK_APP:     
  164.                 if(!Thread.currentThread().isInterrupted()){//当前线程正在运行     
  165.                     Toast.makeText(Main.this"检查应用", Toast.LENGTH_SHORT).show();   
  166. //                  checkAppFlag = checkApp();   
  167.                     progressBar.setProgress(60);   
  168.                 }     
  169.                 break;   
  170.             case INVOKE_APP:     
  171.                 if(!Thread.currentThread().isInterrupted()){//当前线程正在运行     
  172.                     Toast.makeText(Main.this"程序启动", Toast.LENGTH_SHORT).show();   
  173.                     progressBar.setProgress(100);   
  174.                     invokeAPK();   
  175.                     finish();   
  176.                 }     
  177.                 break;   
  178.             case DOWNLOAD_AGAIN:     
  179.                 if(!Thread.currentThread().isInterrupted()){//当前线程正在运行   
  180.                     Toast.makeText(Main.this"下载更新", Toast.LENGTH_SHORT).show();   
  181.                     progressBar.setProgress(80);   
  182.                     downloadAPK(apkUrl);   
  183.                        
  184.                 }     
  185.                 break;   
  186.             case INSTALL_AGAIN:     
  187.                 if(!Thread.currentThread().isInterrupted()){//当前线程正在运行     
  188.                     Toast.makeText(Main.this"安装更新", Toast.LENGTH_SHORT).show();   
  189.                     progressBar.setProgress(100);   
  190.                     installAPK();   
  191.                     finish();   
  192.                 }     
  193.                 break;      
  194.                
  195.             default:   
  196.                 progressBar.setVisibility(View.GONE);     
  197.                 Thread.currentThread().interrupt();//中断当前线程.     
  198.                 break;   
  199.             }     
  200.             super.handleMessage(msg);     
  201.         }     
  202.     };   
  203.   
  204.     private boolean checkNewVersion() {   
  205.         try {   
  206.             URL url=new URL(AppConfig.SERVLET_URL);   
  207.             SAXParserFactory factory=SAXParserFactory.newInstance();   
  208.             factory.setNamespaceAware(true);   
  209.             factory.setValidating(false);   
  210.             SAXParser parser=factory.newSAXParser();   
  211.             InputStream is = url.openStream();   
  212.             parser.parse(is, new DefaultHandler(){   
  213.                 private String cur="";   
  214.                 private int step;   
  215.                    
  216.                 @Override  
  217.                 public void startDocument() throws SAXException {   
  218.                     step = 0;   
  219.                 }   
  220.                    
  221.                 @Override  
  222.                 public void startElement(String uri, String localName,   
  223.                         String qName, Attributes attributes)   
  224.                         throws SAXException {   
  225.                     cur = localName;   
  226.                 }   
  227.                    
  228.                 @Override  
  229.                 public void characters(char[] ch, int start, int length)   
  230.                         throws SAXException {   
  231.                     String str = new String(ch, start, length).trim();   
  232.                     if (str == null || str.equals(""))   
  233.                         return;   
  234.                     if (cur.equals("url")) {   
  235.                         apkUrl = str;   
  236.                     }   
  237.                     if (cur.equals("map_version")) {   
  238.                         mapVersion = str;   
  239.                     }   
  240.                 }   
  241.                    
  242.                 @Override  
  243.                 public void endElement(String uri, String localName,   
  244.                         String qName) throws SAXException {   
  245.                     step = step + 1;   
  246.                 }   
  247.                    
  248.                 @Override  
  249.                 public void endDocument() throws SAXException {   
  250.                     super.endDocument();   
  251.                 }   
  252.             });   
  253.         } catch (MalformedURLException e) {   
  254.             e.printStackTrace();   
  255.         } catch (ParserConfigurationException e) {   
  256.             e.printStackTrace();   
  257.         } catch (SAXException e) {   
  258.             e.printStackTrace();   
  259.         } catch (IOException e) {   
  260.             e.printStackTrace();   
  261.         }   
  262.         if (diffVersion(mapVersion))    
  263.             return true;   
  264.         else  
  265.             return false;   
  266.     }   
  267.        
  268.     private boolean diffVersion(String mapVersion) {   
  269.         String lastVersion = mDB.getLastMapVersion();   
  270.         if (lastVersion == null) {   
  271.             mDB.setMapVersion(mapVersion);   
  272.             return true;   
  273.         }   
  274.            
  275.         if (!lastVersion.equals(mapVersion)) {   
  276.             mDB.setMapVersion(mapVersion);   
  277.             return true;   
  278.         }   
  279.         else  
  280.             return false;   
  281.     }   
  282.   
  283.     private void downloadAPK(String apkUrl) {   
  284.         String filePath = "//sdcard//download//" + AppConfig.APKNAME;   
  285.         URL url = null;   
  286.         try {   
  287.             url = new URL(apkUrl);   
  288.             HttpURLConnection con = (HttpURLConnection) url.openConnection();   
  289.             InputStream in = con.getInputStream();   
  290.             File fileOut = new File(filePath);   
  291.             FileOutputStream out = new FileOutputStream(fileOut);   
  292.             byte[] bytes = new byte[1024];   
  293.             int c;   
  294.             while ((c = in.read(bytes)) != -1) {   
  295.                 out.write(bytes, 0, c);   
  296.             }   
  297.             in.close();   
  298.             out.close();   
  299.         } catch (Exception e) {   
  300.             e.printStackTrace();   
  301.         }   
  302.     }   
  303.        
  304.     private void killProcess() {   
  305.         activityMan = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);   
  306.         process = activityMan.getRunningAppProcesses();   
  307.            
  308.         int len = process.size();   
  309.         for(int i = 0;i<len;i++) {   
  310.             if (process.get(i).processName.equals(AppConfig.PKG)) {   
  311.                 android.os.Process.killProcess(process.get(i).pid);   
  312.             }   
  313.         }   
  314.     }   
  315.        
  316.     private void installAPK() {   
  317.         String fileName = getSDPath() +"/download/"+AppConfig.APKNAME;   
  318.         Intent intent = new Intent(Intent.ACTION_VIEW);   
  319.         intent.setDataAndType(Uri.fromFile(new File(fileName)), "application/vnd.android.package-archive");   
  320.         startActivity(intent);   
  321.     }   
  322.        
  323.     private void invokeAPK() {   
  324.         Intent i=new Intent();   
  325.         i.setComponent(new ComponentName(AppConfig.PKG, AppConfig.CLS));   
  326.         startActivity(i);   
  327.     }   
  328.        
  329.     private boolean checkApp() {   
  330.         Intent intent = new Intent(Intent.ACTION_VIEW);     
  331.         intent.setClassName("com.android.settings",    
  332.                 "com.android.settings.InstalledAppDetails");    
  333.         intent.putExtra("com.android.settings.ApplicationPkgName",     
  334.                 AppConfig.APKNAME);     
  335.         List<ResolveInfo> acts = getPackageManager().queryIntentActivities(     
  336.                 intent, 0);     
  337.         if (acts.size() > 0)   
  338.             return true;   
  339.         else  
  340.             return false;   
  341.     }   
  342.   
  343.     private String getSDPath() {   
  344.         File sdDir = null;   
  345.         boolean sdCardExist = Environment.getExternalStorageState().equals(   
  346.                 android.os.Environment.MEDIA_MOUNTED); // determine whether sd card is exist   
  347.         if (sdCardExist) {   
  348.             sdDir = Environment.getExternalStorageDirectory();// get the root directory   
  349.         }   
  350.         return sdDir.toString();   
  351.     }   
  352.        
  353.     @Override  
  354.     public void finish() {   
  355.         super.finish();   
  356.         Thread.currentThread().interrupt();//中断当前线程.     
  357.     }   
  358.        
  359.     @Override  
  360.     protected void onDestroy() {   
  361.         super.onDestroy();   
  362.         try {              
  363.             mDB.close(); // be sure to close   
  364.         } catch (Exception e) {   
  365.         }   
  366.     }   
  367. }  
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.decarta.db.MapVersionTable;

/**
 * @author Tony Shen
 *
 */
public class Main extends Activity {
	
	private MapVersionTable mDB;
	private String mapVersion;
	private String apkUrl;
	
	private List<RunningAppProcessInfo> process;
	private ActivityManager activityMan;
	private ProgressBar progressBar;
	
	private final int CHECK_NEW_VERSION = 1;
	private final int DOWNLOAD = 2;
	private final int INSTALL = 3;
	private final int CHECK_APP = 4;
	private final int INVOKE_APP = 5;
	private final int DOWNLOAD_AGAIN = 6;
	private final int INSTALL_AGAIN = 7;
	
	private boolean newVersionFlag = false;
	private boolean checkAppFlag = false;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mDB = new MapVersionTable(this);
        
        progressBar = (ProgressBar) findViewById(R.id.progressBar);
        
        progressBar.setIndeterminate(false); 
        progressBar.setVisibility(View.VISIBLE);
        
        progressBar.setMax(100);  
        progressBar.setProgress(0);

        checkAppFlag = checkApp();
        
		new Thread(new Runnable() {
			Message msg = new Message();
			public void run() {
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				msg.what = CHECK_NEW_VERSION;
				mHandler.sendMessage(msg);

				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (newVersionFlag) {
					msg.what = DOWNLOAD;
					mHandler.sendMessage(msg);

					try {
						Thread.sleep(5000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					msg.what = INSTALL;
					mHandler.sendMessage(msg);
				} else {
					msg.what = CHECK_APP;
					mHandler.sendMessage(msg);
					
					try {
						Thread.sleep(5000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (checkAppFlag) {
						msg.what = INVOKE_APP;
						mHandler.sendMessage(msg);
					} else {
						msg.what = DOWNLOAD_AGAIN;
						mHandler.sendMessage(msg);
						
						try {
							Thread.sleep(5000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						msg.what = INSTALL_AGAIN;
						mHandler.sendMessage(msg);
					}
				}
			}
		}).start();
        
    }
    
    private Handler mHandler = new Handler() {  
        public void handleMessage(Message msg) {  
            switch(msg.what){  

            case CHECK_NEW_VERSION:  
                if(!Thread.currentThread().isInterrupted()){//当前线程正在运行 
                	Toast.makeText(Main.this, "检查更新", Toast.LENGTH_SHORT).show();
                	newVersionFlag = checkNewVersion();
                	progressBar.setProgress(30);
                }  
                break;
            case DOWNLOAD:  
                if(!Thread.currentThread().isInterrupted()){//当前线程正在运行  
                	Toast.makeText(Main.this, "下载更新", Toast.LENGTH_SHORT).show();
                	downloadAPK(apkUrl);
                	progressBar.setProgress(60);
                }  
                break;
            case INSTALL:  
                if(!Thread.currentThread().isInterrupted()){//当前线程正在运行  
                	Toast.makeText(Main.this, "安装更新", Toast.LENGTH_SHORT).show();
                	killProcess();
                	progressBar.setProgress(100);
                	installAPK();
                	finish();
                }  
                break;
            case CHECK_APP:  
                if(!Thread.currentThread().isInterrupted()){//当前线程正在运行  
                	Toast.makeText(Main.this, "检查应用", Toast.LENGTH_SHORT).show();
//                	checkAppFlag = checkApp();
                	progressBar.setProgress(60);
                }  
                break;
            case INVOKE_APP:  
                if(!Thread.currentThread().isInterrupted()){//当前线程正在运行  
                	Toast.makeText(Main.this, "程序启动", Toast.LENGTH_SHORT).show();
                	progressBar.setProgress(100);
                	invokeAPK();
                	finish();
                }  
                break;
            case DOWNLOAD_AGAIN:  
                if(!Thread.currentThread().isInterrupted()){//当前线程正在运行
                	Toast.makeText(Main.this, "下载更新", Toast.LENGTH_SHORT).show();
                	progressBar.setProgress(80);
                	downloadAPK(apkUrl);
                	
                }  
                break;
            case INSTALL_AGAIN:  
                if(!Thread.currentThread().isInterrupted()){//当前线程正在运行  
                	Toast.makeText(Main.this, "安装更新", Toast.LENGTH_SHORT).show();
                	progressBar.setProgress(100);
                	installAPK();
                	finish();
                }  
                break;   
            
            default:
            	progressBar.setVisibility(View.GONE);  
                Thread.currentThread().interrupt();//中断当前线程.  
                break;
            }  
            super.handleMessage(msg);  
        }  
    };

	private boolean checkNewVersion() {
		try {
			URL url=new URL(AppConfig.SERVLET_URL);
			SAXParserFactory factory=SAXParserFactory.newInstance();
			factory.setNamespaceAware(true);
			factory.setValidating(false);
		    SAXParser parser=factory.newSAXParser();
		    InputStream is = url.openStream();
		    parser.parse(is, new DefaultHandler(){
				private String cur="";
				private int step;
				
				@Override
				public void startDocument() throws SAXException {
					step = 0;
				}
				
				@Override
				public void startElement(String uri, String localName,
						String qName, Attributes attributes)
						throws SAXException {
					cur = localName;
				}
				
				@Override
				public void characters(char[] ch, int start, int length)
						throws SAXException {
					String str = new String(ch, start, length).trim();
					if (str == null || str.equals(""))
						return;
					if (cur.equals("url")) {
						apkUrl = str;
					}
					if (cur.equals("map_version")) {
						mapVersion = str;
					}
				}
				
				@Override
				public void endElement(String uri, String localName,
						String qName) throws SAXException {
					step = step + 1;
				}
				
				@Override
				public void endDocument() throws SAXException {
					super.endDocument();
				}
			});
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		if (diffVersion(mapVersion)) 
			return true;
		else
			return false;
	}
	
	private boolean diffVersion(String mapVersion) {
		String lastVersion = mDB.getLastMapVersion();
		if (lastVersion == null) {
			mDB.setMapVersion(mapVersion);
			return true;
		}
		
		if (!lastVersion.equals(mapVersion)) {
			mDB.setMapVersion(mapVersion);
			return true;
		}
		else
			return false;
	}

	private void downloadAPK(String apkUrl) {
		String filePath = "//sdcard//download//" + AppConfig.APKNAME;
		URL url = null;
		try {
			url = new URL(apkUrl);
			HttpURLConnection con = (HttpURLConnection) url.openConnection();
			InputStream in = con.getInputStream();
			File fileOut = new File(filePath);
			FileOutputStream out = new FileOutputStream(fileOut);
			byte[] bytes = new byte[1024];
			int c;
			while ((c = in.read(bytes)) != -1) {
				out.write(bytes, 0, c);
			}
			in.close();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private void killProcess() {
		activityMan = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
		process = activityMan.getRunningAppProcesses();
		
		int len = process.size();
		for(int i = 0;i<len;i++) {
			if (process.get(i).processName.equals(AppConfig.PKG)) {
				android.os.Process.killProcess(process.get(i).pid);
			}
		}
	}
	
	private void installAPK() {
		String fileName = getSDPath() +"/download/"+AppConfig.APKNAME;
		Intent intent = new Intent(Intent.ACTION_VIEW);
		intent.setDataAndType(Uri.fromFile(new File(fileName)), "application/vnd.android.package-archive");
		startActivity(intent);
	}
	
	private void invokeAPK() {
		Intent i=new Intent();
		i.setComponent(new ComponentName(AppConfig.PKG, AppConfig.CLS));
		startActivity(i);
	}
	
	private boolean checkApp() {
		Intent intent = new Intent(Intent.ACTION_VIEW);  
		intent.setClassName("com.android.settings", 
		        "com.android.settings.InstalledAppDetails"); 
		intent.putExtra("com.android.settings.ApplicationPkgName",  
				AppConfig.APKNAME);  
		List<ResolveInfo> acts = getPackageManager().queryIntentActivities(  
		        intent, 0);  
		if (acts.size() > 0)
			return true;
		else
			return false;
	}

	private String getSDPath() {
		File sdDir = null;
		boolean sdCardExist = Environment.getExternalStorageState().equals(
				android.os.Environment.MEDIA_MOUNTED); // determine whether sd card is exist
		if (sdCardExist) {
			sdDir = Environment.getExternalStorageDirectory();// get the root directory
		}
		return sdDir.toString();
	}
	
	@Override
	public void finish() {
		super.finish();
		Thread.currentThread().interrupt();//中断当前线程.  
	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		try {			
	    	mDB.close(); // be sure to close
		} catch (Exception e) {
		}
	}
}




效果图如下:



 

转自:http://fengzhizi715.iteye.com/blog/797782

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值