Android Expandable List View

可展开的列表主要用于分组或者分类的列表,在列表中一组就是一行,展开一行可以显示这一组的子列表,也可以收起来节省空间,每一行即每一组可以看做两部分组成,就是头部和子列表部分,点击头部即可展开或者收起,这种组件的应用也很普遍。

1.创建主布局文件

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:background="#f4f4f4" >
 
            <ExpandableListView
                android:id="@+id/lvExp"
                android:layout_height="match_parent"
                android:layout_width="match_parent"/>   
 
</LinearLayout>

2.创建组的布局文件即组的头部

list_group.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="8dp"
    android:background="#000000">
 
 
    <TextView
        android:id="@+id/lblListHeader"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
        android:textSize="17dp"
        android:textColor="#f9f93d" />
 
</LinearLayout>

3.创建子列表行布局文件

list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="55dip"
    android:orientation="vertical" >
 
    <TextView
        android:id="@+id/lblListItem"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="17dip"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:paddingLeft="?android:attr/expandableListPreferredChildPaddingLeft" />
 
</LinearLayout>

4.创建Adapter继承自BaseExpandableAdapter

public class ExpandableListAdapter extends BaseExpandableListAdapter {
 
    private Context _context;
    private List<String> _listDataHeader; // header titles
    // child data in format of header title, child title
    private HashMap<String, List<String>> _listDataChild;
 
    public ExpandableListAdapter(Context context, List<String> listDataHeader,
            HashMap<String, List<String>> listChildData) {
        this._context = context;
        this._listDataHeader = listDataHeader;
        this._listDataChild = listChildData;
    }
 
    @Override
    public Object getChild(int groupPosition, int childPosititon) {
        return this._listDataChild.get(this._listDataHeader.get(groupPosition))
                .get(childPosititon);
    }
 
    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }
 
    @Override
    public View getChildView(int groupPosition, final int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) {
 
        final String childText = (String) getChild(groupPosition, childPosition);
 
        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) this._context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.list_item, null);
        }
 
        TextView txtListChild = (TextView) convertView
                .findViewById(R.id.lblListItem);
 
        txtListChild.setText(childText);
        return convertView;
    }
 
    @Override
    public int getChildrenCount(int groupPosition) {
        return this._listDataChild.get(this._listDataHeader.get(groupPosition))
                .size();
    }
 
    @Override
    public Object getGroup(int groupPosition) {
        return this._listDataHeader.get(groupPosition);
    }
 
    @Override
    public int getGroupCount() {
        return this._listDataHeader.size();
    }
 
    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }
 
    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
            View convertView, ViewGroup parent) {
        String headerTitle = (String) getGroup(groupPosition);
        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) this._context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.list_group, null);
        }
 
        TextView lblListHeader = (TextView) convertView
                .findViewById(R.id.lblListHeader);
        lblListHeader.setTypeface(null, Typeface.BOLD);
        lblListHeader.setText(headerTitle);
 
        return convertView;
    }
 
    @Override
    public boolean hasStableIds() {
        return false;
    }
 
    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }
}

5.在Activity中使用

public class MainActivity extends Activity {
 
    ExpandableListAdapter listAdapter;
    ExpandableListView expListView;
    List<String> listDataHeader;
    HashMap<String, List<String>> listDataChild;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        // get the listview
        expListView = (ExpandableListView) findViewById(R.id.lvExp);
 
        // preparing list data
        prepareListData();
 
        listAdapter = new ExpandableListAdapter(this, listDataHeader, listDataChild);
 
        // setting list adapter
        expListView.setAdapter(listAdapter);
    }
 
    /*
     * Preparing the list data
     */
    private void prepareListData() {
        listDataHeader = new ArrayList<String>();
        listDataChild = new HashMap<String, List<String>>();
 
        // Adding child data
        listDataHeader.add("Top 250");
        listDataHeader.add("Now Showing");
        listDataHeader.add("Coming Soon..");
 
        // Adding child data
        List<String> top250 = new ArrayList<String>();
        top250.add("The Shawshank Redemption");
        top250.add("The Godfather");
        top250.add("The Godfather: Part II");
        top250.add("Pulp Fiction");
        top250.add("The Good, the Bad and the Ugly");
        top250.add("The Dark Knight");
        top250.add("12 Angry Men");
 
        List<String> nowShowing = new ArrayList<String>();
        nowShowing.add("The Conjuring");
        nowShowing.add("Despicable Me 2");
        nowShowing.add("Turbo");
        nowShowing.add("Grown Ups 2");
        nowShowing.add("Red 2");
        nowShowing.add("The Wolverine");
 
        List<String> comingSoon = new ArrayList<String>();
        comingSoon.add("2 Guns");
        comingSoon.add("The Smurfs 2");
        comingSoon.add("The Spectacular Now");
        comingSoon.add("The Canyons");
        comingSoon.add("Europa Report");
 
        listDataChild.put(listDataHeader.get(0), top250); // Header, Child data
        listDataChild.put(listDataHeader.get(1), nowShowing);
        listDataChild.put(listDataHeader.get(2), comingSoon);
    }
}

代码就这么多了,运行之后就看到效果了,如下图所示

image

监听列表展开和收起的事件和子节点的点击事件

// Listview Group expanded listener
expListView.setOnGroupExpandListener(new OnGroupExpandListener() {
 
    @Override
    public void onGroupExpand(int groupPosition) {
        Toast.makeText(getApplicationContext(),
                listDataHeader.get(groupPosition) + " Expanded",
                Toast.LENGTH_SHORT).show();
    }
});
// Listview Group collasped listener
expListView.setOnGroupCollapseListener(new OnGroupCollapseListener() {
 
    @Override
    public void onGroupCollapse(int groupPosition) {
        Toast.makeText(getApplicationContext(),
                listDataHeader.get(groupPosition) + " Collapsed",
                Toast.LENGTH_SHORT).show();
 
    }
});
// Listview on child click listener
expListView.setOnChildClickListener(new OnChildClickListener() {

	@Override
	public boolean onChildClick(ExpandableListView parent, View v,
			int groupPosition, int childPosition, long id) {
		Toast.makeText(
				getApplicationContext(),
				listDataHeader.get(groupPosition)
						+ " : "
						+ listDataChild.get(
								listDataHeader.get(groupPosition)).get(
								childPosition), Toast.LENGTH_SHORT)
				.show();
		return false;
	}
});

Android中使用ListView

数据列表是使用很普遍的组件,Android中的列表用法很灵活,行的布局是可以自定义的,因此可以显示任何形式的数据列表,本示例显示一个数据列表并且响应行的点击事件。

1.创建一个静态的数据xml用于列表数据展示

list_data.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="adobe_products">
        <item>Adobe After Effects</item>
        <item>Adobe Bridge</item>
        <item>Adobe Dreamweaver</item>
        <item>Adobe Edge</item>
        <item>Adobe Fireworks</item>
        <item>Adobe Flash</item>
        <item>Adobe Photoshop</item>
        <item>Adobe Premiere</item>
        <item>Adobe Reader</item>
        <item>Adobe Illustrator</item>
    </string-array>
</resources>

2.创建行的布局list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<!--  Single List Item Design -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/label"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:padding="10dip"
        android:textSize="16dip"
        android:textStyle="bold" >
</TextView>

3.创建acitvity继承自ListActivity

public class AndroidListViewActivity extends ListActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
         
        // storing string resources into Array
        String[] adobe_products = getResources().getStringArray(R.array.adobe_products);
         
        // Binding resources Array to ListAdapter
        this.setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, R.id.label, adobe_products));
         
    }
}

这样一个listview就大功告成了,运行起来就看到效果了

 image

下面我们来添加行点击事件,并且打开另外一个页面

4.给listview添加事件

public class AndroidListViewActivity extends ListActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
         
        // storing string resources into Array
        String[] adobe_products = getResources().getStringArray(R.array.adobe_products);
         
        // Binding resources Array to ListAdapter
        this.setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, R.id.label, adobe_products));
         
        ListView lv = getListView();
 
        // listening to single list item on click
        lv.setOnItemClickListener(new OnItemClickListener() {
          public void onItemClick(AdapterView<?> parent, View view,
              int position, long id) {
               
              // selected item 
              String product = ((TextView) view).getText().toString();
               
              // Launching new Activity on selecting single List Item
              Intent i = new Intent(getApplicationContext(), SingleListItem.class);
              // sending data to new activity
              i.putExtra("product", product);
              startActivity(i);
             
          }
        });
    }
}

5.创建打开页面的布局文件

single_list_item_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <TextView android:id="@+id/product_label"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textSize="25dip"
            android:textStyle="bold"
            android:padding="10dip"
            android:textColor="#ffffff"/>    
</LinearLayout>

6.创建打开页面的acitvity

SingleListItem.java
package com.androidhive.androidlistview;
 
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;
 
public class SingleListItem extends Activity{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.single_list_item_view);
         
        TextView txtProduct = (TextView) findViewById(R.id.product_label);
         
        Intent i = getIntent();
        // getting attached intent data
        String product = i.getStringExtra("product");
        // displaying selected product name
        txtProduct.setText(product);
         
    }
}

7.别忘了在AnroidManifest.xml中加入activity的声明

<application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".AndroidListViewActivity"
                  android:label="Android List View">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
         
        <activity android:name=".SingleListItem"
                    android:label="Single Item Selected"></activity>
    </application>

8.最后运行的时候,列表点击就有效果了,如下所示

image

总结

ListView的用途非常广泛,可以放置在任何地方显示数据,小的方面就是一个菜单列表,或者简单的数据展示,比较大的方面行的布局比较复杂,比如显示新闻列表,行的布局比较复杂有图片和文字或者一些其他一些图标等等,可以对ListView进行扩展得到更复杂的应用效果,不如树形结构的列表等等。


	

如何破解aspose cells words for java

对于文档的处理工具来说,aspose序列的工具包是我接触过最好最简单最强大的东西,没有之一,当然不排除有更好的也许是我还没接触到,当然这是一个付费软件而且价格不菲,但是嘿嘿在我们这个美好的国度里面,这些都不是问题,授人以鱼不如授人以渔,让我们来看看如何让他免费的为我们服务。

在开始之前我们需要三个神器,一个是java decompiler,另一个叫javassist,最后一个就是7-zip.

java decompiler顾名思义就是反编译,可以看到class的代码,这个是直接有UI可以看到;javassist提供一些api让你调用可以修改class的代码,这个没有UI.7-zip就不用介绍了也可以使用winrar之类的。

了解一下aspose cells words的限制,编写一段代码动态生成world文档,每个文档里面加入几百页的内容,保存几百份的文档

这里使用官方网站下载的jar包aspose-words-15.8.0-jdk16.jar

private static void wordApi() throws Exception{
	
	String template_file_path = "c:/aspose.hack/";
	
	File f = new File(template_file_path);
	if(!f.exists()){
		f.mkdirs();
	}
	
	Document doc = new Document();
	DocumentBuilder builder = new DocumentBuilder(doc);
	//每个文档1000页
	for(int index = 0;index< 1001;index++){
		builder.insertHtml("<div>world"+index+"</div>");
		builder.insertBreak(BreakType.PAGE_BREAK);
	}
	//存成pdf1000份
	for(int index = 0;index < 1000; index++){
		String template_file_name = template_file_path + index+"world.pdf";
		doc.save(template_file_name,com.aspose.words.SaveFormat.PDF);		
	}
	System.out.println("ok");
}
public static void main(String[] args) throws Exception{
	
	//excelApi();
	wordApi();
	
}

执行这一段代码之后,打开生成的pdf文件发现有两个水印,一个是在文档开始部分的水印文字Evaluation Only.开头的文字,另外一个限制就是到198页就结束了并且有水印说明是试用版的。

下面我们来一步步去除这些限制,先用java decompiler打开jar包,搜索Evaluation Only,注意所有的搜索选项都勾上

image

打开搜索到的内容看到下面这个方法

image

找到地方就好办了,使用javassist将这个方法的方法体改为直接return就行了,以下是修改的代码

private static void wordHack() throws Exception{
	
	ClassPool pool = ClassPool.getDefault(); 
	//jar包
	pool.insertClassPath("D:\\aspose\\aspose-words-15.8.0-jdk16.jar");
	//修改的class
	CtClass cls = pool.get("com.aspose.words.zzYUI");
	//修改的方法
	CtMethod method = cls.getDeclaredMethod("zzk");
	//方法体
	method.setBody("return;");
	//写到文件
	cls.writeFile("D:\\aspose\\");
	
	System.out.println("ok");
}
public static void main(String[] args) throws Exception{
	
	//cellHack();
	wordHack();
	
}

运行上面的方法之后看到生成的class文件

image

使用java decompiler打开看看我们修改之后的代码

image

发现方法体不见了

使用7-zip打开aspose-words-15.8.0-jdk16.jar文件,将生成之后的zzYUI.class文件覆盖jar包里面原来的文件,将文件拖进去就可以了。

然后执行一下上面生成文档的代码,发现抛异常就是class文件签名的异常,我们把签名文件删除就可以了,删除META-INF里面的.MF文件

image

删除这个.MF文件之后再运行生成文档的代码就没有问题了,打开生成的pdf文件发现也没有水印了,大功告成!!!

同理对aspose-cells-8.5.2.jar的处理也是一样的道理,找到写入水印的地方

cell1

看看zalg.a()这个方法

cell2

发现这个方法才是关键, 判断是否授权过期,直接把这个方法改为return 1;就可以了,同时把jar包里面的.MF删除就大功告成了。

总结

aspose系列的jar包都应该可以通过以上的方式解除水印,借助aspose序列的工具,使用world模板来生成文档真的是非常的方便,付费的软件确实是比一些开源的软件使用起来方便很多。

免责声明

以上的内容只是出于学习使用java的交流,没有任何商业目的,aspose只是作为一个示例,任何人不得使用,因此而引起的任何形式的纠纷都与本站fulllstacks.cn无关,转载请注明出处。

《三体1》5.台球

推开丁仪那套崭新的三居室的房门,汪淼闻到了一股酒味,看到丁仪躺在沙发上,电视开着,他的双眼却望着天花板。汪淼四下打量了一下,看到房间还没怎么装修,也没什么家具和陈设,宽大的客厅显得很空,最显眼的是客厅一角摆放的一张台球桌。
对汪淼的不请自来,丁仪倒没表示反感,他显然也想找人说话。
“这套房子是三个月前买的,”丁仪说,“我买房子干什么?难道她真的会走进家庭?”他带着醉意笑着摇摇头。
“你们……”汪淼想知道杨冬生活中的一切,但又不知该如何问。
“她像一颗星星,总是那么遥远,照到我身上的光也总是冷的。”丁仪走到窗前看着夜空,像在寻找那颗已逝去的星辰。
汪淼也沉默下来。很奇怪,他现在就是想听一听她的声音,一年前那个夕阳西下的时刻,她同他对视的那一瞬间没有说话,他从来没有听到过她的声音。
丁仪一挥手,像要赶走什么,将自己从这哀婉的思绪中解脱出来。
“汪教授,你是对的,别跟军方和警方纠缠到一块儿,那是一群自以为是的白痴。那些物理学家的自杀与‘科学边界’没有关系,我对他们解释过,可解释不清。”
“他们好像也做过一些调查。”
“是,而且这种调查还是全球范围的,那他们也应该知道,其中的两人与‘科学边界’没有任何来往,包括——杨冬。”丁仪说出这个名字时显得很吃力。
“丁仪,你知道,我现在也卷进这件事里了。所以,关于使杨冬做出这种选择的原因,我很想知道,我想你一定知道一些。”汪淼笨拙地说道,试图掩盖他真正的心迹。
“如果知道了,你只会卷得更深。现在你只是人和事卷进来了,知道后连精神也会卷进来,那麻烦就大了。”
“我是搞应用研究的,没有你们理论派那么敏感。”
“那好吧,打过台球吗?”丁仪走到了台球桌前。
“上学时随便玩过几下。”
“我和她很喜欢打,因为这让我们想到了加速器中的粒子碰撞。”丁仪说着拿起黑白两个球,将黑球放到洞旁,将白球放到距黑球仅十厘米左右的位置,问汪淼,“能把黑球打进去吗?”
“这么近谁都能。”
“试试。”
汪淼拿球杆,轻击白球,将黑球撞人洞内。
“很好,来,我们把球桌换个位置。”丁仪招呼一脸迷惑的汪淼,两人抬起沉重的球桌,将它搬到客厅靠窗的一角。放稳后,丁仪从球袋内掏出刚才打进去的黑球,将它放到洞边,又拾起那个白球,再次放到距黑球十厘米左右的地方,“这次还能打进去吗?”
“当然。”
“打吧。”
汪淼再次轻而易举地将黑球打人洞内。
“搬。”丁仪挥手示意,两人再次抬起球桌,搬到客厅的第三个角,丁仪又将黑白两个球摆放到同样的位置,“打吧。”
“我说,我们……”
“打吧。”
汪淼无奈地笑笑,第三次将黑球击人洞内。
他们又搬了两次台球桌,一次搬到了客厅靠门的一角,最后一次搬回了原位。丁仪又两次将黑白球摆到洞前的位置,汪淼又两次将黑球击人洞内。这时两人都有些出汗了。
“好了,实验结束,让我们来分析一下结果。”丁仪点上一枝烟说,“我们总共进行了五次试验,其中四次在不同的空间位置和不同的时间,两次在同一空间位置但时间不同。您不对结果震惊吗?”他夸张地张开双臂,“五次,撞击试验的结果居然都一样!”
“你到底想表达什么?”汪淼喘着气问。
“你现在对这令人难以置信的结果做出解释,用物理学语言。”
“这……在五次试验中,两个球的质量是没有变化的;所处位置,当然是以球桌面为参照系来说,也没有变化;白球撞击黑球的速度向量也基本没有变化,因而两球之间的动量交换也没有变化,所以五次试验中黑球当然都被击人洞中。”
丁仪拿起撂在地板上的一瓶白兰地,把两个脏兮兮的杯子分别倒满,递给汪淼一杯,后者谢绝了。“应该庆祝一下,我们发现了一个伟大的定律:物理规律在时间和空间上是均匀的。人类历史上的所有物理学理论,从阿基米德原理到弦论,以至人类迄今为止的一切科学发现和思想成果,都是这个伟大定律的副产品,与我们相比,爱因斯坦和霍金才真是搞应用的俗人。”
“我还是不明白你想表达什么。”
“想象另一种结果:第一次,白球将黑球撞人洞内;第二次,黑球走偏了;第三次,黑球飞上了天花板;第四次,黑球像一只受惊的麻雀在房间里乱飞,最后钻进了您的衣袋;第五次,黑球以接近光速的速度飞出,把台球桌沿撞出一个缺口,击穿了墙壁,然后飞出地球,飞出太阳系,就像阿西莫夫(注:这里指阿西莫夫的科幻小说《台球》。)描写的那样。这时您怎么想?”
丁仪盯着汪淼,后者沉默许久才问:“这事真的发生了,是吗?”
丁仪将手中的两杯酒都仰头灌下去,两眼直勾勾地看着台球桌,仿佛那是个魔鬼,“是的,发生了。近年来,基础理论研究的实验验证条件渐渐成熟,有三个昂贵的‘台球桌’被造了出来,一个在北美,一个在欧洲,还有一个你当然知道,在中国良湘,你们纳米中心从那里赚了不少钱。
“这些高能加速器将实验中粒子对撞的能量提高了一个数量级,这是人类以前从未达到过的。在新的对撞能级下,同样的粒子,同样的撞击能量,一切试验条件都相同,结果却不一样。不但在不同的加速度上不一样,在同一加速器不同时间的试验中也不一样,物理学家们慌了,把这种相同条件的超高能撞击试验一次次地重复,但每次的结果都不同,也没有规律。”
“这意味着什么呢?”汪淼问,看到丁仪盯着自己不做声,他又补充道,“哦,我搞纳米,也接触物质微观结构,但比起你们来要浅好几个层次,请指教一下。”
“这意味着物理规律在时间和空间上不均匀。”
“这又意味着什么呢?”
“往下您应该能推论出来吧,那个将军都想出来了,他真是个聪明人。”
汪淼看着窗外沉思着,外面城市的灯海一片灿烂,夜空中的星星被淹没得看不见了。
“这就意味着宇宙普适的物理规律不存在,那物理学……也不存在了。”汪淼从窗外收回目光说。
“‘我知道自己这样做是不负责任的,但别无选择。’”丁仪紧接着说,“这是她遗书的后半部分,您无意中刚说出了前半部分,现在多少能够理解她吧。”
汪淼从台球桌上拿起刚才他打过五次的那个白球,抚摸了一会儿轻轻放下,“这对一个前沿理论的探索者确实是个灾难。”
“在理论物理这个领域要想有所建树,需要一种宗教般的执著,这很容易把人引向深渊。”
告辞时,丁仪给了汪淼一个地址。“你如果有空,拜托去看看杨冬的母亲。杨冬一直和她住在一起,女儿是她生活的全部,现在就一个人了,很可怜。”
汪淼说:“丁仪,你知道得显然比我多,就不能再透露一点吗?你真的相信物理规律在时空上不均匀?”
“我什么都不知道……”
丁仪与汪淼对视了好长时间,最后说:“这是个问题。”
汪淼知道,他不过是接下了那位英军上校的话:生存还是死亡,这是个问题。

Android录制并播放视频

录制视频已经是一些社交软件的必备功能,使用内置的录制视频软件其实跟拍个照片一样简单,如果说要自定义录制视频的界面,需要做的事情稍微复杂一些,只有一些比较专业的软件才会做,那么我们来看看内置的视频如何使用。

在布局文件中使用VideoView来播放视频

<!-- To preview video recorded -->
<VideoView
	android:id="@+id/videoPreview"
	android:layout_width="wrap_content"
	android:layout_height="400dp"
/>

调用录制视频的方法

/**
判断SD卡是否就绪
*/
private boolean isSDCardReady(){
	String state = Environment.getExternalStorageState();  
    if (state.equals(Environment.MEDIA_MOUNTED)) {
		return true;
	else {  
        Toast.makeText(getApplicationContext(), "请确认已经插入SD卡", Toast.LENGTH_LONG).show();  
    }
	return false;
}

/**
* 判断设备是否支持相机功能
* */
private boolean isDeviceSupportCamera() {
	if (getApplicationContext().getPackageManager().hasSystemFeature(
			PackageManager.FEATURE_CAMERA)) {
		// this device has a camera
		return true;
	} else {
		// no camera on this device
		Toast.makeText(getApplicationContext(), "设备不支持相机功能", Toast.LENGTH_LONG).show();  
		return false;
	}
}
/**
视频存放路径
*/
private String videoFile;
/*
 * 录制视频
 */
private void recordVideo() {
	if(!(isSDCardReady() && isDeviceSupportCamera()))return;
        
	//存放路径
	String out_file_path = Environment.getExternalStorageDirectory() + "/myapp/";  
	File dir = new File(out_file_path);  
	if (!dir.exists()) {  
		dir.mkdirs();  
	}
	videoFile = out_file_path + System.currentTimeMillis() + ".mp4";  
 
    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
  
    // set video quality
    // 1- for high quality video
    intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
 
    intent.putExtra(MediaStore.EXTRA_OUTPUT, new Uri(videoFile));
 
    // start the video capture Intent
    startActivityForResult(intent, CAMERA_CAPTURE_VIDEO_REQUEST_CODE);
}

/**
播放视频
*/
private void previewVideo() {
	try {
		videoPreview = (VideoView) findViewById(R.id.videoPreview);
		videoPreview.setVisibility(View.VISIBLE);
		videoPreview.setVideoPath(new Uri(videoFile));
		// start playing
		videoPreview.start();
	} catch (Exception e) {
		e.printStackTrace();
	}
}

 /**
 * 视频录制完成回调方法
 * */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	if (requestCode == CAMERA_CAPTURE_VIDEO_REQUEST_CODE) {
		if (resultCode == RESULT_OK) {
			// video successfully recorded
			// preview the recorded video
			previewVideo();
		}
	}
}

视频录制调用的过程和拍照的过程完全一样,只是给的参数稍微不同,其中视频的画面质量参数可以根据实际情况而定,清晰度越高生成的视频文件越大,当然最大的清晰度最终由摄像头而定了。

总结

视频录制除了社交工具可以使用,在很多应用场合也很有意义,比如实时在线教育,设备管理的现场拍摄,还有一些巡查等等的应用里面都可以加入视频录制的功能。

Android中显示提示框

显示提示框很容易,一般提示框中可以显示的按钮有一个确定按钮,一个取消按钮,有时候可能需要三个按钮比如是、否和取消。

最简单的是至少显示一个按钮,然后给按钮添加事件以便监听用户的行为

显示一个提示框中主要的内容包括标题,内容和按钮

一个按钮的示例

AlertDialog alertDialog = new AlertDialog.Builder(
                        AlertDialogActivity.this).create();
// Setting Dialog Title
alertDialog.setTitle("Alert Dialog");

// Setting Dialog Message
alertDialog.setMessage("Welcome to fullstacks.cn");

// Setting Icon to Dialog
alertDialog.setIcon(R.drawable.tick);

// Setting OK Button
alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
		public void onClick(DialogInterface dialog, int which) {
		// Write your code here to execute after dialog closed
		Toast.makeText(getApplicationContext(), "You clicked on OK", Toast.LENGTH_SHORT).show();
		}
});

// Showing Alert Message
alertDialog.show();

两个按钮的示例

AlertDialog.Builder alertDialog = new AlertDialog.Builder(AlertDialogActivity.this);
 
// Setting Dialog Title
alertDialog.setTitle("Confirm Delete...");

// Setting Dialog Message
alertDialog.setMessage("Are you sure you want delete this?");

// Setting Icon to Dialog
alertDialog.setIcon(R.drawable.delete);

// Setting Positive "Yes" Button
alertDialog.setPositiveButton("YES", new DialogInterface.OnClickListener() {
	public void onClick(DialogInterface dialog,int which) {

	// Write your code here to invoke YES event
	Toast.makeText(getApplicationContext(), "You clicked on YES", Toast.LENGTH_SHORT).show();
	}
});

运行的效果图

image

三个按钮的示例

AlertDialog.Builder alertDialog = new AlertDialog.Builder(AlertDialogActivity.this);
 
// Setting Dialog Title
alertDialog.setTitle("Save File...");

// Setting Dialog Message
alertDialog.setMessage("Do you want to save this file?");

// Setting Icon to Dialog
alertDialog.setIcon(R.drawable.save);

// Setting Positive "Yes" Button
alertDialog.setPositiveButton("YES", new DialogInterface.OnClickListener() {
	public void onClick(DialogInterface dialog, int which) {
	// User pressed YES button. Write Logic Here
	Toast.makeText(getApplicationContext(), "You clicked on YES",
						Toast.LENGTH_SHORT).show();
	}
});

// Setting Negative "NO" Button
alertDialog.setNegativeButton("NO", new DialogInterface.OnClickListener() {
	public void onClick(DialogInterface dialog, int which) {
	// User pressed No button. Write Logic Here
	Toast.makeText(getApplicationContext(), "You clicked on NO", Toast.LENGTH_SHORT).show();
	}
});

// Setting Netural "Cancel" Button
alertDialog.setNeutralButton("Cancel", new DialogInterface.OnClickListener() {
	public void onClick(DialogInterface dialog, int which) {
	// User pressed Cancel button. Write Logic Here
	Toast.makeText(getApplicationContext(), "You clicked on Cancel",
						Toast.LENGTH_SHORT).show();
	}
});

// Showing Alert Message
alertDialog.show();

运行之后的效果

image

总结

系统默认的提示框是简单的显示文本信息再加上一些图标,已提示用户系统中正要做的一些重要事件,由用户做最终决定是否执行,这种提示框是比较简单,如果需要复杂的提示框,可以采用对话框的模式,可以通过自定页面布局达到复杂的业务要求。

ANDROID中获取照片和拍照

拍照的功能一般的应用都不会自己去实现,那也算是比较专业的领域,只有一些做系统UI的厂商才会涉及这些领域,尝试去完善或者定制与硬件相关的优化,那么对一般的开发来说,系统中提供了通用的组件去获取图片或者拍照。

不管是获取照片还是拍照其实都是启动第三方应用,然后获取返回值,那么需要获取返回值的的时候启动方法不一样,下面来看如何来获取相片

/**
启动获取相片的activity
*/
protected void getImageFromAlbum() {  
   Intent intent = new Intent(Intent.ACTION_PICK);  
   intent.setType("image/*");//相片类型  
   startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);  
}
/**
打开的activity返回时调用这个方法
*/
@Override  
protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
	if (requestCode == REQUEST_CODE_PICK_IMAGE) {             
		Uri uri = data.getData();  
		//uri 为图片的路径 
		//...这处理获取到的图片
	} 
}

下面来看看如何启动拍照并获取到相片

/**
拍照相片的文件路径
*/
private String captureImgPath = null;  
/**
启动拍照的 activity
*/
protected void getImageFromCamera() {  
	String state = Environment.getExternalStorageState();  
	if (state.equals(Environment.MEDIA_MOUNTED)) {  
		Intent cameraIntent = new Intent("android.media.action.IMAGE_CAPTURE");  
		//存放相片的路径
		String out_file_path = Environment.getExternalStorageDirectory() + "/myapp/";  
		File dir = new File(out_file_path);  
		if (!dir.exists()) {  
			dir.mkdirs();  
		}
		captureImgPath = out_file_path + System.currentTimeMillis() + ".jpg";  
		cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(captureImgPath)));  
		cameraIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);  
		startActivityForResult(cameraIntent, Constant.REQUEST_CODE_CAPTURE_CAMEIA);  
	}  
	else {  
		Toast.makeText(getApplicationContext(), "请确认已经插入SD卡", Toast.LENGTH_LONG).show();  
	}  
}
/**
打开的activity返回时调用这个方法
/*
@Override  
protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
	if (requestCode == REQUEST_CODE_CAPTURE_CAMEIA) {             
		Uri uri = data.getData();  
		// 这个uri为空
		Bitmap  photo = (Bitmap) bundle.get("data"); //get bitmap 
		//这样获取的 photo是压缩过的
		
		//获取原始相片就直接使用路径获取
		File photo_file = new File(captureImgPath);
	} 
}

总结

单纯的读取相片较为简单,可以理解文打开个对话框然后选择一个图片,对话框的结果就是返回图片的路径,那么打开照相的过程也类似,只不过需要传入一些参数,比如相片保存的路径,照相的参数比如说相片的质量啊等等,这些参数用于控制照相的一些特性,照相完成之后可以直接从给定的相片路径取到相片了。

在Linux和Mac OS X系统上运行.NET

.NET Core运行时已经看到了实现真正的跨平台的美好前景,它最终出现在Linux和Mac OS X平台上。在Microsoft Build大会上,来自微软的项目经理Habib Heydarian为听众分析了这一举措对开发者们所带来的益处,并告诉开发者们如何开始探索这些新的机会。在名为“让.NET实现跨平台”的一场讲座中,Heydarian首先介绍了开发者如何进行一次全新的.NET Core安装。

首先,所有的.NET代码都包含在一个单独的文件夹中,而无需将它安装在某个系统级别的位置。这样,只要愿意,每个.NET应用都可以使用一个完全不同的编译版本。并且在Windows系统上进行编译的代码也能够在Mac OS X和Linux系统上运行。

要在以上系统中运行一个基于命令行的标准HelloWorld程序,可使用以下方式:

./corerun HelloWorld.exe

// corereun是一个原生的运行app的环境

// 在Windows上,引导.NET应用的功能已经内建于操作系统中了

在非Windows平台上使用.NET,就意味着开发者们能够使用ASP.NET 5、CoreCLR,并且从以下共享的功能中受益了:

  1. 运行时组件
    1. 64位的JIT编译器与SIMD指令
    2. 垃圾回收器
  2. 类库
    1. 基础类库
    2. NuGet包
  3. 编译器
    1. .NET编译器平台(Roslyn)

如何获取.NET Core

对于Mac OS X开发者来说,推荐的方式是使用Homebrew以获取必要的组件。当安装好Homebrew之后,就可以通过以下命令获取.NET组件了:

brew tap aspnet/dnx
brew update
brew install dnvm
dnx . kestrel

Linux用户可以从该项目的网站上下载一个包含了所有必要组件的TAR文件,随后按照以下方式进行安装:

tar zxvf PartsUnlimited-demo-app-linux.tar.gz -C ~/
source ~/.dnx/dnvm/dnvm.sh
dnvm use 1.0.0-beta5-11624 -r coreclr -arch x64
dnx . kestrel

你一定注意到了一点,在这个两个平台上所运行的最后一条指令都是kestrel的执行。Kestrel也正是“跨平台的ASP.NET 5 web服务器”,DNVM则是.NET的版本管理器。目前,该项目只支持64位平台的Linux和Mac OS X。开发团队仍然在继续研究如何让它支持32位的系统。

紧随Linux和Mac OS X之后,对FreeBSD的支持最近也加入到该项目中。对于这三个平台来说,目前还存在着一个限制,那就是从源代码编译.NET Core的功能仅限于Windows版本。要从源代码编译.NET,开发者需要首先编译CoreCLR,然后再编译CoreFX。

正如Windows平台上的.NET开发者能够利用平台调用(PInvoke)功能一样,Linux平台上的开发者也能够使用DLL Import这一命令:

[DllImport(“libc”)]
private static extern int printf(string format);

Printf(“Hello, //BUILD 2015!\n”);

下一步计划

Heydarian在演讲余下的部分谈到了该团队下一步的计划,以及微软对这一项目的目标。随着Visual Studio不断地扩展到非Windows的平台上,微软希望能够改进在这些新环境中的调试功能。对于VS2015来说,就是要实现远程调试。而对于VS Code来说,首先要从实现本地调试开始。

另一个改进的方向是整体的上线预备。为了在这方面有所突破,团队打算整合MSBuild的支持,并消除目前对Mono在这方面功能的依赖。

Heydarian表示,当.NET在Linux和Mac OS X平台上正式发布,并成为“RTM”版本之后,微软将做出以下正式的承诺:

  1. .NET Core应用能够在基于Linux的生产环境中运行,包括Docker、本地部署和云端部署
  2. 开发者可以使用VS Code或其它任何喜爱的编辑器,对运行在Mac OS X环境中的.NET代码进行编辑、编译与调试
  3. 全部使用无关平台特性创建的应用在Windows与其它平台上具有相同的行为
  4. .NET Core将把现有.NET云端生态系统的类库也带到Linux上
  5. 微软对.NET在Linux上的支持、服务和维护与其它微软产品一视同仁

在你的应用中加入对Linux和Mac OS X的支持

微软将推出一套API可移植性工具,用于对现有的代码进行分析,找出所需的程序集和目标平台。目前为止,唯一对兼容性进行了测试的Linux分发平 台是Ubuntu 14.04.2 LTS。虽然没有明确地表示不支持其它的Linux分发平台,但无法保证在这些平台上是否能够正常运行。

Heydarian认为目前来看,微软所提供的.NET与Mono版本相比,所针对的市场方向并不相同。Haydarian表示:“……虽然 [Mono]在移动场合的表现优秀,但它并不是为服务器或云端生产环境的使用场景而设计的……”,而.NET Core倾向于在具有高吞吐量、高伸缩性,以及更高的修复前平均时间(MTTF)的服务器环境中所使用。

希望通过.NET即将提供的功能,从跨平台方式中受益的开发者可以首先从VS2015RC中的ASP.NET 5项目模板开始打造及测试应用,并且参考GitHub上的ASP.NET示例应用Parts Unlimited。凡是能够在Windows上的ASP.NET 5中成功运行的应用,一旦等到.NET Core RTM之后,就能够无缝地迁移至Linux平台上。

.NET 4.6中的性能改进

.NET 4.6中带来了一些与性能改进相关的CLR特性,这些特性中有一部分将会自动生效,而另外一些特性,例如SIMD与异步本地存储(Async Local Storage)则需要对编写应用的方式进行某些改动。

SIMD

Mono团队一直以他们对SIMD,即单指令流多数据流特性的支持引以为傲。SIMD是一种CPU指令集,它能够在同一时间对最多8个值进行同一操作。而随着.NET CLR版本4.6的推出,Windows开发者终于也能够使用这一特性了。

为了实际观察一下SIMD的效果,可以参考一下这个示例。假设你需要通过c[i] = a[i] + b[i]这种形式对两个数组进行相加,以得到第三个数组。通过使用SIMD,你可以按照以下方式编写代码:

for (int i = 0; i < size; i += Vector.Count)
 {
     Vector v = new Vector(A,i) + new Vector(B,i);
     v.CopyTo(C,i);
 }

请注意这个循环是如何按Vector<int>.Count的取值进行递增的,根据CPU类型的不同,它的取值可能是4或是8。.NET JIT编译器将根据CPU的不同生成相应的代码,以4或8的值对数组进行批量相加。

这种方式看起来有些繁琐,因此微软还提供了一系列辅助类,包括:

程序集卸载

恐怕大多数开发者都不知道这一点:.NET经常会对同一个程序集加载两次。发生这种情况的条件是.NET首先加载了某个程序集的IL版本,随后又加 载了同一程序集的NGEN版本(即预编译版本)。这种方式对于物理内存来说是相当严重的浪费,尤其是对诸如Visual Studio这样的大型32位应用程序来说更为明显。

而在.NET 4.6中,一旦CLR加载了某个程序集的NGEN版本,它会自动清空对应的IL版本所占用的内存。

垃圾回收

在.NET 4.6中,你将能够通过一种更精密的方式临时中止垃圾回收器的运作,新的TryStartNoGCRegion方法允许你指定在小对象以及大对象的堆中需要多少内存。

如果出现内存不足的情况,运行时将会返回false,或是停止运行,直到通过GC清理得到足够的内存为止。你可以通过为 TryStartNoGCRegion传入某个标记的方式控制这一行为,如果你成功地进入了某个无GC区域(在过程结束前不允许进行GC),那么在过程结 束时必须调用EndNoGCRegion方法。

在官方文档中并没有说明该方法是否是线程安全的,不过考虑到GC的工作原理,你应当尽量避免让两个进程同时尝试改变GC状态的做法。

对于GC的另一项改进是它处理pinned对象(即一旦分配后不可移动位置的对象)的方式。虽然在文档中对此方面的描述有些语焉不详,但当你固定了某个对象的位置时,通常也会固定其相邻对象的位置。Rich Lander在文中写道:

GC将以一种更优化的方式处理pinned对象,因此GC能够将pinned对象周围的内存进行更有效地压缩。对于大量使用pin方式的大规模应用来说,这一改动将极大地改进应用的性能。

GC对于如何使用较早的几代中的内存方面也体现出更好的智能性,Rich继续写道:

第1代对象升级为第2代对象的方式也得到了改进,以更有效地使用内存。在为某一代分配新的内存空间之前,GC会先尝试使用可用的空间。同时,在利用可用空间区域创建对象时使用了新的算法,使新分配的空间大小比起从前更接近于对象的大小。

异步本地存储

最后一项改进与性能并没有直接的关系,但通过有效的利用仍然能达到优化的效果。在异步API还没有流行起来的年代,开发者可以利用线程本地存储 (TLS)缓存信息。TLS对于某个特定的线程来说就像是一种全局对象,这意味着你可以直接访问上下文信息并进行缓存,而无需显式地传递某种上下文对象。

而在async/await模式中,线程本地存储就变得毫无用武之地了。因为每次调用await的时候,都有可能跳转至另一个线程。而且即便侥幸避开了这种情况,但其它代码也有可能跳转到你的线程中并干扰TLS中的信息。

新版本的.NET引入了异步本地存储(ALS)机制以解决这一问题,ALS在语义上等价于线程本地存储,但它能够随着await的调用进行相应的跳转。这一功能将通过AsyncLocal泛型类实现,其内部将调用CallContext对象用于保存数据。

ANDROID开机启动

当操作系统启动完毕之后会广播一个消息叫ACTION_BOOT_COMPLETED,应用程序可以通过实现BroadcastReceiver接口来监听这个消息,接到消息之后就可以启动程序,一般启动一个后台运行的Service,当然也可以直接启动某个activity.

我们来写个简单的service,以便在接收到系统启动完成的消息之后来启动这个后台执行的service

public class BootStartService extends IntentService {

    public UpdateService(){
        this("app start service");
    }
    public UpdateService(String name) {
        super(name);
    }
 
    @Override
    protected void onHandleIntent(Intent workIntent) {
        //启动
	String arg = workIntent.getStringExtra("arg");
        Log.d("BootStartService", "BootStartService start");
	//...做一些默默无闻的事情
    }
}

实现接口BroadcastReceiver来监听消息

public class BootStartReceiver extends BroadcastReceiver {
    static final String action_boot="android.intent.action.BOOT_COMPLETED";
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(action_boot)){
            Intent mServiceIntent = new Intent(context, BootStartService.class);
	    mServiceIntent.putExtra("arg", "app start args");
	    context.startService(mServiceIntent);
        }
    }
}

那么代码就已经完成了,记得在AndroidManifest.xml中加入下面的声明

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission>
<receiver android:name="cn.fullstacks.blogapp.BootStartReceiver">
	<intent-filter>
	<action android:name="android.intent.action.BOOT_COMPLETED" />
	<category android:name="android.intent.category.HOME" />
	</intent-filter>
</receiver>
<service android:name="cn.fullstacks.blogapp.BootStartService" android:exported="false"/>

重启之后就会看到你要的效果了

总结

开机启动一般是会启动一个后台的程序而不会启动个界面,这样就太霸道了,在系统管理或者一些安全软件里面会有控制开机启动的地方,那么那里面就会看到你的开机启动项了,如果被禁止掉了,你的这个启动消息就不会受到了,这也就是系统级别的消息过滤,如果说必须要开机启动而不想让其他软件阻止的话,那就是把程序写成安全软件了,这就是流氓的开端。

在一些即时通的软件里或者一些需要接受消息的软件,大部分都会做成开机启动,然后启动一个后台服务监听消息,当服务器推送消息过来之后在通知栏里面显示一条消息,这是最常用的场景了。

《三体1》4.科学边界

三十八年后。

汪淼觉得,来找他的这四个人是一个奇怪的组合:两名警察和两名军人,如果那两个军人是武警还算正常,但这是两名陆军军官。

汪淼第一眼就对来找他的警察没有好感。其实那名穿警服的年轻人还行,举止很有礼貌,但那位便衣就让人讨厌了。这人长得五大三粗,一脸横肉,穿着件脏兮兮的皮夹克,浑身烟味,说话粗声大嗓,是最令汪淼反感的那类人。

“汪淼?”那人问,直呼其名令汪淼很不舒服,况且那人同时还在点烟,头都不抬一下。不等汪淼回答,他就向旁边那位年轻人示意了一下,后者向汪淼出示了警官证,他点完烟后就直接向屋里闯。

“请不要在我家里抽烟。”汪淼拦住了他。

“哦,对不起,汪教授。这是我们史强队长。”年轻警官微笑着说,同时对姓史的使了个眼色。

“成,那就在楼道里说吧。”史强说着,深深地吸了一大口,手中的烟几乎燃下去一半,之后竟不见吐出烟来。

“你问。”他又向年轻警官偏了一下头。

“汪教授,我们是想了解一下,最近你与‘科学边界’学会的成员有过接触,是吧?”

“‘科学边界’是一个在国际学术界很有影响的学术组织,成员都是著名学者。这样一个合法的学术组织,我怎么就不能接触了呢?”

“你看看你这个人!”史强大声说,“我们说它不合法了吗?我们说不让你接触了吗?”他说着,刚才吸进肚子里的烟都喷到汪淼脸上。

“那好,这属于个人隐私,我没必要回答你们的问题。”

“还啥都成隐私了,像你这样一个著名学者,总该对公共安全负责吧。”史强把手中的烟头扔掉,又从压扁了的烟盒里抽出一根。

“我有权不回答,你们请便吧。”汪淼说着要转身回屋。

“等等!”史强厉声说,同时朝旁边的年轻警官挥了一下手,“给他地址和电话,下午去走一趟。”

“你要干什么!”汪淼愤怒地质问,这争吵引得邻居探出头来,想看看出了什么事。

“史队!你说你——”年轻警官生气地将史强拉到一边,显然他的粗俗不止是让汪淼一人不适应。

“汪教授,请别误会。”一名少校军官急忙上前,“下午有一个重要会议,要请几位学者和专家参加,首长让我们来邀请您。”

“我下午很忙。”

“这我们清楚,首长已经向超导中心领导打了招呼。这次会议上不能没有您,实在不行,我们只有把会议延期等您了。”

史强和他的同事没再说话,转身下楼了,两位军官看着他们走远,似乎都长出了一口气。

“这人怎么这样儿。”少校小声对同事说。

“他劣迹斑斑,前几年在一次劫持人质事件中,他不顾人质的死活擅自行动,结果导致一家三口惨死在罪犯手中;据说他还和黑社会打得火热,用一帮黑道势力去收拾另一帮;去年又搞刑讯逼供,使一名嫌疑人致残,因此被停职了……”

“这种人怎么能进作战中心?”

“首长点名要他,应该有什么过人之处吧。不过,对他限制挺严,除了公安方面的事务,几乎什么都不让他知道。”

作战中心?那是什么?汪淼不解地看着面前的两位军官。

接汪淼的汽车驶进了城市近郊的一座大院,从那只有门牌号码没有单位名牌的大门,汪淼知道这里是军方而不是警方的地盘。

会议是在一个大厅里举行的,汪淼一进去就对这里的纷乱吃惊不小。大厅周围是一圈胡乱安放的电脑设备,有的桌子上放不下就直接搁地板上,电线和网线纠缠着散在地上;一大摞网络交换机没有安在机架内,而是随手堆放在服务器上;有好几个投影仪的大屏幕,在大厅的角落里呈不同角度随意立着,像吉普赛人的帐篷;烟雾像晨雾般在半空浮了一层……汪淼不知道这是否就是那名军官所说的作战中心,有一点他可以肯定:这里在处理的事情,已经让人们顾不上其他了。

临时拼凑的会议桌上也是堆满了文件和杂物,与会者大多神情疲惫,衣服皱巴巴的,有领带的都扯开了,好像熬了一夜。主持会议的是一位叫常伟思的陆军少将,与会者有一半是军人。经过简单的介绍,他知道还有少部分警方人员,其他的人都是和他一样参加会议的专家学者,其中有几位还是很有名望的科学家,而且是研究基础科学的。

令他感到意外的是还有四个外国人,这些人的身份令他大吃一惊:其中的两个人也是军人,分别是美军空军上校和英国陆军上校,职务是北约联络员;另外两人居然是美国中央情报局的官员,在这里的职务是什么观察员。

从所有人的脸上,汪淼都读出了一句话:我们已经尽力了,快他妈的结束吧!

汪淼看到了史强,他倒是一反昨天的粗鲁,向汪淼打招呼,但那一脸傻笑让汪淼愉快不起来。他不想挨史强坐,但也只有那一个空位,他只好坐过去,屋里本来已经很浓的烟味更加重了。

发文件时,史强凑近汪淼说:“汪教授,你好像是在研究什么……新材料?”

“纳米材料。”汪淼简单地回答。

“我听说过,那玩意儿强度很高,不会被用于犯罪吧?”从史强那带有一半调侃的表情上,汪淼看不出他是不是开玩笑。

“什么意思?”

“呵,听说那玩意儿一根头发丝粗就能吊起一辆大卡车,犯罪分子要是偷点儿去做把刀,那一刀就能把一辆汽车砍成两截吧。”

“哼,根本不用做成刀,用那种材料做一根只有头发丝百分之一粗细的线,拦在路上,就能把过往的汽车像切奶酪那样切成两半……啥不能用于犯罪?刮鱼鳞的刀都能!”

史强把面前的文件从袋中抽出一半又塞了回去,显然没了兴趣。“说得对,鱼都能犯罪呢!我办过一个杀人案,一个娘们儿把她丈夫的那玩意儿割下来了。知道用的是什么?冰箱里冷冻的罗非鱼!鱼冻硬后,背上的那排刺就跟一把快刀似的……”

“我没兴趣,怎么,让我来开会就是为这事儿?”

“鱼?纳米材料?不、不,与那些都没关系。”史强把嘴凑到汪淼耳边,“别给这帮家伙好脸,他们歧视咱们,只想从咱们这里掏情报,但什么都不告诉咱们。像我,在这儿混了一个多月,还和你一样什么都不知道。”

“同志们,会议开始。”常伟思将军说,“在全球各战区,我们这里现在成为焦点。首先把当前情况向与会的同志们介绍一下。”

“战区”这个不寻常的术语令汪淼迷惑,他还注意到,首长好像并没有打算向他这样的新人介绍来龙去脉,这倒是印证了史强的话。在常将军这简短的开场白中,他两次提到了“同志们”,汪淼看看对面的两名北约军人和两个美国中情局官员,感觉将军似乎漏掉了“先生们”。

“他们也是同志,反正这边的人都是这么称呼的。”史强低声地对汪淼说,同时用手中的烟指了指那四个外国人。

在迷惑的同时,汪淼对史强的观察力留下了些印象。

“大史,你把烟熄了,这儿的烟味够浓了。\”常伟思说,低头翻着文件。

史强拿着刚点着的烟四下看看,没找到烟灰缸,就“吱啦“一声扔到茶杯里了。他抓住这个机会举手要求发言,没等常伟思表态就大声说道:“首长,我提个要求,以前提过的——信息对等!”

常伟思将军抬起头,“没有任何一个军事行动是信息对等的,这点也请到会的专家学者们谅解,我们不可能给你们介绍更多的背景资料。”

“但我们不一样。”史强说,“警方从作战中心成立之初就一直参与,可直到现在,我们连这个机构到底是干什么的都不知道。而且,你们正在把警方排挤出去,你们一步步熟悉我们的工作,然后把我们一个个赶走。”

与会的另外几名警官都在低声制止史强。史强敢对常伟思这样级别的首长这么说话,汪淼有些吃惊,而后者的反击更犀利。

“我说大史,现在看来,你在部队上的老毛病还没改。你能代表警方吗?你因为自己的恶劣行为已被停职好几个月了,马上就要被清除出公安队伍。我调你来,是看重你在城市警务方面的经验,你要珍惜这次机会。”

大史用粗嗓门说:“那我是戴罪立功了?你们不是说那都是些歪门邪道的经验吗?”

“但有用。”常伟思对史强点点头,“有用就行,现在顾不了那么多了,这是战争时期。”

“什么都顾不了了,”一位CIA的情报官员用标准的普通话说,“我们不能再用常规思维。”

那位英军上校显然也能听懂中文,他点点头,“Tobeornottobe……”

“他说什么?”史强问汪淼。

“没什么。”汪淼机械地回答。这些人似乎在梦呓,战争时期?战争在哪儿?他扭头望向大厅的落地窗,透过窗子可以看到远处大院外面的城市:春天的阳光下,街道上车流如织;草坪上有人在遛狗,还有几个孩子在玩耍……

里面和外面的世界,哪个更真实?

常将军讲道:“最近,敌人的攻击明显加强了,目标仍是科学界高层,请你们先看一下文件中的那份名单。”

汪淼抽出文件中最上面的那张纸,是用大号字打印的,名单显然拟得很仓促,中文和英文姓名都有。

“汪教授,看到这份名单,您有什么印象?”常伟思看着汪淼问。

“我知道其中的三人,都是物理学最前沿的著名学者。”汪淼答道,有些心不在焉,他的目光锁定在最后一个名字上,在他的潜意识中,那两个字的色彩与上面几行字是不同的。怎么会在这里看到她的名字?她怎么了?

“认识?”大史用一根被烟熏黄的粗指头指着文件上的那个名字问,汪淼没有反应。“呵,不太认识。想认识?”

现在,汪淼知道常伟思把他以前的这个战士调来是有道理的,这个外表粗俗的家伙,眼睛跟刀子一样。他也许不是个好警察,但确实是个狠角色。

那是一年前,汪淼是“中华二号”高能加速器项目纳米构件部分的负责人。那天下午在良湘的工地上,一次短暂的休息中,他突然被眼前的一幅构图吸引了。作为一名风景摄影爱好者,现实的场景经常在他眼中形成一幅幅艺术构图。构图的主体就是他们正在安装的超导线圈,那线圈有三层楼高,安装到一半,看上去是一个由巨大的金属块和乱麻般的超低温制冷剂管道组成的怪物,仿佛一堆大工业时代的垃圾,显示出一种非人性的技术的冷酷和钢铁的野蛮。就在这金属巨怪前面,出现了一个年轻女性纤细的身影。这构图的光线分布也很绝:金属巨怪淹没在临时施工顶棚的阴影里,更透出那冷峻、粗糙的质感;而一束夕阳金色的光,透过顶棚的孔洞正好投在那个身影上,柔和的暖光照着她那柔顺的头发,照着工作服领口上白皙的脖颈,看上去就像一场狂暴的雷雨后,巨大的金属废墟上开出了一朵娇柔的花……

“看什么看,干活儿!”

汪淼吓了一跳,然后发现纳米研究中心主任说的不是他,而是一名年轻工程师,后者也和自己一样呆呆地望着那个身影。汪淼从艺术中回到现实,发现那位女性不是一般的工作人员,因为总工程师陪同着她,在向她介绍着什么,一副很尊敬的样子。

“她是谁?”汪淼问主任。

“你应该知道她的,”主任说,用手划了一大圈,“这个投资二百亿的加速器建成后,第一次运行的可能就是验证她提出的一个超弦模型。要说在论资排辈的理论研究圈子,本来轮不到她的,可那些老家伙不敢先来,怕丢人,就让她捡了个便宜。”

“什么?杨冬是……女的?!”

“是的,我们也是在前天见到她时才知道。”主任说。

那名工程师问:“她这人是不是有什么心理障碍,要不怎么会从来不上媒体呢?别像是钱钟书似的,到死大家也没能在电视上看上一眼。”

“可我们也不至于不知道钱钟书的性别吧?我觉得她童年一定有什么不寻常的经历,以致得了自闭症。”汪淼说,多少有一些酸葡萄心理。

杨冬和总工程师走过来,在经过时她对他们微笑着点点头,没说一句话,但汪淼记住了她那清澈的眼睛。

当天晚上汪淼坐在书房里,欣赏着挂在墙上的自己最得意的几幅风景摄影,他的目光落在一幅塞外风光上——那是一个荒凉的山谷,雪山从山谷的尽头露出一抹白;山谷的这一端,半截沧桑的枯木占据了几乎三分之一的画面。汪淼在想象中把那个萦绕在他脑海中的身影叠印到画面上,让她位于山谷的深处,看去很小很小;这时汪淼惊奇地发现,整个画面苏醒过来,仿佛照片中的世界认出了那个身影,仿佛这一切本来就是为她而存在。他又依次在想象中将那个身影叠印到另外几幅作品上,有时还将她那双眼睛作为照片上空旷苍穹的背景,那些画面也都苏醒过来,展现出一种汪淼从未想象过的美。以前,汪淼总觉得自己的摄影作品缺少某种灵魂;现在他知道了,缺的是她。

“名单上的这些物理学家,在不到两个月的时间里,先后自杀。”常伟思说。

晴天霹雳,汪淼的大脑一片空白。后来这空白中渐渐有了图像,那是他那些黑白风景照片,照片中的大地没有了她的身影,天空抹去了她的眼睛,那些世界死了。

“是……什么时候?”汪淼呆呆地问。

“在不到两个月的时间里。”常将军重复道。

“你是指最后一位吧。”坐在汪淼旁边的大史得意地说,然后压低声音,“她是最后一位自杀者,前天晚上,服过量安眠药。她死得很顺溜,没有痛苦。”刹那间,汪淼居然对大史有了那么一丝感激。

“为什么?”汪淼问,那些照片上死去的风景画仍在他的脑海中幻灯似的循环浮现。

常伟思回答道:“现在能肯定的只有一点:促使他们自杀的原因是相同的。但原因本身在这里很难说清,也可能对我们这些非专业人士根本就说不清。文件中附加了他们遗书的部分内容,各位会后可以仔细看看。”

汪淼翻翻那些遗书的复印件,都是长篇大论。

“丁仪博士,您能否把杨冬的遗书给汪教授看一下?她的最简短,也最有概括性。”

那个一直低着头沉默的人半天才有所反应,掏出一个白色的信封隔着桌子递给汪淼,大史在旁边低声说:“他是杨冬的男友。”汪淼这才想起自己在良湘的高能加速器工地中也见过丁仪,他是理论组的成员,这名物理学家因在对球状闪电(注:此处参见作者本人的《球状闪电》。)的研究中发现宏原子而闻名于世。汪淼从信封中抽出一片散发出清香的东西,形状不规则,不是纸,竟是一片白桦树皮,上面有一行娟秀的字:

一切的一切都导向这样一个结果:物理学从来就没有存在过,将来也不会存在。我知道自己这样做是不负责任的,但别无选择。

连签字都没有,她就走了。

“物理学……不存在?”汪淼茫然四顾。

常将军合上文件夹,“有一些相关的具体信息与世界上三台新的高能加速器建成后取得的实验结果有关,很专业,我们就不在这里讨论了。我们首先要调查的是‘科学边界’学会。联合国教科文组织将2005年定为世界物理年,这个组织就是在这一年国际物理学界频繁的学术会议和交流活动中逐渐诞生的,是一个松散的国际性学术组织。丁博士,您是理论物理专业的,能进一步介绍一下它的情况吗?”

丁仪点点头说:“我与‘科学边界’没有任何直接联系,不过这个组织在学术界很有名。它的宗旨是:自上个世纪下半叶以来,物理学古典理论中的简洁有力渐渐消失了,理论图像变得越来越复杂、模糊和不确定,实验验证也越来越难,这标志着物理学的前沿探索似乎遇到了很大的障碍和困难。‘科学边界’试图开辟一条新的思维途径,简单地说就是试图用科学的方法找出科学的局限性,试图确定科学对自然界的认知在深度和精度上是否存在一条底线——底线之下是科学进入不了的。现代物理学的发展,似乎隐隐约约地触到了这条底线。”

“很好。”常伟思说,“据我们了解,这些自杀的学者大部分与‘科学边界’有过联系,有些还是它的成员。但没有发现诸如邪教精神控制或使用违法药物这类的犯罪行为。也就是说,即使‘科学边界’对那些学者产生过影响,也是通过合法的学术交流途径。汪教授,他们最近与您有联系,我们想了解一些情况。”

大史粗声粗气地开口说:“包括联系人的姓名、见面地点和时间、谈话内容,如果交换过文字资料或电子邮件的话……”

“大史!”常伟思厉声制止了他。

“不吱声没人拿你当哑巴!”旁边一位警官探过身去对大史低声说,后者拿起桌上的茶杯,看到里面的烟头后,“咚”的一声又放下了。

大史又令汪淼像吃了苍蝇一样难受,刚才那一丝感激消失得无影无踪。但他还是克制着回答了这个问题:“我与‘科学边界’的接触是从认识申玉菲开始的,她是一名日籍华裔物理学家,现在为一家日资公司工作,就住在这个城市。她曾在三菱电机的一家实验室从事纳米材料研究,我们是在今年年初的一次技术研讨会上认识的。通过她,又认识了几位物理专业的朋友,都是‘科学边界’的成员,国内国外的都有。和他们的交往时,谈的都是一些很……怎么说呢,很终极的问题,主要就是丁博士刚才提到的科学底线的问题。”

“我一开始对这些问题没有太大的兴趣,只是作为消遣。我是搞应用研究的,在这方面水平不高,主要是听他们讨论和争论。这些人思想都很深刻,观点新颖,自己感觉同他们交流,思想开阔了许多,渐渐变得很投入了。但讨论的话题仅限于此,都是天马行空的纯理论,没有什么特别的。他们曾邀请我加入‘科学边界’,但那样的话,参加这样的研讨会就变成了一项义务,我因为精力有限就谢绝了。”

“汪教授,我们希望您接受邀请,加入‘科学边界’学会,这也是我们今天请您来的主要目的。”常将军说,“我们希望能通过您这个渠道,得到一些这个组织的内部信息。”

“您是说让我去卧底吗?”汪淼不安地问。

“哇哈哈,卧底!”大史大笑一声。

常伟思责备地看了大史一眼,对汪淼说:“只是提供一些情况,我们也没有别的渠道。”

汪淼摇摇头:“对不起,首长。我不能干这事。”

“汪教授,‘科学边界’是一个由国际顶尖学者构成的组织,对它的调查是一件极其复杂和敏感的事,我们真的是如履薄冰。没有知识界的帮助,我们寸步难行,所以才提出了这个唐突的要求,希望您能理解。不过我们也尊重您的意愿,如果不同意,我们也是能够理解的。”

“我……工作很忙,也没有时间。”汪淼推托道。

常伟思点点头:“好的,汪教授,那我们就不再耽误您的时间了,谢谢您能来参加这次会议。”

汪淼愣了几秒钟,才明白他该离开了。

常伟思礼貌地把汪淼送到会议室门口时,大史在后面大声说:“这样挺好,我压根儿就不同意这个方案。已经有这么多书呆子寻了短见,让他去不是‘肉包子打狗’吗?”

汪淼返身回去,走到大史身旁,努力克制着自己的愤怒,“你这么说话实在不像一名合格的警官。”

“我本来就不是。”

“那些学者自杀的原因还没有搞清楚。你不该用这么轻蔑的口气谈论他们,他们用自己的智慧为人类社会做出的贡献,是任何人都不可替代的。”

“你是说他们比我强?”大史在椅子上仰头看着汪淼,“我总不至于听人家忽悠几句就去寻短见。”

“那你是说我会?”

“总得对您的安全负责吧。”大史看着汪淼,又露出他招牌式的傻笑。

“在那种情况下我比你要安全得多,你应该知道,一个人的鉴别能力是和他的知识成正比的。”

“那不见得,像您这样的……”

“大史,你要再多说一句,也从这里出去好了!”常伟思严厉地喝斥道。

“没关系,让他说,”汪淼转向常将军,“我改变主意了,决定按您的意思加入‘科学边界’。”

“很好,”大史连连点头,“进去后机灵点儿,有些事顺手就能做,比如瞄一眼他们的电脑,记个邮件地址或网址什么的……”

“够了!你误会了,我不是去卧底,只是想证明你的无知和愚蠢!”

“如果您过一阵儿还活着,那自然也就证明了。不过恐怕……嘿嘿。”大史仰着头,傻笑变成了狞笑。

“我当然会一直活下去,但实在不想再见到你这号人了!”

常伟思一直把汪淼送下了楼梯,并安排车送他,在道别时说:“史强就那种脾气,其实他是一名很有经验的刑警和反恐专家。二十多年前,他曾是我连里的一名战士。”

走到车前,常伟思又说:“汪教授,你一定有很多问题要问。”

“刚才您说的那些,与军方有什么关系?”

“战争与军方当然有关系。”

汪淼迷惑地看看周围明媚春光中的一切,“可战争在哪儿?现在全球一处热点都没有,应该是历史上最和平的年代了。”

常伟思露出了高深莫测的笑容:“你很快就会知道一切的,所有人都会知道。汪教授,你的人生中有重大的变故吗?这变故突然完全改变了你的生活,对你来说,世界在一夜之间变得完全不同。”

“没有。”

“那你的生活是一种偶然,世界有这么多变幻莫测的因素,你的人生却没什么变故。”

汪淼想了半天还是不明白。

“大部分人都是这样嘛。”

“那大部分人的人生都是偶然。”

“可……多少代人都是这么平淡地过来的。”

“都是偶然。”

汪淼摇头笑了起来,“得承认今天我的理解力太差了,您这岂不是说……”

“是的,整个人类历史也是偶然,从石器时代到今天,都没什么重大变故,真幸运。但既然是幸运,总有结束的一天;现在我告诉你,结束了,做好思想准备吧。”

汪淼还想问下去,但将军与他握手告别,阻止了他下面的问题。

上车后,司机开口问汪淼家的地址,汪淼告诉他后,随口问道:“哦,接我来的不是你?我看车是一样的。”

“不是我,我是去接丁博士的。”

汪淼心里一动,便向司机打听丁仪的住处,司机告诉了他。当天晚上,他就去找丁仪。

ANDROID中下载并打开文档

应用系统中往往会需要打开world文档进行阅读,或者pdf文档等等,而阅读文档这个过程那是非常复杂,一般的人是很难实现的,这时候就需要引入第三方工具进行阅读,比如使用wps打开各种office文档,使用pdf reader打开pdf文档,或者使用一些付费的阅读工具打开,不管是采用哪一种方式,基本上需要执行几个基本的步骤:

1.判断系统中是否已经安装了阅读工具

2.如果没有安装则下载安装

3.下载要打开的文档

4.调用阅读工具打开文档

下面使用wps来做一个示例

1.判断系统中是否已经安装了wps

    /**
     * 查找安装的wps包
     * @param context
     * @return
     */
    public static List getWPSInstalledPackage(Context context)
    {
        if(wpspackage != null && wpspackage.size() >0){
            return wpspackage;
        }
        ArrayList arraylist = new ArrayList();
        ArrayList arraylist1 = (ArrayList)context.getPackageManager().getInstalledPackages(0);
        int i = 0;
        do
        {
            if (i >= arraylist1.size())
            {
                wpspackage = arraylist;
                return arraylist;
            }
            PackageInfo packageinfo = (PackageInfo)arraylist1.get(i);
            if (packageinfo.packageName.matches("cn.wps.moffice.*"))
            {
                arraylist.add(packageinfo.packageName);
            }
            i++;
        } while (true);
    }

这里是通过查找系统里面所有的安装包,判断包名是否符合wps的包名,得到一个列表从而判断是否已经安装了。

2.下载安装包

if(getWPSInstalledPackage(this).size() == 0){
	String url = "localhost:8080/wps.apk";//安装包的地址
	Intent intent = new Intent(context,UpdateService.class);
	intent.putExtra("url",url);
	context.startService(intent);
}

如何下载并安装请参阅前篇文章《ANDROID Service下载APK并安装》

3.下载并打开文档

这里使用异步方法下载文档,当然也可以使用service方式下载,可参阅文章《ANDROID异步调用》

/**
通用打开下载文档任务
*/
public class DownloadDocTask extends AsyncTask<Void, Void, Object> {
    private Context context;
    private String fileUrl;
    private String fileName;
    private String localFile;
    private View progress;
    /**
     * 传入需要的参数
     * @param contextf
     * @param fileUrlf 稳当地址
     * @param fileNamef 文件名
     * @param progressf 进度条
     */
    public void setContext(Context contextf,String fileUrlf,String fileNamef, View progressf) {
        context = contextf;
        fileUrl = fileUrlf;
        fileName = fileNamef;
        progress = progressf;
        progress.setVisibility(View.VISIBLE);
    }
    @Override
    protected Object doInBackground(Void... params) {
        doDownload();
        return null;
    }
    /**
     * 下载完成之后打开文档,并取消进度条
     * @param result
     */
    @Override
    protected void onPostExecute(Object result) {
        if(progress!= null)
            progress.setVisibility(View.GONE);
        if(localFile != null)
        openFile(context,localFile);
    }
    /**
     * 下载
     */
    private void doDownload() {
        try {
            //String PATH = "/mnt/sdcard/Download/";
            File file = new File(Environment.getExternalStorageDirectory() + "/myapp/");
            file.mkdirs();
            File outputFile = new File(file, fileName);
            if(outputFile.exists()){
                outputFile.delete();
            }
            String cookies = "sessionid=abc";//这里可以传入cookie以保证和登陆连接一致
            URL url = new URL(fileUrl);
            HttpURLConnection c = (HttpURLConnection) url.openConnection();
            c.setRequestMethod("GET");
            c.setDoOutput(true);
            c.addRequestProperty("Cookie",cookies);
            c.connect();

            FileOutputStream fos = new FileOutputStream(outputFile);
            InputStream is = c.getInputStream();
            byte[] buffer = new byte[c.getContentLength()];
            int len1 = 0;
            while ((len1 = is.read(buffer)) != -1) {
                fos.write(buffer, 0, len1);
            }
            fos.close();
            is.close();
            localFile = outputFile.getPath();
        } catch (Exception e) {
            Log.e("DownloadDocTask", "DownloadDocTask error! " + e.getMessage(), e);
        }
    }
	 /**
     * 打开office文档
     * @param context
     * @param path
     * @return
     */
    public static boolean openFile(Context context, String path)
    {
        String packageName ="cn.wps.moffice_i18n";// "cn.wps.moffice_eng";
        String className = "cn.wps.moffice.documentmanager.PreStartActivity2";
        Intent intent = new Intent();
        Bundle bundle = new Bundle();
        //bundle.putString("OpenMode", "ReadMode");
        bundle.putString("OpenMode", "Normal");
        bundle.putBoolean("SendCloseBroad", false);
        bundle.putString("ThirdPackage", "");
        bundle.putBoolean("ClearBuffer", true);
        bundle.putBoolean("ClearTrace", true);
        bundle.putFloat("ViewProgress", 0.0F);
        bundle.putFloat("ViewScale", 1.0F);
        bundle.putInt("ViewScrollX", 0);
        bundle.putInt("ViewScrollY", 0);
        bundle.putBoolean("AutoJump", false);
        //bundle.putBoolean(CLEAR_FILE, true);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(android.content.Intent.ACTION_VIEW);

        intent.setClassName(packageName, className);

        File file = new File(path);
        if (file == null || !file.exists())
        {
            Toast.makeText(context,"文件不存在",Toast.LENGTH_LONG).show();
            return false;
        }

        Uri uri = Uri.fromFile(file);
        intent.setData(uri);
        intent.putExtras(bundle);

        try
        {
            context.startActivity(intent);
        }
        catch (ActivityNotFoundException e)
        {
            Log.e("Utils",e.getMessage(),e);
            Toast.makeText(context, "WPS打开文档是发生异常", Toast.LENGTH_LONG).show();
            return false;
        }

        return true;
    }
}

下载文档之前一般需要启动进度条,完成之后隐藏进度条,这里使用的是传入一个进度条的view应用,显示一个无限时的进度,当然也可以使用一个确定的进度,参考前面的下载apk的文章,里面有具体的根据下载的进度显示进度条。

下载完成之后记得一定要在UI线程里面去打开文档,打开文档wps官方有文档参考提供很多参数进行对文档的控制,这里就简单的打开文档,不再有后续的操作了,也就是我们的应用只需要打开文档,其他的事情就交给wps了。

4.最后的代码调用

private Handler handler = new Handler();
/**
 * 打开文档
 * @param url
 */
public void openDoc(String url,String fileName){

	final String myurl = url;
	final String name = fileName;

	//2.下载并打开
	//在ui thread运行以便打开进度条
	handler.post(new Runnable() {
		@Override
		public void run() {

			//1.判断是否能够打开
			if(getWPSInstalledPackage(WebViewActivity.this).size() == 0){
				downloadWPS(WebViewActivity.this);//当前的acitvity实例
				return;
			}

			View v = WebViewActivity.this.findViewById(R.id.webview_progress);

			DownloadDocTask task = new DownloadDocTask();
			task.setContext(WebViewActivity.this, myurl, name, v);
			task.execute();
		}
	});
}

这里使用Handler避免界面卡顿,那么以上的过程完成之后最终用户的操作可以这样更人性化的一个过程:

1.当用户点击打开文档按钮,判断是否存在wps工具,不存在提示用户是否下载wps进行安装

2.下载并安装wps,这时系统通知栏有下载的进度,下载完成之后,这时候界面转到安装界面,需要用户确认安装之后返回应用界面

3.用户再次点击打开文档按钮

4.打开进度条提示下载文档

5.文档下载完成之后自动打开文档

6.用户阅读文档

7.用户关闭文档返回应用界面

总结

一个打开文档对用户来说操作非常简单,只需要点击一个按钮,但是后台做了很多默默无闻的工作,以上的方法算是较为通用的,可以和webview进行结合,将打开文档的方法暴露给webview里面的页面javascript来调用,这样的话就可以直接在网页上打开文档,具体的javascript和webview的通讯可以参考前面的文章《ANDROID WebView简单使用》

Entity Framework 7中的影子属性

影子属性是类本身中并不存在,但Entity Framework却认为存在的字段。它们能够参与查询、创建/更新操作和数据库迁移。微软认为影子属性有两个主要的应用场景:

  • 允许数据访问层访问那些不该由领域模型暴露到应用其它部分的属性
  • 允许开发者高效地添加属性到没有源代码的类中

影子属性在OnModelCreating事件中被定义,该事件在DBContext中为可重载方法。这里有一个绑定DataTime属性LastUpdated到Blog实体的例子。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity().Property("LastUpdated");
}

这个属性的一个通用用例是在执行保存操作时自动赋值给LastUpdated属性。为此,你可以使用DBContext.ChangeTracker来获取DBEntry类型的对象列表。你可以这样写:

foreach (var item in modifiedEntries)
{
    Item.Property("LastModified").CurrentValue = DateTime.Now;
}

一般可以通过重载DBContext类的SaveChanges()方法实现。通过这里的重载,你可以更新所有需要更新的数据,而又不必在每一个更新数据的地方重复代码。

当ChangeTracker适合用于修改保存事件的时候,你会很想绕过DBEntry直接访问影子属性。通过EF.Property函数就可以做到,如下所示:

EF.Property(entity, "LastModified")

这个表达式放在一个查询中能生成服务器端的WHERE和ORDER BY子句。

ASP.NET 5与MVC 6中的新特性

虽然人们的目光都专注于ASP.NET 5与跨平台的执行引擎上,但作为微软推荐的UI与Web Service框架,MVC也引入了多项变更。其中最重要的一点莫过于MVC、Web API与Web Pages三者的统一了。

差点忘了提一句,MVC 6中默认的渲染引擎Razor也将得到更新,以支持C# 6中的新语法。而Razor中的新特性还不只这一点。

在某些情况下,直接在Web页面中嵌入某些JSON数据的方式可能比向服务端发起一次额外请求的方法更合适。在之前的版本中,实现这一点需要编写一 些繁琐的映射代码,然后用某种JSON转换器对数据对象进行序列化,并将结果通过view model进行暴露。而在MVC 6中,以上所有的样板代码都可以简化为一句“@Json.Serialize(Model)”。

在实现图片缓存时,同样也会遇到大量样板代码的问题。图片的缓存本身很简单,但要找到某种方式通知浏览器让缓存失效,往往要用到许多繁琐的临时方 案。而通过使用全新的Image Tag Helper,只需将asp-file-version这一属性设置为true就可以了,MVC将“自动为图片文件名附加上一个用于清除缓存的版本号”。

Tag Helper框架也得到了一定程度的改进,用户现在可以“将Tag Helper中的服务端属性与Dictionary的属性进行绑定”。服务端属性的存在与否,将使Tag Helper选择性地生效。如果想要了解更多如何编写自定义Tag Helper的内容,请参考Jeff Fritz的文章“开始使用ASP.NET MVC Tag Helper”。

路由token能够让你在类级别编写类似于“[Route(“Products/[action]”)”这样的表达式,而在MVC 6中,可以在路由名称中使用相同的token,这一点对于诊断过程来说很有帮助。

 

ANDROID Service下载APK并安装

自动判断是否有新的版本然后下载安装是客户端程序的必备功能之一,让用户自己下载新版本的程序那是比较困难的事情,手机上那么多程序谁会一个个去更新呢,只有在用到的时候提示有新的版本需要更新,用户只需要确认一下就可以了。

下面使用Service来实现下载并在通知栏显示进度条,下载完成之后开始安装,当然不一定需要使用service来实现,也可以使用异步方法来下载。

这个下载和安装需要一定的权限包括访问网络,读写文件等,并且service也需要在AndroidManifest.xml中进行声明,如下所示

<!--访问网路-->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

<!-- service声明 -->
<service android:name="cn.fullstacks.blogapp.UpdateService" android:exported="false"/>

创建UpdateService,继承IntentService

public class UpdateService extends IntentService {
    private Context context;

    public UpdateService(){
        this("app update service");
    }
    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public UpdateService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(Intent workIntent) {
        // Gets data from the incoming Intent
        String address = workIntent.getStringExtra("url");
        context = getApplicationContext();
        doUpdate(address);
    }


    private void doUpdate(String address) {
        try {

            int id = new Random().nextInt();
            NotificationManager mNotifyManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);
            mBuilder.setContentTitle(context.getText(R.string.app_name)+" 更新")
                    .setContentText("下载...")
                    .setSmallIcon(R.drawable.ic_launcher)
                    .setPriority(NotificationCompat.PRIORITY_MAX);

            URL url = new URL(address);
            HttpURLConnection c = (HttpURLConnection) url.openConnection();
            c.setRequestMethod("GET");
            c.setDoOutput(true);
            c.connect();

            //String PATH = "/mnt/sdcard/Download/";
            File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
            file.mkdirs();
            File outputFile = new File(file, "myapp.apk");
            if(outputFile.exists()){
                outputFile.delete();
            }
            FileOutputStream fos = new FileOutputStream(outputFile);

            InputStream is = c.getInputStream();
            //int size = is.available();
            int size = c.getContentLength();
            int downsize = 0;

            mBuilder.setTicker(context.getText(R.string.app_name) + " 更新");
            mBuilder.setProgress(size, downsize, false);
            mBuilder.setPriority(NotificationCompat.PRIORITY_MAX);
            mBuilder.setDefaults(NotificationCompat.DEFAULT_SOUND);
            // Displays the progress bar for the first time.
            mNotifyManager.notify(id, mBuilder.build());

            mBuilder.setDefaults(0);

            byte[] buffer = new byte[1024];
            int len1 = 0;
            int index = 0;
            while ((len1 = is.read(buffer)) != -1) {
                fos.write(buffer, 0, len1);
                downsize += len1;
                index ++;
                if(index % 15 == 0) {//进度更新不能太过频发,界面会卡
                    mBuilder.setProgress(size, downsize, false).setContentText("下载..."+downsize+"/"+size);
                    // Displays the progress bar for the first time.
                    mNotifyManager.notify(id, mBuilder.build());
                }
            }

            fos.close();
            is.close();

            // When the loop is finished, updates the notification
            mBuilder.setContentText("下载结束").setProgress(0, 0, false);         // Removes the progress bar
            mNotifyManager.notify(id, mBuilder.build());
            mNotifyManager.cancel(id);

            //启动安装
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.fromFile(outputFile), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
            context.startActivity(intent);


        } catch (Exception e) {
            Log.e("UpdateService", "UpdateService error! " + e.getMessage(), e);
        }
    }

}

在activity里面判断需要更新的时候,就调用这个服务就开始跟新了,调用方法如下

Intent mServiceIntent = new Intent(getApplicationContext(), UpdateService.class);
mServiceIntent.putExtra("url", "http://192.168.1.111:8080/test.apk");
getApplication().startService(mServiceIntent);

总结

这个下载安装不单单是可以用来更新自己的程序,也可以下载其他的一些程序的安装包进行安装,一些自己程序依赖的安装包,在需要的时候进行下载安装,当然这种下载的服务不单单是可以用来下载安装包,很多的资源都是可以下载的,特别是一些图片的加载,通过这种异步多线程进行下载还是很方便的。

JAVA读写DBF文件

dbf是dBase和Foxpro所使用的数据库格式,也可以用Excel打开,需要选择.dbf格式的文件才能看到,貌似Access也是可以搞定的,这种文件格式属于比较古老,新的软件一般都很少会用到,现在用的比较多的就是sqlite之类的,各种嵌入式和移动设备,但是也不可避免的要和一些老版本的系统做借口,那很可能就需要解析这种文件了,下面来看看java是怎么完成这个任务的。

用java的好处就是你想做的事情别人都替你做完了,拿来主义就可以了,这个dbf读取就有一个实现库在这里:DANS DBF

dbf简单的理解就是一个表一个文件,一个目录就是一个数据库

在eclipse 里面引入dans dbf依赖包 dans-dbf-lib…jar ,下面献上读写的示例

读文件返回map的list,一切都是map

public static List<Map> read(String dbfFile) throws Exception {
	final Table table = new Table(new File(dbfFile));

	List<Map> results = new ArrayList<Map>();
	try {
		table.open(IfNonExistent.ERROR);
		final List<Field> fields = table.getFields();
		final Iterator<Record> recordIterator = table.recordIterator();
		while (recordIterator.hasNext()) {
			final Record record = recordIterator.next();
			Map item = new HashMap();
			for (final Field field : fields) {
				// byte[] rawValue = record.getRawValue(field);
				//可以判断其他的数据类型
				if (field.getType().equals(Type.NUMBER)) {
					item.put(field.getName().toUpperCase(),record.getNumberValue(field.getName()));
				} else {
					byte[] rawValue = record.getRawValue(field);
					String v = new String(rawValue, "GBK");
					if (v != null) {
						v = v.trim();
					}
					item.put(field.getName().toUpperCase(), v);
				}
			}
			results.add(item);
		}
	} finally {
		table.close();
	}
	return results;
}

写文件

/**
创建表字段
*/
private static List<Field> buildDbfField(){
	
	List<Field> fields = new ArrayList<Field>();
	
	fields.add(new Field("COLUMN1",Type.CHARACTER,20));
	fields.add(new Field("COLUMN2",Type.CHARACTER,20));
	fields.add(new Field("COLUMN3",Type.CHARACTER,8));
	fields.add(new Field("COLUMN4",Type.NUMBER,18,5));
	//...
	return fields;
}
/**
写数据
*/
public static void write(String dbfFile, List<Map> datas) throws Exception {
	if (datas == null || datas.size() == 0)
		return;
	File dbfile = new File(dbfFile);
	if(dbfile.exists()){
		dbfile.delete();
	}
	//创建表
	Table table = new Table(dbfile,Version.DBASE_3,buildDbfField(),"GBK");
	try {
		table.open(IfNonExistent.CREATE);
		for (Map item : datas) {
			Map<String, Value> insertItem = new HashMap<String, Value>();
			for (Object key : item.keySet()) {
				Object v = item.get(key);
				if(v == null)continue;
				if (v instanceof Number || v instanceof Integer || v instanceof Float || v instanceof Long) {
					insertItem.put(key.toString().toUpperCase(),
							new NumberValue((Number) v));
				} else {
					insertItem.put(key.toString().toUpperCase(),
							new StringValue((String) v,"GBK"));
				}
			}
			Record record = new Record(insertItem);
			table.addRecord(record);
		}
	} finally {
		table.close();
	}
}

总结

上面的示例只是处理了整数和字符串的数据类型,根据业务需要加上其他的数据类型处理,基本上读写就这么搞定,读写的效率还是不错的,上万的数据量也就两三秒的事情,当然跟字段的多少和长度有关以及服务器的性能了,如果是比较大的数据量处理,还是可以做一些定时任务在后台默默执行。

PHP 5.6.0发布

8月28日,PHP开发团队宣布PHP 5.6.0发布下载)。该版本带来了很多新特性以及若干改进,另外还有一些新特性并不向后兼容。

该版本带来的新特性包括:

要了解引入的所有新特性,可以阅读迁移指南中的新特性部分。

此外,该版本还引入了一些与之前版本并不兼容的特性,如json_decode()在解析JSON语法时会更为严格,GMP资源现在是对象了,等等。

PHP Session可能会引起并发问题

在进行Web应用程序开发的时候,人们经常会用Session存储数据。但可能有人不知道,在PHP中,Session使用不当可能会引起并发问题。印度医疗行业软件解决方案提供商Plus91 Technologies高级工程师Kishan Gor在个人博客上对这个问题进行了阐释。

如果同一个客户端并发发送多个请求,而每个请求都使用了Session,那么PHP Session锁的存在会导致服务器串行响应这些请求,而不是并行。这是因为在默认情况下,PHP使用文件存储Session数据。对于每一个新的 Session,PHP会创建一个文件,并持续向其中写入数据。所以,每次调用session_start()方法,就会打开Session文件,并取得 文件的独占锁。这样,如果服务器脚本正在处理一个请求,而客户端又发送了一个同样需要使用Session的请求,那么后一个请求会阻塞,直至前一个请求处 理完成释放了文件上的独占锁。不过,这只限于来自同一个客户端的多个请求,也就是说,来自一个客户端的请求并不会阻塞另一个客户端的请求。

如果脚本很短,这通常没有问题。但如果脚本运行时间比较长,那就可能会产生问题。在现代Web应用程序开发中,有一个非常常见的情况,就是使用 AJAX技术在同一个页面内发送多个请求获取数据。如果这些请求都需要使用Session,那么第一个请求到达服务器后会取得Session锁,其它请求 就必须等待,所有请求将串行处理,即使它们彼此之间并没有依赖关系。这将大大增加页面的响应时间。

有一个方法可以避免这个问题,就是在使用完Session以后立即调用session_write_close()方法关闭Session。这样 Session锁就会释放,即使当前脚本还在等在处理。需要注意的是,调用该方法后,当前脚本就不能进一步操作Session了。

需要特别指出的是,本文所陈述的问题和观点只适用于使用session_start()方法的PHP默认Session管理模式。比如,有用户就指出,如果将应用程序托管在AWS EC2上,并正确配置DynamoDB,Session锁定问题就不会出现。

《三体1》3.红岸之一

不知过了多长时间,叶文洁听到了沉重的轰鸣声。这声音来自所有的方向,在她那模糊的意识中,似乎有某种巨大的机械在钻开或锯开她置身于其中的大冰块。世界仍是一片黑暗,但轰鸣声却变得越来越真实,她终于能够确定这声音的来源既不是天堂也不是地狱。她意识到自己仍闭着眼睛,便努力地睁开沉重的眼皮……首先看到了一盏灯,灯深嵌在天花板内部,被罩在一层似乎是用于防撞击的铁丝网后面,发出昏暗的光,天花板似乎是金属的。
她听到有个男声在轻轻叫自己的名字。
“你在发高烧。”那人说。
“这是哪儿?”叶文洁无力地问,感觉声音不是自己发出的。
“在飞机上。”叶文洁感到一阵虚弱,又昏睡过去,朦胧中轰鸣声一直伴随着她。时间不长,她再次清醒过来,这时麻木消失,痛苦的感觉出现了:头和四肢的关节都很痛,嘴里呼出的气是发烫的,喉咙也痛,咽下一口唾沫感觉像咽下一块火炭。┴米┴花┴书┴库┴ www.7mihua.com
叶文洁转过头,看到旁边有两个穿着和程代表一样的军大衣的人,不同的是他们都戴着有红五星的军绵帽,敞开的大衣露出了里面军服上的红领章,其中一名军人戴着眼镜。叶文洁发现自己也盖着一件军大衣,身上的衣服是干的,很暖和。
她吃力地想支起身,居然成功了。她看到了另一边的舷窗,窗外是缓缓移去的滚滚云海,被阳光照得很刺眼;她赶紧收回目光,看到狭窄的机舱中堆满了军绿色的铁箱子,从另一个舷窗中可以看到上方旋翼的影子。她猜自己可能是在一架直升机上。
“还是躺下吧。”戴眼镜的军人说,扶她重新躺下,把大衣盖好。
“叶文洁,这篇论文是你写的吗?”另一名军人把一本翻开的英文杂志伸到她眼前,她看到那文章的题目是《太阳辐射层内可能存在的能量界面和其反射特性》,他把杂志的封面让她看,那是1966年的一期《天体物理学杂志》。
“肯定是的,这还用证实吗?”戴眼镜的军人拿走了杂志,然后介绍说,
“这位是红岸基地的雷志成政委。我是杨卫宁,基地的总工程师。离降落还有一个小时,你休息吧。”
你是杨卫宁?叶文洁没有说出口,只是吃惊地看着他,发现他的表情很平静,显然不想让旁人知道他们认识。杨卫宁曾是叶哲泰的一名研究生,他毕业时叶文洁刚上大一。叶文洁现在还清楚地记得杨卫宁第一次到家里来的情形,那时他刚考上研究生,与导师谈课题方向。杨卫宁说他想搞倾向于实验和应用的课题,尽可能离基础理论远些。叶文洁记得父亲当时是这样说:我不反对,但我们毕竟是理论物理专业,你这样要求的理由呢?杨卫宁回答:我想投身于时代,做一些实际的贡献。父亲说:理论是应用的基础,发现自然规律,难道不是对时代最大的贡献?杨卫宁犹豫了一下,终于说出了真话:搞理论研究,容易在思想上犯错误。这话让父亲沉默了。
杨卫宁是个很有才华的人,数学功底扎实,思维敏捷,但在不长的研究生生涯中,他与导师的关系若即若离,他们相互之间保持着敬而远之的距离。那时叶文洁与杨卫宁经常见面,也许是受父亲影响,叶文洁没有过多地注意他,至于他是否注意过自己,叶文洁就不知道了。后来杨卫宁顺利毕业,不久就与导师中断了联系。
叶文洁再次虚弱地闭上眼睛后,两名军人离开了她,到一排箱子后面低声交谈。机舱很狭窄,叶文洁在引擎的轰鸣声中还是听到了他们的话——
“我还是觉得这事儿不太稳妥。”这是雷志成的声音。
杨卫宁反问:“那你能从正常渠道给我需要的人吗?”
“唉,我也费了很大劲。这种专业从军内找不到,从地方上找,问题就更多了,你知道这项目的保密级别,首先得参军,更大的问题还是保密条例要求的在基地的隔离工作周期。那么长时间,家属随军怎么办?也得到基地里,这谁都不愿意。找到的两个合适的候选人宁肯待在五七干校也不来。当然可以硬调,但这种工作的性质,要是不安心什么都干不出来的。”
“所以只能这么办。”
“可这也太违反常规了。”
“这个项目本来就违反常规,出了事儿我负责就是了。”
“我的杨总啊,这责你负得了吗?你一头钻在技术里,‘红岸’可是与其他国防重点项目不同,它的复杂,是复杂在技术之外的。”
“你这倒是实话。”
降落时已是傍晚,叶文洁谢绝了杨卫宁和雷志成的挽扶,自己艰难地走下飞机,一阵强风差点把她吹倒,风吹在仍转动的旋翼上,发出尖利的啸声。风中的森林气息文洁很熟悉,她认识这风,这风也认识她,这是大兴安岭的风。
她很快听到了另一种声音,一个低沉浑厚的嗡嗡声,浑厚而有力,似乎构成了整个世界的背景,这是不远处抛物面天线在风中的声音,只有到了跟前,才能真正感受到这张天网的巨大。叶文洁的人生在这一个月里转了一个大圈又回来了——她现在是在雷达峰上。
叶文洁不由得转头朝她的建设兵团连队所在的方向望去,只看到暮色中一片迷蒙的林海。
直升机显然不是专为接她的,几名士兵走过来,从机舱里卸下那些军绿色的货箱,他们从她身边走过,没人看她一眼。她和雷志成、杨卫宁一行三人继续向前走去,叶文洁发现雷达峰的峰顶是这样的宽阔,在天线的下面有一小群白色建筑物,与天线相比,它们像几块精致的积木。他们正朝有两名哨兵站岗的基地大门走去,走到门前,他们停了下来。
雷志成转向叶文洁,郑重地说:“叶文洁,你的反革命罪行证据确凿,将要面临的审判也是罪有应得;现在,你面前有一个立功赎罪的机会,你可以接受,也可以拒绝。”他向天线方向指了指,“这是一个国防科研基地,其中正在进行的研究项目需要你掌握的专业知识,更具体的,请杨总工程师为你介绍,你要慎重考虑。”说完他对杨卫宁点了点头,尾随搬运物资的士兵一起走进了基地。
杨卫宁等别人走远了,向叶文洁示意了一下,带她走远些,显然是怕哨兵听到下面的谈话。这时,他不再隐藏自己与她的相识:“叶文洁,我可向你说清楚,这不是什么机会。我向法院军管会了解过,虽然程丽华力主重判,但具体到你的情节,刑期最多也就是十年,考虑到可能的减刑,也就是六七年的样子。而这里——”他向基地方向偏了一下头,“是最高密级的研究项目,以你的身份,走进这道门,可能……”他停了好一会儿,似乎想让天线在风中的轰鸣声加重自己的语气,“一辈子都出不来了。”
“我进去。”叶文洁轻声说。
杨卫宁对她这么快的回答很吃惊。“你不必这么匆忙做决定,可以先回到飞机上去,它三小时后才起飞,你要是拒绝,我送你回去。”
“我不回去,我们进去吧。”叶文洁的声音仍很轻,但其中有一种斩钉截铁的坚定。现在除了死后不知是否存在的另一个世界,她最想去的地方就是这样与世隔绝的峰顶了,在这里,她有一种久违的安全感。
“还是慎重些吧,你想清楚这意味着什么。”
“我可以在这里待一辈子。”
杨卫宁低头沉默了,他看着远方,似乎强行给叶文洁一些思考权衡的时间,叶文洁也沉默着,在风中裹紧军大衣看着远方,那里,大兴安岭已消失在浓浓的夜色中。在严寒下不可能有很多时间,杨卫宁下决心起步走向大门,走得很快,像要把叶文洁甩掉似的,但叶文洁紧跟着他,走进了红岸基地的大门。两名哨兵在他们通过后关上了两扇沉重的铁门。
走了一段后,杨卫宁站住,指着天线对文洁说:“这是一个大型武器研究项目,如果成功,其意义可能比原子弹和氢弹都大。”
在路过基地内最大的一幢建筑时,杨卫宁径直过去推开了门,叶文洁在门口看到了“发射主控室”的字样,迈进门,一股带着机油味的热气迎面扑来,她看到宽敞的大厅中,密集地摆放着各类仪器设备,信号灯和示波仪上的发光图形闪成一片,十多名穿军装的操作人员坐在几乎将他们埋没的一排排仪器前,仿佛是蹲守在深深的战壕中。操作口令此起彼伏,显得紧张而混乱。“这里暖和些,你先等一会儿,我去安排好你的住处就来。”杨卫宁对叶文洁说,并指指门旁边一张桌子旁的椅子让她坐。叶文洁看到,那张桌前已经坐了一个人,那是一位带手枪的卫兵。
“我还是在外面等吧。”叶文洁停住脚步说。
杨卫宁和善地笑笑,“你以后就是基地的工作人员了,除了少数地方,你哪里都可以去。”说完,他脸上有一种不安的表情,显然意识到了这话另一层的意思:你再也不能离开这里了。
“我还是去外面吧。”叶文洁坚持说。
“那……好吧。”杨卫宁看看那位并没有注意他们的卫兵,似乎理解了叶文洁,带她走出主控室,“你到这个避风的地方,我几分钟就回来,主要是找人给那个房间生上火,基地的条件现在还不太好,没有暖气。”说完快步走去。
叶文洁站在主控室的门边,巨大的天线就竖立在她身后,整整占据了半个夜空。在这里,她能够清楚地听到里面传出的声音。突然,那纷乱的操作口令声消失了,主控室里一片寂静,只能隐约听到仪器设备偶尔发出的蜂鸣声,接着出现了一个压倒一切的男音:
“中国人民解放军第二炮兵,红岸工程第147次常规发射,授权确认完毕,30秒倒数!”
“目标类别:甲三;坐标序号:BN20l97F;定位校核完毕,25秒倒数!”
“发射文档号:22;附加:无;续传:无;文档最后校核完毕,20秒倒数!”
“能源单元报告,正常!”
“编码单元报告:正常!”
“功放单元报告:正常!”
“干扰监测报告:在许可范围!”
“程序不可逆,l5秒倒数!”
一切又安静下来,十几秒钟后,随着一个警铃声响起,天线上的一盏红灯急剧闪烁起来。
“发射启动!各单元注意监测!”
叶文洁感到脸上有轻微的瘙痒感,她知道一个巨大的电场出现了。她仰头顺着天线所指的方向望去,看到夜空中的一缕薄云发出幽幽蓝光,那光很微弱,最初她以为是自己的幻觉,但当那缕云飘离那片空域后,云的微光就消失了,另外一缕飘人的云也同样发出光来。在主控室中,口令声又响成一片,她只能隐约听出其中的几句:
“功放单元故障,3号磁控电子管烧毁!”
“冗余单元投入正常!”
“断点l,续传正常!”
……
叶文洁听到另外一种“呼啦啦”的声音,朦胧中,看到一片片黑影从山下的密林中出现,盘旋着升上夜空,她没想到严冬的森林中还有这么多的鸟儿被惊起。接着她目睹了恐怖的一幕:一个鸟群飞进了天线指向的范围,以发出幽光的那缕云为背景,她清楚地看到了群鸟纷纷从空中坠落。
这一过程大约持续了十五分钟,天线上的红灯熄灭了,叶文洁皮肤上的瘙痒感也消失了,主控室中,纷乱的口令声依旧,即使在那个洪亮的男音响起后也没有停止。
“红岸工程第147次发射进行完毕,发射系统关闭,红岸进入监测状态,请监测部接过系统控制权,并上传断点数据。”
“请各单元组认真填写发射日志,各组长到会议室参加发射例会,完毕。”
一切都沉寂下来,只有天线在风中发出的混响依旧。叶文洁看着夜空中的鸟群纷纷落回森林中。她再次仰望天线,感觉它像一只向苍穹张开的巨大手掌,拥有一种超凡脱俗的力量。她向“手掌”对着的夜空看去,并没有看到已被它打击的BN20197F号目标,在稀疏的云缕后面,只有1969年寒冷的星空。

ANDROID下滑刷新列表官方实现

在屏幕上用你的拇指按住屏幕下滑刷新列表,这个动作现在基本成了习惯,基本上绝大部分手机的应用都是这么实现的,那么这个过程到底是怎么实现的呢,不了解的话感觉不太好实现,但是现在Android官方已经提实现的组件,用起来非常方便而且简单,简单到你都敢相信这是真的,下面就来看看

如何创建项目什么的这里就不再多说了,可以查阅前面的一些文章

直接贴上SwipeRefreshLayout布局文件,activity_refreshview.xml

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipe_refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ListView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/listView">
    </ListView>

</android.support.v4.widget.SwipeRefreshLayout>

布局里面放一个listview,那么listview需要一行的布局list_row.xml

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

    <TextView
        android:id="@+id/title"
        android:layout_centerVertical="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:paddingLeft="20dp"
        android:textSize="18dp" />

</LinearLayout>

 

这里只是个实例,一行就显示一个文本,下面开始代码部分,先来定义一行对应的一个实体Movie.java

public class Movie {
    public int id;
    public String title;

    public Movie() {
    }

    public Movie(int id, String title) {
        this.title = title;
        this.id = id;
    }
}

实现listview的adapter ,SwipeListAdapter.java

public class SwipeListAdapter extends BaseAdapter {
    private Activity activity;
    private LayoutInflater inflater;
    private List<Movie> movieList;

    public SwipeListAdapter(Activity activity, List<Movie> movieList) {
        this.activity = activity;
        this.movieList = movieList;
    }

    @Override
    public int getCount() {
        return movieList.size();
    }

    @Override
    public Object getItem(int location) {
        return movieList.get(location);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if (inflater == null)
            inflater = (LayoutInflater) activity
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (convertView == null)
            convertView = inflater.inflate(R.layout.list_row, null);

        TextView title = (TextView) convertView.findViewById(R.id.title);

        title.setText(movieList.get(position).title);

        return convertView;
    }

}

然后再看看activity里面的代码

public class RefreshActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {

    private SwipeRefreshLayout swipeRefreshLayout;
    private ListView listView;
    private SwipeListAdapter adapter;
    private List<Movie> movieList;

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

        listView = (ListView) findViewById(R.id.listView);
        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);

        movieList = new ArrayList<>();
        adapter = new SwipeListAdapter(this, movieList);
        listView.setAdapter(adapter);

        swipeRefreshLayout.setOnRefreshListener(this);

        fetchMovies();
    }

    @Override
    public void onRefresh() {
        fetchMovies();
    }

    /**
     * Fetching movies json by making http call
     */
    private void fetchMovies() {

        // showing refresh animation before making http call
        swipeRefreshLayout.setRefreshing(true);

        //异步请求数据
        Movie movie = new Movie();
        movie.title = "new item";
        movieList.add(movie);

        //这个方法需要在UI线程中执行
        adapter.notifyDataSetChanged();

        //
        swipeRefreshLayout.setRefreshing(false);

    }
}

这里来简单解释一下

1.实现刷新接口OnRefreshListener的事件onRefresh,这就是拇指下滑产生的事件了

2.onRefresh里面做的事情有三部,第一显示加载的图标swipeRefreshLayout.setRefreshing(true);然后就使用异步方式去后台请求数据,这个参阅之前的文章如何异步调用ANDROID异步调用,获取导数据之后添加的列表里面movieList.add(movie); 最后在UI线程里面通知列表有数据要更新了,最后把加载的图标隐藏swipeRefreshLayout.setRefreshing(false);

3.大功告成

总结

这个下滑刷新列表貌似是那个什么特推首创,具体不可考究,不过现在是所有的应用都可以这么做了,所有使用手机的人都享受着这样的创意所带来的优美用户体验,Android发展到现在,这些体验的代码实现已经变得简单而不足为道了。