代码地址如下:
http://www.demodashi.com/demo/11212.html

前言

本案例已经开源!如果你想免费下载,可以访问我的Github,所有案例均在上面,只求给个star。当然愿意支付小小金额请我喝茶也行(大学穷狗-.-)

一、准备工作

  • 使用Android Studio开发
  • 微信和QQ第三方sdk,需要自行申请(这个简单)
  • 本案例使用干活集中营提供的api,使用MVp+Material Design作为主体架构进行开发
  • 体验完整功能,点击下载APK

二、程序实现

目录结构

目录结构如下,我按照功能分包:

实现思路

整体架构–MVP+Material

  • 首先你得了解MVP架构在android中的使用,如果你还不了解,可以阅读我的这篇文章
  • 如果你不熟悉Material可以读官方文档

重点代码分析

如果讲述整个App,估计一篇文章说不清楚。那我干脆取其中一条线来分析。
下面主要分析文章列表–文章详情–文章分享

主页文章列表

这里只选择Android文章模块进行介绍:

GankContract

public interface GankContract {interface View extends BaseView<Presenter>{//错误void showError();//正在加载void showLoading();//停止加载void Stoploading();//显示数据列表void showResult(ArrayList<GankNews.Question> list);//网络错误void showNotNetError();}interface Presenter extends BasePresenter{// 请求数据void loadPosts(int PagerNum, boolean cleaing);//刷新数据void  reflush();//加载更多void loadMore(int PagerNum);//显示详情void StartReading(int positon);//随便看看void LookAround();}
}

GankFragment

Fragment的内容主要是文章列表,我们只分享重点:

//下拉刷新实现
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {boolean isScrollState=false;@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);LinearLayoutManager manager= (LinearLayoutManager) recyclerView.getLayoutManager();//没有滚动时候if (newState==RecyclerView.SCROLL_STATE_IDLE){//获的最后一个可见的itemint lastVisibilityItem=manager.findLastCompletelyVisibleItemPosition();int totalItemCount=manager.getItemCount();//判断是否滚动到底部并且是向下滑动if (lastVisibilityItem==(totalItemCount-1)&&isScrollState){presenter.loadMore(1);}}}//通知Presenter加载数据和设置item点击事件
@Overridepublic void showResult(ArrayList<GankNews.Question> list) {if (adapter==null){Log.i(TAG, "showResult: "+list.size());adapter=new GankNewsAdapter(list,getContext());adapter.setItemOnClickListener(new OnRecyclerViewOnClickListener() {@Overridepublic void onItemClick(View v, int position) {presenter.StartReading(position);}@Overridepublic void onItemLongClick(View v, int position) {}});recyclerView.setAdapter(adapter);}else {adapter.notifyDataSetChanged();}}

GankPresenter

同样只分析重点代码:

//根据当前页数加载列表数据@Overridepublic void loadPosts(int PagerNum, final boolean cleaing) {CurrentPagerNum=PagerNum;if (cleaing) {view.showLoading();}if (Network.networkConnected(context)) {model.load(Api.Gank_Android + PagerNum, new OnStringListener() {@Overridepublic void onSuccess(String result) {try {
//                        Log.i(TAG, "gankpresenter.model.load.result"+result);GankNews news = gson.fromJson(result, GankNews.class);//contenvalues只能存储基本类型的数据,像string,int之类的,不能存储对象这种东西,而HashTable却可以存储对象。
//                        ContentValues values = new ContentValues();if (cleaing) {list.clear();}for (GankNews.Question item : news.getResults()) {/*** 1.数据库查重:首先检测数据库中是否已经储存过该条数据* 2:因为每次重启后都是在网络上重新下载数据 如果是数据库已经存在的数据则不会重新加载,也导致了这些数据当前id值为空* ,所有要绑定队友的id值.*/if (!queryIfIdExists(item.get_id())){DbLiteOrm.insert(item, ConflictAlgorithm.Replace);}else {ArrayList<GankNews.Question> ganklist=App.DbLiteOrm.query(new QueryBuilder<GankNews.Question>(GankNews.Question.class).where(GankNews.Question.COL_ID+"=?",new String[]{item.get_id()}));GankNews.Question gankitem=ganklist.get(0);item.setId(gankitem.getId());}list.add(item);}view.showResult(list);}catch (JsonSyntaxException e){view.showError();}view.Stoploading();}@Overridepublic void onError(VolleyError error) {view.Stoploading();view.showError();}});} else {//更新列表缓存 因为详情页都是用webView呈现 所以缓存content为空if (cleaing){QueryBuilder query=new QueryBuilder(GankNews.Question.class);query.appendOrderDescBy("id");query.limit(0,10*CurrentPagerNum);list.addAll(DbLiteOrm.<GankNews.Question>query(query));view.showResult(list);}else {view.showNotNetError();}}}//判断数据库是否已经存在public boolean queryIfIdExists(String _id){ArrayList<GankNews.Question> questionArrayList=App.DbLiteOrm.query(new QueryBuilder(GankNews.Question.class).where(GankNews.Question.COL_ID+"=?",new String[]{_id}));if (questionArrayList.size()==0){return false;}return true;}//传递当前点击item的信息,进入详情阅读
@Overridepublic void StartReading(int positon) {//每个item就是一组数据GankNews.Question item=list.get(positon);Intent intent = new Intent(context, DetailActivity.class);intent.putExtra("type", BeanTeype.TYPE_Gank);intent.putExtra("id",list.get(positon).getId());int id=list.get(positon).getId();Log.i(TAG, "StartReading: "+id);intent.putExtra("_id", list.get(positon).get_id());intent.putExtra("url",list.get(positon).getUrl());intent.putExtra("title", list.get(positon).getDesc());if (item.getImages()==null){intent.putExtra("imgUrl", "");}else {intent.putExtra("imgUrl", list.get(positon).getImages().get(0));}/*** Content的startActivity方法,需要开启一个新的task。如果使用 Activity的startActivity方法,* 不会有任何限制,因为Activity继承自Context,重载了startActivity方法。*/intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(intent);}//随便看看 随机选取
@Overridepublic void LookAround() {if (list.isEmpty()){view.showError();return;}StartReading(new Random().nextInt(list.size()));}

GankNewsAdapter

因为文章分两种:有图和无图。所有要进行分类加载

//判断是否有图和是否是底部加载item@Overridepublic int getItemViewType(int position) {if (position==getItemCount()-1){return TYPE_FOOTER;}if (list.get(position).getImages()==null){return TYPE_NO_IMG;}return TYPE_NORMTAL;}//根据type加载不同ViewHolder
@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {switch (viewType){case TYPE_NORMTAL:return new NormalViewHolder(inflater.inflate(R.layout.home_list_item_layout,parent,false),listener);case TYPE_FOOTER:return new FooterViewHolder(inflater.inflate(R.layout.list_footer,parent,false));case TYPE_NO_IMG:return new NoImageViewHolder(inflater.inflate(R.layout.home_list_item_without_image,parent,false),listener);}return null;}//使用Glide加载图片。无图则不加载@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {if (!(holder instanceof FooterViewHolder)){GankNews.Question item=list.get(position);if (item!=null){if (holder instanceof NormalViewHolder){Glide.with(context).load(item.getImages().get(0)).asBitmap().placeholder(R.mipmap.loading).diskCacheStrategy(DiskCacheStrategy.SOURCE).error(R.mipmap.loading).centerCrop().into(((NormalViewHolder) holder).imageView);((NormalViewHolder) holder).textView.setText(item.getDesc());}else if (holder instanceof NoImageViewHolder){((NoImageViewHolder) holder).textViewNoImg.setText(item.getDesc());}}}}

详情页

DetailActivity

 @Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.frame);if (savedInstanceState!=null){detailFragment= (DetailFragment) getSupportFragmentManager().getFragment(savedInstanceState,"detailFragment");}else {detailFragment=DetailFragment.newInstance();getSupportFragmentManager().beginTransaction().replace(R.id.container,detailFragment).commit();}//获取列表传过来的具体item数据Intent intent=getIntent();DetailPresenter presenter=new DetailPresenter(detailFragment,DetailActivity.this);presenter.setType((BeanTeype) intent.getSerializableExtra("type"));presenter.setId(intent.getIntExtra("id",1));presenter.set_id(intent.getStringExtra("_id"));presenter.setTitle(intent.getStringExtra("title"));presenter.setUrl(intent.getStringExtra("url"));presenter.setImgUrl(intent.getStringExtra("imgUrl"));}

DetailContract

public class DetailContract {interface Presenter extends BasePresenter{/*** 流浪器中打开* 复制文本* 复制连接* 添加收藏或取消收藏* 查询是否收藏* 请求数据* 分享到QQ* 分享到微信* 分享到朋友圈* 分享到微信收藏*/void openInBrower();void copyText();void copyLink();void addToOrDeleteFromBookMarks();boolean queryIsBooksMarks();void requestData();void shareArticleToQQ(final MyQQListener listener);void shareArticleToWx();void shareArticleToWxCommunity();void shareArticleToWxCollect();}interface View extends BaseView<Presenter> {// 显示正在加载void showLoading();// 停止加载void stopLoading();// 显示加载错误void showLoadingError();// 显示分享时错误void showSharingError();// 正确获取数据后显示内容
//        void showResult(String result);
//        // 对于body字段的消息,直接接在url的内容void showResultWithoutBody(String url);// 设置顶部大图void showCover(String url);// 设置标题void setTitle(String title);// 设置是否显示图片void setImageMode(boolean showImage);// 用户选择在浏览器中打开时,如果没有安装浏览器,显示没有找到浏览器错误void showBrowserNotFoundError();// 显示已复制文字内容void showTextCopied();// 显示文字复制失败void showCopyTextError();// 显示已添加至收藏夹void showAddedToBookmarks();// 显示已从收藏夹中移除void showDeletedFromBookmarks();void  showNotNetError();void shareSuccess();void shareError();void shareCancel();}
}

DetailFragment

详情页主题是使用WebView显示,重点注意好设置属性和正确销毁:

 @Overridepublic void initView(View view) {......//webview设置属性webview.getSettings().setJavaScriptEnabled(true);//缩放,设置为不能缩放可以防止页面上出现放大和缩小的图标webview.getSettings().setBuiltInZoomControls(false);//缓存webview.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);//开启DOM storage API功能webview.getSettings().setDomStorageEnabled(true);//开启application Cache功能webview.getSettings().setAppCacheEnabled(false);.....}//早onDestroy中销毁WebView的对象
@Overridepublic void onDestroyView() {super.onDestroyView();webview.removeAllViews();webview.destroy();webview=null;}

DetailPresenter

//复制链接地址@Overridepublic void copyLink() {ClipboardManager manager= (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);ClipData data=null;switch (type){case TYPE_Gank:data=ClipData.newPlainText("text",url);}manager.setPrimaryClip(data);view.showTextCopied();}//添加到收藏或者移除收藏@Overridepublic void addToOrDeleteFromBookMarks() {switch (type){case TYPE_Gank:GankNews.Question gank= App.DbLiteOrm.queryById(id,GankNews.Question.class);if (queryIsBooksMarks()){view.showDeletedFromBookmarks();gank.mark=false;}else {view.showAddedToBookmarks();gank.mark=true;}App.DbLiteOrm.update(gank);break;case TYPE_Front:FrontNews.Question front=App.DbLiteOrm.queryById(id,FrontNews.Question.class);if (queryIsBooksMarks()){view.showDeletedFromBookmarks();front.mark=false;}else {view.showAddedToBookmarks();front.mark=true;}App.DbLiteOrm.update(front);break;case TYPE_IOS:IosNews.Question ios=App.DbLiteOrm.queryById(id,IosNews.Question.class);if (queryIsBooksMarks()){view.showDeletedFromBookmarks();ios.mark=false;}else {view.showAddedToBookmarks();ios.mark=true;}App.DbLiteOrm.update(ios);}}//查询是否已经收藏@Overridepublic boolean queryIsBooksMarks() {if (_id ==null || type==null){view.showLoadingError();return false;}//true为已经收藏 false未收藏switch (type){case TYPE_Gank:GankNews.Question gank= App.DbLiteOrm.queryById(id,GankNews.Question.class);OrmLog.i(TAG,gank);boolean isMark=gank.mark;if (isMark){return true;}else {return false;}case  TYPE_Front:FrontNews.Question front=App.DbLiteOrm.queryById(id,FrontNews.Question.class);if (front.mark){return true;}else {return false;}case TYPE_IOS:Log.i(TAG, "queryIsBooksMarks: "+id);IosNews.Question ios=App.DbLiteOrm.queryById(id,IosNews.Question.class);OrmLog.i(TAG,ios);if (ios.mark){return true;}else {return false;}}return false;}//分享到QQ@Overridepublic void shareArticleToQQ(MyQQListener listener) {//title == descif (TextUtils.isEmpty(imgUrl)){ShareSingleton.getInstance().shareToQQ((Activity) context,url,"推荐给你一篇文章",title, R.string.app_name, QQShare.SHARE_TO_QQ_FLAG_QZONE_ITEM_HIDE,listener);}else {ShareSingleton.getInstance().shareToQQ((Activity) context,url,"推荐给你一篇文章",title,imgUrl,R.string.app_name, QQShare.SHARE_TO_QQ_FLAG_QZONE_ITEM_HIDE,listener);}}//分享到微信@Overridepublic void shareArticleToWx() {//title == descShareSingleton.getInstance().shareWebToWx(url,"",title,true);}//分享到朋友圈@Overridepublic void shareArticleToWxCommunity() {//title == descShareSingleton.getInstance().shareWebToWx(url,"",title,false);}//分享到微信收藏@Overridepublic void shareArticleToWxCollect() {//title == descShareSingleton.getInstance().shareWebToWxCollect(url,"干货",title);}

ShareSingleton

关于微信和QQ分享的具体方法还得参考官方文章,我这里提出我自己写好的分享单例类

public class ShareSingleton {private Tencent mTencent;public static IWXAPI api;private static final int THUMB_SIZE = 150;//单例模式private ShareSingleton() {}public static final ShareSingleton getInstance(){return Singleton.INSTANCE;}private static class Singleton{private static final ShareSingleton INSTANCE=new ShareSingleton();}/*** 图文分享 图片来源网络* !! 分享操作要在主线程中完成* @param activity* @param targetUrl  这条分享消息被好友点击后的跳转URL。* @param shareTitle    分享的标题, 最长30个字符。* @param shareSummary 分享的消息摘要,最长40个字。* @param netImgUrl 可填 分享图片的URL或者本地路径* @param appName 手Q客户端顶部,替换“返回”按钮文字,如果为空,用返回代替* @param shareToQQExtInt 额外选项  是否自动打开分享到QZone的对话框* @param listener 分享回调接口*/public void shareToQQ(Activity activity,String targetUrl,String shareTitle,String shareSummary,@Nullable String netImgUrl,@StringRes int appName,int shareToQQExtInt,MyQQListener listener){if (mTencent==null){mTencent=Tencent.createInstance(Constants.QQ_APP_ID,activity.getApplicationContext());}final Bundle params = new Bundle();params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT);params.putString(QQShare.SHARE_TO_QQ_TARGET_URL,targetUrl);params.putString(QQShare.SHARE_TO_QQ_TITLE, shareTitle);params.putString(QQShare.SHARE_TO_QQ_SUMMARY, shareSummary );params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL,  netImgUrl);params.putString(QQShare.SHARE_TO_QQ_APP_NAME,activity.getString(appName));params.putInt(QQShare.SHARE_TO_QQ_EXT_INT,  shareToQQExtInt);mTencent.shareToQQ(activity, params, listener);}/*** 文章分享 无图* !! 分享操作要在主线程中完成* @param activity* @param targetUrl  这条分享消息被好友点击后的跳转URL。* @param shareTitle    分享的标题, 最长30个字符。* @param shareSummary 分享的消息摘要,最长40个字。* @param appName 手Q客户端顶部,替换“返回”按钮文字,如果为空,用返回代替* @param shareToQQExtInt 额外选项  是否自动打开分享到QZone的对话框* @param listener 分享回调接口*/public void shareToQQ(Activity activity,String targetUrl,String shareTitle,String shareSummary,@StringRes int appName,int shareToQQExtInt,MyQQListener listener){if (mTencent==null){mTencent=Tencent.createInstance(Constants.QQ_APP_ID,activity.getApplicationContext());}final Bundle params = new Bundle();params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT);params.putString(QQShare.SHARE_TO_QQ_TARGET_URL,targetUrl);params.putString(QQShare.SHARE_TO_QQ_TITLE, shareTitle);params.putString(QQShare.SHARE_TO_QQ_SUMMARY, shareSummary );params.putString(QQShare.SHARE_TO_QQ_APP_NAME,activity.getString(appName));params.putInt(QQShare.SHARE_TO_QQ_EXT_INT,  shareToQQExtInt);mTencent.shareToQQ(activity, params, listener);}/*** 分享文章到微信/朋友圈* @param webUrl* @param webTitle* @param webDesc* @param isShareFriend*/public void shareWebToWx(@NonNull String webUrl,String webTitle,String webDesc,boolean isShareFriend){
//        注册操作也可以写死在Application中// 通过WXAPIFactory工厂,获取IWXAPI的实例api=WXAPIFactory.createWXAPI(App.getContext(),Constants.WX_APP_ID,true);// 将该app注册到微信api.registerApp(Constants.WX_APP_ID);//初始化一个WXWebpageObject对象,填写urlWXWebpageObject webpag=new WXWebpageObject();webpag.webpageUrl=webUrl;//用WXWebpageObject对象初始化一个WXMediaMessage对象  填写标题和描述WXMediaMessage msg=new WXMediaMessage(webpag);msg.title=webTitle;msg.description=webDesc;//构造一个ReqSendMessageToWX.Req req=new SendMessageToWX.Req();req.transaction=buildTransaction("webpage");//transaction 字段用于唯一标识一个请求req.message= msg;req.scene=isShareFriend ? SendMessageToWX.Req.WXSceneSession : SendMessageToWX.Req.WXSceneTimeline;api.sendReq(req);}/*** 分享文章到微信收藏* @param webUrl* @param webTitle* @param webDesc*/public void shareWebToWxCollect(@NonNull String webUrl, String webTitle, String webDesc){
//        注册操作也可以写死在Application中// 通过WXAPIFactory工厂,获取IWXAPI的实例api=WXAPIFactory.createWXAPI(App.getContext(),Constants.WX_APP_ID,true);// 将该app注册到微信api.registerApp(Constants.WX_APP_ID);//初始化一个WXWebpageObject对象,填写urlWXWebpageObject webpag=new WXWebpageObject();webpag.webpageUrl=webUrl;//用WXWebpageObject对象初始化一个WXMediaMessage对象  填写标题和描述WXMediaMessage msg=new WXMediaMessage(webpag);msg.title=webTitle;msg.description=webDesc;//构造一个ReqSendMessageToWX.Req req=new SendMessageToWX.Req();req.transaction=buildTransaction("webpage");//transaction 字段用于唯一标识一个请求req.message= msg;req.scene=SendMessageToWX.Req.WXSceneFavorite;api.sendReq(req);}

这篇文章就分析这么多,如果你想了解跟多,欢迎下载源码。主要部分源码都有注释

三、部分运行效果





四、其他补充

如果你有问题可以提交到Github的issue上,也可以给我发邮件。我的邮件是yeshuwei.swy@gmail.com

Android–从零开始开发一款文章阅读APP

代码地址如下:
http://www.demodashi.com/demo/11212.html

注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权

Android--从零开始开发一款文章阅读APP相关推荐

  1. 如何从零开始开发一款嵌入式产品(20年的嵌入式经验分享学习)

    如何从零开始开发一款嵌入式产品(20年的嵌入式经验分享学习)_转 来源:www.armjishu.com 作者:jesse 转载请注明出处 首先,如果你有幸看到这篇文章,千万不要试图在2个小时内阅读完 ...

  2. 开发一款即时通讯App,从这几步开始

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由腾讯云视频发表于云+社区专栏 关注公众号"腾讯云视频",一键获取 技术干货 | 优惠活动 | 视频方案 " ...

  3. AndroidFire,一款新闻阅读 App

    AndroidFire 项目地址:AndroidFire 简介:AndroidFire,一款新闻阅读 App,基于 Material Design + MVP + RxJava + Retrofit ...

  4. 餐饮水单打印软件_开发一款餐饮手机app系统软件什么价格?有哪些方面需要考虑?...

    开发一款餐饮手机app系统软件什么价格?有哪些方面需要考虑? 近年来,餐饮类的APP如雨后春笋般快速增长,无论是上档次的酒店,还是各大餐厅,都有各自的专属APP.餐饮APP的开发能让大型酒店/餐厅获得 ...

  5. 爆款文章阅读量快速提升的一个关键点!

    还记得新媒体行业发展之初,第一批入场的选手进入赛道,大家都是不断的批量生产内容,获得大量阅读:然后通过各种方式野蛮生长,获得更多阅读量,一步步成为头部账号. 那时候阅读量是个好东西,快速涨粉是个好现象 ...

  6. 爆款文章阅读量快速提升的一个关键点

    还记得新媒体行业发展之初,第一批入场的选手进入赛道,大家都是不断的批量生产内容,获得大量阅读:然后通过各种方式野蛮生长,获得更多阅读量,一步步成为头部账号. 那时候阅读量是个好东西,快速涨粉是个好现象 ...

  7. 一个传统物流企业开发一款专属物流app需要多少成本?

    物流APP开发专家指出:近年来,我国电子商务经济处于快速发展阶段,在一定程度上促进了物流业的发展.同时,没有物流系统的支持,电子商务经济也能取得今天的成就!根据小编,获得的数据,淘宝手机交易量占近70 ...

  8. 《iOS移动开发从入门到精通》图书连载一:如果你也想开发一款自己的APP,可以看一下这篇文

    前言:互联网+时代给自己多一个选择的机会,尝试开发一款属于自己的APP,绝对是件激动人心的事情!<iOS移动开发从入门到精通>已经上市并和大家见面.从今天起,我会将把图书的部分内容以连载的 ...

  9. Android商城开发系列(二)——App启动欢迎页面制作

    商城APP一般都会在应用启动时有一个欢迎界面,下面我们来实现一个最简单的欢迎页开发:就是打开商城App,先出现欢迎界面,停留几秒钟,自动进入应用程序的主界面. 首先先定义WelcomeActivity ...

最新文章

  1. 河南科技大学c语言章节作业答案,河南科技大学C语言试题
  2. HDU3037(卢卡斯定理)
  3. es6新特性中...的用法
  4. Python3 局部变量与全局变量作用域
  5. 1、webpack入门例子。
  6. 逻辑学 —— 复杂问题谬误
  7. [BJOI2017]开车
  8. 漫画小说听书三合一分销平台源码
  9. web项目中如何启动爬虫程序?Django+Requests+Ajax制作可视化翻译界面详解
  10. 计算机cpu位数是啥,怎么看电脑cpu的位数
  11. SAP SD销售订单保存提示 定价错误: 必要条件 MWSI 丢失解决方法
  12. [4G/5G/6G专题基础-159]: CQI值的滤波方法
  13. 100句充满智慧的人生格言
  14. 论文复现-1:Perturbation CheckLists for Evaluating NLG Evaluation Metrics
  15. 解释源代码文件、目标代码文件和可执行文件之间的区别
  16. Linux学习 高级网络配置
  17. 阿里巴巴牵头发起对雅虎的250亿美元并购
  18. 男人最喜欢女人说的五句话
  19. 基于深度残差学习的图像识别 Deep Residual Learning for Image Recognition
  20. 与女儿谈商业模式(1):盖茨的商业模式

热门文章

  1. 永中word页码怎么从第二页开始_pdf如何在线转word?这样做,分分钟搞定pdf转word...
  2. 2022.管理类软件工具
  3. S3C2440裸奔篇之MMU
  4. 卡尔曼滤波 -- 从推导到应用(二)
  5. ElasticSearch API实现CRUD
  6. wpf异常:指定的 Visual 不是此 Visual 的上级问题处理解析
  7. https证书设置以及设置301跳转
  8. Python学习week2
  9. java根据sessionid获取session
  10. xcode6的项目中虚拟键盘无法弹出