MobileSafeNotes Day12

##Day12##
#12.1获取签名信息#
前边将扫描程序实现后,接下来就要实现杀毒的效果,去匹配杀毒,对比金山卫士的病毒查杀,它每次也是扫描,扫描到病毒之后,会以不同颜色作为区分标明出来

怎么实现呢,前面说了android目前用的是黑名单查杀病毒,将E:\itcast\20150721-JAVAME\二期\王松\手机卫士day01\Day01资料\安全卫士\APK资料\com.ijinshan.mguard.1324644228599\assets中的antivirus.db(反病毒程序,抗病毒素)拖到sqlite expert中打开,点击查看datable,首先可以看出它是一个MD5,后边的desc是病毒的描述信息,重点是看前边的MD5,它是病毒的一个特征码,
所以其实它在扫描的时候,是拿到一个程序的特征码,然后MD5一下,转化成相应的特征码,去和数据库antivirus.db里面比较,如果数据库中有的话,它就认为你是一个病毒,没有的话,说明不是病毒,这个就是黑名单查杀

那么怎么去获取程序特征码呢?
程序的特征码其实就是一个程序的唯一标识,这个唯一标识不是我们的包名,我们都知道,上线发布到应用市场上的程序都是打过签名的,这个签名就是应用程序在市场上的唯一标识,它就是拿到签名MD5一下,转化为了特征码,然后进行操作的,所以我们需要获取一下签名信息

获取签名信息步骤:
1.添加标签
	//2.获取安装的程序   参数必须设置标签,才可以获取到(在log中)签名信息(特别长) 否则会报空指针异常
	List<PackageInfo> installedPackages = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
2.获取签名信息
在AntivirusActivity.java中的//7.设置扫描的文字之后添加如下代码:
	//获取应用的签名数组
	Signature[] singnatures = packageInfo.signatures;
	//将获取签名信息转化成字符串 我们的应用程序就一个签名,所以0代表的就是我们的签名
	String singnature = singnatures[0].toCharsString();
	//转化成特征码
	singnature = MD5Utils.digestPassword(singnature);

注意:
为什么参数必须设置标签?
因为获取的时候,只要是flags里面标明的,都是我们在0的时候获取不到的,因为我们所有的里边是不包含这些的,设置这个PackageManager.GET_SIGNATURES标签就表明是要额外的再拿一下它的

为什么是签名数组呢?
在工作的时候,可能会遇到这种情况,但是机率很小,一般在给银行工作的时候会出现这种问题,即一个app是由两家公司来开发的,两家公司每家开发一半,这样做的原因是为了每家公司都不能拿到完整的信息,保证安全性,但两家公司开发就会出现一个问题,两家公司开发完后,一般打包签名的时候,是每家公司都会打包一个签名,注意,它那个签名是按照一定规则去做的,是后期可以整合成一个的,但是在获取签名的时候,有一定机率是获取到两个签名,即它还没有打包成一个签名,两个签名分别代表的是一半应用程序。所以是签名数组

#12.2杀毒操作#

上边我们拿到了MD5特征码,接下来就可以和antivirus.db(病毒数据库)进行匹配了,即我们又要进行一些拷贝数据库,查询数据的操作了,首先找到我们数据库,这里已经准备了一个数据库,找到E:\itcast\20150721-JAVAME\二期\王松\手机卫士day01\Day01资料\安全卫士\上课资料\binddu下的antivirus.db数据库,注意,这个数据库经过特殊处理,加上getkey.apk,t2t.apk这两个所谓的病毒,在这里边加上特征码,以便我们测试
将antivirus.db数据库拷贝到我们项目的assets目录下,紧接着要去拷贝数据库,如下:

1.拷贝数据库
	找到splashactivity.java中的onCreate方法,在这里边有个copyDb();注意,以前在这里拷贝address.db数据库的时候,是把address.db数据库名字写死了,即:
	private void copyDb(){
	File file = new File(getFilesDir(), "address.db");
	以及
		// 2.通过assets管理者打开数据库
			in = assets.open("address.db");
	此时我们又要拷贝antivirus.db数据库,写死了是肯定不行的,
	所以可以这样干,在private void copyDb()方法中,改为
	private void copyDb(String name) {
	File file = new File(getFilesDir(), name);
	以及
	// 2.通过assets管理者打开数据库
			in = assets.open(name);
	然后把onCreate中调用的方法改为:
	copyDb("address.db");
	copyDb("antivirus.db");
	就可以实现拷贝多个数据库了

接下来运行一下应用中的手机杀毒功能,看数据库有没有拷贝成功,找到ddms,打开data/data 找到我们的应用cn.itcast.mobilesafexian02下的files,里面有antivirus.db数据库,这就说明我们拷贝成功了

2.查询数据,参考addressDao.java,创建AntivirusActivity.java如下:
	package cn.itcast.mobilesafexian02.db.dao;

	import java.io.File;
	import android.content.Context;
	import android.database.Cursor;
	import android.database.sqlite.SQLiteDatabase;

		public class AntiVirusDao {
		/**
		 * 查询是否是病毒
	 	* @param context
		 * @param md5
		 * @return
		 */
		//判断应用否是病毒,返回的是boolean值,参数需要上下文,我们比较的时候是比较MD5值,所以还要传入MD5过来
	public static boolean isAntiViruse(Context context,String md5){
		File file = new File(context.getFilesDir(), "antivirus.db");
		//打开数据库
		SQLiteDatabase database = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
		//查询数据库 参数:1:表名 antivirus.db数据库中的datable表  3:查询条件 4.查询参数 
		Cursor cursor = database.query("datable", null, "md5=?", new String[]{md5}, null, null, null, null);
		if (cursor.moveToNext()) {
			//数据库中有查询的数据的话返回true 表明是病毒
		return true;
		}
		return false;
	}
}

注意:我们通过MD5查询条件查询每个MD5,对应的是一条数据,所以这里我们用的是if,如果是查询的是多条记录的话就要用while

查询数据完成后,就要调用这个方法了  

3.调用查询数据库的操作,判断应用是否是病毒  (在AntiVirusActivity.java中//转化成特征码 后边添加如下代码:)
	boolean b = AntiVirusDao.isAntiViruse(getApplicationContext(), singnature);

4.根据返回boolean值设置显示应用的名称文本颜色 对比金山卫士病毒查杀的下拉列表,当是病毒的时候,它的颜色就会不一样
	if (b) {
		//如果是true,表明是病毒 那就将它的值改成红色
		textView.setTextColor(Color.RED);
		list.add(packageInfo.packageName);
	}else{
		textView.setTextColor(Color.BLACK);
	}

此时在运行应用测试一下,发现病毒查杀的下拉列表中没有病毒,为了测试用,我们可以安装两个上边准备好的病毒getkey.apk,t2t.apk,cmd打开dos窗口,输入命令adb -s emulator-5554 install 然后将getkey.apk拖进来,回车,即可将病毒getkey.apk安装到相应的模拟器中,重复上边的步骤,将病毒t2t.apk也安装进去。

安装成功后,再次运行应用的手机杀毒功能,就会在下来列表出现两个红色名称的应用,但问题又来了,发现两个病毒了,还显示扫描完成,很安全,显然是不行的,所以还要改改这个显示







5.创建一个list集合,用来存储病毒应用的包名,并在发现有病毒的时候,添加到集合中
6.在扫描完成后根据list集合的长度,判断是否有病毒,有 提醒并卸载
				if (list.size() > 0) {
						tv_antivirus_name.setText("扫描完成,发现病毒");
						//发现病毒,提醒用户是否卸载
						AlertDialog.Builder builder = new Builder(AntivirusActivity.this);
						builder.setTitle("警告!");
						builder.setIcon(R.drawable.ic_launcher);
						builder.setMessage("发现"+list.size()+"个病毒");
						builder.setPositiveButton("卸载", new DialogInterface.OnClickListener() {
							
							@Override
							public void onClick(DialogInterface dialog, int which) {
								//卸载的操作
								for (int i = 0; i < list.size(); i++) {
									Intent intent = new Intent();
									intent.setAction("android.intent.action.DELETE");
									intent.addCategory("android.intent.category.DEFAULT");
									intent.setData(Uri.parse("package:"+list.get(i)));//content://
									startActivityForResult(intent, 0);	
								}
							}
						});
						builder.setNegativeButton("暂不处理", null);
						builder.show();
					}else{
						//8.设置扫描完成的文字和停止动画操作
						tv_antivirus_name.setText("扫描完成,很安全");
					}
					//停止动画
					iv_antivirus_scanner.clearAnimation();

#12.3缓存清理框架#

1.创建两个fragment,继承v4包下的fragment
	public class CacheFragment extends Fragment {
	}
2.设置布局,并加载
	public class CacheFragment extends Fragment {

		//类似activity中oncreate方法,加载布局
		//参数1:布局文件加载
		//参数2:容器
		//参数3:是否保存状态
		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container,
				Bundle savedInstanceState) {
			//参数1:布局文件
			//参数2:容器
			//参数3:是否挂载,一般false
			return inflater.inflate(R.layout.fragment_cache, container, false);
		}
		
	}
3.将clearcacheactivity,改为继承fragmentActivity
	public class ClearCacheActivity extends FragmentActivity {
4.修改将clearcacheactivity布局
	<!-- 是fragment用来替换的布局控件 -->
    <RelativeLayout 
        android:id="@+id/rl_clearcache_relative"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="200"
        ></RelativeLayout>
	<LinearLayout 
	    android:layout_weight="1"
	    android:layout_width="match_parent"
	    android:layout_height="wrap_content"
	    >
	    <Button 
	        android:layout_width="0dp"
	        android:layout_height="wrap_content"
	        android:layout_weight="1"
	        android:text="缓存清理"
	        android:background="@drawable/selector_contact_button"
	        android:onClick="cache"
	        />
	    <Button 
	        android:layout_width="0dp"
	        android:layout_height="wrap_content"
	        android:layout_weight="1"
	        android:text="SD卡清理"
	        android:background="@drawable/selector_contact_button"
	        android:onClick="sd"
	        />
	</LinearLayout>
5.添加fragment,在oncreate方法中执行
	cacheFragment = new CacheFragment();
	sdFragment = new SDFragment();
	
	//1.获取fragment的管理者
	fragmentManager = getSupportFragmentManager();
	//2.获取事务,保证fragment切换的一致性
	FragmentTransaction beginTransaction = fragmentManager.beginTransaction();
	//3.添加fragment
	//参数1:被替换的控件的id
	//参数2:替换的fragment
	beginTransaction.add(R.id.rl_clearcache_relative, cacheFragment);
	//跟add效果相似

// beginTransaction.replace(arg0, arg1)
beginTransaction.add(R.id.rl_clearcache_relative, sdFragment);
//3.1隐藏fragment
beginTransaction.hide(sdFragment);
beginTransaction.commit();
6.按钮操作
public void cache(View v){
FragmentTransaction beginTransaction = fragmentManager.beginTransaction();
beginTransaction.hide(sdFragment);
beginTransaction.show(cacheFragment);//显示fragment
beginTransaction.commit();
}
public void sd(View v){
FragmentTransaction beginTransaction = fragmentManager.beginTransaction();
beginTransaction.hide(cacheFragment);
beginTransaction.show(sdFragment);//显示fragment
beginTransaction.commit();
}

#12.4进度条的操作#

参考杀毒进度条操作
	/**
	 * 扫描
	 */
	private void scanner() {
		//1.获取包的管理者
		//getActivity() : 获取fragment挂载的activity
		final PackageManager pm = getActivity().getPackageManager();
		tv_antivirus_name.setText("正在初始化64核缓存清理引擎...");
		new Thread(){
			public void run() {
				//5.睡1秒,让程序延时1秒钟扫描
				SystemClock.sleep(1000);
				//获取安装的程序
				List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
				//设置总进度
				pb_antivirus_progressbar.setMax(installedPackages.size());
				//设置当前进度
				int progress = 0;
				for (PackageInfo packageInfo : installedPackages) {
					//5.睡100毫秒,增加进度显示的真实性
					SystemClock.sleep(100);
					//设置进度
					progress++;
					pb_antivirus_progressbar.setProgress(progress);
					//获取应用程序名称
					final String name = packageInfo.applicationInfo.loadLabel(pm).toString();
					//在退出的时候,子线程还在运行,还没被及时处理,所以getActivity()会出现为null的现象
					if (getActivity() != null) {
						getActivity().runOnUiThread(new Runnable() {
							
							@Override
							public void run() {
								tv_antivirus_name.setText("正在扫描:"+name);
							}
						});
					}
				}
				if (getActivity() != null) {
					getActivity().runOnUiThread(new Runnable() {
						
						@Override
						public void run() {
							//隐藏进度条和textivew文本
							tv_antivirus_name.setVisibility(View.GONE);
							pb_antivirus_progressbar.setVisibility(View.GONE);
						}
					});
				}
			};
		}.start();
	}

#12.5获取缓存的操作#

1.反射获取相应的方法
	PackageManager pm = getPackageManager();
	// pm.getPackageSizeInfo("com.itcast.cache", mStatsObserver);

	// 反射操作
	// 获取类加载器,获取相应类的class
	Class<?> loadClass;
	try {
		loadClass = MainActivity.class.getClassLoader().loadClass(
				"android.content.pm.PackageManager");
		// 获取相应的方法
		Method method = loadClass.getDeclaredMethod("getPackageSizeInfo",
				String.class, IPackageStatsObserver.class);
		// 执行相应的方法
		method.invoke(pm, "com.itcast.cache", mStatsObserver);
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
2.获取缓存大小
	IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {

		@Override
		public void onGetStatsCompleted(PackageStats pStats, boolean succeeded)
				throws RemoteException {
			long cacheSize = pStats.cacheSize;
			long codeSize = pStats.codeSize;
			long dataSize = pStats.dataSize;
			String cache = Formatter.formatFileSize(getApplicationContext(),
					cacheSize);
			String code = Formatter.formatFileSize(getApplicationContext(),
					codeSize);
			String data = Formatter.formatFileSize(getApplicationContext(),
					dataSize);
			System.out.println("cachesize:" + cache + "     datasize:" + data
					+ "   codesize:" + code);
		}
	};

#12.6显示缓存#

1.将获取缓存的操作移植到手机卫士中,移植到for循环中,表示去获取每个应用的缓存信息
			  try {
					Class<?> loadClass = getActivity().getClassLoader().loadClass(
							"android.content.pm.PackageManager");
					// 获取相应的方法
					Method method = loadClass.getDeclaredMethod("getPackageSizeInfo",
							String.class, IPackageStatsObserver.class);
					// 执行相应的方法
					method.invoke(pm, packageInfo.packageName, mStatsObserver);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
2.在获取应用缓存的方法中,判断是否有缓存,有就添加到list集合
		if (cacheSize > 0) {
			//将应用保存到集合中
			list.add(new CacheInfo(pStats.packageName,cacheSize));
		}
3.通过listview展示出来
		private class Myadapter  extends BaseAdapter{

		@Override
		public int getCount() {
			return list.size();
		}
		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			
			View view;
			ViewHolder viewHolder;
			if (convertView == null) {
				view = View.inflate(getActivity(), R.layout.item_cache, null);
				viewHolder = new ViewHolder();
				viewHolder.iv_itemcache_icon = (ImageView) view.findViewById(R.id.iv_itemcache_icon);
				viewHolder.tv_itemcache_name = (TextView) view.findViewById(R.id.tv_itemcache_name);
				viewHolder.tv_itemcache_cachesize = (TextView) view.findViewById(R.id.tv_itemcache_cachesize);
				view.setTag(viewHolder);
			}else{
				view = convertView;
				viewHolder = (ViewHolder) view.getTag();
			}
			CacheInfo cacheInfo = list.get(position);
			String size = Formatter.formatFileSize(getActivity(), cacheInfo.getCachesize());
			//显示缓存大小
			viewHolder.tv_itemcache_cachesize.setText(size);
			try {
				ApplicationInfo applicationInfo = pm.getApplicationInfo(cacheInfo.getPackagename(), 0);
				Drawable icon = applicationInfo.loadIcon(pm);
				String name = applicationInfo.loadLabel(pm).toString();
				//设置显示的应用的图标和名称
				viewHolder.tv_itemcache_name.setText(name);
				viewHolder.iv_itemcache_icon.setImageDrawable(icon);
			} catch (NameNotFoundException e) {
				e.printStackTrace();
			}
			
			return view;
		}
		

		@Override
		public Object getItem(int position) {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public long getItemId(int position) {
			// TODO Auto-generated method stub
			return 0;
		}
		
	}

#12.7清理缓存#

1.进入详情界面
	lv_cache_caches.setOnItemClickListener(new OnItemClickListener() {

		@Override
		public void onItemClick(AdapterView<?> parent, View view,
				int position, long id) {
			// 跳转到详情页面
			Intent intent = new Intent();
			intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
			intent.setData(Uri.parse("package:"
					+ list.get(position).getPackagename()));
			startActivity(intent);
		}
	});
2.全部清理系统中应用程序缓存
					if (list.size() > 0) {
							btn_cache_clear.setVisibility(View.VISIBLE);
							btn_cache_clear
									.setOnClickListener(new OnClickListener() {

										@Override
										public void onClick(View v) {
											// 清理全部缓存
											// freeStorageAndNotify
											try {
												Class<?> loadClass = getActivity()
														.getClassLoader()
														.loadClass(
																"android.content.pm.PackageManager");
												Method method = loadClass
														.getDeclaredMethod(
																"freeStorageAndNotify",
																Long.TYPE,
																IPackageDataObserver.class);
												method.invoke(
														pm,
														Long.MAX_VALUE,
														new MyIPackageDataObserver());
											} catch (Exception e) {
												// TODO Auto-generated catch
												// block
												e.printStackTrace();
											}
											//更新界面
											list.clear();
											myadapter.notifyDataSetChanged();
										}
										
									});
						}

			private class MyIPackageDataObserver extends IPackageDataObserver.Stub {

				@Override
				public void onRemoveCompleted(String packageName, boolean succeeded)
						throws RemoteException {
					// 缓存清理完之后会调用的方法
				}
			}

			权限: <uses-permission android:name="android.permission.CLEAR_APP_CACHE"/>

#12.8全局bug收集#

1.创建一个application
	//应用启动的时候,先调用就是application,才去调用的主界面
	public class MyApplication extends Application {
	
		
		@Override
		public void onCreate() {
			super.onCreate();
			System.out.println("application启动了...");
			Thread.currentThread().setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
		}
		private class MyUncaughtExceptionHandler implements UncaughtExceptionHandler{
			//有未捕获的异常时会调用的方法,临终遗言
			//Throwable : Exception和Error的父类
			@Override
			public void uncaughtException(Thread thread, Throwable ex) {
				System.out.println("哥捕获了异常!!!!");
				try {
					//将出现的异常保存到本地文件中
					ex.printStackTrace(new PrintStream(new File("/mnt/sdcard/log.txt")));
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				}
				//杀死程序,自杀,android.os.Process.myPid():就会得到自己的pid
				android.os.Process.killProcess(android.os.Process.myPid());
			}
			
		}
	}
2.将清单文件中的application的name设置为我们的application
	<application
    	android:name=".MyApplication"

#12.9代码混淆#

sdk\tools\proguard\proguard-android.txt
拷贝到   工程的根目录下,是个清单文件一个目录
修改project.properties路径
	proguard.config=proguard-android.txt:proguard-project.txt

四大组件和自定义控件,以及已经混淆过的第三方sdk不能混淆了

#12.10广告#

1.厂商直接找你,第三方广告平台   1000次  1毛钱,app都是跟用户基数
2.第三方的广告平台

id : b55327eed7258e53

秘钥: b486156834be6e52
  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园信息化系统解决方案旨在通过先进的信息技术,实现教育的全方位创新和优质资源的普及共享。该方案依据国家和地方政策背景,如教育部《教育信息化“十三五”规划》和《教育信息化十年发展规划》,以信息技术的革命性影响为指导,推进教育信息化建设,实现教育思想和方法的创新。 技术发展为智慧校园建设提供了强有力的支撑。方案涵盖了互连互通、优质资源共享、宽带网络、移动APP、电子书包、电子教学白板、3D打印、VR虚拟教学等技术应用,以及大数据和云计算技术,提升了教学数据记录和分析水平。此外,教育资源公共服务平台、教育管理公共服务平台等平台建设,进一步提高了教学、管控的效率。 智慧校园系统由智慧教学、智慧管控和智慧办公三大部分组成,各自具有丰富的应用场景。智慧教学包括微课、公开课、精品课等教学资源的整合和共享,支持在线编辑、录播资源、教学分析等功能。智慧管控则通过平安校园、可视对讲、紧急求助、视频监控等手段,保障校园安全。智慧办公则利用远程视讯、无纸化会议、数字会议等技术,提高行政效率和会议质量。 教育录播系统作为智慧校园的重要组成部分,提供了一套满足学校和教育局需求的解决方案。它包括标准课室、微格课室、精品课室等,通过自动五机位方案、高保真音频采集、一键式录课等功能,实现了优质教学资源的录制和共享。此外,录播系统还包括互动教学、录播班班通、教育中控、校园广播等应用,促进了教育资源的均衡化发展。 智慧办公的另一重点是无纸化会议和数字会议系统的建设,它们通过高效的文件管理、会议文件保密处理、本地会议的音频传输和摄像跟踪等功能,实现了会议的高效化和集中管控。这些系统不仅提高了会议的效率和质量,还通过一键管控、无线管控等设计,简化了操作流程,使得会议更加便捷和环保。 总之,智慧校园信息化系统解决方案通过整合先进的信息技术和教学资源,不仅提升了教育质量和管理效率,还为实现教育均衡化和资源共享提供了有力支持,推动了教育现代化的进程。
智慧校园信息化系统解决方案旨在通过先进的信息技术,实现教育的全方位创新和优质资源的普及共享。该方案依据国家和地方政策背景,如教育部《教育信息化“十三五”规划》和《教育信息化十年发展规划》,以信息技术的革命性影响为指导,推进教育信息化建设,实现教育思想和方法的创新。 技术发展为智慧校园建设提供了强有力的支撑。方案涵盖了互连互通、优质资源共享、宽带网络、移动APP、电子书包、电子教学白板、3D打印、VR虚拟教学等技术应用,以及大数据和云计算技术,提升了教学数据记录和分析水平。此外,教育资源公共服务平台、教育管理公共服务平台等平台建设,进一步提高了教学、管控的效率。 智慧校园系统由智慧教学、智慧管控和智慧办公三大部分组成,各自具有丰富的应用场景。智慧教学包括微课、公开课、精品课等教学资源的整合和共享,支持在线编辑、录播资源、教学分析等功能。智慧管控则通过平安校园、可视对讲、紧急求助、视频监控等手段,保障校园安全。智慧办公则利用远程视讯、无纸化会议、数字会议等技术,提高行政效率和会议质量。 教育录播系统作为智慧校园的重要组成部分,提供了一套满足学校和教育局需求的解决方案。它包括标准课室、微格课室、精品课室等,通过自动五机位方案、高保真音频采集、一键式录课等功能,实现了优质教学资源的录制和共享。此外,录播系统还包括互动教学、录播班班通、教育中控、校园广播等应用,促进了教育资源的均衡化发展。 智慧办公的另一重点是无纸化会议和数字会议系统的建设,它们通过高效的文件管理、会议文件保密处理、本地会议的音频传输和摄像跟踪等功能,实现了会议的高效化和集中管控。这些系统不仅提高了会议的效率和质量,还通过一键管控、无线管控等设计,简化了操作流程,使得会议更加便捷和环保。 总之,智慧校园信息化系统解决方案通过整合先进的信息技术和教学资源,不仅提升了教育质量和管理效率,还为实现教育均衡化和资源共享提供了有力支持,推动了教育现代化的进程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值