专栏首页老欧说安卓Android开发笔记(一百零九)利用网盘实现云存储

Android开发笔记(一百零九)利用网盘实现云存储

网盘存储

个人开发者往往没有自己的后台服务器,但同时又想获取app的运行信息,这就要借助于第三方的网络存储(也叫网盘、云盘、微盘等等)。通过让app自动在网盘上存取文件,可以间接实现后台服务器的存储功能,同时开发者也能及时找到app的用户信息。 曾几何时,各大公司纷纷推出免费的个人网盘服务,还开放了文件管理api给开发者调用,一时间涌现了网盘提供商的八大金刚:百度网盘、阿里云、华为网盘、腾讯微云、新浪微盘、360云盘、金山快盘、115网盘。可是好景不长,出于盈利、监管等等因素,各大网盘开放平台要么停止免费服务、推出收费服务,要么停止个人服务、推出企业服务,要么保留老用户、不再接受新用户,总之现在开发者已不能无偿使用网盘的sdk集成功能了。要想实现app的云存储,得用点真金白银了。

百度网盘

百度的个人网盘又称pcs(Personal Cloud Storage),百度网盘的存储服务本身是免费的,只是对开发者来说,已经不再接受新应用接入pcs。之前已申请的老应用则不受影响(大约是2015年之前的老应用),所以博主寻寻觅觅,总算找到了一个曲线救国道路,让新开发者也能在app中使用网盘服务。 具体方法如下: 1、先注册成为百度用户,并开通个人网盘服务,正常的话访问http://pan.baidu.com/就能看到网盘的管理页面; 2、把代码中的mbApiKey和mbRootPath替换为wp2pcs应用的信息,注意存储路径只能是wp2pcs,因为该应用在申请时就指定了这个默认路径,如果使用其他路径会报没权限;

    private final static String mbApiKey = "CuOLkaVfoz1zGsqFKDgfvI0h";
    private final static String mbRootPath = "/apps/wp2pcs";

3、app首次运行进行登录时,要把你的帐号授权给wp2pcs应用,以便创建目录、读写文件等操作;下面是百度pcs的登录授权页面截图

百度网盘的代码调用流程是:先调用BaiduOAuth对象的startOAuth鉴权方法(logout是注销方法),从返回包中获取AccessToken,后续的文件读写操作会用到这个AccessToken。接着实例化一个AccessToken对象,然后对该对象设置AccessToken,再根据业务要求分别调用相应的方法,常用方法说明如下: quota : 获取磁盘空间。返回PCSQuotaResponse对象。 list : 获取文件/目录列表。返回PCSListInfoResponse对象。 makeDir : 创建目录。返回PCSFileInfoResponse对象。 deleteFiles : 删除文件。返回PCSSimplefiedResponse对象。 downloadFileFromStream : 下载文件。返回PCSSimplefiedResponse对象。 uploadFile : 上传文件。返回PCSFileInfoResponse对象。 rename : 文件改名。返回PCSFileFromToResponse对象。 下面是app操作百度pcs的页面截图

下面是使用百度网盘的代码示例:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.baidu.oauth.BaiduOAuth;
import com.baidu.oauth.BaiduOAuth.BaiduOAuthResponse;
import com.baidu.pcs.BaiduPCSActionInfo;
import com.baidu.pcs.BaiduPCSActionInfo.PCSCommonFileInfo;
import com.baidu.pcs.BaiduPCSActionInfo.PCSFileFromToInfo;
import com.baidu.pcs.BaiduPCSActionInfo.PCSFileFromToResponse;
import com.baidu.pcs.BaiduPCSActionInfo.PCSFileInfoResponse;
import com.baidu.pcs.BaiduPCSActionInfo.PCSListInfoResponse;
import com.baidu.pcs.BaiduPCSActionInfo.PCSQuotaResponse;
import com.baidu.pcs.BaiduPCSActionInfo.PCSSimplefiedResponse;
import com.baidu.pcs.BaiduPCSClient;
import com.baidu.pcs.BaiduPCSStatusListener;

import com.example.exmnetdisk.R;
import com.example.exmnetdisk.pcs.ConfirmDialogFragment.ConfirmCallbacks;
import com.example.exmnetdisk.pcs.InputDialogFragment.InputCallbacks;

public class PCSActivity extends Activity implements OnClickListener
	,OnItemClickListener,OnItemLongClickListener,ConfirmCallbacks,InputCallbacks {
	
    private static final String TAG = "PCSActivity";
    private TextView tv_desc;
    private TextView tv_path;
	private ListView lv_content;
    private Button btn_login;
    private Button btn_logout;
    private Button btn_upload;
    private Button btn_rename;
    private Button btn_back;
    private Button btn_create;

    private String mbOauth = null;
    private final static String mbApiKey = "CuOLkaVfoz1zGsqFKDgfvI0h";
    private final static String mbRootPath = "/apps/wp2pcs";
    private String mCurrentPath = mbRootPath;
    private String mLocalPath;
    private Handler mbUiThreadHandler = new Handler();
    private final static String POSITION = "position";
    private final static String OPERATION = "operation";
    private final static String FILEPATH = "file_path";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_pcs);

		tv_desc = (TextView)findViewById(R.id.tv_desc);
		tv_path = (TextView)findViewById(R.id.tv_path);
		lv_content = (ListView)findViewById(R.id.lv_content);
		lv_content.setOnItemClickListener(this);
		lv_content.setOnItemLongClickListener(this);
		btn_login = (Button)findViewById(R.id.btn_login);
		btn_logout = (Button)findViewById(R.id.btn_logout);
		btn_upload = (Button)findViewById(R.id.btn_upload);
		btn_rename = (Button)findViewById(R.id.btn_rename);
		btn_back = (Button)findViewById(R.id.btn_back);
		btn_create = (Button)findViewById(R.id.btn_create);
        btn_login.setOnClickListener(this);
        btn_logout.setOnClickListener(this);
        btn_upload.setOnClickListener(this);
        btn_rename.setOnClickListener(this);
        btn_back.setOnClickListener(this);
        btn_create.setOnClickListener(this);
        mLocalPath = Environment.getExternalStorageDirectory().toString()+"/Download";
	}

	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.btn_login) {
			login();
		} else if (v.getId() == R.id.btn_logout) {
			logout();
		} else if (v.getId() == R.id.btn_upload) {
			upload("2.txt");
		} else if (v.getId() == R.id.btn_rename) {
			rename();
		} else if (v.getId() == R.id.btn_back) {
	    	if (null != mbOauth) {
	    		if (mCurrentPath.equals(mbRootPath)) {
	    			showTip("当前已是根目录");
	    		} else {
					mCurrentPath = mCurrentPath.substring(0, mCurrentPath.lastIndexOf("/"));
					getList(mCurrentPath);
	    		}
	    	}
		} else if (v.getId() == R.id.btn_create) {
			InputDialogFragment dialog = InputDialogFragment.newInstance("请输入目录名称");
			String fragTag = getResources().getString(R.string.app_name);
			dialog.show(getFragmentManager(), fragTag);
		}
	}
	
	private void showTip(String message) {
		Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
	}
	
	//登录
	private void login() {
		BaiduOAuth oauthClient = new BaiduOAuth();
		oauthClient.startOAuth(this, mbApiKey, new String[]{"basic", "netdisk"}, new BaiduOAuth.OAuthListener() {
			@Override
			public void onException(String msg) {
    			showTip("Login failed " + msg);
			}
			@Override
			public void onComplete(BaiduOAuthResponse response) {
				if (null != response) {
					mbOauth = response.getAccessToken();
	    			showTip("Token: " + mbOauth + "    User name:" + response.getUserName());
					tv_desc.setText("当前登录用户是"+response.getUserName()+",token为"+mbOauth);
					getQuota();
					getList(mbRootPath);
				}
			}
			@Override
			public void onCancel() {
    			showTip("Login cancelled");
			}
		});
	}
	
	//获取磁盘空间
	private void getQuota() {
    	if (null != mbOauth) {
    		Thread workThread = new Thread(new Runnable() {
				@Override
				public void run() {
		    		BaiduPCSClient api = new BaiduPCSClient();
		    		api.setAccessToken(mbOauth);
		    		final PCSQuotaResponse info = api.quota();
		    		mbUiThreadHandler.post(new Runnable() {
		    			public void run() {
				    		if (null != info) {
				    			if (0 == info.status.errorCode) {
				        			showTip("Quota :" + info.total + "  used: " + info.used);
				    				tv_desc.setText(tv_desc.getText()+"\n网盘总容量是"+info.total/1024/1024+"M,已经使用"+info.used/1024/1024+"M.");
				    			} else {
				        			showTip("Quota failed: " + info.status.errorCode + "  " + info.status.message);
				    			}
				    		}
		    			}
		    		});
				}
			});
    		workThread.start();
    	}
	}
	
	//注销
	private void logout() {
    	if (null != mbOauth) {
    		Thread workThread = new Thread(new Runnable() {
				@Override
				public void run() {
		    		BaiduOAuth oauth = new BaiduOAuth();
		    		final boolean ret = oauth.logout(mbOauth);
		    		mbUiThreadHandler.post(new Runnable() {
		    			@Override
						public void run() {
		        			showTip("Logout " + ret);
	    				}
		    		});	
				}
    		});
    		workThread.start();
    	}
	}

	private List<PCSCommonFileInfo> mFileList = new ArrayList<PCSCommonFileInfo>();
	//获取文件/目录列表
    private void getList(final String path) {
    	if (null != mbOauth) {
    		Thread workThread = new Thread(new Runnable() {
    			@Override
				public void run() {
		    		BaiduPCSClient api = new BaiduPCSClient();
		    		api.setAccessToken(mbOauth);
		    		final PCSListInfoResponse ret = api.list(path, "name", "asc");
		    		mbUiThreadHandler.post(new Runnable() {
		    			public void run() {
		    				if (ret.status.errorCode == 0) {
		    					mFileList.clear();
		    					mFileList = ret.list;
		    					FileListAdapter adapter = new FileListAdapter(getApplicationContext(), mFileList);
		    					lv_content.setAdapter(adapter);
		    					tv_path.setText("当前目录是"+mCurrentPath);
		    				} else {
		    					tv_path.setText("获取目录"+mCurrentPath+"失败,原因是"+ret.status.message);
		    				}
		    			}
		    		});
				}
			});
    		workThread.start();
    	}
    }

	@Override
	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
		PCSCommonFileInfo file = mFileList.get(position);
		if (file.isDir == true) {
			mCurrentPath = file.path;
			showTip("您点击了目录"+mCurrentPath);
			getList(mCurrentPath);
		} else {
			ConfirmDialogFragment fsf = ConfirmDialogFragment.newInstance(
					R.drawable.ic_launcher, "下载确认", "是否下载文件"+file.path+"?");
			Map<String, Object> map_param = new HashMap<String, Object>();
			map_param.put(OPERATION, "download");
			map_param.put(POSITION, position);
			map_param.put(FILEPATH, file.path);
			fsf.setParam(map_param);
			fsf.show(getFragmentManager(), "");
		}
	}

	@Override
	public boolean onItemLongClick(AdapterView<?> parent, View view,
			int position, long id) {
		PCSCommonFileInfo file = mFileList.get(position);
		String desc = String.format("是否删除%s%s?", file.isDir?"目录":"文件", file.path);
		ConfirmDialogFragment fsf = ConfirmDialogFragment.newInstance(
				R.drawable.ic_launcher, "删除确认", desc);
		Map<String, Object> map_param = new HashMap<String, Object>();
		map_param.put(OPERATION, "delete");
		map_param.put(POSITION, position);
		map_param.put(FILEPATH, file.path);
		fsf.setParam(map_param);
		fsf.show(getFragmentManager(), "");
		return true;
	}

	@Override
	public void onConfirmSelect(Map<String, Object> map_param) {
		String operation = (String) map_param.get(OPERATION);
		if (operation.equals("delete")) {
			int position = (Integer) map_param.get(POSITION);
			PCSCommonFileInfo file = mFileList.get(position);
			deletePCSFile(file.path);
		} else if (operation.equals("download")) {
			String file = (String) map_param.get(FILEPATH);
			download(file);
		}
	}

	@Override
	public void onInput(String dir) {
		createDir(dir);
	}

	//创建目录
	private void createDir(final String dir) {
    	if (null != mbOauth) {
    		Thread workThread = new Thread(new Runnable() {
    			@Override
				public void run() {
		    		BaiduPCSClient api = new BaiduPCSClient();
		    		api.setAccessToken(mbOauth);
		    		String path = mCurrentPath + "/" + dir;
		    		final PCSFileInfoResponse ret = api.makeDir(path);
		    		mbUiThreadHandler.post(new Runnable() {
		    			public void run() {
		    				showTip("Mkdir:  " + ret.status.errorCode + "   " + ret.status.message);
		    				getList(mCurrentPath);
		    			}
		    		});	
				}
			});
    		workThread.start();
    	}
	}

	//删除文件。注意不能用deleteFile这个函数名,因为deleteFile在ContextWrapper类中已经定义
	private void deletePCSFile(final String file_path) {
    	if (null != mbOauth) {
    		Thread workThread = new Thread(new Runnable() {
    			@Override
				public void run() {
		    		BaiduPCSClient api = new BaiduPCSClient();
		    		api.setAccessToken(mbOauth);
		    		List<String> files = new ArrayList<String>();
		    		files.add(file_path);
		    		final PCSSimplefiedResponse ret = api.deleteFiles(files);
		    		mbUiThreadHandler.post(new Runnable() {
		    			public void run() {
		    				showTip("Delete files:  " + ret.errorCode + "  " + ret.message);
		    				getList(mCurrentPath);
		    			}
		    		});	
				}
			});
    		workThread.start();
    	}
	}
	
	private BaiduPCSStatusListener mStatusListener = new BaiduPCSStatusListener() {
		@Override
		public void onProgress(long bytes, long total) {
			final long bs = bytes;
			final long tl = total;
    		mbUiThreadHandler.post(new Runnable() {
    			public void run() {
    				showTip("total: " + tl + "    loaded:" + bs);
    			}
    		});		
		}
		
		@Override
		public long progressInterval() {
			return 500;
		}
	};

	//下载文件
    private void download(final String file) {
    	if (null != mbOauth) {
    		final String local_file = String.format("%s/%s", mLocalPath, file.substring(1+file.lastIndexOf("/")));
    		Thread workThread = new Thread(new Runnable() {
    			@Override
				public void run() {
		    		BaiduPCSClient api = new BaiduPCSClient();
		    		api.setAccessToken(mbOauth);
		    		final PCSSimplefiedResponse ret = api.downloadFileFromStream(file, local_file, mStatusListener);
		    		mbUiThreadHandler.post(new Runnable() {
		    			public void run() {
		    				Log.d(TAG, "Download files:  " + ret.errorCode + "   " + ret.message);
		    				showTip("download:  " + ret.errorCode + "   " + ret.message);
		    			}
		    		});	
				}
			});
    		workThread.start();
    	}
    }

    // 上传文件
    private void upload(String file) {
    	if (null != mbOauth) {
    		final String local_file = String.format("%s/%s", mLocalPath, file);
    		final String remote_file = String.format("%s/%s", mCurrentPath, file);
    		Thread workThread = new Thread(new Runnable() {
    			@Override
				public void run() {
		    		BaiduPCSClient api = new BaiduPCSClient();
		    		api.setAccessToken(mbOauth);
		    		final PCSFileInfoResponse response = api.uploadFile(local_file, remote_file, mStatusListener);
		    		mbUiThreadHandler.post(new Runnable() {
		    			public void run() {
		    				showTip("upload:  " + response.status.errorCode + "  " + response.status.message + "  " + response.commonFileInfo.blockList);
		    				getList(mCurrentPath);
		    			}
		    		});	
				}
			});
    		workThread.start();
    	}
    }

    //文件改名
    private void rename() {
    	if (null != mbOauth) {
    		Thread workThread = new Thread(new Runnable() {
    			@Override
				public void run() {
		    		BaiduPCSClient api = new BaiduPCSClient();
		    		api.setAccessToken(mbOauth);
		    		List<PCSFileFromToInfo> info = new ArrayList<PCSFileFromToInfo>();
		    		PCSFileFromToInfo data1 = new PCSFileFromToInfo();
		    		data1.from = mCurrentPath + "/2.txt";
		    		data1.to = "1.txt";
		    		info.add(data1);
		    		Log.d(TAG, "from="+data1.from+",to="+data1.to);
		    		final PCSFileFromToResponse ret = api.rename(info);
		    		mbUiThreadHandler.post(new Runnable() {
		    			public void run() {
		    				showTip("Rename:  " + ret.status.errorCode + "    " + ret.status.message);
		    				getList(mCurrentPath);
		    			}
		    		});	
				}
			});
    		workThread.start();
    	}
    }

}

阿里云

阿里云的存储服务又称oss(Online Storage Service),现只提供收费服务,最经济的是40G空间半年5元,或一年9元,个人开发者的app有一定用户量时可以考虑购买。 阿里云的sdk的集成比较简单,官方也有提供demo,这里就不再赘述了。下面是阿里云对文件存储进行基本操作的代码示例:

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.alibaba.sdk.android.oss.ClientConfiguration;
import com.alibaba.sdk.android.oss.OSS;
import com.alibaba.sdk.android.oss.OSSClient;
import com.alibaba.sdk.android.oss.common.OSSLog;
import com.alibaba.sdk.android.oss.common.auth.OSSCredentialProvider;
import com.alibaba.sdk.android.oss.common.auth.OSSPlainTextAKSKCredentialProvider;

import com.example.exmnetdisk.R;

public class AliyunActivity extends Activity {
    private OSS oss;

    // 以下字段为注册账号并开通oss服务后由阿里云自动分配
    private static final String endpoint = "http://oss-cn-shanghai.aliyuncs.com";
    private static final String accessKeyId = "<accessKeyId>";
    private static final String accessKeySecret = "<accessKeySecret>";
    private static final String testBucket = "<testBucket>";
    
    // 需付费购买存储空间,才能正常使用上传、下载、列表等功能
    private static final String uploadFilePath = "<upload_file_path>"; //上传的目录
    private static final String uploadObject = "sampleObject"; //上传的文件
    private static final String downloadObject = "sampleObject"; //下载的文件

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aliyun);

        OSSCredentialProvider credentialProvider = new OSSPlainTextAKSKCredentialProvider(accessKeyId, accessKeySecret);

        ClientConfiguration conf = new ClientConfiguration();
        conf.setConnectionTimeout(15 * 1000); // 连接超时,默认15秒
        conf.setSocketTimeout(15 * 1000); // socket超时,默认15秒
        conf.setMaxConcurrentRequest(5); // 最大并发请求书,默认5个
        conf.setMaxErrorRetry(2); // 失败后最大重试次数,默认2次
        OSSLog.enableLog();
        oss = new OSSClient(getApplicationContext(), endpoint, credentialProvider, conf);

        // 上传
        Button upload = (Button) findViewById(R.id.upload);
        upload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        new PutObjectSamples(oss, testBucket, uploadObject, uploadFilePath).asyncPutObjectFromLocalFile();
                    }
                }).start();
            }
        });

        // 下载
        Button download = (Button) findViewById(R.id.download);
        download.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        new GetObjectSamples(oss, testBucket, downloadObject).asyncGetObjectSample();
                    }
                }).start();
            }
        });

        // 罗列
        Button list = (Button) findViewById(R.id.list);
        list.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        new ListObjectsSamples(oss, testBucket).asyncListObjectsWithPrefix();
                    }
                }).start();
            }
        });

        // 管理
        Button manage = (Button) findViewById(R.id.manage);
        manage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        new ManageObjectSamples(oss, testBucket, uploadObject).headObject();
                    }
                }).start();
            }
        });

        // 分块上传
        Button multipart = (Button) findViewById(R.id.multipart);
        multipart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            new MultipartUploadSamples(oss, testBucket, uploadObject, uploadFilePath).multipartUpload();
                        }
                        catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        });


        // 断点续传
        Button resumable = (Button) findViewById(R.id.resumable);
        resumable.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        new ResuambleUploadSamples(oss, testBucket, uploadObject, uploadFilePath).resumableUpload();
                    }
                }).start();
            }
        });

        // 签名URL
        Button sign = (Button) findViewById(R.id.sign);
        sign.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        new SignURLSamples(oss, testBucket, uploadObject, uploadFilePath).presignConstrainedURL();
                    }
                }).start();
            }
        });

        // bucket管理
        Button bucket = (Button) findViewById(R.id.bucket);
        bucket.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        new ManageBucketSamples(oss, testBucket, uploadFilePath).deleteNotEmptyBucket();
                    }
                }).start();
            }
        });
    }
}

点此查看Android开发笔记的完整目录

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android开发笔记(一百一十)使用http框架上传文件

    与文件下载相比,文件上传的场合不是很多,通常用于上传用户头像、朋友圈发布图片/视频动态等等,而且上传文件需要服务器配合,所以容易被app开发者忽略。就上传的...

    用户4464237
  • Android开发笔记(七十一)区分开发模式和上线模式

    许多开发者(包括博主在内)都是闷骚的程序员,为了开发调试方便,常常在代码里加上日志,还经常在页面上各种弹窗提示。这固然有利于发现bug、提高软件质量,但过多...

    用户4464237
  • Android开发笔记(一百零六)支付缴费SDK

    第三方支付指的是第三方平台与各银行签约,在买方与卖方之间实现中介担保,从而增强了支付交易的安全性。国内常用的支付平台主要是支付宝和微信支付,其中支付宝的市场...

    用户4464237
  • SpringBoot几个注解MockMvcWireMockSwagger2@JsonViewHibernate Validator异常处理拦截方式上传下载异步处理RESTSpring Security

    只有特定名称或者类型的Bean(通过@ConditionalOnMissingBean修饰)不存在于BeanFactory中时才创建某个Bean

    spilledyear
  • 基于Netty实现Redis协议的编码解码器

    上面是Netty的服务器端基本消息处理结构,为了便于初学者理解,它和真实的结构有稍许出入。Netty是基于NIO的消息处理框架,用来高效处理网络IO。处理网络消...

    老钱
  • Eclispe下集成JFinal中jetty包作为开发环境

    1.如果是gradle 或是maven项目地址在这,jetty-server包http://maven.oschina.net/index.html#nexu...

    用户2603479
  • java基础第十七篇之网络编程和装饰者模式

    1:网络概述 1.1 网络的发展Net 1964年,美国人—> 阿帕网—>以太网Internet 1.2 网络的通信协议 windows电脑,andro...

    海仔
  • 一步步封装实现自己的网络请求框架

    现如今 Android 领域流行的网络请求框架基本都是用 Retrofit 加 RxJava 来搭配构建的,而以 ViewModel + LiveData + ...

    叶志陈
  • 八、适配器模式与桥接模式详解

    适配器模式的英文翻译是 Adapter Design Pattern。顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼...

    编程之心
  • Spring Security如何优雅的增加OAuth2协议授权模式

    数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。

    陶陶技术笔记

扫码关注云+社区

领取腾讯云代金券