Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

编写原生看书页面 轮播图模块时遇到的fragment问题

$
0
0

书籍页面需要显示轮播图组件,上网找了个现成封装好的,感谢这个哥们儿 http://blog.csdn.net/stevenhu_223/article/details/45577781

down下来用到看书中,是封装在fragment里的,我是在BookView中动态调用的,调用方式:

LayoutInflater lif = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View lubotuView = lif.inflate(R.layout.lunbotu, null);
CycleViewPager cycleViewPager = (CycleViewPager) ((Activity) context).getFragmentManager().findFragmentById(R.id.lunbotuCycle);
LinearLayout lunbotuLL = (LinearLayout) lubotuView.findViewById(R.id.lunbotuLL);
addView(lubotuView);

好使,成功调用。

但是在翻页过程中,第二次翻到此页就崩溃了,崩溃信息:

E/AndroidRuntime(6462): android.view.InflateException: Binary XML file line #8: Error inflating class fragment
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:719)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at android.view.LayoutInflater.rInflate(LayoutInflater.java:761)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at android.view.LayoutInflater.inflate(LayoutInflater.java:498)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at android.view.LayoutInflater.inflate(LayoutInflater.java:398)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at android.view.LayoutInflater.inflate(LayoutInflater.java:354)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at com.sea.testRatingBar.bookView.loadCarouselNode(bookView.java:132)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at com.sea.testRatingBar.bookView.loadElement(bookView.java:63)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at com.sea.testRatingBar.bookView.<init>(bookView.java:50)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at com.sea.testRatingBar.BookPageAdapter.getView(BookPageAdapter.java:55)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at com.aphidmobile.flip.FlipViewController.viewFromAdapter(FlipViewController.java:478)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at com.aphidmobile.flip.FlipViewController.flippedToView(FlipViewController.java:545)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at com.aphidmobile.flip.FlipViewController$2.run(FlipViewController.java:519)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at android.os.Handler.handleCallback(Handler.java:733)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at android.os.Handler.dispatchMessage(Handler.java:95)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at android.os.Looper.loop(Looper.java:136)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at android.app.ActivityThread.main(ActivityThread.java:5479)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at java.lang.reflect.Method.invokeNative(Native Method)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at java.lang.reflect.Method.invoke(Method.java:515)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at dalvik.system.NativeStart.main(Native Method)
10-14 06:51:37.676: E/AndroidRuntime(6462): Caused by: java.lang.IllegalArgumentException: Binary XML file line #8: Duplicate id 0x7f09000a, tag null, or parent id 0x7f090009 with another fragment for cn.androiddevelop.cycleviewpager.lib.CycleViewPager
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at android.app.Activity.onCreateView(Activity.java:5002)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:695)
10-14 06:51:37.676: E/AndroidRuntime(6462): 	... 20 more

一看就是fragment重复调用,造成了这个错误

解决方案1:所以用完了得remove掉,在CycleViewPager这个fragment中添加个ondestroy方法,当ondestroy销毁时remove此frg

public void onDestroyView() {
	    super.onDestroyView();
	    FragmentManager fm = getActivity().getSupportFragmentManager();
	    Fragment fragment = (fm.findFragmentById(R.id.cyclepic));
	    FragmentTransaction ft = fm.beginTransaction();
	    ft.remove(fragment);
	    ft.commit();
	}

测试,不好使,Sh*t 

解决方案2:优化oncreate生命周期,如果之前创建过,就删除,老一套:

<span style="white-space:pre">	</span>public CycleViewPager() {
	}

	private static View rootView;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		// View view =
		// LayoutInflater.from(getActivity()).inflate(R.layout.view_cycle_viewpager_contet,
		// null);
		if (rootView != null) {
			ViewGroup parent = (ViewGroup) rootView.getParent();
			if (parent != null)
				parent.removeView(rootView);
		}
		try {
			rootView = LayoutInflater.from(getActivity()).inflate(R.layout.view_cycle_viewpager_contet, null);
		} catch (InflateException e) {
		}
测试,不好使,double Sh*t

解决方案3:这些都不好使,那还试试那个remove的,经过了漫长的逻辑方面的探索,终于锁定在adapter中,移除所有view之前remove

int childCount0 = viewHolder.ll.getChildCount();
for (int i = 0; i < childCount0; i++) {
	View v = viewHolder.ll.getChildAt(i);
	BookView bv = (BookView) v;
	int bvChildCount = bv.getChildCount();
	for (int j = 0; j < bvChildCount; j++) {
		View bvv = bv.getChildAt(j);
		if(bvv instanceof LinearLayout){
			if(bvv.getId() == R.id.lunbotuLL){
				FragmentManager fm = ((Activity) c).getFragmentManager();
				Fragment fragment = fm.findFragmentById(R.id.lunbotuCycle);
				FragmentTransaction ft = fm.beginTransaction();
				ft.remove(fragment);
				ft.commit();
			}
		}
	}
}
viewHolder.ll.removeAllViews();
测试,还tm不好使,triple Sh*t 

解决方法4:思考了一下,逻辑肯定是对的,还是报错的话,是不是ft.commit这个方法不执行啊,

上网搜到了一篇文章http://blog.csdn.net/picasso_l/article/details/50994143 ,按照文章说法,添加了一个

fm.executePendingTransactions();
即 立即执行 !

测试,好使!





贴一下http://blog.csdn.net/picasso_l/article/details/50994143 这个博客的内容,当做记录,对博主表示感谢:

FragmentTransaction是异步的,commit()仅是相当于把操作加入到FragmentManager的队列,然后FragmentManager会在某一个时刻来执行,并不是立即执行。所以,真正开始执行commit()时,如果Activity的生命周期发生了变化,比如走到了onPause,或者走到了onStop,或者onDestroy都走完了,那么就会报出IllegalStateException。

这个地方确实是很坑的,我在做一个功能,需要从FragmentA跳转到FragmentB,然后调用FragmentB的刷新方法,那我的思路是从FragmentA和B的MainActivity中将A隐藏,将B显示,然后调用刷新。 
于是我先将A隐藏B显示

<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">private void switchFragment(Fragment newFragment) {

        FragmentManager fm = getSupportFragmentManager()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        FragmentTransaction transaction = fm<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.beginTransaction</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>

        LogCat<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.i</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"newFragment isAdded="</span> + newFragment<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.isAdded</span>())<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        if (newFragment<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.isAdded</span>()) {
            transaction<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.hide</span>(mCurrentFragment)<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.show</span>(newFragment)<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.commitAllowingStateLoss</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        } else {
            transaction<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.hide</span>(mCurrentFragment)<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.add</span>(R<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.id</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.main</span>_content, newFragment)<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.commitAllowingStateLoss</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        }
        mCurrentFragment = newFragment<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>

然后,再switchFragment之后调用FragmentB的刷新功能,但是问题出现了,发现FragmentB里面的一些空间没有初始化,打了log之后发现,初始化在我的初始化在我的刷新功能后面执行,查了资料发现,FragmentTransaction的commit方法是异步的,难怪~

解决方法:executePendingTransactions

这里写图片描述

在用FragmentTransaction.commit()方法提交FragmentTransaction对象后,会在进程的主线程中,用异步的方式来执行。如果想要立即执行这个等待中的操作,就要调用这个方法(只能在主线程中调用)。要注意的是,所有的回调和相关的行为都会在这个调用中被执行完成,因此要仔细确认这个方法的调用位置。

于是我重写switchFragment方法

<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">FragmentManager fm = getSupportFragmentManager()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        FragmentTransaction transaction = fm<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.beginTransaction</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>

        if (fragment<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.isAdded</span>()) {
            transaction<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.hide</span>(mCurrentFragment)<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.show</span>(fragment)<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.commitAllowingStateLoss</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        } else {
            transaction<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.hide</span>(mCurrentFragment)<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.add</span>(R<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.id</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.main</span>_content, fragment)<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.commitAllowingStateLoss</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
        }
        mCurrentFragment = fragment<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>


        fm<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.executePendingTransactions</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>

        ((DiscoverFragment) fragment)<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.refresh</span>(searchWord)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li></ul><div class="save_code tracking-ad" data-mod="popu_249" style="box-sizing: border-box; position: absolute; height: 60px; right: 30px; top: 5px; color: rgb(255, 255, 255); cursor: pointer; z-index: 2;"><a target=_blank target="_blank" style="box-sizing: border-box; color: rgb(12, 137, 207);"><img src="http://static.blog.csdn.net/images/save_snippets.png" style="border: none; box-sizing: border-box;" alt="" /></a></div><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li></ul>

多加了一句fm.executePendingTransactions()

作者:jbb0403 发表于2016/10/14 11:45:46 原文链接
阅读:9 评论:0 查看评论

《高性能Android应用开发》--互动出版网

$
0
0

基本信息

编辑推荐

 

本书主要关于如何提高Android App的性能以便为用户提供极致的体验,在智能设备广泛应用的今天,这本书对开发人员来说十分有用。本书有助于开发人员更进一步了解Android App性能方面的问题,不断改善App性能,更适应用户需求。

内容简介

    书籍
    计算机书籍
性能问题在很大程度上决定了用户是否会使用一款App,本书正是Android性能方面的关键性指南。全书共8章,主要从电池、内存、CPU和网络方面讲解了电池管理、工作效率和速度这几个方面的性能优化问题,并介绍了一些有助于确定和定位性能问题所属类型的工具。同时也会探讨开发人员面临的一些主要问题,进而提出一些可行的补救措施。全书旨在通过提高App性能完善App,以便用户可以获得极致体验。 

作译者

Doug Sillars 
是 AT&T 开发者计划中的性能推广领导者。他帮助了成千上万的移动开发人员将性能的最佳实践应用到 App 上。他开发的工具和总结的最佳实践,帮助开发人员使 App 运行得更快,同时使用了更少的数据和电量。他和妻子生活在华盛顿州的一个小岛上,并在家教育三个孩子。 

目录

译者序 xi 
序 xiii 
前言 xv 
第1章 Android 的性能指标  1 
1.1 性能对用户很重要  2 
1.1.1 电子商务和性能  2 
1.1.2 电子商务之外的影响  3 
1.1.3 性能可以节省基础设备  4 
1.2 最恶劣的性能影响因素:宕机  4 
1.2.1 低性能就像持续的宕机  5 
1.2.2 消费者对性能bug 的态度  7 
1.2.3 智能手机电池寿命:矿井中的金丝雀  8 
1.3 App 性能问题的检测  8 
1.3.1 模拟测试  9 
1.3.2 真实用户监测  9 
1.4 总结  9 
第2章 构建Android 设备实验室  10 
2.1 你的用户都在使用什么设备  11 
2.2 设备特性分布  11 
2.2.1 屏幕  11 

译者序

  译者序
  相信所有早期的Android 开发者都被性能问题折腾过。那时这方面的资源几乎搜索不到,更不要提性能优化的最佳实践了。开发者真的是在实战中摸着石头过河,四处碰壁,一身是伤地总结出了很多经验。相信到了现在,每个技术团队都有自己沉淀的一套方法。但时至今日,市面上并没有出现一本指导性的书籍。
  当本书的原著刚出版的时候,我们有幸很早就看到了。我们认为这本书非常有价值,有关性能方面的内容非常全面且具有实际的指导意义。当时我们的团队也正在尝试更多的性能优化,于是就决定将这本书翻译出来,让更多的人能够了解和学习到这些经验,并且运用到实际开发中。因此,我们组建了一个翻译小组,一边学习实践,一边翻译这本书。
  这里先感谢所有参与到本书翻译过程当中的小伙伴。
  酒店部:周丹红、王若兰、杨鑫、罗佳妮、杜航宇。
  猫眼部:夏恩龙、陈文超、李欣欣、雷健龙、张涛。
  感谢辅助翻译工作的马圣超、高飞、于振兴。
  尤其感谢以下五位同学:周丹红、王若兰、夏恩龙、陈文超、李欣欣,他们牺牲了自己的大量业余时间来做这件事情,非常辛苦。
  另外,还要感谢后期参与技术指导和校对的同学,找到这些同学的时候,他们毫不犹豫地答应了,为我们提供了大量的建议,同时指正了很多错误。
  感谢参与校对的小伙伴(不分先后):袭建帅、王康、武智、田洪晖、王京。
  这里特别感谢刘江老师。我们之前没有太多的翻译经验,是刘江老师为我们提供了很多建设性的建议,让我们少走了很多弯路。
  总之,这本书能够顺利出版,离不开大家的努力和帮助。因为能力有限,书中免不了出现一些问题,还请大家包容并给出建议。若对本书内容有任何疑问或建议,可发邮件至:sankuaimj@gmail.com。 

前言

  
  你正在构建一个Android App 吧(或者你已经构建了一个App)?你肯定对自己App 的性能并不满意。(不然你为什么要看这本书呢?)揭示移动App 的性能问题是一个持续性的工作。我的研究发现,98% 的App 存在潜在的性能改进空间。本书将涵盖移动性能的隐患,并为你介绍一些测试这些问题的工具。我的目标是帮助你获得这些必要的技能,在重大的性能问题影响到用户之前捕获它。
  研究表明,用户期望移动App 能够快速加载,迅速响应用户的交互,并且在视觉上很流畅、美观。随着App 变得更加快速,用户的参与度和收益也在增长。没有关注性能的App的卸载率和那些会崩溃的App 的卸载率相同。那些资源利用率低的App 会造成不必要的电池消耗。运营商和设备制造商收到用户投诉最多的就是电池寿命了。
  在过去的几年里,我和成千上万的开发者谈过Android App 的性能问题。很少有开发者知道有可用的工具能够解决他们遇到的问题。
  明确的共识是:运行快速、流畅的移动App 会更多地被使用,能够为开发者带来更多的收益。令人惊奇的是,哪怕知道这些,很多开发者还是没有使用可用的工具来诊断和定位他们App 中的性能问题。通过关注性能的提升是如何影响用户体验的,你能够快速地了解你对App 所做的性能优化工作所带来的收益。
  本书读者
  本书以Android 性能为中心涵盖了一系列广泛的主题。任何和移动开发相关的人员都会喜欢本书中关于App 性能的研究。非Android 移动开发者将会发现书中关于App 性能的争论和问题是非常有用的,但用于隔离问题的工具是专门用于Android 的。
  测试人员将会发现用于测试Android 性能的工具的教程也同样非常实用。
  我为什么写这本书
  开发者在Web 性能这个广阔的新兴领域里分享了提高Web 速度的技巧。Steve Souders 在2007 年写了《高性能网站建设指南》一书,众多书籍、博客和会议都讨论了这个主题。
  此前,移动App 的性能很少受到关注。App 运行缓慢都被归罪于操作系统或者移动网络,而电量持续时间短则被归罪于设备的硬件。随着手机越来越快,操作系统越来越成熟,用户开始明白App 对手机性能的影响。
  有很多非常棒的工具可以用来衡量Android App 的性能,但是到目前为止,还没有人对它们进行归纳和整理。通过介绍Google、Qualcomm、AT&T 以及其他公司的性能测量工具,我希望本书能将Android 性能测试的奥秘展现出来,帮助你的App 在不增加用户耗电量的情况下运行得更加快速。
  本书预览
  当研究App 性能时,我选择了研究App 的代码对Android 设备不同方面的影响。我们将从一个比较高阶的层面开始:性能和Android 的生态系统,然后查看App 对屏幕、CPU 以及网络栈等的影响。
  第1 章,Android 的性能指标这一章介绍了移动App 的性能这一话题。我们将用一些例子来证明App 性能的重要性。文中会强调现在面临的挑战,同样也会列出性能低下在应用市场中的影响。我们还会列出一些统计数据,你可以拿这些数据去说服管理层,让他们在提高App 性能方面投入更多的精力和时间。这里所给出的数据一般涵盖了所有的移动平台和设备。
  第2 章,构建Android 设备实验室这一章将讨论测试。Android 是一个巨大的生态系统,包括了上万种设备,并且每一种设备都有不同的UI、屏幕、处理器以及操作系统版本(仅举几例)。我将探索一些方法,帮助你测试尽可能多的设备,并且不会花费过高。
  第3 章,硬件性能和电池续航接下来,我们将讨论电池,包括电量流失的原因以及流失的多少。另外,这一章将讨论用户是如何发现App 中的电量问题的,以及如何使用开发工具来避免这些问题。我们也会学习新的JobScheduler API(在Lollipop 版本中发布),它可以从操作系统中唤起App。
  第4 章,屏幕和UI 性能屏幕是手机上功耗最大的一部分。屏幕是App 的主要接口,性能差的App 的卡顿(跳帧)和慢速渲染正是通过屏幕展现出来的。这一章将通过使层级更加扁平化来一步步优化UI,然后介绍如何使用Systrace 等工具对App 进行卡顿和抖动的测试。
  第5 章,内存性能;第6 章,CPU 与CPU 性能我们在这两章讨论内存和CPU 问题,如垃圾回收、内存泄露,以及它们是如何影响App 性能的。你将学会如何运用测试工具,如procstats、内存分析工具 (MAT)和Traceview,剖析App 以发现潜在的问题。
  第7 章,网络性能我们将在这一章讨论App 的网络性能。我们从这里开始探讨移动性能优化,探究App是如何与服务器进行通信的,以及我们应该如何加强这些通信。然后介绍如何测试App在慢网上的性能,因为许多发展中国家未来几十年用的可能都是2G 和3G 网络。

序言

  序
  对于广大的Android 开发者来说,性能是他们最后才考虑的事情。大多数的App 开发更强调个性化,开发者的目标是使UI 看起来完美并且找到一个可行的商业化道路。但是,App的性能很大程度上像是家里的管道;当它正常工作时,没有人会关注或者考虑到它,然而一旦出错,人们马上就会陷入麻烦当中。
  你看,用户在注意到社交小工具、图像过滤器或者是支持克林贡语等其他特性之前,会先注意到App 的性能不好。并且你猜怎么着?用户因为不满意性能而给App 差评的比例要高于因其他问题而给App 差评的比例。
  这也是我们说性能很重要的原因。开发App 的时候,很容易就会忽略性能,但坦率地说,性能涉及你所做的一切。当性能体验不好时,用户就会开始抱怨,进而卸载你的App,然后报复性地给你一个差评。考虑到这些,性能听起来更像是应该关注的一个特征,而不是必须忍受的一种负担。
  但实话实说,提升性能是一件非常困难的事情。仅仅了解算法是不够的,你还需要了解Android 系统是如何执行它的,以及硬件又是如何响应Android 系统的操作的。事实上,一行代码有可能会破坏整个App 的性能,只是因为它滥用了一些硬件限制。但困难不仅仅是这些,因为有时候为了了解后台发生的事情,你甚至必须学习一整套的性能分析工具。这基本上是看待App 开发的一种全新的方式,并不适合怯于挑战的人。
  Doug 写的这本书有什么了不起的地方呢?这本书是Android 性能方面的实战指南,不仅涵盖了基本的算法话题,还深入到了硬件和平台的工作方式,让你能够了解工具的异常显示是什么含义。这是一本能够帮助工程师转换视角的书。它不再只是关注视图和事件监听器,而是慢慢转换为理解内存边界和线程问题了。
  凌晨4 点,你的App 运行状况不好,咖啡机也坏了,并且创业孵化器室里有股烂白菜的味道;为了确保上午10:00 同风险投资者的会议能够顺利进行,你应该看看这本书。祝你好运!
  ——Colt McAnlis,资深布道师,谷歌公司团队主管,Google 的Android 性能模式系列视频的讲师(https://goo.gl/4ZJkY1) 

媒体评论

  “这本书将使得任何Android开发者都能够构建高效、运行良好的App。” 
  ——Brad Zeschuk,M2Catalyst公司工程副总裁 
  “本书是Android性能方面的权威实战指南,可以帮助工程师转换视角。书中不仅涵盖了基本的算法话题,还深入到了硬件和平台的工作方式,让你了解工具的异常显示是什么含义。”

  ——Colt McAnlis,资深布道师,Google公司团队主管 


作者:chinapub_2009 发表于2016/10/14 11:51:47 原文链接
阅读:8 评论:0 查看评论

位掩码(BitMask)

$
0
0


位运算在实际开发中用得很少,主要原因还是它对于不熟悉的人不好读不好懂不好计算,如果不经常实践会生疏。但它的优点自然是计算快,代码更少。在某些地方它的优势会更加明显比如如下代码(http://xxgblog.com/2013/09/15/java-bitmask/):

public class NewPermission {
	// 是否允许查询,二进制第1位,0表示否,1表示是
	public static final int ALLOW_SELECT = 1 << 0; // 0001
	
	// 是否允许新增,二进制第2位,0表示否,1表示是
	public static final int ALLOW_INSERT = 1 << 1; // 0010
	
	// 是否允许修改,二进制第3位,0表示否,1表示是
	public static final int ALLOW_UPDATE = 1 << 2; // 0100
	
	// 是否允许删除,二进制第4位,0表示否,1表示是
	public static final int ALLOW_DELETE = 1 << 3; // 1000
	
	// 存储目前的权限状态
	private int flag;

	/**
	 *  重新设置权限
	 */
	public void setPermission(int permission) {
		flag = permission;
	}

	/**
	 *  添加一项或多项权限
	 */
	public void enable(int permission) {
		flag |= permission;
	}
	
	/**
	 *  删除一项或多项权限
	 */
	public void disable(int permission) {
		flag &= ~permission;
	}
	
	/**
	 *  是否拥某些权限
	 */
	public boolean isAllow(int permission) {
		return (flag & permission) == permission;
	}
	
	/**
	 *  是否禁用了某些权限
	 */
	public boolean isNotAllow(int permission) {
		return (flag & permission) == 0;
	}
	
	/**
	 *  是否仅仅拥有某些权限
	 */
	public boolean isOnlyAllow(int permission) {
		return flag == permission;
	}
}

业务上,权限可以有多个,通过普通方式实现上面的业务功能也不会太麻烦。但上面这种取巧的方式在android的源码中是不少的,可以说是随处可见,比如EditText的InputType:

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.text;

import android.text.TextUtils;

/**
 * Bit definitions for an integer defining the basic content type of text
 * held in an {@link Editable} object. Supported classes may be combined
 * with variations and flags to indicate desired behaviors.
 *
 * <h3>Examples</h3>
 *
 * <dl>
 * <dt>A password field with with the password visible to the user:
 * <dd>inputType = TYPE_CLASS_TEXT |
 *     TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
 *
 * <dt>A multi-line postal address with automatic capitalization:
 * <dd>inputType = TYPE_CLASS_TEXT |
 *     TYPE_TEXT_VARIATION_POSTAL_ADDRESS |
 *     TYPE_TEXT_FLAG_MULTI_LINE
 *
 * <dt>A time field:
 * <dd>inputType = TYPE_CLASS_DATETIME |
 *     TYPE_DATETIME_VARIATION_TIME
 * </dl>
 */
public interface InputType {
    /**
     * Mask of bits that determine the overall class
     * of text being given.  Currently supported classes are:
     * {@link #TYPE_CLASS_TEXT}, {@link #TYPE_CLASS_NUMBER},
     * {@link #TYPE_CLASS_PHONE}, {@link #TYPE_CLASS_DATETIME}.
     * <p>IME authors: If the class is not one you
     * understand, assume {@link #TYPE_CLASS_TEXT} with NO variation
     * or flags.<p>
     */
    public static final int TYPE_MASK_CLASS = 0x0000000f;
    
    /**
     * Mask of bits that determine the variation of
     * the base content class.
     */
    public static final int TYPE_MASK_VARIATION = 0x00000ff0;
    
    /**
     * Mask of bits that provide addition bit flags
     * of options.
     */
    public static final int TYPE_MASK_FLAGS = 0x00fff000;
    
    /**
     * Special content type for when no explicit type has been specified.
     * This should be interpreted to mean that the target input connection
     * is not rich, it can not process and show things like candidate text nor
     * retrieve the current text, so the input method will need to run in a
     * limited "generate key events" mode, if it supports it. Note that some
     * input methods may not support it, for example a voice-based input
     * method will likely not be able to generate key events even if this
     * flag is set.
     */
    public static final int TYPE_NULL = 0x00000000;
    
    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------
    
    /**
     * Class for normal text.  This class supports the following flags (only
     * one of which should be set):
     * {@link #TYPE_TEXT_FLAG_CAP_CHARACTERS},
     * {@link #TYPE_TEXT_FLAG_CAP_WORDS}, and.
     * {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}.  It also supports the
     * following variations:
     * {@link #TYPE_TEXT_VARIATION_NORMAL}, and
     * {@link #TYPE_TEXT_VARIATION_URI}.  If you do not recognize the
     * variation, normal should be assumed.
     */
    public static final int TYPE_CLASS_TEXT = 0x00000001;
    
    /**
     * Flag for {@link #TYPE_CLASS_TEXT}: capitalize all characters.  Overrides
     * {@link #TYPE_TEXT_FLAG_CAP_WORDS} and
     * {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}.  This value is explicitly defined
     * to be the same as {@link TextUtils#CAP_MODE_CHARACTERS}. Of course,
     * this only affects languages where there are upper-case and lower-case letters.
     */
    public static final int TYPE_TEXT_FLAG_CAP_CHARACTERS = 0x00001000;
    
    /**
     * Flag for {@link #TYPE_CLASS_TEXT}: capitalize the first character of
     * every word.  Overrides {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}.  This
     * value is explicitly defined
     * to be the same as {@link TextUtils#CAP_MODE_WORDS}. Of course,
     * this only affects languages where there are upper-case and lower-case letters.
     */
    public static final int TYPE_TEXT_FLAG_CAP_WORDS = 0x00002000;
    
    /**
     * Flag for {@link #TYPE_CLASS_TEXT}: capitalize the first character of
     * each sentence.  This value is explicitly defined
     * to be the same as {@link TextUtils#CAP_MODE_SENTENCES}. For example
     * in English it means to capitalize after a period and a space (note that other
     * languages may have different characters for period, or not use spaces,
     * or use different grammatical rules). Of course,
     * this only affects languages where there are upper-case and lower-case letters.
     */
    public static final int TYPE_TEXT_FLAG_CAP_SENTENCES = 0x00004000;
    
    /**
     * Flag for {@link #TYPE_CLASS_TEXT}: the user is entering free-form
     * text that should have auto-correction applied to it. Without this flag,
     * the IME will not try to correct typos. You should always set this flag
     * unless you really expect users to type non-words in this field, for
     * example to choose a name for a character in a game.
     * Contrast this with {@link #TYPE_TEXT_FLAG_AUTO_COMPLETE} and
     * {@link #TYPE_TEXT_FLAG_NO_SUGGESTIONS}:
     * {@code TYPE_TEXT_FLAG_AUTO_CORRECT} means that the IME will try to
     * auto-correct typos as the user is typing, but does not define whether
     * the IME offers an interface to show suggestions.
     */
    public static final int TYPE_TEXT_FLAG_AUTO_CORRECT = 0x00008000;
    
    /**
     * Flag for {@link #TYPE_CLASS_TEXT}: the text editor (which means
     * the application) is performing auto-completion of the text being entered
     * based on its own semantics, which it will present to the user as they type.
     * This generally means that the input method should not be showing
     * candidates itself, but can expect the editor to supply its own
     * completions/candidates from
     * {@link android.view.inputmethod.InputMethodSession#displayCompletions
     * InputMethodSession.displayCompletions()} as a result of the editor calling
     * {@link android.view.inputmethod.InputMethodManager#displayCompletions
     * InputMethodManager.displayCompletions()}.
     * Note the contrast with {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} and
     * {@link #TYPE_TEXT_FLAG_NO_SUGGESTIONS}:
     * {@code TYPE_TEXT_FLAG_AUTO_COMPLETE} means the editor should show an
     * interface for displaying suggestions, but instead of supplying its own
     * it will rely on the Editor to pass completions/corrections.
     */
    public static final int TYPE_TEXT_FLAG_AUTO_COMPLETE = 0x00010000;
    
    /**
     * Flag for {@link #TYPE_CLASS_TEXT}: multiple lines of text can be
     * entered into the field.  If this flag is not set, the text field 
     * will be constrained to a single line. The IME may also choose not to
     * display an enter key when this flag is not set, as there should be no
     * need to create new lines.
     */
    public static final int TYPE_TEXT_FLAG_MULTI_LINE = 0x00020000;
    
    /**
     * Flag for {@link #TYPE_CLASS_TEXT}: the regular text view associated
     * with this should not be multi-line, but when a fullscreen input method
     * is providing text it should use multiple lines if it can.
     */
    public static final int TYPE_TEXT_FLAG_IME_MULTI_LINE = 0x00040000;
    
    /**
     * Flag for {@link #TYPE_CLASS_TEXT}: the input method does not need to
     * display any dictionary-based candidates. This is useful for text views that
     * do not contain words from the language and do not benefit from any
     * dictionary-based completions or corrections. It overrides the
     * {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} value when set.
     * Please avoid using this unless you are certain this is what you want.
     * Many input methods need suggestions to work well, for example the ones
     * based on gesture typing. Consider clearing
     * {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} instead if you just do not
     * want the IME to correct typos.
     * Note the contrast with {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} and
     * {@link #TYPE_TEXT_FLAG_AUTO_COMPLETE}:
     * {@code TYPE_TEXT_FLAG_NO_SUGGESTIONS} means the IME should never
     * show an interface to display suggestions. Most IMEs will also take this to
     * mean they should not try to auto-correct what the user is typing.
     */
    public static final int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 0x00080000;

    // ----------------------------------------------------------------------
    
    /**
     * Default variation of {@link #TYPE_CLASS_TEXT}: plain old normal text.
     */
    public static final int TYPE_TEXT_VARIATION_NORMAL = 0x00000000;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering a URI.
     */
    public static final int TYPE_TEXT_VARIATION_URI = 0x00000010;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering an e-mail address.
     */
    public static final int TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 0x00000020;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering the subject line of
     * an e-mail.
     */
    public static final int TYPE_TEXT_VARIATION_EMAIL_SUBJECT = 0x00000030;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering a short, possibly informal
     * message such as an instant message or a text message.
     */
    public static final int TYPE_TEXT_VARIATION_SHORT_MESSAGE = 0x00000040;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering the content of a long, possibly 
     * formal message such as the body of an e-mail.
     */
    public static final int TYPE_TEXT_VARIATION_LONG_MESSAGE = 0x00000050;

    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering the name of a person.
     */
    public static final int TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000060;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing address.
     */
    public static final int TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000070;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering a password.
     */
    public static final int TYPE_TEXT_VARIATION_PASSWORD = 0x00000080;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering a password, which should
     * be visible to the user.
     */
    public static final int TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 0x00000090;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of a web form.
     */
    public static final int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x000000a0;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering text to filter contents
     * of a list etc.
     */
    public static final int TYPE_TEXT_VARIATION_FILTER = 0x000000b0;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering text for phonetic
     * pronunciation, such as a phonetic name field in contacts. This is mostly
     * useful for languages where one spelling may have several phonetic
     * readings, like Japanese.
     */
    public static final int TYPE_TEXT_VARIATION_PHONETIC = 0x000000c0;
    
    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering e-mail address inside
     * of a web form.  This was added in
     * {@link android.os.Build.VERSION_CODES#HONEYCOMB}.  An IME must target
     * this API version or later to see this input type; if it doesn't, a request
     * for this type will be seen as {@link #TYPE_TEXT_VARIATION_EMAIL_ADDRESS}
     * when passed through {@link android.view.inputmethod.EditorInfo#makeCompatible(int)
     * EditorInfo.makeCompatible(int)}.
     */
    public static final int TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS = 0x000000d0;

    /**
     * Variation of {@link #TYPE_CLASS_TEXT}: entering password inside
     * of a web form.  This was added in
     * {@link android.os.Build.VERSION_CODES#HONEYCOMB}.  An IME must target
     * this API version or later to see this input type; if it doesn't, a request
     * for this type will be seen as {@link #TYPE_TEXT_VARIATION_PASSWORD}
     * when passed through {@link android.view.inputmethod.EditorInfo#makeCompatible(int)
     * EditorInfo.makeCompatible(int)}.
     */
    public static final int TYPE_TEXT_VARIATION_WEB_PASSWORD = 0x000000e0;

    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------
    
    /**
     * Class for numeric text.  This class supports the following flags:
     * {@link #TYPE_NUMBER_FLAG_SIGNED} and
     * {@link #TYPE_NUMBER_FLAG_DECIMAL}.  It also supports the following
     * variations: {@link #TYPE_NUMBER_VARIATION_NORMAL} and
     * {@link #TYPE_NUMBER_VARIATION_PASSWORD}.
     * <p>IME authors: If you do not recognize
     * the variation, normal should be assumed.</p>
     */
    public static final int TYPE_CLASS_NUMBER = 0x00000002;
    
    /**
     * Flag of {@link #TYPE_CLASS_NUMBER}: the number is signed, allowing
     * a positive or negative sign at the start.
     */
    public static final int TYPE_NUMBER_FLAG_SIGNED = 0x00001000;
    
    /**
     * Flag of {@link #TYPE_CLASS_NUMBER}: the number is decimal, allowing
     * a decimal point to provide fractional values.
     */
    public static final int TYPE_NUMBER_FLAG_DECIMAL = 0x00002000;
    
    // ----------------------------------------------------------------------

    /**
     * Default variation of {@link #TYPE_CLASS_NUMBER}: plain normal
     * numeric text.  This was added in
     * {@link android.os.Build.VERSION_CODES#HONEYCOMB}.  An IME must target
     * this API version or later to see this input type; if it doesn't, a request
     * for this type will be dropped when passed through
     * {@link android.view.inputmethod.EditorInfo#makeCompatible(int)
     * EditorInfo.makeCompatible(int)}.
     */
    public static final int TYPE_NUMBER_VARIATION_NORMAL = 0x00000000;

    /**
     * Variation of {@link #TYPE_CLASS_NUMBER}: entering a numeric password.
     * This was added in {@link android.os.Build.VERSION_CODES#HONEYCOMB}.  An
     * IME must target this API version or later to see this input type; if it
     * doesn't, a request for this type will be dropped when passed
     * through {@link android.view.inputmethod.EditorInfo#makeCompatible(int)
     * EditorInfo.makeCompatible(int)}.
     */
    public static final int TYPE_NUMBER_VARIATION_PASSWORD = 0x00000010;

    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------
    
    /**
     * Class for a phone number.  This class currently supports no variations
     * or flags.
     */
    public static final int TYPE_CLASS_PHONE = 0x00000003;
    
    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------
    // ----------------------------------------------------------------------
    
    /**
     * Class for dates and times.  It supports the
     * following variations:
     * {@link #TYPE_DATETIME_VARIATION_NORMAL}
     * {@link #TYPE_DATETIME_VARIATION_DATE}, and
     * {@link #TYPE_DATETIME_VARIATION_TIME}.
     */
    public static final int TYPE_CLASS_DATETIME = 0x00000004;
    
    /**
     * Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
     * both a date and time.
     */
    public static final int TYPE_DATETIME_VARIATION_NORMAL = 0x00000000;
    
    /**
     * Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
     * only a date.
     */
    public static final int TYPE_DATETIME_VARIATION_DATE = 0x00000010;
    
    /**
     * Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
     * only a time.
     */
    public static final int TYPE_DATETIME_VARIATION_TIME = 0x00000020;
}


由于位掩码方式又一个很大的问题,就是类型安全,如果不对类型安全控制后果会难以想象,因此枚举被用进去,以对参数类型进行控制,源码当中使用的是@IntDef,而EnumSet作为新的实现形式,在任何方面都比位掩码方式差不多或者更好,可读性,性能,以及类型安全等。权限逻辑如下:


public class NewPermission2 {

  private EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);

  private enum Permission {
    SELECT, INSERT, UPDATE, DELETE;

    public static final EnumSet<Permission> ALL_PERMISSIONS = EnumSet.allOf(Permission.class);
    public static final EnumSet<Permission> VIP_PERMISSIONS =
        EnumSet.range(Permission.SELECT, Permission.UPDATE);
  }

  /**
   * 重新设置权限
   */
  public void setPermission(EnumSet<Permission> s) {
    this.permissions.clear();
    this.permissions = s;
  }

  /**
   * 删除一项或多项权限
   */
  public void disable(Permission permission) {
    permissions.remove(permission);
  }

  /**
   * 是否拥某些权限
   */
  public boolean isAllow(Permission s) {
    return permissions.contains(s);
  }

  /**
   * 是否是游客
   */
  public boolean isVip(Permission s) {
    return Permission.VIP_PERMISSIONS.contains(s);
  }
}

EnumSet具体实现可参看源码。个人觉得这种方式最大的好处,在于可控,无论是对业务逻辑很好理解,还是对代码实现的方便,各个方面来看,都是要比位掩码这种取巧方式的官方化。可是对于android开发的性能方面:

1 存在个别反射的身影

2 EnumSet是实现Set的集合类

基于以上两点,那么在设计用到Enumset的类的时候,还是需要谨慎一些,尽管官方给出了很可靠的保证。



另外一些讲得不错的博文:

http://eddmann.com/posts/using-bit-flags-and-enumsets-in-java/

http://dhruba.name/2008/12/31/effective-java-item-32-use-enumset-instead-of-bit-fields/


作者:Pizza_Lawson 发表于2016/10/14 12:01:29 原文链接
阅读:11 评论:0 查看评论

Android群英传知识点回顾——第九章:Android系统信息与安全机制

$
0
0

Android群英传知识点回顾——第九章:Android系统信息与安全机制


知识点目录

  • 9.1 Android系统信息获取
    • 9.1.1 android.os.Build
    • 9.1.2 SystemProperty
    • 9.1.3 Android系统信息实例
  • 9.2 Android Apk应用信息获取之PackageManager
    • 9.2.1 PackageManager
  • 9.3 Android Apk应用信息获取之ActivityManager
  • 9.4 解析Packages.xml获取系统信息
  • 9.5 Android安全机制
    • 9.5.1 Android安全机制简介
    • 9.5.2 Android系统安全隐患
    • 9.5.3 Android Apk反编译
    • 9.5.4 Android Apk加密

知识点回顾

9.1 Android系统信息获取

要获取系统的配置信息,通常可以从以下两个方面获取:

  • android.os.Build
  • SystemProperty

9.1.1 android.os.Build

该类包含系统编译时大量设备、配置信息:

  • Build.BOARD:主板
  • Build.BRAND:Android系统定制商
  • Build.SUPPORTED_ABIS:CPU指令集
  • Build.DEVICE:设备参数
  • Build.DISPLAY:显示屏参数
  • Build.FINGERPRINT:唯一编号
  • Build.SERIAL:硬件序列号
  • Build.ID:修订版本列表
  • Build.MANUFACTURER:硬件制造商
  • Build.MODEL:版本
  • Build.HARDWARE:硬件名
  • Build.PRODUCT:手机产品名
  • Build.TAGS:描述Build的标签
  • Build.TYPE:Builder类型
  • Build.VERSION.CODENAME:当前开发代号
  • Build.VERSION.INCREMENTAL:源码控制版本号
  • Build.VERSION.RELEASE:版本字符串
  • Build.VERSION.SDK_INT:版本号
  • Build.HOST:host值
  • Build.USER:User名
  • Build.TIME:编译时间

9.1.2 SystemProperty

SystemProperty包含了许多系统配置属性值和参数:

  • os.version:OS版本
  • os.name:OS名称
  • os.arch:OS架构
  • user.home:Home属性
  • user.name:Name属性
  • user.dir:Dir属性
  • user.timezone:时区
  • path.separator:路径分隔符
  • line.separator:行分隔符
  • file.separator:文件分隔符
  • java.vendor.url:Java vender Url属性
  • java.class.path:Java Class属性
  • java.class.version:Java Class版本
  • java.vendor:Java Vender属性
  • java.version:Java版本
  • java.home:Java Home属性

9.1.3 Android系统信息实例

上面列举了那么多,让我们通过代码获取他们的系统信息:

String board = Build.BOARD;
String brand = Build.BRAND;

String os_version = System.getProperty("os.version");
String os_name = System.getProperty("os.name");

我们还可以通过命令行模式查看系统信息:

  • 命令行模式进入system/build.prop文件目录,使用cat build.prop命令查看文件信息

我们还可以通过adb shell的getprop来获取对应的属性值:

  • 进入adb shell,使用getprop ro.build.id获取信息

还有一个非常重要的目录存储系统信息,那就是/proc目录:

  • 命令行模式进入/proc文件目录,使用cat cpuinfo命令打开cpuinfo文件查看系统信息

9.2 Android Apk应用信息获取之PackageManager

看了这么多系统信息,应该看Apk应用信息了,在ADB Shell命令中,有两个非常强大的助手,PM和AM,PM主宰着应用的包管理,而AM主宰着应用的活动管理

9.2.1 PackageManager

  • ActivityInfo
    ActivityInfo封装在了Mainfest文件中的< activity >和< eceiver>之间的所有信息,包括name、icon、label、launchMode等。
  • ServiceInfo
    ServiceInfo与ActivityInfo类似,封装了< service>之间的所有信息。
  • ApplicationInfo
    它封装了< application>之间的信息,特别的是,ApplicationInfo包含了很多Flag
    • FLAG_SYSTEM表示为系统应用
    • FLAG_EXTERNAL_STORAGE表示为安装在SDcard上的应用
  • PackageInfo
    PackageInfo与前面三个Info类类似,都是用于封装Manifest文件的相关节点信息,而PageageInfo包含了所有的Activity和Service信息。
  • ResolveInfo
    ResolveInfo包含了< intent>信息的上一级信息,所以它可以返回ActivityInfo、ServiceInfo等包含了< intent>的信息
  • PackageManager中封装的用来获取这些信息的方法:
    • getPackageManager():通过这个方法可以返回一个PackageManager对象
    • getApplicationInfo():以ApplicationInfo的形式返回指定包名的ApplicationInfo
    • getApplicationIcon():返回指定包名的Icon
    • getInstalledApplications():以ApplicationInfo的形式返回安装的应用
    • getInstalledPackages():以PackageInfo的形式返回安装的应用
    • queryIntentActivities():返回指定Intent的ResolveInfo对象、Activity集合
    • queryIntentServices():返回指定Intent的ResolveInfo对象、Service集合
    • resolveActivity():返回指定Intent的Activity
    • resolveService():返回指定Intent的Service

下面通过一个实际的例子通过PackageManager筛选不同类型的App,利用ApplicationInfo中的FLAG_SYSTEM来判断:

app.flags & ApplicationInfo.FLAG_SYSTEM
  • 如果当前应用的flags & ApplicationInfo.FLAG_SYSTEM != 0则为系统应用
  • 如果flags & ApplicationInfo.FLAG_SYSTEM <= 0 则为第三方应用
  • 特殊的,当系统应用升级后,也将会成为第三方应用: flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP != 0
  • 如果当前应用的flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE != 0 则为安装在SDCard上的应用

我们封装一个Bean来保存我们所需的字段:

public class PMAppInfo {

    private String appLabel;
    private Drawable appIcon;
    private String pkgName;

    public PMAPPInfo(){
    }

    public String getAppLabel() {
        return appLabel;
    }

    public void setAppLabel(String appLabel) {
        this.appLabel = appLabel;
    }

    public Drawable getAppIcon() {
        return appIcon;
    }

    public void setAppIcon(Drawable appIcon) {
        this.appIcon = appIcon;
    }

    public String getPkgName() {
        return pkgName;
    }

    public void setPkgName(String pkgName) {
        this.pkgName = pkgName;
    }
}

接下来,通过上面所说的判断方法来判断各种类型的应用:

private List<PMAppInfo> getAppInfo(int flag) {
    // 获取PackageManager对象
    pm = this.getPackageManager();
    // 获取应用信息
    List<ApplicationInfo> listAppcations = pm
            .getInstalledApplications(
                    PackageManager.GET_UNINSTALLED_PACKAGES);
    List<PMAppInfo> appInfos = new ArrayList<PMAppInfo>();
    // 判断应用类型
    switch (flag) {
        case ALL_APP:
            appInfos.clear();
            for (ApplicationInfo app : listAppcations) {
                appInfos.add(makeAppInfo(app));
            }
            break;
        case SYSTEM_APP:
            appInfos.clear();
            for (ApplicationInfo app : listAppcations) {
                if ((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
                    appInfos.add(makeAppInfo(app));
                }
            }
            break;
        case THIRD_APP:
            appInfos.clear();
            for (ApplicationInfo app : listAppcations) {
                if ((app.flags & ApplicationInfo.FLAG_SYSTEM) <= 0) {
                    appInfos.add(makeAppInfo(app));
                } else if ((app.flags &
                        ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
                    appInfos.add(makeAppInfo(app));
                }
            }
            break;
        case SDCARD_APP:
            appInfos.clear();
            for (ApplicationInfo app : listAppcations) {
                if ((app.flags &
                        ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
                    appInfos.add(makeAppInfo(app));
                }
            }
            break;
        default:
            return null;
    }
    return appInfos;
}

private PMAppInfo makeAppInfo(ApplicationInfo app) {
    PMAppInfo appInfo = new PMAppInfo();
    appInfo.setAppLabel((String) app.loadLabel(pm));
    appInfo.setAppIcon(app.loadIcon(pm));
    appInfo.setPkgName(app.packageName);
    return appInfo;
}

效果图

9.3 Android Apk应用信息获取之ActivityManager

ActivityManager获取应用程序信息封装了不少Bean对象:

  • ActivityManager.MemoryInfo
    MemoryInfo有几个非常重要的字段:availMem(系统可用内存),totalMem(总内存),threshold(低内存的阈值,即区分是否低内存的临界值),lowMemory(是否处于低内存)
  • Debug.MemoryInfo
    这个MemoryInfo用于统计进程下的内存信息
  • RunningAppProcessInfo
    运行进程的信息,存储的字段有:processName(进程名),pid(进程pid),uid(进程uid),pkgList(该进程下的所有包)
  • RunningServiceInfo
    运行的服务信息,在它里面同样包含了一些服务进程信息,同时还有一些其他信息,activeSince(第一次被激活的时间、方式),foreground(服务是否在后台执行)

下面同样通过例子来看看如何使用ActivityManager,我们封装一个Bean来保存我们所需的字段:

public class AMProcessInfo {

    private String pid;
    private String uid;
    private String memorySize;
    private String processName;

    public AMProcessInfo() {
    }

    public String getPid() {
        return pid;
    }

    public void setPid(String pid) {
        this.pid = pid;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getMemorySize() {
        return memorySize;
    }

    public void setMemorySize(String memorySize) {
        this.memorySize = memorySize;
    }

    public String getProcessName() {
        return processName;
    }

    public void setProcessName(String processName) {
        this.processName = processName;
    }
}

接下来,调用getRunningAppProcesses方法,返回当前运行的进程信息,并将我们关心的信息保存到Bean中:

private List<AMProcessInfo> getRunningProcessInfo() {
        mAmProcessInfoList = new ArrayList<AMProcessInfo>();

        List<ActivityManager.RunningAppProcessInfo> appProcessList =
                mActivityManager.getRunningAppProcesses();

        for (int i = 0; i < appProcessList.size(); i++) {
            ActivityManager.RunningAppProcessInfo info =
                    appProcessList.get(i);
            int pid = info.pid;
            int uid = info.uid;
            String processName = info.processName;
            int[] memoryPid = new int[]{pid};
            Debug.MemoryInfo[] memoryInfo = mActivityManager
                    .getProcessMemoryInfo(memoryPid);
            int memorySize = memoryInfo[0].getTotalPss();

            AMProcessInfo processInfo = new AMProcessInfo();
            processInfo.setPid("" + pid);
            processInfo.setUid("" + uid);
            processInfo.setMemorySize("" + memorySize);
            processInfo.setProcessName(processName);
            mAmProcessInfoList.add(processInfo);
        }
        return mAmProcessInfoList;
    }

效果图

9.4 解析Packages.xml获取系统信息

在系统初始化的时候,PackageManager的底层实现类PackageManagerService会去扫描系统的一些特定目录,并且解析其中的Apk文件,同时,Android把它获取到的应用信息,保存到XML文件中,做成一个应用的花名册,当系统中的APK安装、删除、升级时,它也会被更新

这个packages.xml位于/data/system/目录下,我们用adb pull命令把他导出来,进行查看分析:

  • < permissions>标签
    permissions标签定义了目前系统所有的权限,并分为两类:系统定义的和Apk定义的
  • < package>标签
    package代表的是一个apk的属性,其中各节点的信息含义大致为

    • name:APK的包名
    • cadePath:APK安装路径,主要在system/app和data/app两种,前者是厂商定制的Apk,后者是用户安装的第三方Apk
    • userid:用户ID
    • version:版本
  • < perms>标签
    对应Apk的AndroidManifest文件中的< uses-permission>标签,记录Apk的权限信息

9.5 Android安全机制

无知识点

9.5.1 Android安全机制简介

  • 第一道防线:代码安全机制——代码混淆proguard
    proguard可以混淆关键代码、替换命名让破坏者阅读难,同样也可以压缩代码,优化编译后的Java字节码
  • 第二道防线:应用接入权限控制——清单文件权限声明,权限检查机制
    任何App在使用Android受限资源的时候,都需要显示向系统声明所需的权限,只有当一个应用App具有相应的权限,才能申请受限资源的时候,通过权限机制的检查并使用系统的Binder对象完成对系统服务的调用,但是这道防线也有先天性不足,如以下几项:
    • 被授予的权限无法停止
    • 在应用声明App使用权限的时,用户无法针对部分权限进行限制
    • 权限的声明机制与用户的安全理念相关
      Android系统通常按照以下顺序来检查操作者的权限:
      • 首先,判断permission名称,如果为空则直接返回PERMISSION_DENIED
      • 其次,判断Uid,如果为0则为root权限,不做权限控制,如果为System Service的Uid则为系统服务,不做权限控制,如果Uid与参数中的请求Uid不同则返回PERMISSION_DENIED
      • 最后,通过调用packagemanageservice.checkUidPermission()方法来判断该Uid是否具有相应的权限,该方法会去XML的权限列表和系统级的platform.xml中进行查找
  • 第三道防线:应用签名机制一数字证书
    Android中所有的App都会有一个数字证书,这就是App的签名,数字证书用于保护App的作者和其App的信任关系,只有拥有相同数字签名的App,才会在升级时被认为是同一App,而且Android系统不会安装没有签名的App
  • 第四道防线:Linux内核层安全机制一一Uid 访问权限控制
    Animid本质是基于Linux内核开发的,所以Android同样继承了Linux的安全特性,比如文件系统的权限控制是由user,group,other与读(r),写(w),执行(x)的不同组合来实现的,同样,Android也实现了这套机制,通常情况下,只有System、root用户才有权限访问到系统文件,而一般用户无法访问
  • 第五道防线:Android虚拟机沙箱机制——沙箱隔流
    Android的App运行在虚拟机中,因此才有沙箱机制,可以让应用之间相互隔离,通常情况下,不同的应用之间不能互相访问,每个App都有与之对应的Uid,每个App也运行在单独的虚似机中,与其他应用完全隔离,在实现安全机制的基础上,也让应用之间能够互不影响,即时一个应用崩溃,,也不会导致其他应用异常

9.5.2 Android系统安全隐患

  • 代码漏洞
  • Root风险
  • 安全机制不健全
  • 用户安全意识
  • Android开发原则与安全

书本列举的几个点相信大家也都比较熟悉,所以不做解释了

9.5.3 Android Apk反编译

这里个人的博客有简单的介绍:Android四大组件——Activity切换效果、杀死进程、杀死所有Activity、安装及反编译

9.5.4 Android Apk加密

为了能够对编译好的JavaClass文件进行一些保护,通常会用ProGuard混淆代码:

  • ProGuard:用无意义的字母来重命名类、字段、方法和属性
  • 删除无用的类、字段、方法和属性,以及删除没用的注释,最大限度地优化字节码文件

使用ProGuard也简单,在Android Studio中可以打开build.gradle(Module:app):

buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

这里的minifyEnabled就是打开ProGuard的开关,proGuardFiles属于配置混淆文件,分为两部分:

  • 使用默认的混淆文件,位于< SDK目录>/tools/proguard/proguard-android.txt目录下
  • 使用自定义混淆文件,可以在项目的App文件夹找到这个文件,在这个文件里可以定义引入的第三方依赖包的混淆规则
作者:qq_30379689 发表于2016/10/14 12:36:37 原文链接
阅读:2 评论:0 查看评论

IOS 10 推送

$
0
0

背景

iOS10 新特性一出,各个大神就早已研究新特性能给场景智能化所带来的好处(唉,可惜我只是一个小白)。我也被安排适配iOS10的推送工作!

Apple 表示这是 iOS 有史以来最大的升级(our biggest release yet),更加智能开放的 Siri 、强化应用对 3D Touch 支持、 HomeKit 、电话拦截及全新设计的通知等等…

iOS 10 中将之前繁杂的推送通知统一成UserNotifications.framework 来集中管理和使用通知功能,还增加一些实用的功能——撤回单条通知、更新已展示通知、中途修改通知内容、在通知中显示多媒体资源、自定义UI等功能,功能着实强大!

本文主要是针对iOS 10的消息通知做介绍,所以很多代码没有对iOS 10之前做添加适配。

基本原理

iOS推送分为Local Notifications(本地推送) 和 Remote Notifications(远程推送)(原理图来源于网络,如有侵权请告知,我会添加来源,我怕我赔不起)

Local Notifications(本地推送)


Local Notifications.png
  1. App本地创建通知,加入到系统的Schedule里,
  2. 如果触发器条件达成时会推送相应的消息内容

Remote Notifications(远程推送)


Remote Notifications1.jpg

图中,Provider是指某个iPhone软件的Push服务器,这篇文章我将使用我花了12块大洋(心疼)买的 APNS Pusher 作为我的推送源。

APNS 是Apple Push Notification Service(Apple Push服务器)的缩写,是苹果的服务器。

上图可以分为三个阶段:

第一阶段:APNS Pusher应用程序把要发送的消息、目的iPhone的标识打包,发给APNS。

第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发到iPhone。

第三阶段:iPhone把发来的消息传递给相应的应用程序, 并且按照设定弹出Push通知。


Remote Notifications2.jpeg

从上图我们可以看到:

  1. 首先是应用程序注册消息推送。

  2. IOS跟APNS Server要deviceToken。应用程序接受deviceToken。

  3. 应用程序将deviceToken发送给PUSH服务端程序。

  4. 服务端程序向APNS服务发送消息。

  5. APNS服务将消息发送给iPhone应用程序。

基本配置和基本方法

如果只是简单的本地推送,跳过1 2 步骤,直接到3

1、 如果你的App有远端推送的话,那你需要开发者账号的,需要新建一个对应你bundle的push 证书。证书这一块我就不说了,如果针对证书有什么问题可以给我留言,我会单独把证书相关的知识点整理起来!如果你没有账号,可以到某宝买个,很便宜。
2、 Capabilities中打开Push Notifications 开关
在XCode7中这里的开关不打开,推送也是可以正常使用的,但是在XCode8中,这里的开关必须要打开,不然会报错:

Error Domain=NSCocoaErrorDomain Code=3000 "未找到应用程序的“aps-environment”的授权字符串" UserInfo={NSLocalizedDescription=未找到应用程序的“aps-environment”的授权字符串}

打开后会自动在项目里生成entitlements文件。


Push Notification开关.png

entitlements文件.png

3、 推送的注册

第一步: 导入 #import <UserNotifications/UserNotifications.h>
且要遵守<UNUserNotificationCenterDelegate>的协议,在Appdelegate.m中。
这里需要注意,我们最好写成这种形式(防止低版本找不到头文件出现问题)

#ifdef NSFoundationVersionNumber_iOS_9_x_Max
#import <UserNotifications/UserNotifications.h>
#endif

第二步:我们需要在
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中注册通知,代码如下

  - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self replyPushNotificationAuthorization:application];
    return YES;
}


#pragma mark - 申请通知权限
// 申请通知权限
- (void)replyPushNotificationAuthorization:(UIApplication *)application{

    if (IOS10_OR_LATER) {
        //iOS 10 later
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        //必须写代理,不然无法监听通知的接收与点击事件
        center.delegate = self;
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (!error && granted) {
                //用户点击允许
                NSLog(@"注册成功");
            }else{
                //用户点击不允许
                NSLog(@"注册失败");
            }
        }];

        // 可以通过 getNotificationSettingsWithCompletionHandler 获取权限设置
        //之前注册推送服务,用户点击了同意还是不同意,以及用户之后又做了怎样的更改我们都无从得知,现在 apple 开放了这个 API,我们可以直接获取到用户的设定信息了。注意UNNotificationSettings是只读对象哦,不能直接修改!
        [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
            NSLog(@"========%@",settings);
        }];
    }else if (IOS8_OR_LATER){
        //iOS 8 - iOS 10系统
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
        [application registerUserNotificationSettings:settings];
    }else{
        //iOS 8.0系统以下
        [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound];
    }

    //注册远端消息通知获取device token
    [application registerForRemoteNotifications];
}

上面需要注意:

1. 必须写代理,不然无法监听通知的接收与点击事件
 center.delegate = self;

下面是我在项目里定义的宏
#define IOS10_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0)
#define IOS9_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0)
#define IOS8_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
#define IOS7_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)

2. 之前注册推送服务,用户点击了同意还是不同意,以及用户之后又做了怎样的更改我们都无从得知,现在 apple 开放了这个 API,我们可以直接获取到用户的设定信息了。注意UNNotificationSettings是只读对象哦,不能直接修改!只能通过以下方式获取
 [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
            NSLog(@"========%@",settings);
  }];
打印信息如下:
========<UNNotificationSettings: 0x1740887f0; authorizationStatus: Authorized, notificationCenterSetting: Enabled, soundSetting: Enabled, badgeSetting: Enabled, lockScreenSetting: Enabled, alertSetting: NotSupported, carPlaySetting: Enabled, alertStyle: Banner>

4、 远端推送需要获取设备的Device Token的方法是没有变的,代码如下

#pragma  mark - 获取device Token
//获取DeviceToken成功
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{

    //解析NSData获取字符串
    //我看网上这部分直接使用下面方法转换为string,你会得到一个nil(别怪我不告诉你哦)
    //错误写法
    //NSString *string = [[NSString alloc] initWithData:deviceToken encoding:NSUTF8StringEncoding];


    //正确写法
    NSString *deviceString = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    deviceString = [deviceString stringByReplacingOccurrencesOfString:@" " withString:@""];

    NSLog(@"deviceToken===========%@",deviceString);
}

//获取DeviceToken失败
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    NSLog(@"[DeviceToken Error]:%@\n",error.description);
}

5、这一步吊了,这是iOS 10系统更新时,苹果给了我们2个代理方法来处理通知的接收和点击事件,这两个方法在<UNUserNotificationCenterDelegate>的协议中,大家可以查看下。

@protocol UNUserNotificationCenterDelegate <NSObject>

@optional

// The method will be called on the delegate only if the application is in the foreground. If the method is not implemented or the handler is not called in a timely manner then the notification will not be presented. The application can choose to have the notification presented as a sound, badge, alert and/or in the notification list. This decision should be based on whether the information in the notification is otherwise visible to the user.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler __IOS_AVAILABLE(10.0) __TVOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0);

// The method will be called on the delegate when the user responded to the notification by opening the application, dismissing the notification or choosing a UNNotificationAction. The delegate must be set before the application returns from applicationDidFinishLaunching:.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler __IOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) __TVOS_PROHIBITED;

@end

此外,苹果把本地通知跟远程通知合二为一。区分本地通知跟远程通知的类是UNPushNotificationTrigger.h类中,UNPushNotificationTrigger的类型是新增加的,通过它,我们可以得到一些通知的触发条件 ,解释如下:

  1. UNPushNotificationTrigger (远程通知) 远程推送的通知类型
  2. UNTimeIntervalNotificationTrigger (本地通知) 一定时间之后,重复或者不重复推送通知。我们可以设置timeInterval(时间间隔)和repeats(是否重复)。
  3. UNCalendarNotificationTrigger(本地通知) 一定日期之后,重复或者不重复推送通知 例如,你每天8点推送一个通知,只要dateComponents为8,如果你想每天8点都推送这个通知,只要repeats为YES就可以了。
  4. UNLocationNotificationTrigger (本地通知)地理位置的一种通知,
    当用户进入或离开一个地理区域来通知。
    现在先提出来,后面我会一一代码演示出每种用法。还是回到两个很吊的代理方法吧
#pragma mark - iOS10 收到通知(本地和远端) UNUserNotificationCenterDelegate

//App处于前台接收通知时
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{

    //收到推送的请求
    UNNotificationRequest *request = notification.request;

    //收到推送的内容
    UNNotificationContent *content = request.content;

    //收到用户的基本信息
    NSDictionary *userInfo = content.userInfo;

    //收到推送消息的角标
    NSNumber *badge = content.badge;

    //收到推送消息body
    NSString *body = content.body;

    //推送消息的声音
    UNNotificationSound *sound = content.sound;

    // 推送消息的副标题
    NSString *subtitle = content.subtitle;

    // 推送消息的标题
    NSString *title = content.title;

    if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        //此处省略一万行需求代码。。。。。。
        NSLog(@"iOS10 收到远程通知:%@",userInfo);

    }else {
        // 判断为本地通知
        //此处省略一万行需求代码。。。。。。
        NSLog(@"iOS10 收到本地通知:{\\\\nbody:%@,\\\\ntitle:%@,\\\\nsubtitle:%@,\\\\nbadge:%@,\\\\nsound:%@,\\\\nuserInfo:%@\\\\n}",body,title,subtitle,badge,sound,userInfo);
    }


    // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以设置
    completionHandler(UNNotificationPresentationOptionBadge|
                      UNNotificationPresentationOptionSound|
                      UNNotificationPresentationOptionAlert);

}


//App通知的点击事件
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler{
    //收到推送的请求
    UNNotificationRequest *request = response.notification.request;

    //收到推送的内容
    UNNotificationContent *content = request.content;

    //收到用户的基本信息
    NSDictionary *userInfo = content.userInfo;

    //收到推送消息的角标
    NSNumber *badge = content.badge;

    //收到推送消息body
    NSString *body = content.body;

    //推送消息的声音
    UNNotificationSound *sound = content.sound;

    // 推送消息的副标题
    NSString *subtitle = content.subtitle;

    // 推送消息的标题
    NSString *title = content.title;

    if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        NSLog(@"iOS10 收到远程通知:%@",userInfo);
        //此处省略一万行需求代码。。。。。。

    }else {
        // 判断为本地通知
        //此处省略一万行需求代码。。。。。。
        NSLog(@"iOS10 收到本地通知:{\\\\nbody:%@,\\\\ntitle:%@,\\\\nsubtitle:%@,\\\\nbadge:%@,\\\\nsound:%@,\\\\nuserInfo:%@\\\\n}",body,title,subtitle,badge,sound,userInfo);
    }

    //2016-09-27 14:42:16.353978 UserNotificationsDemo[1765:800117] Warning: UNUserNotificationCenter delegate received call to -userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: but the completion handler was never called.
    completionHandler(); // 系统要求执行这个方法
}

需要注意的:

1.下面这个代理方法,只会是app处于前台状态 前台状态 and 前台状态下才会走,后台模式下是不会走这里的
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler


2.下面这个代理方法,只会是用户点击消息才会触发,如果使用户长按(3DTouch)、弹出Action页面等并不会触发。点击Action的时候会触发!
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler

3.点击代理最后需要执行:completionHandler(); // 系统要求执行这个方法
不然会报:
2016-09-27 14:42:16.353978 UserNotificationsDemo[1765:800117] Warning: UNUserNotificationCenter delegate received call to -userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: but the completion handler was never called.

4.不管前台后台状态下。推送消息的横幅都可以展示出来!后台状态不用说,前台时需要在前台代理方法中设置 ,设置如下:
// 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以设置
completionHandler(UNNotificationPresentationOptionBadge|
                  UNNotificationPresentationOptionSound|
                  UNNotificationPresentationOptionAlert);

6、 iOS 10之前接收通知的兼容方法

#pragma mark -iOS 10之前收到通知

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    NSLog(@"iOS6及以下系统,收到通知:%@", userInfo);
    //此处省略一万行需求代码。。。。。。
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    NSLog(@"iOS7及以上系统,收到通知:%@", userInfo);
    completionHandler(UIBackgroundFetchResultNewData);
    //此处省略一万行需求代码。。。。。。
}

段结:是不是以为就结束了?NO NO NO(你以为离开了幻境,其实才刚刚踏入幻境!)上面的介绍了基本原理、基本配置以及基本方法说明,现在做完这些工作,我们的学习才刚刚开始!现在天时、地利、人和、可以开始下面推送coding的学习和测试了。

在用户日常生活中会有很多种情形需要通知,比如:新闻提醒、定时吃药、定期体检、到达某个地方提醒用户等等,这些功能在 UserNotifications 中都提供了相应的接口。


图片来源于网络.jpeg

我们先学会基本的技能简单的推送(爬),后面在学习进阶定制推送(走),最后看看能不能高级推送(飞不飞起来看个人了,我是飞不起来):

基本Local Notifications(本地推送) 和 Remote Notifications(远程推送)

一、 基本的本地推送

本地推送生成主要流程就是:

1. 创建一个触发器(trigger)
2. 创建推送的内容(UNMutableNotificationContent)
3. 创建推送请求(UNNotificationRequest)
4. 推送请求添加到推送管理中心(UNUserNotificationCenter)中

1、新功能trigger可以在特定条件触发,有三类:UNTimeIntervalNotificationTrigger、UNCalendarNotificationTrigger、UNLocationNotificationTrigger

1.1、 UNTimeIntervalNotificationTrigger:一段时间后触发(定时推送

//timeInterval:单位为秒(s)  repeats:是否循环提醒
//50s后提醒
UNTimeIntervalNotificationTrigger *trigger1 = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:50 repeats:NO];

1.2 UNCalendarNotificationTrigger :调用
+ (instancetype)triggerWithDateMatchingComponents:(NSDateComponents *)dateComponents repeats:(BOOL)repeats;进行注册;时间点信息用 NSDateComponents.(定期推送

//在每周一的14点3分提醒
NSDateComponents *components = [[NSDateComponents alloc] init]; 
components.weekday = 2;
components.hour = 16;
components.minute = 3;
 // components 日期
UNCalendarNotificationTrigger *calendarTrigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:YES];

1.3、UNLocationNotificationTrigger:调用
+ (instancetype)triggerWithRegion:(CLRegion *)region repeats:(BOOL)repeats;
进行注册,地区信息使用CLRegion的子类CLCircularRegion,可以配置region属性 notifyOnEntrynotifyOnExit,是在进入地区、从地区出来或者两者都要的时候进行通知,这个测试过程专门从公司跑到家时刻关注手机有推送嘛,果然是有的(定点推送

  //首先得导入#import <CoreLocation/CoreLocation.h>,不然会regin创建有问题。
  // 创建位置信息
  CLLocationCoordinate2D center1 = CLLocationCoordinate2DMake(39.788857, 116.5559392);
  CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center1 radius:500 identifier:@"经海五路"];
  region.notifyOnEntry = YES;
  region.notifyOnExit = YES;
  // region 位置信息 repeats 是否重复 (CLRegion 可以是地理位置信息)
  UNLocationNotificationTrigger *locationTrigger = [UNLocationNotificationTrigger triggerWithRegion:region repeats:YES];

2、创建推送的内容(UNMutableNotificationContent)
UNNotificationContent:属性readOnly
UNMutableNotificationContent:属性有title、subtitle、body、badge、sound、lauchImageName、userInfo、attachments、categoryIdentifier、threadIdentifier

本地消息内容 内容限制大小 展示
title NSString 限制在一行,多出部分省略号
subtitle NSString 限制在一行,多出部分省略号
body NSString 通知栏出现时,限制在两行,多出部分省略号;预览时,全部展示

注意点: body中printf风格的转义字符,比如说要包含%,需要写成%% 才会显示,\同样

    // 创建通知内容 UNMutableNotificationContent, 注意不是 UNNotificationContent ,此对象为不可变对象。
    UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
    content.title = @"Dely 时间提醒 - title";
    content.subtitle = [NSString stringWithFormat:@"Dely 装逼大会竞选时间提醒 - subtitle"];
    content.body = @"Dely 装逼大会总决赛时间到,欢迎你参加总决赛!希望你一统X界 - body";
    content.badge = @666;
    content.sound = [UNNotificationSound defaultSound];
    content.userInfo = @{@"key1":@"value1",@"key2":@"value2"};

3、创建完整的本地推送请求Demo

//定时推送
+ (void)createLocalizedUserNotification{

    // 设置触发条件 UNNotificationTrigger
    UNTimeIntervalNotificationTrigger *timeTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0f repeats:NO];

    // 创建通知内容 UNMutableNotificationContent, 注意不是 UNNotificationContent ,此对象为不可变对象。
    UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
    content.title = @"Dely 时间提醒 - title";
    content.subtitle = [NSString stringWithFormat:@"Dely 装逼大会竞选时间提醒 - subtitle"];
    content.body = @"Dely 装逼大会总决赛时间到,欢迎你参加总决赛!希望你一统X界 - body";
    content.badge = @666;
    content.sound = [UNNotificationSound defaultSound];
    content.userInfo = @{@"key1":@"value1",@"key2":@"value2"};

    // 创建通知标示
    NSString *requestIdentifier = @"Dely.X.time";

    // 创建通知请求 UNNotificationRequest 将触发条件和通知内容添加到请求中
    UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifier content:content trigger:timeTrigger];

    UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
    // 将通知请求 add 到 UNUserNotificationCenter
    [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
        if (!error) {
            NSLog(@"推送已添加成功 %@", requestIdentifier);
            //你自己的需求例如下面:
            UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"本地通知" message:@"成功添加推送" preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
            [alert addAction:cancelAction];
            [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
            //此处省略一万行需求。。。。
        }
    }];

}

运行结果如下:


装X决赛通知.jpg

二、 基本的远端推送
如果你想模拟远端推送,按照我前面介绍的配置基本环境、证书、push开关和基本方法就可以模拟远端的基本远端推送。
1、运行工程则会拿到设备的Device Token,后面会用到。


device token.png

2、现在我们需要一个推送服务器给APNS发送信息。我前面说了我花了12块大洋(心疼死我了)买了一个APNS pusher 来模拟远端推送服务,当然你可以不花钱也可以用到,例如:
NWPusher


APNS pusher

3、你需要把你刚刚获取的device token填到相应位置,同时你要配置好push证书哦。

4、需要添加aps内容了,然后点击send就OK了

{
  "aps" : {
    "alert" : {
      "title" : "iOS远程消息,我是主标题!-title",
      "subtitle" : "iOS远程消息,我是主标题!-Subtitle",
      "body" : "Dely,why am i so handsome -body"
    },
    "badge" : "2"
  }
}

5、稍纵即逝你就收到了远端消息了


远端消息.jpg

6、Notification Management
对推送进行查、改、删。都需要一个必需的参数requestIdentifier

1、更新通知

Local Notification需要通过更新request.相同的requestIdentifier,重新添加到推送center就可以了,说白了就是重新创建local Notification request(只要保证requestIdentifier就ok了),应用场景如图


Local Notification更新前.png

Local Notification更新后.png

Remote Notification 更新需要通过新的字段apps-collapse-id来作为唯一标示,我前面用的APNS pusher暂不支持这个字段,不过github上有很多这样的工具:
https://github.com/KnuffApp/Knuff
这样remote 也可以更新推送消息

2、推送消息的查找和删除

// Notification requests that are waiting for their trigger to fire
//获取未送达的所有消息列表
- (void)getPendingNotificationRequestsWithCompletionHandler:(void(^)(NSArray<UNNotificationRequest *> *requests))completionHandler;
//删除所有未送达的特定id的消息
- (void)removePendingNotificationRequestsWithIdentifiers:(NSArray<NSString *> *)identifiers;
//删除所有未送达的消息
- (void)removeAllPendingNotificationRequests;

// Notifications that have been delivered and remain in Notification Center. Notifiations triggered by location cannot be retrieved, but can be removed.
//获取已送达的所有消息列表
- (void)getDeliveredNotificationsWithCompletionHandler:(void(^)(NSArray<UNNotification *> *notifications))completionHandler __TVOS_PROHIBITED;
//删除所有已送达的特定id的消息
- (void)removeDeliveredNotificationsWithIdentifiers:(NSArray<NSString *> *)identifiers __TVOS_PROHIBITED;
//删除所有已送达的消息
- (void)removeAllDeliveredNotifications __TVOS_PROHIBITED;

测试如下:

+  (void)notificationAction{
    NSString *requestIdentifier = @"Dely.X.time";
    UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];

    //删除设备已收到的所有消息推送
   // [center removeAllDeliveredNotifications];

    //删除设备已收到特定id的所有消息推送
   // [center removeDeliveredNotificationsWithIdentifiers:@[requestIdentifier]];

    //获取设备已收到的消息推送
    [center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * _Nonnull notifications) {
    }];
}

段结: 收到通知时你需要在appdelegate里面的代理方法里处理你的需求逻辑,这个需要你自己写了。到目前为止你掌握了基本的本地推送基本的远端推送!

不知不觉写了这么多字(全是TM废话)、本来继续打算写进阶的本地和远端推送(Media Attachments、Notification Actions、自定义推送界面等),留着下一篇博客继续分享吧,欲知后事如何,且听下会装X!

如果你喜欢可以点个喜欢^_^(竟有如此厚颜无耻之人)

下集预告:


推送图片.jpg

推送图片2.jpg


文/Dely(简书作者)
原文链接:http://www.jianshu.com/p/c58f8322a278
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
作者:st646889325 发表于2016/10/15 8:40:43 原文链接
阅读:67 评论:0 查看评论

文章标题

$
0
0

编码规范和优化建议

公司需要写一个代码规范,本人第一次写,完成后记录在blog

一、命名规范

1、类定义

例: ThisIsAClass, 

采用苹果推荐的方式,首字母大写,多个有实际意义的英文单词组成,每个单词的首字母大写。

在此基础上类名需要体现出这个类的类型

试图控制器:ThisIsAViewController
试图:ThisIsAView
按钮:ThisIsAButton
等等

个人不建议使用缩写,因为代码补全非常完善,单词再长也不会影响方便性,但缩写影响可读性
可讨论决定是否可使用类似 ThisIsAVC,ThisABtn,之类的缩写方式

2、属性

例:thisIsAObject

首字母小写,多个有实际意义的英文单词组成,每个单词的首字母大写。

在此基础上,需要提示这个变量的类型或者用途。

例如:

UITextField * userNameTextField;

UIButton * leftTopButton,doSomeActionButton;

等等

缩写问题同上,可讨论。

3、成员变量

_menberObject

在属性的基础上,需要以下划线开头。

其他规则和属性一样。

4、局部变量

参照属性

5、函数命名

首字母小写,多个有意义的单词组成,:后面首字母小写

例:

- (void)loginButtonDidPressed;
- (void)loginWithUserName:(NSString) userName password:(NSString *) password;

等等

6、函数形参

参照属性

若有可能与属性命名相同或者冲突,建议加上in,a等前缀

例:

- (void) someMethod:(id) inValue anotherObject:(id) aObject
{
    _vlaue = inVlaue;
    self.object = aObject;
}

7 、前缀

由于oc没有命名空间的概念,所有苹果建议使用前缀来防止第三方库、子工程之间命名冲突。

目前我们的工程中有的没有前缀,有前缀的又不统一,需要讨论是否统一。

二、代码组织

1、概述

目前我们的工程都采用MVC构架

所以页面代码都至少要包含,View(特别简单的可能省略),VewController(UIVewController子类),ModelManager(这么命名是区分于瘦model,也就是entity)

三者之间最好通过接口互相访问,隐藏实现细节。

比如,VC不需要知道view有哪些label,textfileld,只需要view提供加载数据的方法就可以,实现松耦合。这样如果只是页面排版变了,只要修改接口实现就可以,不用修改其他部分。这也是swift发布后,苹果提出的面相接口(协议)编程的思想。

规则提出的目的,都是指导我们对各模块之间职能划分,使各自功能单一,松散耦合,任一一方修改,尽可能的不影响其他模块。

2、页面
UIView:只做显示逻辑不做业务逻辑,它应该是纯被动的接受VC提供的数据,进行展示。
对用户操作进行收集,提示VC事件发生。

下面提供一些开发场景的处理建议:

case 1

如果view包含一个imageview,性别变量sex,值为male,显示图a,sex为female,显示图b,这种逻辑应该在VC中实现,而不应该把sex传给给View去保存。

//VC中
- (void) someMethod
{
    if (self.sex == male) //假设male为枚举
    {
        [view loadImage:imageA];
    }
    else
    {
        [view loadImage:imageB];
    }
}

//view中

(void)loadImage:(UIImage *)inImage
{
    self.imageView.image = inImage;
}

case 2

不要在init方法中,去取自身的frame,并以此对子view设置frame,而应该在layoutSubviews中设置
因为很多时候,init方法中,view本身的frame并不是最后显示的frame

- (void)layoutSubviews
 {
    // 一定要调用super的方法
    [super layoutSubviews];

    //子view的frame设置
    …
}

case 3

只是堆砌子View的情况一般不太需要自定义view。
自定义view,其实更应该发生在需要重载drawRect方法,或者事情响应touchesBegan/touchesEnded等方法时。而大部分情况其实不需要重载这些方法。只需要addSubvew。
这种情况其实更应该考虑是建一个子视图控制器,毕竟管理一个视图层级(view hierarchy)是VC的事情。

3、视图控制器

试图控制器负责:从并且只从modelManager获取数据,展示到View上。并且接受modelManager数据变化发生时通知,提供回调方法。

开发场景:
case 1

loadView:一般不建议重载,实在要用根据文档所诉,只应该创建视图层级(view hierarchy)也就是只应该做alloc和addSubview。并且如果重载它了,不要调用super,额外的赋值需在viewDidload之后再做。
注意:如果使用了xib或者storyboad就不要重载这个函数。而应该使用awakeFromNib。

case 2
viewDidload:作为入口,希望它能比较简洁直观,所以建议它内部只出现toDoList型的函数,具体的实现到各个函数中实现。
例如:

- (void) viewDidLoad
{
    [super viewDidLoad];

    [self setupViews];
    [self registerNotifications];
    [self setupModelManager];
    [self doOtherThing];
}

建议在viewDidload中使用自动布局来管理View及子view的frame

case 3

viewwill/DidlayoutSubviews:这对函数在 view调用layoutSubviews前后调用。如果你没用使用自动布局,应该在这两个函数中对view进行位置的设置。

case 4

关于展示用的数据,除非必要,不建议用额外的属性/成员变量保存。而直接通过modelManager的函数获得。以便由modelManager控制数据保存的位置是内存变量,还是持久化方式。
实在需要在内部函数间传递,也尽量根据最小知道原则,保存索引,状态值之类的数据。

4、modelManager

modelmananger负责从一个或多个数据源,采集数据业务操作后提供给VC,数据来源可能是数据库,内存缓存,网络等中的一个或多个。

开发场景

case 1

对VC隐藏数据源的原始数据结构。
也就是说尽量不要把数据源原始数据直接交给VC,而应该与VC约定好相互之间需要传递的最小单元(实体/瘦model),把从一个或多个数据源获取的原始数据,转化成该最小单元。提供给VC

case 2

与VC交互的协议接口定义,应足够独立,一个事件/一次数据请求,通常不应该需要调用到其他接口(这里不是说不能调其他内部函数,而是对VC 开放的其他接口)

case 3

接口实现优先进行内存操作,内存没有的数据再操作数据库,还没有再进行网络操作。实现多级缓存及时反馈。

case 4

页面之间的数据变更响应,应该在modelManager之间通知和响应
具体来说:
vc1某操作,导致vc1的modelManager有数据变更,这个变更影响到v2,那么不应该由v2去接受这个变更通知,而应该由vc2的modelManager接受并修改自身的数据,通过自己的数据变更接口通知vc2。
也就是说一个VC只接受一个modelManager的数据更新通知。

case 5

尽管上面的建议一个VC只对应一个modelManager。但有些场景下,多个VC可以共享一个modelManager。
例:
设置功能下有多个子设置项,这些设置项是不同子页面操作去设置的并且很简单,当我们把设置项的数据作为一个整体去考虑的时候,这些子设置项操作的其实应该是同一个数据对象。此时应该支持这些页面传递modelManager对象来共享它。

5、其他衍生组件

在MVC的框架下,可根据功能单一原则,拆分出多个功能组件,供三大框架模块使用,比如数据库模块,网络模块,加密模块,播放器模块等等。

三、代码优化建议

case 1

任何情况下总是使用setValue:forKey代替setObject:forKey
因为前者在value非nil情况下等于后者。
在value为nil删除key所对应的键值。由于赋值时一般当前并不会存在key的键值,即使是覆盖某对键值,在值为空的情况下,删除了它也是合理的。
所以前者效果完全等效于检测value非空后调用后者。
后者object为nil则直接crash。
所以使用setValue:forKey更安全。

case 2

隐藏不必要的接口和属性。
那些只在内部使用的属性和函数,应定义在类扩展之中,把少数愿意暴露出去的函数和属性定义在头文件中。

case 3

尽量不要使用成员变量,用类扩展中的属性代替它。然后总是使用self.去访问它。
这样有更清晰的内存管理。
并且,当有一天你突然需要在赋值或者取值的时候做一些事情的时候,你就只需要在set/get中操作。而不用整个类里面到处修改了。

case 4

尽量不要阻塞主线程,尽量把非UI操作放到其他线程执行,完成后回到主线程通知UI刷新。

case 5

NSSet/NSArray,他们的区别是,NSSet是无序的,当一个数据集合不关注排序时,使用NSSet,查找元素是否存在和移除效率是高于NSArray的。
例:
维护一个操作中的数据集合,操作开始后加到集合中,操作完成后移除出集合,这种情况下应当使用NSSet

case 6

无论什么时候通过索引访问数组都应该进行越界判断。或者try catch

case 7

懒加载,对不常用的View,不要在页面打开时就创建,采用懒加载的方式去实现。以减少页面启动时间。

case 8

预加载,对于加载速度比较慢,又需要进入页面就直接显示的数据,考虑在适当的时间提前加载好,驻留在内存中。空间换时间。

case 9

tableView的使用除了注意cell正确重用以外,还需注意,数据源的count变化,特别是变小是要及时reloadData。如果使用insertRow/removerow操作,一定要保证数据源数量等于变化前数量加/减变化数量,否则就会crash

case 10

实体中只能进行简单的数据操作,例如json转字典,数值转字符,取整之类的。不能进行业务操作,如存取数据库,网络操作等。

case 11

使用大量临时变量时,可用@autoreleasepool来包含起来及时释放。

作者:buyuxing 发表于2016/10/15 10:23:26 原文链接
阅读:64 评论:0 查看评论

(详细)Service、IntentService、BindService

$
0
0

/**
* Service 服务
* Service是一个不提供用户界面在后台执行耗时操作的应用程序组件。
* 没界面 运行在后台 耗时操作
*
* 注意:service服务运行在主进程的主线程中
* service默认不会开启工作线程
* 如果执行耗时操作时 需要程序员手动开启工作线程
* Service不是一个进程也不是一个线程
*
* 什么情况下使用service什么情况使用worker Thread ? 耗时操作
* 如果执行耗时操作时用户不需要与app交互(耗时操作的结果可能不展示到用户界面)时采用service 例如:下载文件存储
* 如果执行耗时操作用户需要与app交互(耗时操作的结果需要展示到ui)时采用开启worker Thread 例如:ui界面网络加载
*
* 按照服务的启动方式 将服务划分为两类
* 启动服务 startService()
* 绑定服务 bindService()
*
* 启动式服务:应用程序组件(Activity)调用startService()方法启动服务时称为启动时服务
* 特点:
* 1.调用startService()方法一旦启动服务就会一直运行在后台 直到服务被杀死(自杀、他杀、系统回收)
* 2.启动服务的组件(Activity)被销毁后 启动的服务会一直运行 不受影响
* 3.启动式服务在后台执行单一的操作 不会将服务的操作结果*返回*给启动它的组件
*/

/**
     * 点击按钮启动服务
     * @param view
     */
    public void start(View view){
        Intent intent=new Intent(MainActivity.this,MyService.class);
        intent.putExtra("str","传递数据到service");
        startService(intent);//启动服务
    }

    /**
     * 点击按钮停止服务
     * @param view
     */
    public void stop(View view){
        Intent intent=new Intent(MainActivity.this,MyService.class);
        stopService(intent);//根据intent中指定的对象停止服务
    }

/**
* 启动式服务的生命周期
* 当应用程序组件(Activity)调用startService()启动服务先回调onCreate()方法创建和初始化service—
* 接着回调onStartCommand()方法接收Intent意图请求并且开启工作线程执行耗时操作—-
* 当service中的操作执行完毕后调用StopSelf()或者时其它组件调用 stopService()就会回调onDestory()释放资源
*/
启动时服务生命周期
onCreate()—>onStartCommand()—>StopService()

public class MyService extends Service{

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 表示当service第一次创建时回调的函数  初始化  注意:service只能被创建一次
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("tag","--------onCreate-----");
    }

    /**
     * 表示当应用程序组件调用startService()方法启动服务时 服务就会启动并且回调该方法
     * @param intent  应用程序组件调用startService()启动服务时传递的Intent对象
     * @param flags   表示开启服务是够需要传递额外的数据
     * @param startId 用来唯一标示start请求
     * @return 表示当系统回调完onStartCommand()方法后 service被系统意外杀死时  是否能够重新启动服务以及
     * 是否可以继续执行请求操作
     *  根据返回值将service划分为粘性service和非粘性service
     *
     * START_STICKY(常量1) STICKY粘性  当应用程序执行完onStartCommand()方法后 service被异常kill
     * 系统会自动重启服务  但是在重启服务时传入的intent为null  车祸苏醒失忆
     *
     * START_NOT_STICKY(常量2) 非粘性  当应用程序执行完onStartCommand()方法后 service被异常kill
     * 系统不会自动重启服务       车祸死亡
     *
     * START_REDELIVER_INTENT(常量3) 当应用程序执行完onStartCommand()方法后 service被异常kill
     * 系统会自动的重启服务并且将Intent重新传入   车祸苏醒正常
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("tag","--------onStartCommand-----"+intent.getStringExtra("str"));
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        stopSelf();//自杀
        return START_REDELIVER_INTENT;
    }

    /**
     * 标示当service被销毁时回调的函数   资源释放
     * stopService();
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("tag","--------onDestroy-----");
    }
}

示例:
/**
* 需求:点击按钮时启动服务下载图片 下载完成后存储到本地并且发送一个通知
* 点击通知打开下载的图片
*/

public class MainActivity extends AppCompatActivity {
    private String imageUrl="http://f.hiphotos.baidu.com/image/h%3D200/sign=236c94ef2c381f3081198aa999004c67/242dd42a2834349bbe78c852cdea15ce37d3beef.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    //点击按钮启动服务下载图片
    public void downLoad(View view){
        Intent intent=new Intent(MainActivity.this,DownLoadService.class);
        intent.putExtra("imagePath",imageUrl);
        startService(intent);
    }
}
public class DownLoadService extends Service{
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    //获取下载图片的地址--启动工作线程--下载图片--存储到本地--发送通知
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
         //1.获取下载图片的地址
        final String imagePath=intent.getStringExtra("imagePath");
        //2.启动工作线程 service本身是在主线程的主进程中且本身不会开启线程,因而需要自己手动开启线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                //3.下载图片
                if(HttpUtils.isNetWorkConn(DownLoadService.this)){
                    byte[] buff=HttpUtils.getHttpResult(imagePath);
                    if(buff!=null && buff.length!=0){
                        String fileName=imagePath.substring(imagePath.lastIndexOf("/")+1);
                        //4.存储到本地
                        boolean bl=ExternalStorgaUtils.
                                writeExternalStoragePublic(Environment.DIRECTORY_DOWNLOADS,
                                        fileName,buff);
                        if(bl){
                            Log.i("tag","存储成功");
                            //5.发送通知
                            sendNotification(fileName);
                        }else{
                            Log.i("tag","存储失败");
                        }
                    }else{
                        Log.i("tag","下载失败");
                    }
                }else{
                    Log.i("tag","网络异常");
                }
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
 //发送通知的饿方法
    public void sendNotification(String fileName){
        NotificationCompat.Builder builder=new NotificationCompat.Builder(DownLoadService.this);
        builder.setContentTitle("提示信息");
        builder.setContentText("下载完成");
        builder.setSmallIcon(R.mipmap.ic_launcher);

        Intent intent=new Intent(DownLoadService.this,ResultActivity.class);
        intent.putExtra("fileName",fileName);
        PendingIntent pi=PendingIntent.getActivity(DownLoadService.this,1,
                intent,PendingIntent.FLAG_ONE_SHOT);
        builder.setContentIntent(pi);

        NotificationManager manager= (NotificationManager)
                getSystemService(Context.NOTIFICATION_SERVICE);
        manager.notify(1,builder.build());
    }
    public class ResultActivity extends AppCompatActivity{
    private ImageView iv;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_result);
        iv= (ImageView) findViewById(R.id.iv);
        //获取通知中传递的文件名称
         String fileName=getIntent().getStringExtra("fileName");
        //获取图片文件的路径  storage/sdcard/downloads/filename
        String pathName= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                        .getAbsolutePath()+ File.separator+fileName;
        //decodeFile(String path) 根据参数中指定图片的路径转换成bitmap对象
        Bitmap bm= BitmapFactory.decodeFile(pathName);
        iv.setImageBitmap(bm);

        //关闭通知
        NotificationManager manager= (NotificationManager)
                getSystemService(Context.NOTIFICATION_SERVICE);
        manager.cancel(1);
    }
}

IntentService
/**
* IntentService和Service的区别?
* IntentService是Service的具体子类
* 当服务中只需要开启一个工作线程就可以完成耗时操作时 这时建议采用IntentService
* 当服务中仅开启一个工作线程并不能满足需求时 建议开启多个工作线程 使用service
*
* IntentService的特点:
* 1.IntentService底层采用队列的形式管理发送的 Intent对象 其它组件发送的请求都会
* 存储到该队列中 IntentService中回调onHandleIntent()方法依次处理队列中的请求
* onHandleIntent()底层已经开启工作线程
*
* 2. 当应用程序组件(Activity)调用startService()启动 IntentService时会执行
* onCreate()-onStartCommand()-onHandleIntent()-onDestory()
* 注意:onHandleIntent()执行完成请求后将服务自动销毁
*/

/**
 * 需求:根据网络地址下载apk文件 并且下载完成后在线安装
 */
public class MainActivity extends AppCompatActivity {
    private String apkPath="http://apk.99danji.com/99apk/FlappyBird_20150730.apk";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    //点击按钮启动服务
    public void start(View view){
        Intent intent=new Intent(MainActivity.this,MyIntentService.class);
        intent.putExtra("path",apkPath);//启动服务时传递apk的下载地址
        startService(intent);
    }
}

/**
* 绑定服务
* 应用程序组件(Activity)调用bindService()方法启动服务时 称为绑定服务
* bindService()将服务玉启动服务绑定到一起
*
* 特点:
* 1.绑定式服务类似与客户端-服务端的接口形式 允许应用程序组件(Activity)与服务进行交互
* (activity可以访问service的方法)
* 2.应用程序组件(Activity)调用bindService()方法将activity与service绑定到一起并且启动运行service
* 3.应用程序组件与service运行时间一致
* 多个应用程序组件可以同时绑定到同一个服务 服务销毁 绑定解除
* 当绑定多个应用程序的组件都被销毁时 服务也跟着被销毁
* 一方销毁另一方跟着销毁
*/
/**
* 绑定式服务的生命周期:
* 应用程序组件(Activity)调用bindService()绑定服务时回调用onCreate()创建服务--
* 回调onbind()方法将应用组件与service绑定到一起建立链接–当应用程序组件退出或者调用
* unBindService()方法时解除绑定回调onUnBind()方法-- 最后当所有绑定服务的应用程序
* 都退出时回调onDestory()销毁服务
*/

public class MainActivity extends AppCompatActivity {
    private MyServiceConnection connection;
    private MyService myService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        connection=new MyServiceConnection();
    }

    /**
     * 以内部类的形式构建ServiceConnection接口子类
     */
    public class MyServiceConnection implements ServiceConnection{
        /**
         * 表示当应用程序组件与service绑定成功后回调的函数
         * @param name
         * @param service
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
                Log.i("tag","-------onServiceConnected------");
            MyService.MyBinder myBinder= (MyService.MyBinder) service;//向下转型 Ibinder---MyBinder
            myService=myBinder.getService();//获取service的对象
            int num=myService.getRandom();
            Log.i("tag","获取的随机数是:"+num);
        }

        /**
         * 表示绑定服务意外中断时回调的函数
         * @param name
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }

    /**
     * 点击按钮绑定服务
     */
    public void bind(View view){
        /*
        bindService(Intent service, @NonNull ServiceConnection conn,int flags)
        Intent service,   表示绑定服务的意图对象(允许采用隐式意图和显式意图)
        @NonNull ServiceConnection conn, 表示监听当前组件绑定的服务变化的接口
        int flags 表示对绑定服务操作的标记
        返回值为boolean类型  表示是否成功绑定服务
        BIND_AUTO_CREATE 常量 表示绑定服务时若服务没有创建 则先创建再绑定
         */
        Intent intent=new Intent(MainActivity.this,MyService.class);
        boolean bl=bindService(intent,connection, Context.BIND_AUTO_CREATE);
        Log.i("tag","服务绑定成功了吗?"+bl);
    }

    /**
     * 点击按钮解除绑定服务
     */
    public void unBind(View view){
        unbindService(connection);//解除绑定
    }
}
public class MyService extends Service{
    /**
     * 表示服务第一次创建时回调的函数
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("tag","---------onCreate------");
    }




    /*
     * 需求:绑定服务的activity组件访问service中的getRandom()方法该如何操作?
     *  如果想要调用类中的函数 需要创建类的对象 然后对象.函数() 但是service是一个应用程序组件
     *  所以在使用时不能直接通过new service()de形式构建对象 如何获取service类的对象?
     *  发现应用程序组件调用bindService()方法绑定服务成功可以获取onBind()方法的返回值IBinder对象
     *  IBinder是一个接口 所以在service中定义一个IBinder类的子类 发现如果直接实现IBinder接口 需要
     *  重写的函数比较多 所以选择继承IBinder这个接口的子类Binder类  在继承Binder类的内部类中
     *  定义一个函数返回当前servive的对象 那么activity与service链接成功后就可以获取Ibinder对象
     *  进而就获取服务对象
     */
    public int getRandom(){
        return (int) (Math.random()*10+1);
    }
    /**
     * 表示应用程序组件(activity)调用bindService()方法绑定服务时
     *  注意:应用程序与service只能绑定一次
     * @param intent
     * @return
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i("tag","---------onBind------");
        return new MyBinder();
    }
    /*
    构建Ibinde若接口的具体子类对象
     */
    public class MyBinder extends Binder{
        //定义函数 返回当前service服务类的对象
        public MyService getService(){
            return MyService.this;
        }
    }




    /**
     * 表示应用程序组件(activity)与service解除绑定时回调的函数
     * @param intent
     * @return
     */
    @Override
    public boolean onUnbind(Intent intent) {
        Log.i("tag","---------onUnbind------");
        return super.onUnbind(intent);
    }

    /**
     * 表示当其它的应用程序组件绑定服务时回调的函数
     * @param intent
     */
    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        Log.i("tag","---------onRebind------");
    }

    /**
     * 表示当service销毁时回调的函数
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("tag","---------onDestroy------");
    }
}

示例:bindService实现后台播放音乐

public class MainActivity extends AppCompatActivity {
    private ServiceConnection conn;
    private PlayMusicService playMusicService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        conn=new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                PlayMusicService.MyBinder myBinder= (PlayMusicService.MyBinder) service;
                playMusicService=myBinder.getService();
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        };
        Intent intent=new Intent(this,PlayMusicService.class);
        boolean bl=bindService(intent,conn, Context.BIND_AUTO_CREATE);
        Log.i("tag","服务绑定成功了吗?"+bl);
    }



    /**
     * 点击按钮执行操作
     * @param view
     */
    public void clickView(View view){
        switch (view.getId()){
            case R.id.btn_play:
                playMusicService.playMusic();
                break;
            case R.id.btn_pause:
                playMusicService.pauseMusic();
                break;
            case R.id.btn_stop:
                playMusicService.stopMusic();
                break;
        }
    }
}
public class PlayMusicService extends Service{
    private MediaPlayer mediaPlayer;
    private boolean isStop=false;//标示是否停止
    @Override
    public void onCreate() {
        super.onCreate();
        initMediaPlayer();//初始化操作
    }
    /*
    初始化MediaPlayer
     */
    public void initMediaPlayer(){
        if(mediaPlayer==null){
            mediaPlayer=new MediaPlayer();
        }
        mediaPlayer.reset();
        try {
            //设置assets资产中的文件作为MediaPlayer播放音频源
            AssetFileDescriptor sdf=getAssets().openFd("thatway.mp3");
            mediaPlayer.setDataSource(sdf.getFileDescriptor(),
                    sdf.getStartOffset(),sdf.getLength());
            mediaPlayer.prepare();//准备方法  播放之前MediaPlayer必须处于准备状态
            if(sdf!=null){
                sdf.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //表示播放异常时触发的监听器
        mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                mp.reset();
                Toast.makeText(PlayMusicService.this,"播放异常!",Toast.LENGTH_LONG).show();
                return false;
            }
        });
        //表示播放完毕后触发的监听器
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                Toast.makeText(PlayMusicService.this,"音乐播放完毕!",Toast.LENGTH_LONG).show();
            }
        });

    }
    /*
    播放音乐
     */
     public void playMusic(){
         if(isStop){
             initMediaPlayer();
             isStop=false;
         }
         if(mediaPlayer!=null && !mediaPlayer.isPlaying()){//isPlaying() true表示正在播放
             mediaPlayer.start();//播放音乐
         }
     }

    /*
    暂停音乐
     */
    public void pauseMusic(){
        if(mediaPlayer!=null && mediaPlayer.isPlaying()){
            mediaPlayer.pause();
        }
    }
    /*
    停止播放
     */
    public void stopMusic(){
        if(mediaPlayer!=null){
            mediaPlayer.stop();
            isStop=true;
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    public class MyBinder extends Binder{
        public PlayMusicService getService(){
            return PlayMusicService.this;
        }
    }

    @Override
    public boolean onUnbind(Intent intent) {
        if(mediaPlayer!=null){
            mediaPlayer.release();//重置
            mediaPlayer=null;
        }
        return super.onUnbind(intent);
    }
}
作者:u010296640 发表于2016/10/15 10:27:04 原文链接
阅读:53 评论:0 查看评论

命令行下载更新Android SDK

$
0
0

前序

最近需要在服务器上用Jenkins自动打包Android app,从google官网上下载的Linux版本sdk结果发现里面就只有一个tools目录有文件,其他的都没有。。。
无奈,服务器是没有界面的,之前都习惯用IDE去安装更新,现在尝试用命令行下载更新了。

下载Android SDK for Linux

从google的官网下载最新Linux版本SDK,由于dl.google.com域名一直没有被墙,所以才可以直接从官网下了。这点不错~

$ wget https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz

解压
$ tar zxvf android-sdk_r24.4.1-linux.tgz

更新Android SDK

前面说到了,我们下载的这个包其实只有tools目录下才有东西。既然google给了我们这个,表示这里面肯定有可以更新SDK的工具啦。
其实就是tools/android这个文件

$ cd android-sdk-linux/tools

查看命令帮助
$ ./android --help
       Usage:
       android [global options] action [action options]
       Global options:
  -s --silent     : Silent mode, shows errors only.
  -v --verbose    : Verbose mode, shows errors, warnings and all messages.
     --clear-cache: Clear the SDK Manager repository manifest cache.
  -h --help       : Help on a specific command.

                                                                    Valid
                                                                    actions
                                                                    are
                                                                    composed
                                                                    of a verb
                                                                    and an
                                                                    optional
                                                                    direct
                                                                    object:
-    sdk              : Displays the SDK Manager window.
-    avd              : Displays the AVD Manager window.
-   list              : Lists existing targets or virtual devices.
-   list avd          : Lists existing Android Virtual Devices.
-   list target       : Lists existing targets.
-   list device       : Lists existing devices.
-   list sdk          : Lists remote SDK repository.
- create avd          : Creates a new Android Virtual Device.
-   move avd          : Moves or renames an Android Virtual Device.
- delete avd          : Deletes an Android Virtual Device.
- update avd          : Updates an Android Virtual Device to match the folders
                        of a new SDK.
- create project      : Creates a new Android project.
- update project      : Updates an Android project (must already have an
                        AndroidManifest.xml).
- create test-project : Creates a new Android project for a test package.
- update test-project : Updates the Android project for a test package (must
                        already have an AndroidManifest.xml).
- create lib-project  : Creates a new Android library project.
- update lib-project  : Updates an Android library project (must already have
                        an AndroidManifest.xml).
- create uitest-project: Creates a new UI test project.
- update adb          : Updates adb to support the USB devices declared in the
                        SDK add-ons.
- update sdk          : Updates the SDK by suggesting new platforms to install
                        if available.

我们需要关注 list 和 update 。

查看当前可安装的SDK版本
$ ./android list sdk

Refresh Sources:
  Fetching https://dl.google.com/android/repository/addons_list-2.xml
  Validate XML
  Parse XML
  Fetched Add-ons List successfully
  Refresh Sources
  Fetching URL: https://dl.google.com/android/repository/repository-11.xml
  Validate XML: https://dl.google.com/android/repository/repository-11.xml
  Parse XML:    https://dl.google.com/android/repository/repository-11.xml
  Fetching URL: https://dl.google.com/android/repository/addon.xml
  Validate XML: https://dl.google.com/android/repository/addon.xml
  Parse XML:    https://dl.google.com/android/repository/addon.xml
  Fetching URL: https://dl.google.com/android/repository/glass/addon.xml
  Validate XML: https://dl.google.com/android/repository/glass/addon.xml
  Parse XML:    https://dl.google.com/android/repository/glass/addon.xml
  Fetching URL: https://dl.google.com/android/repository/extras/intel/addon.xml
  Validate XML: https://dl.google.com/android/repository/extras/intel/addon.xml
  Parse XML:    https://dl.google.com/android/repository/extras/intel/addon.xml
  Fetching URL: https://dl.google.com/android/repository/sys-img/android/sys-img.xml
  Validate XML: https://dl.google.com/android/repository/sys-img/android/sys-img.xml
  Parse XML:    https://dl.google.com/android/repository/sys-img/android/sys-img.xml
  Fetching URL: https://dl.google.com/android/repository/sys-img/android-wear/sys-img.xml
  Validate XML: https://dl.google.com/android/repository/sys-img/android-wear/sys-img.xml
  Parse XML:    https://dl.google.com/android/repository/sys-img/android-wear/sys-img.xml
  Fetching URL: https://dl.google.com/android/repository/sys-img/android-tv/sys-img.xml
  Validate XML: https://dl.google.com/android/repository/sys-img/android-tv/sys-img.xml
  Parse XML:    https://dl.google.com/android/repository/sys-img/android-tv/sys-img.xml
  Fetching URL: https://dl.google.com/android/repository/sys-img/google_apis/sys-img.xml
  Validate XML: https://dl.google.com/android/repository/sys-img/google_apis/sys-img.xml
  Parse XML:    https://dl.google.com/android/repository/sys-img/google_apis/sys-img.xml
Packages available for installation or update: 41
   1- Android SDK Tools, revision 25.2.2
   2- Android SDK Platform-tools, revision 24.0.4
   3- Android SDK Build-tools, revision 24.0.3
   4- Documentation for Android SDK, API 24, revision 1
   5- SDK Platform Android 7.0, API 24, revision 2
   6- SDK Platform Android 6.0, API 23, revision 3
   7- SDK Platform Android 5.1.1, API 22, revision 2
   8- SDK Platform Android 5.0.1, API 21, revision 2
   9- SDK Platform Android 4.4W.2, API 20, revision 2
  10- SDK Platform Android 4.4.2, API 19, revision 4
  11- SDK Platform Android 4.3.1, API 18, revision 3
  12- SDK Platform Android 4.2.2, API 17, revision 3
  13- SDK Platform Android 4.1.2, API 16, revision 5
  14- SDK Platform Android 4.0.3, API 15, revision 5
  15- SDK Platform Android 4.0, API 14, revision 4
  16- SDK Platform Android 3.2, API 13, revision 1
  17- SDK Platform Android 3.1, API 12, revision 3
  18- SDK Platform Android 3.0, API 11, revision 2
  19- SDK Platform Android 2.3.3, API 10, revision 2
  20- SDK Platform Android 2.3.1, API 9, revision 2
  21- SDK Platform Android 2.2, API 8, revision 3
  22- SDK Platform Android 2.1, API 7, revision 3
  23- Google APIs, Android API 24, revision 1
  24- Google APIs, Android API 23, revision 1
  25- Google APIs, Android API 22, revision 1
  26- Google APIs, Android API 21, revision 1
  27- Google APIs, Android API 19, revision 20
  28- Glass Development Kit Preview, Android API 19, revision 11
  29- Google APIs, Android API 18, revision 4
  30- Google APIs, Android API 17, revision 4
  31- Google APIs, Android API 16, revision 4
  32- Google APIs, Android API 15, revision 3
  33- Android Support Repository, revision 38
  34- Android Auto Desktop Head Unit emulator, revision 1.1
  35- Google Play services, revision 33
  36- Google Repository, revision 36
  37- Google Play APK Expansion library, revision 1
  38- Google Play Licensing Library, revision 1
  39- Google Play Billing Library, revision 5
  40- Android Auto API Simulators, revision 1
  41- Google Web Driver, revision 2

因为是首次安装,所有有非常多的版本可下载。
我们可以有2个选择:

  • 安装所有版本的SDK
  • 只安装我们需要SDK版本
    因为是在服务器上,建议直接安装所有版本的吧,不然后续可能有些app需要这个版本,又有的需要那个版本。还不如一开始就全部安装好。

更新命令

根据上面的工具使用帮助,可以看到更新命令是使用update sdk
但是这个命令又是如何使用的呢?查看帮助

$ ./android update sdk --help

Usage:
       android [global options] update sdk [action options]
       Global options:
  -s --silent     : Silent mode, shows errors only.
  -v --verbose    : Verbose mode, shows errors, warnings and all messages.
     --clear-cache: Clear the SDK Manager repository manifest cache.
  -h --help       : Help on a specific command.

                     Action "update sdk":
  Updates the SDK by suggesting new platforms to install if available.
Options:
  -f --force     : Forces replacement of a package or its parts, even if
                   something has been modified.
  -n --dry-mode  : Simulates the update but does not download or install
                   anything.
     --proxy-host: HTTP/HTTPS proxy host (overrides settings if defined)
  -s --no-https  : Uses HTTP instead of HTTPS (the default) for downloads.
  -t --filter    : A filter that limits the update to the specified types of
                   packages in the form of a comma-separated list of
                   [platform, system-image, tool, platform-tool, doc, sample,
                   source]. This also accepts the identifiers returned by
                   'list sdk --extended'.
  -u --no-ui     : Updates from command-line (does not display the GUI)
     --proxy-port: HTTP/HTTPS proxy port (overrides settings if defined)
  -p --obsolete  : Deprecated. Please use --all instead.
  -a --all       : Includes all packages (such as obsolete and non-dependent
                   ones.)

在这里,重点关注-u-t参数就好。

-u --no-ui  表示在命令行环境下使用,刚好符合我们服务器环境
-t --filter 表示过滤,只安装指定版本的sdk 

好了,我们现在就开始更新吧

首先下载更新全部可安装的SDK版本,不指定过滤即可
$ ./android update sdk -u

经过漫长时间的等待基本都安装好了。。
如果只需要安装指定版本的话,就需要过滤了,如何过滤呢?我们上面已经介绍查看可安装更新sdk版本命令list sdk了。根据查看到的序号过滤即可。

只安装指定序号的版本
$ ./android  update sdk -u -t 序号
如:安装Build-tools, revision 24.0.3
$ ./android update sdk -u -t 3
需要同意license,输入 y 回车即可

安装后可跳转到上一级目录查看是否已经有了。

$ cd ..
$ ls
add-ons  build-tools  platforms  SDK Readme.txt  temp  tools
$ cd build-tools
$ ls
24.0.3

可以看到安装成功了。

总结

通过这些我们也可以推测出其实那些IDE图形界面底层调用的也是这些命令吧。。

作者:lusyoe 发表于2016/10/15 10:28:27 原文链接
阅读:59 评论:0 查看评论

Android中GridView的一些特殊属性

$
0
0

GridView的一些特殊属性:


1.android:numColumns=”auto_fit”   //GridView的列数设置为自动

2.android:columnWidth=”90dp "       //每列的宽度,也就是Item的宽度

3.android:stretchMode=”columnWidth"//缩放与列宽大小同步

4.android:verticalSpacing=”10dp”          //两行之间的边距

5.android:horizontalSpacing=”10dp”      //两列之间的边距 

6.android:cacheColorHint="#00000000" //去除拖动时默认的黑色背景

7.android:listSelector="#00000000"        //去除选中时的黄色底色

8.android:scrollbars="none"                   //隐藏GridView的滚动条

9.android:fadeScrollbars="true"             //设置为true就可以实现滚动条的自动隐藏和显示

10.android:fastScrollEnabled="true"      //GridView出现快速滚动的按钮(至少滚动4页才会显示)

11.android:fadingEdge="none"                //GridView衰落(褪去)边缘颜色为空,缺省值是vertical。(可以理解为上下边缘的提示色)

12.android:fadingEdgeLength="10dip"   //定义的衰落(褪去)边缘的长度

13.android:stackFromBottom="true"       //设置为true时,你做好的列表就会显示你列表的最下面

14.android:transcriptMode="alwaysScroll" //当你动态添加数据时,列表将自动往下滚动最新的条目可以自动滚动到可视范围内

15.android:drawSelectorOnTop="false"  //点击某条记录不放,颜色会在记录的后面成为背景色,内容的文字可见(缺省为false)

作者:qq_32059827 发表于2016/10/15 11:28:10 原文链接
阅读:62 评论:0 查看评论

iOS开发--适配iOS 10以及Xcode 8

$
0
0

一、证书管理

用Xcode8打开工程后,比较明显的就是下图了,这个是苹果的新特性,可以帮助我们自动管理证书。建议大家勾选这个Automatically manage signing(Ps.但是在beat2版本我用的时候,完全不可以,GM版本竟然神奇的又好了。)

01.png

下面我来说说可能会出现的问题:

1.Xcode未设置开发者账号情况下的截图

02.png

解决办法是:大家在Xcode的偏好设置中,添加苹果账号,即可。

2.设备机器未添加进开发者的Device情况下的截图

03.png

解决办法是:大家在官网将设备添加进开发机后,陪下描述文件重新下个描述文件即可。

3.正常情况:Xcode配置登录开发者账号后的图片,耐心等待即可。

04.png

等待完成之后的图

05.png

二、Xib文件的注意事项

使用Xcode8打开xib文件后,会出现下图的提示。

06.png

大家选择Choose Device即可。
之后大家会发现布局啊,frame乱了,只需要更新一下frame即可。如下图

07.png

注意:如果按上面的步骤操作后,在用Xcode7打开Xib会报一下错误,

08.png

解决办法:需要删除Xib里面

4.png

这句话,以及把5.png中的toolsVersion和6.png中的version改成你正常的xib文件中的值
,不过不建议这么做,在Xcode8出来后,希望大家都快速上手,全员更新。这就跟Xcode5到Xcode6一样,有变动,但是还是要尽早学习,尽快适应哟!

三、代码及API注意

使用Xcode8之后,有些代码可能就编译不过去了,具体我就说说我碰到的问题。
1.UIWebView的代理方法:
**注意要删除NSError前面的 nullable,否则报错。

1
2
3
4
- (void)webView:(UIWebView *)webView didFailLoadWithError:(nullable NSError *)error
{
    [self hideHud];
}

四、代码注释不能用的解决办法

这个是因为苹果解决xcode ghost,把插件屏蔽了。
解决方法
打开终端,命令运行:  sudo /usr/libexec/xpccachectl
然后必须重启电脑后生效

注意:Xcode8内置了开启注释的功能,位置在这里

707724-9ace6550ccedaa6c.png

快捷键的设置在这里

707724-ed9730ab858c08a7.png

貌似Xcode8取消了三方插件的功能,具体可以查阅下Xcode8 Source Editor

五、权限以及相关设置

注意,添加的时候,末尾不要有空格
我们需要打开info.plist文件添加相应权限的说明,否则程序在iOS10上会出现崩溃。
具体如下图:

707724-d118ca12029c78ab.png

麦克风权限:Privacy - Microphone Usage Description 是否允许此App使用你的麦克风?
相机权限: Privacy - Camera Usage Description 是否允许此App使用你的相机?
相册权限: Privacy - Photo Library Usage Description 是否允许此App访问你的媒体资料库?通讯录权限: Privacy - Contacts Usage Description 是否允许此App访问你的通讯录?
蓝牙权限:Privacy - Bluetooth Peripheral Usage Description 是否许允此App使用蓝牙?

语音转文字权限:Privacy - Speech Recognition Usage Description 是否允许此App使用语音识别?
日历权限:Privacy - Calendars Usage Description 是否允许此App使用日历?

定位权限:Privacy - Location When In Use Usage Description 我们需要通过您的地理位置信息获取您周边的相关数据
定位权限: Privacy - Location Always Usage Description 我们需要通过您的地理位置信息获取您周边的相关数据
定位的需要这么写,防止上架被拒。

六、字体变大,原有frame需要适配

经有的朋友提醒,发现程序内原来2个字的宽度是24,现在2个字需要27的宽度来显示了。。
希望有解决办法的朋友,评论告我一下耶,谢谢啦

七、推送

如下图的部分,不要忘记打开。所有的推送平台,不管是极光还是什么的,要想收到推送,这个是必须打开的哟??

11.png

之后就应该可以收到推送了。另外,极光推送也推出新版本了,大家也可以更新下。

PS.苹果这次对推送做了很大的变化,希望大家多查阅查阅,处理推送的代理方法也变化了。

4.png

iOS10收到通知不再是在
[application: didReceiveRemoteNotification:]方法去处理, iOS10推出新的代理方法,接收和处理各类通知(本地或者远程)

1
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { //应用在前台收到通知 NSLog(@"========%@", notification);}- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler { //点击通知进入应用 NSLog(@"response:%@", response);}

稍后我会更新文章,对推送做一个详细的讲解。

8.屏蔽杂乱无章的bug

更新Xcode8之后,新建立工程,都会打印一堆莫名其妙看不懂的Log.
如这些

1
subsystem: com.apple.UIKit, category: HIDEventFiltered, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 1,

屏蔽的方法如下:
Xcode8里边 Edit Scheme-> Run -> Arguments, 在Environment Variables里边添加
OS_ACTIVITY_MODE = Disable

36.png

如果写了之后还是打印log,请重新勾选对勾,就可以解决了。

Ps.考虑到添加上述内容在Xcode8后,真机调试可能出现异常,大家可以自定义一个宏定义,来做日志输出。

1
2
3
4
5
6
7
8
9
10
11
#ifdef DEBUG
 
#define DDLOG(...) printf(" %s\n",[[NSString stringWithFormat:__VA_ARGS__]UTF8String]);
#define DDLOG_CURRENT_METHOD NSLog(@"%@-%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd))
 
#else
 
#define DDLOG(...) ;
#define DDLOG_CURRENT_METHOD ;
 
#endif

大家有任何问题,可以评论给我~
如果写的不错,希望大家可以关注我。感谢。

作者:autom_lishun 发表于2016/10/15 11:38:09 原文链接
阅读:55 评论:0 查看评论

Android基础控件——CardView的使用、仿支付宝银行卡

$
0
0

CardView的使用、仿支付宝银行卡


今天有空学习了下CardView的使用,既然是使用,不凡使用一个实例操作一下

CardView是Android5.0的新控件,所以我们需要在dependencies中添加支持:

compile 'com.android.support:cardview-v7:24.2.1'

CardView是继承FrameLayout的一个布局控件,从源码可以看出CardView支持的属性有:

  • card_view:cardElevation 阴影的大小
  • card_view:cardMaxElevation 阴影最大高度
  • card_view:cardBackgroundColor 卡片的背景色
  • card_view:cardCornerRadius 卡片的圆角大小
  • card_view:contentPadding 卡片内容于边距的间隔
    • card_view:contentPaddingBottom
    • card_view:contentPaddingTop
    • card_view:contentPaddingLeft
    • card_view:contentPaddingRight
    • card_view:contentPaddingStart
    • card_view:contentPaddingEnd
  • card_view:cardUseCompatPadding 设置内边距,V21+的版本和之前的版本仍旧具有一样的计算方式
  • card_view:cardPreventConrerOverlap 在V20和之前的版本中添加内边距,这个属性为了防止内容和边角的重叠

我们看一下今天要实现的效果图:

有兴趣的朋友可以尝试使用ViewPager+CardView实现卡片画廊的效果

其实CardView的使用相当于加了一个布局使用,其CardView里面内容的实现,还是在布局中设计

银行卡布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:padding="16dp">

    <android.support.v7.widget.CardView
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        app:cardBackgroundColor="#099A8C"
        app:cardCornerRadius="10dp"
        app:cardElevation="10dp"
        app:contentPadding="16dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">

            <ImageView
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:background="@drawable/icon_01" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:orientation="vertical"
                android:padding="8dp">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="中国农业银行"
                    android:textColor="#ffffff"
                    android:textSize="18sp" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="储蓄卡"
                    android:textColor="#ffffff"
                    android:textSize="16sp" />
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:textColor="#ffffff"
                    android:gravity="center_vertical"
                    android:textSize="22sp"
                    android:text="**** **** **** 1234"/>
            </LinearLayout>

            <ImageView
                android:layout_width="60dp"
                android:layout_height="15dp"
                android:background="@drawable/icon_02" />
        </LinearLayout>
    </android.support.v7.widget.CardView>

</RelativeLayout>

特别注意的是:使用CardView的属性时,记得加上命名空间的声明

xmlns:app="http://schemas.android.com/apk/res-auto"

好了,今天CardView的使用就到这里

作者:qq_30379689 发表于2016/10/15 12:26:34 原文链接
阅读:39 评论:0 查看评论

Android群英传知识点回顾——第十三章:Android实例提高

$
0
0

Android群英传知识点回顾——第十三章:Android实例提高


这一章是两个游戏的设计,比较关键的是整个游戏的设计思想和游戏实现的算法,我曾经也做过2048的游戏,觉得内容还是挺不错的,如果光凭文字表达,有些地方很难说清楚,所以我推荐大家可以看以下的视频学习,毕竟跟老师学习学到的东西多

传送门:

2048:

拼图游戏:

多谢大家对本系列的支持,希望大家可以给我的github点点赞

github:https://github.com/CSDNHensen/QunYingZhuang

作者:qq_30379689 发表于2016/10/16 0:21:24 原文链接
阅读:125 评论:0 查看评论

Android OpenGLES2.0(六)——构建圆锥、圆柱和球体

$
0
0

之前的博客中,我们绘制了三角形、正方形、圆形、立方体,今天我们将绘制圆锥、圆柱和球体。能够绘制这些基本的常规几何形体后,其他的常见几何形体的绘制对于我们来说就基本没问题了。

绘制圆锥

由之前的博客,我们大家也应该都知道了,OpenGL ES2.0中物体的绘制重点就是在于把这个物体表面分解成三角形,分解成功后,绘制自然就不成问题了。圆锥我们很容易就能想到把它拆解成一个圆形和一个锥面,锥面的顶点与圆形的顶点,除了锥面的中心点的坐标有了“高度”,其他的完全相同。圆形在Android OpenGLES2.0(四)——正方形和圆形中我们已经绘制过,那么锥面其实对于我们来说也是小case了:

ArrayList<Float> pos=new ArrayList<>();
pos.add(0.0f);
pos.add(0.0f);
pos.add(height);        //给圆心相对圆边增加高度,使之形成锥面
float angDegSpan=360f/n;
for(float i=0;i<360+angDegSpan;i+=angDegSpan){
    pos.add((float) (radius*Math.sin(i*Math.PI/180f)));
    pos.add((float)(radius*Math.cos(i*Math.PI/180f)));
    pos.add(0.0f);
}
float[] d=new float[pos.size()];    //所有的顶点
for (int i=0;i<d.length;i++){
    d[i]=pos.get(i);
}

我们按照绘制圆形的方式,绘制出锥面,然后再在这个锥面的底部绘制一个圆形,这样我们就得到了一个圆锥了:

圆锥

从图中我们可以看到,我们绘制的并不是同样的颜色,如果使用同样的颜色,很难看出圆锥的立体效果。这种颜色怎么实现的呢?我们来看它的顶点着色器(片元着色器和之前相同):

uniform mat4 vMatrix;
varying vec4 vColor;
attribute vec4 vPosition;

void main(){
    gl_Position=vMatrix*vPosition;
    if(vPosition.z!=0.0){
        vColor=vec4(0.0,0.0,0.0,1.0);
    }else{
        vColor=vec4(0.9,0.9,0.9,1.0);
    }
}

在顶点着色器中,并没有传入颜色,而是在程序中直接判断进行赋值的,当然也有可以顶点颜色和定边颜色由外面传入。在着色器中,我们不再是简单的赋值,而是加入了流程控制。在下一篇博客中将会专门讲解我们使用的着色器语言GLSL——Android OpenGLES 2.0(七)——着色器语言GLSL

绘制圆柱

圆柱的与圆锥类似,我们可以把圆柱拆解成上下两个圆面,加上一个圆筒。圆筒我们之前也没画过,它怎么拆解成三角形呢?我们可以如同拆圆的思路来理解圆柱,想想正三菱柱、正八菱柱、正一百菱柱……菱越多,就越圆滑与圆柱越接近了,然后再把每个菱面(矩形)拆解成两个三角形就OK了,拆解的顶点为:

ArrayList<Float> pos=new ArrayList<>();
float angDegSpan=360f/n;
for(float i=0;i<360+angDegSpan;i+=angDegSpan){
    pos.add((float) (radius*Math.sin(i*Math.PI/180f)));
    pos.add((float)(radius*Math.cos(i*Math.PI/180f)));
    pos.add(height);
    pos.add((float) (radius*Math.sin(i*Math.PI/180f)));
    pos.add((float)(radius*Math.cos(i*Math.PI/180f)));
    pos.add(0.0f);
}
float[] d=new float[pos.size()];
for (int i=0;i<d.length;i++){
    d[i]=pos.get(i);
}

这样我们就可以绘制出一个圆筒了,只需要在顶部绘制一个圆,底部绘制一个圆,就得到了一个圆柱了:
圆柱

绘制球体

相对于圆锥圆柱来说,球体的拆解就复杂了许多,比较常见的拆解方法是将按照经纬度拆解和按照正多面体拆解,下图分别为正多面体示意和经纬度拆解示意:

  • 正多面体的方法拆解:
    多面体
  • 经纬度的方法拆解(每一个小块看做一个矩形,再拆成三角形。PS:人懒,不想做图。):
    这里写图片描述

由图我们也能看出来,多面体虽然看起来好看点,但是还是按照经纬度的方式来拆解计算容易点,毕竟规律那么明显。

球上点的坐标

无论是按照经纬度拆还是按照多面体拆,都需要知道球上面点的坐标,这算是基本的几何知识了。以球的中心为坐标中心,球的半径为R的话,那么球上点的坐标则为:

(Rcos(ψ)sin(λ),Rsin(ψ),Rcos(ψ)cos(λ))

其中,ψ为圆心到点的线段与xz平面的夹角,λ为圆心到点的线段在xz平面的投影与z轴的夹角。用图形表示如下:
这里写图片描述

拆解顶点

按照经纬度方式拆解球体,得到球体的顶点数组:

ArrayList<Float> data=new ArrayList<>();
float r1,r2;
float h1,h2;
float sin,cos;
for(float i=-90;i<90+step;i+=step){
    r1 = (float)Math.cos(i * Math.PI / 180.0);
    r2 = (float)Math.cos((i + step) * Math.PI / 180.0);
    h1 = (float)Math.sin(i * Math.PI / 180.0);
    h2 = (float)Math.sin((i + step) * Math.PI / 180.0);
    // 固定纬度, 360 度旋转遍历一条纬线
    float step2=step*2;
    for (float j = 0.0f; j <360.0f+step; j +=step2 ) {
        cos = (float) Math.cos(j * Math.PI / 180.0);
        sin = -(float) Math.sin(j * Math.PI / 180.0);

        data.add(r2 * cos);
        data.add(h2);
        data.add(r2 * sin);
        data.add(r1 * cos);
        data.add(h1);
        data.add(r1 * sin);
    }
}
float[] f=new float[data.size()];
for(int i=0;i<f.length;i++){
    f[i]=data.get(i);
}

得到顶点后,剩下的工作就和之前绘制其他图形一样了。

修改着色器

如果继续使用圆锥的着色器,我们会得到这样一个球:

黑球

看起来都不太像个球了,要不是有条白线,这是不是个球就不好说了。我们需要修改下顶点着色器,让它有立体感。把顶点着色器修改为:

uniform mat4 vMatrix;
varying vec4 vColor;
attribute vec4 vPosition;

void main(){
    gl_Position=vMatrix*vPosition;
    float color;
    if(vPosition.z>0.0){
        color=vPosition.z;
    }else{
        color=-vPosition.z;
    }
    vColor=vec4(color,color,color,1.0);
}

运行一下,我们得到的运行结果如下,这样才好意思说是个球嘛。
这里写图片描述

源码

OK,绘制各种简单的几何物体到这里就结束了,现在应该各种常规的几何形体都拦不到我们了。后面开始讲解其他内容了。
所有的代码全部在一个项目中,托管在Github上——Android OpenGLES 2.0系列博客的Demo

作者:junzia 发表于2016/10/16 0:45:35 原文链接
阅读:126 评论:0 查看评论

工作第十三周:身体掏空,精神饱满

$
0
0

一连 7 天班,晨兴理荒秽,带月荷键归,身体累的像条狗,脑子却转得飞快。
心态转变以后,即使工作生活里有些不顺的事,也没那么发愁了,毕竟它们只是过客。

新单词

  • from scratch 从0开始
  • braces 括弧
    吊带,背带;托架( brace的名词复数 );箍子;括弧;(儿童)牙箍
  • sophisticated 复杂的;精致的;富有经验的;深奥微妙的
    • Simplicity is the ultimate sophistication 至繁归于至简
  • occupy 占领;使用,住在…;使从事,使忙碌;任职
  • identical 同一的; 完全同样的,相同的; 恒等的; 同卵的
  • capitalization of the first character 大写首字母
  • redundantly 多余地
  • nested 嵌套地
  • modifier 修饰语
  • mutations 突变 very rare mutations
    • this class provides methods to manipulate the size of the array
  • capacity 容量;性能;才能;生产能力
    • Constructs a new instance of {@code ArrayList} with the specified initial capacity.
  • backed by 依靠,基于 ArrayList is backed by an array

这一周

1.向郭霖大神公众号投稿得了 11 元打赏,第一笔稿费哈哈。

2.在 廖雪峰 前辈博客上看到一篇文章,摘一段:
《软技能:代码之外的生存指南》:

  • 不要做宅男;
  • 和面试官成为好朋友后再去面试(结果你懂的);
    • (是的没错,熟人好办事,不过还是要有里子别人才给你面子)
  • 如何成为自由职业者;
  • 假装自己能成功;
    • (的确,首先要有成功者的姿态,自信)
  • 打造自身品牌:坚持写博客;
    • (劝劝自己:还是先把基础知识学好吧 )
  • 有效管理时间以提升效率;
  • 学会理财:要善于炒股炒房(炒股在中国可能不算理财算赌博);
    • (炒房 - -)
  • 不要刷爆信用卡(这个问题可能美国人比较严重);
    • (买房贷款一样有影响)
  • 少看电视多运动,争取练成肌肉男。
    • (不花钱办健身卡没动力啊 )

3.阅读如果没有真正找出作者想要传递的思想,那么和没读有什么区别。

技术上的收获

1.多种服务器接口地址环境配置思路

  • assets下写个json,把不同环境不同业务的域名写进去
  • 自定义一个环境选择View DebugEnvView
  • 读取 assets 中的文件并解析
  • 用户选择后进行相应配置

2.加载 base64 图片

了解到,图片加载库帮我们做了哪些工作呢?

  • 下载
  • 解压
  • 加载

http://stackoverflow.com/questions/37426711/converting-base64-into-bitmap-and-loading-into-recycler-view
http://stackoverflow.com/questions/17506428/convert-base64-string-to-image-in-java
http://stackoverflow.com/questions/30167205/base64-decode-for-image-jpegbase64-in-android

3.uri 类 :

对一个 url 进行操作,获取 scheme,host,authority, path, queryParameter 等

4.加快 gradle build 时间

https://medium.com/@cesarmcferreira/speeding-up-gradle-builds-619c442113cb#.q6b7onhsu

5.微信第三方接入时回调要求在包名路径下的.wxapi.WXCallBackxxx 固定写死这个文件才能回调。就是说如果你修改了ApplicationId没有修改PackageName是无法收到微信回调的。

6.Building Android Apps — 30 things that experience made me learn the hard way

7.module 不如打成 jar 包或者 aar 包,那样可以减少 build 时间

Don’t use more modules than you actually need. If that modules are not constantly modified, it’s important to have into consideration that the time needed to compile them from scratch (CI builds are a good example), or even to check if the previous individual module build is up-to-date, can be up to almost 4x greater than to simply load that dependency as a binary .jar/.aar.

8.vim 修改文件,解决冲突

  • 打开文件
vim app/src/main/res/values/strings.xml 
  • 按 I 键进入 Intert 模式
  • 删掉冲突内容
  • 按 esc 退出编辑
  • shift + : 进入命令行模式
  • 输入 wq,保存并退出

若要继续 rebase

 git rebase --continue

退出 rebase

git rebase --abort

9.onNewIntent

This is called for activities that set launchMode to “singleTop” in their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag when calling {@link #startActivity}. In either case, when the activity is re-launched while at the top of the activity stack instead of a new instance of the activity being started, onNewIntent() will be called on the existing instance with the Intent that was used to re-launch it.

An activity will always be paused before receiving a new intent, so you can count on {@link #onResume} being called after this method.

Note that {@link #getIntent} still returns the original Intent. You can use {@link #setIntent} to update it to this new Intent.

下面摘自
http://baurine.github.io/2015/12/26/android_onnewintent.html
有图

当 activity (假设为 A) 的 launchMode 为 singleTop 且 A 的实例已经在 task 栈顶,或者 launchMode 为 singleTask 且 A 的实例已在 task 栈里 (无论是栈顶还是栈中),再次启动 activity A 时,便不会调用 onCreate() 去产生新的实例,而是调用 onNewIntent() 并重用 task 栈里的 A 实例。

如果 A 在栈顶,那么调用顺序依次是 A.onPause() –> A.onNewIntent() –> A.onResume()。A 的 launchMode 可以是 singleTop 或者是 singlTask。android 开发者官网 上描述的是这种情况。

如果 A 不在栈顶,此时它处于 A.onStop() 状态,当再次启动时,调用顺序依次是 [A.onStop()] –> A.onNewIntent() –> A.onRestart() –> A.onStart() –> A.onResume()。A 的 launchMode 只能是 singleTask。

10.使用 EventBus 注册后,退出一定要记得 unRegister,否则一个消息会有多个订阅者接受,导致回调多次!!

@Override
protected void onDestroy() {
    super.onDestroy();
    if (EventBus.getDefault().isRegistered(this)) {
        EventBus.getDefault().unregister(this);
    }
}

11.Activity A 可能需要由 Activity B 根据情况关闭,有两种方式:

  • 维护一个 activity stack / list,A 添加进去,需要删除时遍历,删掉
  • 用 EventBus (好像是观察者模式) 在 A 中写一个 onEvent,B 中调用 EventBus.getDefault().post(new XXEvent());
  • 类似上面,写个回调

12.客户端明文保存密码有问题,存在密码泄露隐患

图片压缩网站 https://tinypng.com/

总结

来不及了!继续努力!

作者:u011240877 发表于2016/10/16 1:01:29 原文链接
阅读:221 评论:3 查看评论

实现iOS图片等资源文件的热更新化(五): 一个简单完整的资源热更新页面

$
0
0

简介

更新结果

一个简单的关于页面,有一个图片,版本号,App名称等,着重演示各个系列的文章完整集成示例.心得部分,看了会让人忍不住去更新博客的文章.

动机与意义

这是系列文章的最后一篇.今天抽空写下,收下尾.文章本身会在第四篇的基础上,简单扩充下代码,实现在线下载与重置更改的功能.

如果能较为仔细地阅读前四篇文章,第五篇给出的示例,应当是可以理解为无足轻重的.但是,大多数时候,我们更多的可能只是需要一个简易的解决方案,就是那种拿来就可以用的东西,那种我们需要先能看到一个简要的示例来看下效果再解决是否再继续阅读的方案.如此,对于很久以后,由于各种原因被搜索引擎或者其他文章的链接导向此系列文章的人来说,他们可能更想看到一个简要的示例,来决定系列的文章,在他们那个时间点,是否依然有意义.

截止目前而言,我对博客记录本身的定位,依然是属于一个辅助思考的工具.当你看到这篇文章的时候,可能你已经在用Xcode9 Xcode10了,可能代码示例都已经跑不起来了,但是我相信每篇文章所展示的那些参考链接和本身所透漏出的某些思考,或许对于你仍然是有某种启发的.

思路与实现

  1. App版本和名称,可以直接读取;
  2. 在线下载更新资源,可以借助前一篇的代码实现;
  3. 重置的话,可以选择清除补丁信息或者直接清除补丁,本文选择第一种;

核心代码:

我需要先扩展下更新资源的方法,使其在更新完整后,能返回更新的结果,以便于我进行进一步的操作,如重新显示某个图片:

+ (void)yf_updatePatchFrom:(NSString *) pathInfoUrlStr completionHandler:(void (^)(BOOL success, NSError * error))completionHandler
{
    if ( ! completionHandler) {
        completionHandler = ^(BOOL success, NSError * error){
            // nothing to do...
        };
    }

    [self yf_fetchPatchInfo: pathInfoUrlStr
       completionHandler:^(NSDictionary *patchInfo, NSError *error) {
           if (error) {
               NSLog(@"fetchPatchInfo error: %@", error);
               completionHandler(NO, error);
               return;
           }

           NSString * urlStr = [patchInfo objectForKey: @"url"];
           NSString * md5 = [patchInfo objectForKey:@"md5"];

           NSString * oriMd5 = [[[NSUserDefaults standardUserDefaults] objectForKey: [self yf_sourcePatchKey]] objectForKey:@"md5"];
           if ([oriMd5 isEqualToString:md5]) { // no update
               completionHandler(YES,nil);
               return;
           }

           [self yf_downloadFileFrom:urlStr completionHandler:^(NSURL *location, NSError *error) {
               if (error) {
                   NSLog(@"download file url:%@  error: %@", urlStr, error);
                   completionHandler(NO, error);
                   return;
               }

               NSString * patchCachePath = [self yf_cachePathFor: md5];
               [SSZipArchive unzipFileAtPath:location.path toDestination: patchCachePath overwrite:YES password:nil error:&error];

               if (error) {
                   NSLog(@"unzip and move file error, with urlStr:%@ error:%@", urlStr, error);
                   completionHandler(NO, error);
                   return;
               }

               /* update patch info. */
               NSString * source_patch_key = [self yf_sourcePatchKey];
               [[NSUserDefaults standardUserDefaults] setObject:patchInfo forKey: source_patch_key];
               completionHandler(YES,nil);
           }];
       }];

}

然后是一个自定义的在线更新的点击方法:

- (IBAction)onlineUpdate:(id)sender {
    __weak ViewController * weakSelf = self;
    [UIImage yf_updatePatchFrom:@"https://raw.githubusercontent.com/ios122/ios_assets_hot_update/master/res/patch_04.json" completionHandler:^(BOOL success, NSError *error) {
        UIImage * image = [UIImage yf_imageNamed:@"sub/sample"];
        weakSelf.sampleImageView.image = image;
    }];
}

还需要一个自定义的reset方法,考虑到以后的扩展性和目前的需要,使其支持block传出操作结果:

+ (void )yf_reset:(void (^)(BOOL success, NSError * error))completionHandler
{
    if ( ! completionHandler) {
        completionHandler = ^(BOOL success, NSError * error){
            // nothing to do...
        };
    }

    [[NSUserDefaults standardUserDefaults] setObject:nil forKey: [self yf_sourcePatchKey]];
    completionHandler(YES, nil);
}

重置

具体使用起来,就很简单,重置后,更新下图片即可:

- (IBAction)reset:(id)sender {
    __weak ViewController * weakSelf = self;

    [UIImage yf_reset:^(BOOL success, NSError *error) {
        if (success) {
            UIImage * image = [UIImage yf_imageNamed:@"sub/sample"];
            weakSelf.sampleImageView.image = image;
        }else
        {
            NSLog(@"reset error:%@", error);
        }
    }];
}

系列文章心得小结

这是第二个系列文章.”我们应该相信大多数人们对于美好的东西是有鉴赏的能力” – 如果能在这一点上达成共识,下面我说的,或许值得继续一读:

一些数据

  • 开源中国 推荐了2篇博客: 已发布的四篇系列文章,有两篇在OSC上获得了小编的全站推荐.
  • 简书 首页推荐两篇,获得打赏一次: 简书本身的技术属性,可能算不上很强,但近来搜索技术资料时,有好多都链接指向简书,而且信息大都很及时很新鲜,原因未知,所以最近自己也开始同步在简书更新文章,至于收到打赏,其实就只有2元的辣条钱,但是很明显这个童鞋是搜索某个信息时,被导向了我的文章,而且从其评论来看,确实对其有一定的帮助 – 我觉得,能够被需要的人看到,这才是最让博主开心的事!
  • 微博 相关博文有3位大V转发: 某种程度上,我觉得这算是一种认可.不过,我本身其实并不怎么玩微博;微博的信息太容易被淹没,但如果只考虑传播属性的话,微博的扩散效果其实是极好的.
  • segmentfault 把文章添加到头条之后,被segmentfault的CEO 高阳Sunny 点赞了两次,微博转发一次.有种受宠若惊的感觉,不过后来我想可能人家更多地只是想推一下新出的”头条”功能.
  • csdn首页推荐一次: 通知原文是,”你的实现iOS图片等资源文件的热更新化(四): 一个最小化的补丁更新逻辑被推荐到首页喽!” 这个确实挺值得纪念的!刚开始的时候,我发篇文章,如果有外链,CSDN就必须要审查之后才能被公开看到!

几点心得

  • 工作第一,博客分享第二: 我不指望能将来靠博客挣稿费,那也就意味着工作上的事务永远都必须是优先处理的.所以,博客的更新时间并不能真正固定.还有就是,不希望博客分享本身成为一种负担,如果实在没心情或者生活中有其他事的话,我也就真的搁在那,以后再写.
  • 不要被以前的主题束缚,写自己真正需要或者真正感兴趣的:这个系列,从时间上来说,确实比预期的一周迟了一个月;但是从实际效果来看,要比上一个Spark系列好很多.但是当初决定这个系列的内容时,我也是很纠结,是要继续Spark大数据题材,还是分享下自己一直想深入研究,却一直抽不出时间的资源包优化问题.最终,还是选择了后者,因为目前对Spark需要的场景,在自己工作中确实不多.
  • 记录思路和参考资源,可能比解决方案本身更重要:更多的,是阅读其他人博客的经验;遇到完全一致的问题的可能性很小,而且许多情况下,是从博主的相关引用中关于类似问题更细节的参考中,找到答案的;另外,各种引用资料,可能也给人一种很高大上的感觉.
  • 你需要的时间比你预期的要更长: 你以为半个小时可以搞定的文章,可能会花费两个小时,才勉强收尾;你以为很简答的一个技术点,在某个细节上演绎之后,可能会比你想象中更经验.当你意识到,自己正在做的东西,是会被大家公开阅读和鉴赏时,你会不由自主地想多做一点,多查一些,多优化一点,不想显得太low.

小规划

  • 题材,坚持系列文章: 我发现系列文章,真的有利于帮助自己进行和坚持深入地有序思考.
  • 主题,确定为移动混合开发:最近一年都在用ReactNative开发App,但是单纯地使用,已经不能满足我了,我想深入研究下内部地某些实现机制.作为对比,会研究下勉强算是社区驱动的Weex;另外,还会关注下国内的商业驱动的APICloud平台.
  • 内容会涉及iOS,Android,HTML5和自动化脚本: iOS算是本职工作,Android和HTML是自己迫切需要补上的技能,而自动化脚本的编写能力将在很大程度上决定自己自动处理复杂信息的能力和未来的发展 – 都说Lisp是宇宙第一语言,但目前还是基础的shell脚本用的比较多.
  • 文章和评论宜只谈技术: ReactNative 所代表的混合开发的方向,在一定程度上已经获得了国内以BAT为代表的一线技术公司的认可,大家可以去showcase示例具体看下;Weex,目前只是粗读了下文档,三端公用代码,确实有些脑洞,其内部实现应该具有相当程度的学习价值,但其理念不敢苟同,3端共用代码,意味着要取三端各自平台优势的交集,可能也就意味着要牺牲3个平台的各自的独特性和优势 – 如果真的是这这样,那ReactNative,也是可以自称”一处编写,处处运行”的;APICloud,商业驱动,从产品角度来说,较为完善,混合开发只是服务的一部分,按照目前的发展路线,如果未来HTML发展再迅速一点,或许会有极大出线的可能.

参考资源

作者:sinat_30800357 发表于2016/10/16 1:07:15 原文链接
阅读:137 评论:0 查看评论

侧边栏带字母索引的联系人列表,可定位

$
0
0

先看效果图
这里写图片描述

这是比较常见的效果了吧
列表根据首字符的拼音字母来排序,且可以通过侧边栏的字母索引来进行定位

实现这样一个效果并不难,只要自定义一个索引View,然后引入一个可以对汉字进行拼音解析的jar包——pinyin4j-2.5.0即可

首先,先来定义侧边栏控件View,只要直接画出来即可
字母选中项会变为红色,且滑动时背景会变色,此时SideBar并不包含居中的提示文本

public class SideBar extends View {

    private Paint paint = new Paint();

    private int choose = -1;

    private boolean showBackground;

    public static String[] letters = {"#", "A", "B", "C", "D", "E", "F", "G", "H",
            "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
            "V", "W", "X", "Y", "Z"};

    private OnChooseLetterChangedListener onChooseLetterChangedListener;

    public SideBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public SideBar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SideBar(Context context) {
        super(context);
    }

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (showBackground) {
            canvas.drawColor(Color.parseColor("#D9D9D9"));
        }
        int height = getHeight();
        int width = getWidth();
        //平均每个字母占的高度
        int singleHeight = height / letters.length;
        for (int i = 0; i < letters.length; i++) {
            paint.setColor(Color.BLACK);
            paint.setAntiAlias(true);
            paint.setTextSize(25);
            if (i == choose) {
                paint.setColor(Color.parseColor("#FF2828"));
                paint.setFakeBoldText(true);
            }
            float x = width / 2 - paint.measureText(letters[i]) / 2;
            float y = singleHeight * i + singleHeight;
            canvas.drawText(letters[i], x, y, paint);
            paint.reset();
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int action = event.getAction();
        float y = event.getY();
        int oldChoose = choose;
        int c = (int) (y / getHeight() * letters.length);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                showBackground = true;
                if (oldChoose != c && onChooseLetterChangedListener != null) {
                    if (c > -1 && c < letters.length) {
                        onChooseLetterChangedListener.onChooseLetter(letters[c]);
                        choose = c;
                        invalidate();
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (oldChoose != c && onChooseLetterChangedListener != null) {
                    if (c > -1 && c < letters.length) {
                        onChooseLetterChangedListener.onChooseLetter(letters[c]);
                        choose = c;
                        invalidate();
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                showBackground = false;
                choose = -1;
                if (onChooseLetterChangedListener != null) {
                    onChooseLetterChangedListener.onNoChooseLetter();
                }
                invalidate();
                break;
        }
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

    public void setOnTouchingLetterChangedListener(OnChooseLetterChangedListener onChooseLetterChangedListener) {
        this.onChooseLetterChangedListener = onChooseLetterChangedListener;
    }

    public interface OnChooseLetterChangedListener {

        void onChooseLetter(String s);

        void onNoChooseLetter();

    }

}

SideBar只是画出了侧边栏索引条而已,不包含居中的提示文本,这个在另一个布局添加即可

public class HintSideBar extends RelativeLayout implements SideBar.OnChooseLetterChangedListener {

    private TextView tv_hint;

    private SideBar.OnChooseLetterChangedListener onChooseLetterChangedListener;

    public HintSideBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.view_hint_side_bar, this);
        initView();
    }

    private void initView() {
        SideBar sideBar = (SideBar) findViewById(R.id.sideBar);
        tv_hint = (TextView) findViewById(R.id.tv_hint);
        sideBar.setOnTouchingLetterChangedListener(this);
    }

    @Override
    public void onChooseLetter(String s) {
        tv_hint.setText(s);
        tv_hint.setVisibility(VISIBLE);
        if (onChooseLetterChangedListener != null) {
            onChooseLetterChangedListener.onChooseLetter(s);
        }
    }

    @Override
    public void onNoChooseLetter() {
        tv_hint.setVisibility(INVISIBLE);
        if (onChooseLetterChangedListener != null) {
            onChooseLetterChangedListener.onNoChooseLetter();
        }
    }

    public void setOnChooseLetterChangedListener(SideBar.OnChooseLetterChangedListener onChooseLetterChangedListener) {
        this.onChooseLetterChangedListener = onChooseLetterChangedListener;
    }
}

HintSideBar通过回调接口来更新居中TextView的文本内容和可见性

使用到的布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.czy.demo.SideBar
        android:id="@+id/sideBar"
        android:layout_width="30dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true" />

    <TextView
        android:id="@+id/tv_hint"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_centerInParent="true"
        android:background="#4b0e0e0e"
        android:gravity="center"
        android:textColor="#ffffff"
        android:textSize="30sp"
        android:visibility="invisible" />

</RelativeLayout>

此时就完成了索引View的绘制,不过定位功能还需要再通过回调接口来完成

引入jar包后,先来设定一个工具类,包含一个可以解析字符串的方法,返回值为首字符对应的拼音首字母或者为包含一个空格的char类型数据

public class Utils {

    /**
     * 如果字符串的首字符为汉字,则返回该汉字的拼音大写首字母
     * 如果字符串的首字符为字母,也转化为大写字母返回
     * 其他情况均返回' '
     *
     * @param str 字符串
     * @return 首字母
     */
    public static char getHeadChar(String str) {
        if (str != null && str.trim().length() != 0) {
            char[] strChar = str.toCharArray();
            char headChar = strChar[0];
            //如果是大写字母则直接返回
            if (Character.isUpperCase(headChar)) {
                return headChar;
            } else if (Character.isLowerCase(headChar)) {
                return Character.toUpperCase(headChar);
            }
            // 汉语拼音格式输出类
            HanyuPinyinOutputFormat hanYuPinOutputFormat = new HanyuPinyinOutputFormat();
            hanYuPinOutputFormat.setCaseType(UPPERCASE);
            hanYuPinOutputFormat.setToneType(WITHOUT_TONE);
            if (String.valueOf(headChar).matches("[\\u4E00-\\u9FA5]+")) {
                try {
                    String[] stringArray = PinyinHelper.toHanyuPinyinStringArray(headChar, hanYuPinOutputFormat);
                    if (stringArray != null && stringArray[0] != null) {
                        return stringArray[0].charAt(0);
                    }
                } catch (BadHanyuPinyinOutputFormatCombination e) {
                    return ' ';
                }
            }
        }
        return ' ';
    }

}

然后再定义一个实体类,包含用户名,电话,用户名首字符的拼音首字母等三个属性
需要实现Comparable 接口,用于排序

public class User implements Comparable {

    private String userName;

    private String phone;

    private char headLetter;

    public User(String userName, String phone) {
        this.userName = userName;
        this.phone = phone;
        headLetter = Utils.getHeadChar(userName);
    }

    public String getUserName() {
        return userName;
    }

    public String getPhone() {
        return phone;
    }

    public char getHeadLetter() {
        return headLetter;
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (object == null || getClass() != object.getClass()) {
            return false;
        }
        User that = (User) object;
        return getUserName().equals(that.getUserName()) && getPhone().equals(that.getPhone());
    }

    @Override
    public int compareTo(Object object) {
        if (object instanceof User) {
            User that = (User) object;
            if (getHeadLetter() == ' ') {
                if (that.getHeadLetter() == ' ') {
                    return 0;
                }
                return -1;
            }
            if (that.getHeadLetter() == ' ') {
                return 1;
            } else if (that.getHeadLetter() > getHeadLetter()) {
                return -1;
            } else if (that.getHeadLetter() == getHeadLetter()) {
                return 0;
            }
            return 1;
        } else {
            throw new ClassCastException();
        }
    }

}

主布局文件如下

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_userList"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.czy.demo.HintSideBar
        android:id="@+id/hintSideBar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="right" />

</FrameLayout>

联系人列表使用的是RecyclerView,还需要定义一个Adapter

public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserHolder> {

    private List<User> userList;

    private LayoutInflater inflater;

    public UserAdapter(Context context) {
        inflater = LayoutInflater.from(context);
        userList = new ArrayList<>();
    }

    @Override
    public UserHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.item_user, parent, false);
        return new UserHolder(view);
    }

    @Override
    public void onBindViewHolder(UserHolder holder, int position) {
        holder.tv_userName.setText(userList.get(position).getUserName());
        holder.tv_phone.setText(userList.get(position).getPhone());
    }

    public void setData(List<User> userList) {
        this.userList.clear();
        this.userList = userList;
    }

    public int getFirstPositionByChar(char sign) {
        if (sign == '#') {
            return 0;
        }
        for (int i = 0; i < userList.size(); i++) {
            if (userList.get(i).getHeadLetter() == sign) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public int getItemCount() {
        return userList.size();
    }

    class UserHolder extends RecyclerView.ViewHolder {

        public TextView tv_userName;

        public TextView tv_phone;

        public UserHolder(View itemView) {
            super(itemView);
            tv_userName = (TextView) itemView.findViewById(R.id.tv_userName);
            tv_phone = (TextView) itemView.findViewById(R.id.tv_phone);
        }
    }

}

以下方法用于获取联系人列表中第一个首字符为sign的item的位置

public int getFirstPositionByChar(char sign)

主Activity代码如下

public class MainActivity extends AppCompatActivity implements SideBar.OnChooseLetterChangedListener {

    private List<User> userList;

    private UserAdapter adapter;

    private RecyclerView rv_userList;

    private LinearLayoutManager manager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        HintSideBar hintSideBar = (HintSideBar) findViewById(R.id.hintSideBar);
        rv_userList = (RecyclerView) findViewById(R.id.rv_userList);
        hintSideBar.setOnChooseLetterChangedListener(this);
        manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        rv_userList.setLayoutManager(manager);
        userList = new ArrayList<>();
        adapter = new UserAdapter(this);
        initData();
        adapter.setData(userList);
        rv_userList.setAdapter(adapter);
    }

    @Override
    public void onChooseLetter(String s) {
        int i = adapter.getFirstPositionByChar(s.charAt(0));
        if (i == -1) {
            return;
        }
        manager.scrollToPositionWithOffset(i, 0);
    }

    @Override
    public void onNoChooseLetter() {

    }
}

initData()用于向Adapter填充数据

public void initData() {
        User user1 = new User("陈", "12345678");
        User user2 = new User("赵", "12345678");
        ...
        userList.add(user1);
        userList.add(user2);
        ...
        Collections.sort(userList);
        adapter.notifyDataSetChanged();
    }

这样,整个效果就都完成了

代码我已上传到GitHub——HintSideBar

作者:new_one_object 发表于2016/10/16 2:03:59 原文链接
阅读:116 评论:0 查看评论

[知识总结(转)]flex布局语法篇

$
0
0




Flex 布局教程:语法篇

布局的传统解决方案,基于盒状模型,依赖 display属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。

2009年,W3C提出了一种新的方案—-Flex布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。

Flex布局将成为未来布局的首选方案。本文介绍它的语法,下一篇文章给出常见布局的Flex写法。

以下内容主要参考了下面两篇文章:A Complete Guide to FlexboxA Visual Guide to CSS3 Flexbox Properties

一、Flex布局是什么?

Flex是Flexible Box的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。

任何一个容器都可以指定为Flex布局。


.box{
  display: flex;
}

行内元素也可以使用Flex布局。


.box{
  display: inline-flex;
}

Webkit内核的浏览器,必须加上-webkit前缀。


.box{
  display: -webkit-flex; /* Safari */
  display: flex;
}

注意,设为Flex布局以后,子元素的floatclearvertical-align属性将失效。

二、基本概念

采用Flex布局的元素,称为Flex容器(flex container),简称”容器”。它的所有子元素自动成为容器成员,称为Flex项目(flex item),简称”项目”。

容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end

项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size

三、容器的属性

以下6个属性设置在容器上。

  • flex-direction
  • flex-wrap
  • flex-flow
  • justify-content
  • align-items
  • align-content

3.1 flex-direction属性

flex-direction属性决定主轴的方向(即项目的排列方向)。


.box {
  flex-direction: row | row-reverse | column | column-reverse;
}

它可能有4个值。

  • row(默认值):主轴为水平方向,起点在左端。
  • row-reverse:主轴为水平方向,起点在右端。
  • column:主轴为垂直方向,起点在上沿。
  • column-reverse:主轴为垂直方向,起点在下沿。

3.2 flex-wrap属性

默认情况下,项目都排在一条线(又称”轴线”)上。flex-wrap属性定义,如果一条轴线排不下,如何换行。


.box{
  flex-wrap: nowrap | wrap | wrap-reverse;
}

它可能取三个值。

(1)nowrap(默认):不换行。

(2)wrap:换行,第一行在上方。

(3)wrap-reverse:换行,第一行在下方。

3.3 flex-flow

flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap


.box {
  flex-flow: <flex-direction> || <flex-wrap>;
}

3.4 justify-content属性

justify-content属性定义了项目在主轴上的对齐方式。


.box {
  justify-content: flex-start | flex-end | center | space-between | space-around;
}

它可能取5个值,具体对齐方式与轴的方向有关。下面假设主轴为从左到右。

  • flex-start(默认值):左对齐
  • flex-end:右对齐
  • center: 居中
  • space-between:两端对齐,项目之间的间隔都相等。
  • space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。

3.5 align-items属性

align-items属性定义项目在交叉轴上如何对齐。


.box {
  align-items: flex-start | flex-end | center | baseline | stretch;
}

它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。

  • flex-start:交叉轴的起点对齐。
  • flex-end:交叉轴的终点对齐。
  • center:交叉轴的中点对齐。
  • baseline: 项目的第一行文字的基线对齐。
  • stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。

3.6 align-content属性

align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。


.box {
  align-content: flex-start | flex-end | center | space-between | space-around | stretch;
}

该属性可能取6个值。

  • flex-start:与交叉轴的起点对齐。
  • flex-end:与交叉轴的终点对齐。
  • center:与交叉轴的中点对齐。
  • space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
  • space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
  • stretch(默认值):轴线占满整个交叉轴。

四、项目的属性

以下6个属性设置在项目上。

  • order
  • flex-grow
  • flex-shrink
  • flex-basis
  • flex
  • align-self

4.1 order属性

order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。


.item {
  order: <integer>;
}

4.2 flex-grow属性

flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。


.item {
  flex-grow: <number>; /* default 0 */
}

如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。

4.3 flex-shrink属性

flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。


.item {
  flex-shrink: <number>; /* default 1 */
}

如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。

负值对该属性无效。

4.4 flex-basis属性

flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。


.item {
  flex-basis: <length> | auto; /* default auto */
}

它可以设为跟widthheight属性一样的值(比如350px),则项目将占据固定空间。

4.5 flex属性

flex属性是flex-grow, flex-shrinkflex-basis的简写,默认值为0 1 auto。后两个属性可选。


.item {
  flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。

建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。

4.6 align-self属性

align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch


.item {
  align-self: auto | flex-start | flex-end | center | baseline | stretch;
}

该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

(完)


作者:BaiHuaXiu123 发表于2016/10/16 7:33:38 原文链接
阅读:184 评论:0 查看评论

android常见错误

$
0
0

本文属于个人平时项目开发过程遇到的一些问题,记录下来并总结解决方案,希望能帮到大家解决问题,有些问题的解决方案是在StackoverFlow上找到的,建议大家遇到问题多去上面找,基本上都能找到解决方案的。

(1)将Eclipse项目导入到Android studio 中 很多点9图出现问题解决方法:
在build.gradle里添加以下两句:

    aaptOptions.cruncherEnabled = false     
    aaptOptions.useNewCruncher = false

用来关闭Android Studio的PNG合法性检查的,直接不让它检查。

(2)Android Studio 错误: 非法字符: ‘ufeff’ 解决方案|错误: 需要class, interface或enum

原因:
Eclipse可以智能的把UTF-8+BOM文件转为普通的UTF-8文件,Android Studio还没有这个功能,所以使用Android Studio编译UTF-8+BOM编码的文件时会出现” 非法字符: ‘ufeff’ “之类的错误

解决方法:
手动将UTF-8+BOM编码的文件转为普通的UTF-8文件。**

**用EdItPlus打开.java文件依次:文档》文本编辑》转换文本编码》选择UTF-8编码即可

(3)将项目导入到AS中出现以下问题:

    Error:Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'. > com.android.bui

解决方法:
在build.grade中添加以下代码:

android{
packagingOptions {
exclude 'META-INF/DEPENDENCIES.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
          }
}

(4)未知错误

Error:Timeout waiting to lock cp_proj class cache for build file '/Users/Mr.xiao/Desktop/AndroidShopNC2014MoblieNew/androidShopNC2014Moblie/build.gradle' 
(/Users/Mr.xiao/.gradle/caches/2.10/scripts/build_3cyr7hzjurcc62ge3ixidshos/cp_proj).
It is currently in use by another Gradle instance.
Owner PID: unknown
Our PID: 1412
Owner Operation: unknown
Our operation: Initialize cache
Lock file: /Users/Mr.xiao/.gradle/caches/2.10/scripts/build_3cyr7hzjurcc62ge3ixidshos/cp_proj/cache.properties.lock

解决方案
以上是错误提示。

解决的思路很简单只需要把cache.properties.lock文件删除了就可以了。当时我们删除的时候会被占用这时候需要进入任务管理器结束关于java的进程就行比如 java 的jdk 删除后重启让java jdk启动 启动Android Studio就能启动APK了。

(5)修改了Android项目的最小SDK版本之后出现很多stysle文件找不到

解决方案

compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "net.mmloo2014.android"
minSdkVersion 14
targetSdkVersion 23
}

compileSdkVersion 是多少版本的

那么compile ‘com.android.support:appcompat-v7:23.2.1’ 就是啥版本的。

(6)Android studio 编译问题:finished with non-zero exit value 2

问题:

Error:Execution failed for task ':androidShopNC2014Moblie:transformClassesWithDexForDebug'.
>
com.android.build.api.transform.TransformException: 
com.android.ide.common.process.ProcessException: 
java.util.concurrent.ExecutionException: 
com.android.ide.common.process.ProcessException: 
org.gradle.process.internal.ExecException: 
Process 'command '/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/bin/java'' finished with non-zero exit value 2

解决方案
这个错误在app的build.gradle里面添加下面这句就好了。

android {
defaultConfig {
multiDexEnabled true
}
}
(7)Android studio 编译问题:finished with non-zero exit value 1(由于导入的依赖出现重复造成的)

问题:

Error:Execution failed for task ‘:app:transformClassesWithDexForDebug’.

com.Android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process ‘command ‘F:\Program Files (x86)[Java](http://lib.csdn.net/base/17)\jdk1.8.0_31\bin\java.exe” finished with non-zero exit value 1
解决方案
这个是因为依赖包重复了 (像v4和nineoldandroids),app中实现了对easeUI的依赖,但是app和easeUI都添加了对这个包的依赖。所以就报这个错误,修改之后再报,就clean,rebuild一下。

(8)问题

Error:Execution failed for task 
':app:transformClassesWithJarMergingForDebug'.> 
com.android.build.api.transform.TransformException: 
java.util.zip.ZipException:
 duplicate entry: org/apache/http/ConnectionClosedException.class

解决方案
这个是在我们启动的时候报错的,而不是在编译的时候,原因是这样的,报这个错是因为有2个库中存在相同的类。大家可以看到stackoverflow上有人也提了这样的问题。只需要删除其中的一个就可以解决了。

(9)添加第三方依赖出现的问题

Error:Execution failed for task ‘:app:processDebugManifest’.

Manifest merger failed :
uses-sdk:minSdkVersion 14 cannot be smaller than version 19 declared in library [com.github.meikoz:basic:2.0.3]
/AndroidStudioCode/EnjoyLife/app/build/intermediates/exploded-aar/
com.github.meikoz/basic/2.0.3/AndroidManifest.xml
Suggestion: use tools:overrideLibrary=”com.android.core” to force usage
错误原因
出现这个错误的原因是我引入的第三方库最低支持版本高于我的项目的最低支持版本,异常中的信息显示:我的项目的最低支持版本为14,而第三方库的最低支持版本为19,所以抛出了这个异常。

解决方案
在AndroidManifest.xml文件中标签中添加

其中的xxx.xxx.xxx为第三方库包名,如果存在多个库有此异常,则用逗号分割它们,例如:

这样做是为了项目中的AndroidManifest.xml和第三方库的AndroidManifest.xml合并时可以忽略最低版本限制。

(10)Android studio 编译问题:finished with non-zero exit value 1(由于buildtools版本太高造成的)

错误

Error:Execution failed for task ‘:app:transformClassesWithDexForDebug’.

com.android.ide.common.process.ProcessException:
org.gradle.process.internal.ExecException:
Process ‘command ‘/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/bin/java” finished with non-zero exit value 1
错误原因
buildToolsVersion版本太高,我原来的 buildToolsVersion “24.0.0” 需要jdk1.8,而我的是jdk1.7,所以一直报这个错,刚开始以为是v4包和V7包冲突,因为之前遇到这样的问题,而这次删除V4包之后依然报这个错,上stackoverflow搜了一下,把buildTools版本降下来就好了。

解决方案

android {

compileSdkVersion 23

buildToolsVersion “23.0.3”

}
(11)Android studio 编译问题:Gradle DSL not found ‘android()’

问题

clipboard.png

解决方案

配置build.gradle:

buildscript {
repositories {   
jcenter()
}

dependencies {   
classpath 'com.android.tools.build:gradle:2.1.2'
   }
}

allprojects {  
repositories {    
jcenter()
   }
}
buildscript {
repositories {      
jcenter()
}

dependencies {
classpath 'com.android.tools.build:gradle:2.1.2'
    }
}


allprojects {
repositories {
jcenter()
    }
}

配置app/build.gradle:

apply plugin: 'com.android.application'android { 
compileSdkVersion 23
 buildToolsVersion '23.0.3' 
defaultConfig { 
minSdkVersion 9 
targetSdkVersion 23 
versionCode 1 
versionName '1.0' 
             }
}
dependencies { 
compile 'com.android.support:appcompat-v7:23.2.1'
}

最后再同步一下sync即可。

(12)Android studio 编译问题:Gradle DSL not found ‘android()’

问题描述

Error:(51, 52) 错误: -source 1.6 中不支持 diamond 运算符
(请使用 -source 7 或更高版本以启用 diamond 运算符)
解决方案

方案一

将标红处设置为1.7.png

修改soure为1.7.png

方案二
在build gradle中进行配置如下代码:

android {
 compileOptions {
 sourceCompatibility JavaVersion.VERSION_1_7
 targetCompatibility JavaVersion.VERSION_1_7
 }
}

最后同步一下即可

(13)Glide使用问题:使用Glide加载圆角图片,第一次显示占位图

问题描述
最近在项目中使用Glide加载圆形图片,并且设置placehloder和error两个占位图,运行发现,第一次加载图片只显示占位图,需要第二次进入的时候才会正常显示。

如果你刚好使用了这个圆形Imageview库或者其他的一些自定义的圆形Imageview,而你又刚好设置了占位的话,那么,你就会遇到第一个问题。如何解决呢?

方案一
不设置占位图

方案二
使用Glide的Transformation API自定义圆形Bitmap的转换

/**
* Glide圆形图片处理
*/
“`android
static class CircleTransform extends BitmapTransformation {
public CircleTransform(Context context) {
super(context);
}

    @Override
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        return circleCrop(pool, toTransform);
    }

    private static Bitmap circleCrop(BitmapPool pool, Bitmap source) {
        if (source == null) return null;

        int size = Math.min(source.getWidth(), source.getHeight());
        int x = (source.getWidth() - size) / 2;
        int y = (source.getHeight() - size) / 2;

        Bitmap squared = Bitmap.createBitmap(source, x, y, size, size);

        Bitmap result = pool.get(size, size, Bitmap.Config.RGB_565);
        if (result == null) {
            result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        }

        Canvas canvas = new Canvas(result);
        Paint paint = new Paint();
        paint.setShader(new BitmapShader(squared, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
        paint.setAntiAlias(true);
        float r = size / 2f;
        canvas.drawCircle(r, r, r, paint);
        return result;
    }

    @Override
    public String getId() {
        return getClass().getName();
    }
}
```

使用方法:

Glide.with(context).load(imageUrl).placeholder(placeholder).error(errorImage).transform(new CircleTransform(context)).into(imageView);
方案三
重写Glide的图片加载监听方法,具体如下:

Glide.with(mContext)
.load(url)
.placeholder(R.drawable.loading_drawable)
.into(new SimpleTarget(width, height) {
@Override public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
// setImageBitmap(bitmap) on CircleImageView
}
});
注意事项:
该方法在listview上复用有问题的bug,如果在listview中加载CircleImageView,请不要使用该方法。

方案四:不使用Glide的默认动画:

Glide.with(mContext)
.load(url)
.dontAnimate()
.placeholder(R.drawable.loading_drawable)
.into(circleImageview);
(14)json数据解析问题:json串头部出现字符:”ufeff” 解决方法
异常信息

org.json.JSONException: Value  of type java.lang.String cannot be converted to JSONObject
解析服务器返回 的json格式数据时,我们可能会发现,数据格式上是没有问题的,但是仔细对比会发现,在json串头部发现字符:”ufeff”

客户端解决方案:

/**
* 异常信息:org.json.JSONException: Value  of type java.lang.String cannot be converted to JSONObject
* json串头部出现字符:”\ufeff” 解决方法
* @param data
* @return
*/
public static final String removeBOM(String data) {
if (TextUtils.isEmpty(data)) {
return data;
}
if (data.startsWith(“\ufeff”)) {
return data.substring(1);
}
else {
return data;
}
}
服务器端解决方案:
将输出此json的php源码重新用editplus之类用utf-8无BOM的编码保存。不要用windows系统自带的记事本编辑php源码,这个BOM就是记事本这些windows自带的编辑器引入的。

(15)Android studio编译问题:not found ndk()
问题

Error:(15, 0) Gradle DSL method not found: ‘ndk()’ method-not-found-ndk
解决方案
出现该问题,可能是由于ndk配置在build.gradle配置文件中位置弄错导致的

apply plugin: ‘com.android.application’
android {
compileSdkVersion 23
buildToolsVersion “23.0.2”

defaultConfig {
    applicationId "com.guitarv.www.ndktest"
    minSdkVersion 17
    targetSdkVersion 23
    versionCode 1
    versionName "1.0"
    ndk {
        moduleName = "HelloJNI"
    }
    sourceSets.main {
        jni.srcDirs = []
        jniLibs.srcDir "src/main/libs"
    }
}
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

}
原文地址:http://www.apkbus.com/blog-624339-61818.html?_dsign=3b9a6f6c

作者:u011275152 发表于2016/10/16 8:40:03 原文链接
阅读:81 评论:0 查看评论

View Animation

$
0
0

简介

视图动画,主要包括位移,透明度,旋转和缩放,View本身的属性并没有发生变化,只是在这个视图上添加一些渐变的效果,所以总体而言,视图动画只能实现一些简单的动画效果,属性动画功能更强大。

使用

res/anim目录下创建动画资源文件,存放帧动画和渐变动画,主要tag:
set, alpha, scale, tranlate, rotate分别对应动画集合,透明动画,缩放动画,位移动画,旋转动画

格式

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:shareInterpolator=["true" | "false"] >//子元素是否共享这个插值器

    <!--[0.0   ,   1.0] -->
    <alpha
        android:fromAlpha="float"
        android:toAlpha="float" />

    <!--[0.0, ?]
    <!-- pivotX,pivotY用来控制缩放的中心点-->
    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />

    <!-- in pixels relative to the normal position (such as "5"), in percentage relative to the element width (such as "5%"), or in percentage relative to the parent width (such as "5%p")-->
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />

    <!-- android:pivotX, android:pivotY ----  n pixels relative to the object's left edge (such as "5"), in percentage relative to the object's left edge (such as "5%"), or in percentage relative to the parent container's left edge (such as "5%p") -->
    <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>

< scale >

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:duration="2000"//时常(运行代码不要在这里做注释)
        android:pivotX="50%"
        android:pivotY="50%"//缩放中心点为视图的中心
        android:fromXScale="1.0"//起点大小
        android:fromYScale="1.0"
        android:toXScale="2.0"//目标大小
        android:toYScale="2.0" />
</set>

这里写图片描述

< alpha>

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:duration="2000"//时常
        android:fromAlpha="1.0"//起始透明度
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"//插值
        android:toAlpha="0.0" />//目标透明度
</set>

这里写图片描述

< rotate>

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <rotate
        android:duration="2000"//时常
        android:fromDegrees="0"//起始角度
        android:interpolator="@android:anim/decelerate_interpolator"//插值
        android:pivotX="50%"
        android:pivotY="50%"//旋转中心点为视图中心
        android:toDegrees="180" />//目标角度
</set>

这里写图片描述

< translate>

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="2000"//时常
        android:fromXDelta="0"
        android:fromYDelta="0"//起始位置相对于视图的位移
        android:interpolator="@android:anim/accelerate_interpolator"
        android:toXDelta="300"
        android:toYDelta="300" />//目标位置相对于视图的位移
</set>

这里写图片描述

< set>

将上面的代码各种渐变动画全部防止在< set >tag下,实现共同效果

这里写图片描述

备注

代码中加载动画,例如旋转动画:

rotateAnim = AnimationUtils.loadAnimation(this, R.anim.rotate_anim);
imageView.startAnimation(rotateAnim);
作者:poorkick 发表于2016/10/16 9:07:18 原文链接
阅读:77 评论:0 查看评论

Json与XML解析的区别比较

$
0
0

JSON与XML的区别比较

1.定义介绍

(1).XML定义
扩展标记语言 (Extensible Markup Language, XML) ,用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。 XML使用DTD(document type definition)文档类型定义来组织数据;格式统一,跨平台和语言,早已成为业界公认的标准。
XML是标准通用标记语言 (SGML) 的子集,非常适合 Web 传输。XML 提供统一的方法来描述和交换独立于应用程序或供应商的结构化数据。

(2).JSON定义
JSON(JavaScript Object Notation)一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。可在不同平台之间进行数据交换。JSON采用兼容性很高的、完全独立于语言文本格式,同时也具备类似于C语言的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)体系的行为。这些特性使JSON成为理想的数据交换语言。
JSON基于JavaScript Programming Language , Standard ECMA-262 3rd Edition - December 1999 的一个子集。

2.XML和JSON优缺点

(1).XML的优缺点
<1>.XML的优点
  A.格式统一,符合标准;
  B.容易与其他系统进行远程交互,数据共享比较方便。
<2>.XML的缺点
  A.XML文件庞大,文件格式复杂,传输占带宽;
  B.服务器端和客户端都需要花费大量代码来解析XML,导致服务器端和客户端代码变得异常复杂且不易维护;
  C.客户端不同浏览器之间解析XML的方式不一致,需要重复编写很多代码;
  D.服务器端和客户端解析XML花费较多的资源和时间。

(2).JSON的优缺点
<1>.JSON的优点:
  A.数据格式比较简单,易于读写,格式都是压缩的,占用带宽小;
  B.易于解析,客户端JavaScript可以简单的通过eval()进行JSON数据的读取;
  C.支持多种语言,包括ActionScript, C, C#, ColdFusion, Java, JavaScript, Perl, PHP, Python, Ruby等服务器端语言,便于服务器端的解析;
  D.在PHP世界,已经有PHP-JSON和JSON-PHP出现了,偏于PHP序列化后的程序直接调用,PHP服务器端的对象、数组等能直接生成JSON格式,便于客户端的访问提取;
  E.因为JSON格式能直接为服务器端代码使用,大大简化了服务器端和客户端的代码开发量,且完成任务不变,并且易于维护。
<2>.JSON的缺点
  A.没有XML格式这么推广的深入人心和喜用广泛,没有XML那么通用性;
  B.JSON格式目前在Web Service中推广还属于初级阶段。

3.XML和JSON的优缺点对比

(1).可读性方面。
JSON和XML的数据可读性基本相同,JSON和XML的可读性可谓不相上下,一边是建议的语法,一边是规范的标签形式,XML可读性较好些。
(2).可扩展性方面。
XML天生有很好的扩展性,JSON当然也有,没有什么是XML能扩展,JSON不能的。
(3).编码难度方面。
XML有丰富的编码工具,比如Dom4j、JDom等,JSON也有json.org提供的工具,但是JSON的编码明显比XML容易许多,即使不借助工具也能写出JSON的代码,可是要写好XML就不太容易了。
(4).解码难度方面。
XML的解析得考虑子节点父节点,让人头昏眼花,而JSON的解析难度几乎为0。这一点XML输的真是没话说。
(5).流行度方面。
XML已经被业界广泛的使用,而JSON才刚刚开始,但是在Ajax这个特定的领域,未来的发展一定是XML让位于JSON。到时Ajax应该变成Ajaj(Asynchronous Javascript and JSON)了。
(6).解析手段方面。
JSON和XML同样拥有丰富的解析手段。
(7).数据体积方面。
JSON相对于XML来讲,数据的体积小,传递的速度更快些。
(8).数据交互方面。
JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互。
(9).数据描述方面。
JSON对数据的描述性比XML较差。
(10).传输速度方面。
JSON的速度要远远快于XML。

4.XML与JSON数据格式比较

(1).关于轻量级和重量级
轻量级和重量级是相对来说的,那么XML相对于JSON的重量级体现在哪呢?应该体现在解析上,XML目前设计了两种解析方式:DOM和 SAX
<1>.DOM
DOM是把一个数据交换格式XML看成一个DOM对象,需要把XML文件整个读入内存,这一点上JSON和XML的原理是一样的,但是XML要考虑父节点和子节点,这一点上JSON的解析难度要小很多,因为JSON构建于两种结构:key/value,键值对的集合;值的有序集合,可理解为数组;
<2>.SAX
SAX不需要整个读入文档就可以对解析出的内容进行处理,是一种逐步解析的方法。程序也可以随时终止解析。这样,一个大的文档就可以逐步的、一点一点的展现出来,所以SAX适合于大规模的解析。这一点,JSON目前是做不到得。
所以,JSON和XML的轻/重量级的区别在于
JSON只提供整体解析方案,而这种方法只在解析较少的数据时才能起到良好的效果;
XML提供了对大规模数据的逐步解析方案,这种方案很适合于对大量数据的处理。

(2).关于数据格式编码及解析难度
<1>.在编码方面。
虽然XML和JSON都有各自的编码工具,但是JSON的编码要比XML简单,即使不借助工具,也可以写出JSON代码,但要写出好的XML代码就有点困难;与XML一样,JSON也是基于文本的,且它们都使用Unicode编码,且其与数据交换格式XML一样具有可读性。
主观上来看,JSON更为清晰且冗余更少些。JSON网站提供了对JSON语法的严格描述,只是描述较简短。从总体来看,XML比较适合于标记文档,而JSON却更适于进行数据交换处理。
<2>.在解析方面。
在普通的web应用领域,开发者经常为XML的解析伤脑筋,无论是服务器端生成或处理XML,还是客户端用 JavaScript 解析XML,都常常导致复杂的代码,极低的开发效率。
实际上,对于大多数Web应用来说,他们根本不需要复杂的XML来传输数据,XML宣称的扩展性在此就很少具有优势,许多Ajax应用甚至直接返回HTML片段来构建动态Web页面。和返回XML并解析它相比,返回HTML片段大大降低了系统的复杂性,但同时缺少了一定的灵活性。同XML或 HTML片段相比,数据交换格式JSON 提供了更好的简单性和灵活性。在Web Serivice应用中,至少就目前来说XML仍有不可动摇的地位。

(3).实例比较
XML和JSON都使用结构化方法来标记数据,下面来做一个简单的比较。
<1>.用XML表示中国部分省市数据如下:

复制代码
<?xml version="1.0" encoding="utf-8" ?>
<country>
  <name>中国</name>
  <province>
    <name>黑龙江</name>
    <citys>
      <city>哈尔滨</city>
      <city>大庆</city>
    </citys>    
  </province>
  <province>
    <name>广东</name>
    <citys>
      <city>广州</city>
      <city>深圳</city>
      <city>珠海</city>
    </citys>   
  </province>
  <province>
    <name>台湾</name>
    <citys>
       <city>台北</city>
       <city>高雄</city>
    </citys> 
  </province>
  <province>
    <name>新疆</name>
    <citys>
      <city>乌鲁木齐</city>
    </citys>
  </province>
</country>
复制代码

<2>.用JSON表示中国部分省市数据如下:

复制代码
 var country =
        {
            name: "中国",
            provinces: [
            { name: "黑龙江", citys: { city: ["哈尔滨", "大庆"]} },
            { name: "广东", citys: { city: ["广州", "深圳", "珠海"]} },
            { name: "台湾", citys: { city: ["台北", "高雄"]} },
            { name: "新疆", citys: { city: ["乌鲁木齐"]} }
            ]
        }
复制代码

编码的可读性来说,XML有明显的优势,毕竟人类的语言更贴近这样的说明结构。JSON读起来更像一个数据块,读起来就比较费解了。不过,我们读起来费解的语言,恰恰是适合机器阅读,所以通过JSON的索引country.provinces[0].name就能够读取“黑龙江”这个值。
编码的手写难度来说,XML还是舒服一些,好读当然就好写。不过写出来的字符JSON就明显少很多。去掉空白制表以及换行的话,JSON就是密密麻麻的有用数据,而XML却包含很多重复的标记字符。

作者:yanlei_cs 发表于2016/10/16 9:48:02 原文链接
阅读:70 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>